2018-09-06

Victimhood poker - the implementation

Back in 2006, blogger Marlinschamps proposed the rules for the game of victimhood poker. In a spare couple of hours last weekend, I decided to code this up so that we had an implementation of it. Beloved readers, here is that implementation. It's in Python; I show it in chunks, but it should all go in a single file called e.g. victimhood.py.

First we define the cards in the deck, their points, and their class:

#!/usr/bin/python
# This code is in the public domain. Copy and use as you see fit.
# Original author: http://hemiposterical.blogspot.com/, credit 
# would be nice but is not required.
import random
deck = {
 # Key: (points,class)
 'Black':           (14, 'skin'),
 'Native-American': (13, 'ethnicity'),
 'Muslim':          (12, 'religion'),
 'Hispanic':        (11, 'ethnicity'),
 'Transgender':     (10, 'gender'),
 'Gay':              (9, 'none'),
 'Female':           (8, 'gender'),
 'Oriental':         (7, 'ethnicity'),
 'Handicapped':      (6, 'none'),
 'Satanist':         (6, 'religion'),
 'Furry':            (5, 'none'),
 'Non-Christian':    (4, 'religion'),
 'East-Indian':      (3, 'ethnicity'),
 'Hindu':            (3, 'religion'),
 'Destitute':        (2, 'economic'),
 'White':            (0, 'skin'),
 'Straight':         (0, 'gender'),
 'Christian':        (0, 'religion'),
 'Bourgeois':        (0, 'economic'),
}
# Categories in the order you'd describe someone
category_list = [
 'economic','none','skin','religion','ethnicity','gender',
]
categories = set(category_list)
In addition, a couple of helper functions to make it easier to ask questions about a specific card:
def cardscore(card):
 """ How much does this card score? """
 (s, unused_cls) = deck[card]
 return s

def cardclass(card):
 """ What class does this card represent? """
 (unused_s, cls) = deck[card]
 return cls
Now we define what a "hand" is, with a bunch of functions to make it easier to merge other cards into a hand and compute the best score and hand from these cards:
class Hand(object):
 """ A hand is a list of cards with some associated scoring functions """
 def __init__(self, start_cards=None):
  if start_cards is None:
   self.cards = []
  else:
   self.cards = start_cards[:]

 def add(self, card):
  self.cards.append(card)
  
 def bestscore(self):
  (score, bestcards) = self.besthand()
  return score

 def bestcards(self):
  (score, bestcards) = self.besthand()
  return bestcards

 def besthand(self):
  """ What's the highest possible score for this hand?
  Limitations: one card per class, no more than 5
  cards in total
  Return (score, best_hand)
  """
  score_by_class = { }
  card_by_class = { }
  for card in self.cards:
    try:
      s = cardscore(card)
      card_class = cardclass(card) 
    except KeyError, err:
      raise KeyError("Invalid card name '%s'" % card)
    if card_class not in score_by_class:
      score_by_class[card_class] = s
    if s >= score_by_class[card_class]:
      score_by_class[card_class] = s
      card_by_class[card_class] = card
  # We now have the best scoring card in each
  # class. But we can only use the best 5.
  cards = card_by_class.values()
  cards.sort(lambda x,y: cmp(cardscore(x),cardscore(y)))
  if len(cards) > 5:
    cards = cards[0:5]
  tot = 0
  for card in cards:
    tot += cardscore(card)
  best_hand = Hand(cards)
  return (tot, best_hand)

 def merge(self, hand):
  """ Merge this hand and another to return a new one """
  ans = self.copy()
  for c in hand.cards:
   ans.add(c)
  return ans

 def copy(self):
  return Hand(self.cards)
 
 def __str__(self):
  return ', '.join(['%s (%d)' % (c, cardscore(c)) for c in self.cards])

 def card_in_class(self,class_name):
  """Returns a card in the given class, if the hand has one"""
  for card in self.cards:
   (s,c) = deck[card] 
   if c == class_name:
    return card
  # No match
  return None

 def description(self):
   card_order = [self.card_in_class(c) for c in category_list]
   card_order = filter(lambda x: x is not None, card_order)
   return ' '.join(card_order)
Now we can define a game with a number of players, and specify how many copies of the deck we want to use for the game:
class Game(object):
 def __init__(self, player_count, deck_multiple=2):
   self.player_count = player_count
   self.deck_multiple = deck_multiple
   self.player_hands = { }
   for i in range(1,1+player_count):
     self.player_hands[i] = Hand()
   self.shuffle_deck()
   self.community = Hand()

 def shuffle_deck(self):
   self.deck = []
   for i in range(self.deck_multiple):
    self.deck.extend(deck.keys())
   random.shuffle(self.deck)

 def deal(self, cards_per_player):
   for p in range(1,1+self.player_count):
     for c in range(cards_per_player): 
       card = self.deck.pop()  # might run out
       self.player_hands[p].add(card)

 def deal_community(self, community_cards):
   self.community = Hand()
   for c in range(community_cards):
    card = self.deck.pop()
    self.community.add(card)

 def get_community(self):
  return self.community

 def best_hand(self, player_num):
   h = self.player_hands[player_num]
   # Expand the hand with any community cards
   h2 = h.merge(self.community)
   return h2.besthand()
Finally, we have some code to demonstrate the game being played. We give 5 cards each to 4 players, and have 3 community cards which they can use. We display each player's best hand and score, and announce the winner:
if __name__ == '__main__':
 player_count=4
 g = Game(player_count=player_count, deck_multiple=2)
 # Everyone gets 5 cards
 g.deal(5)
 # There are 3 community cards
 g.deal_community(3)
 print "Community cards: %s\n" % g.get_community()
 winner = None
 win_score = 0
 for p in range(1,1+player_count):
  (score, hand) = g.best_hand(p)
  print "Player %d scores %d with %s" % (p, score, hand)
  print "  which is a %s" % hand.description()
  if score > win_score:
    winner = p
    win_score = score
 print "\nPlayer %d wins!" % winner

Don't judge my Python, y'all; it's quick and dirty Python 2.7. If I wanted a code review, I'd have set this up in GitHub.

So what does this look like when it runs? Here are a few games played out:


Community cards: Christian (0), Native-American (13), Gay (9)

Player 1 scores 40 with Non-Christian (4), Gay (9), Native-American (13), Black (14)
 which is a Gay Black Non-Christian Native-American
Player 2 scores 22 with Christian (0), Bourgeois (0), Gay (9), Native-American (13)
 which is a Bourgeois Gay Christian Native-American
Player 3 scores 30 with Destitute (2), Satanist (6), Gay (9), Native-American (13)
 which is a Destitute Gay Satanist Native-American
Player 4 scores 42 with Female (8), Gay (9), Muslim (12), Native-American (13)
 which is a Gay Muslim Native-American Female

Player 4 wins!


Community cards: Non-Christian (4), Bourgeois (0), Furry (5)

Player 1 scores 24 with Straight (0), Destitute (2), Non-Christian (4), Furry (5), Native-American (13)
 which is a Destitute Furry Non-Christian Native-American Straight
Player 2 scores 26 with Bourgeois (0), East-Indian (3), Non-Christian (4), Furry (5), Black (14)
 which is a Bourgeois Furry Black Non-Christian East-Indian
Player 3 scores 30 with Bourgeois (0), Non-Christian (4), Furry (5), Oriental (7), Black (14)
 which is a Bourgeois Furry Black Non-Christian Oriental
Player 4 scores 33 with Destitute (2), Handicapped (6), Muslim (12), Native-American (13)
 :which is a Destitute Handicapped Muslim Native-American
Player 4 wins!


Community cards: Transgender (10), Muslim (12), Oriental (7)

Player 1 scores 53 with Handicapped (6), Transgender (10), Hispanic (11), Muslim (12), Black (14)
 which is a Handicapped Black Muslim Hispanic Transgender
Player 2 scores 33 with Bourgeois (0), White (0), Transgender (10), Hispanic (11), Muslim (12)
 which is a Bourgeois White Muslim Hispanic Transgender
Player 3 scores 40 with Furry (5), Transgender (10), Muslim (12), Native-American (13)
 which is a Furry Muslim Native-American Transgender
Player 4 scores 37 with Destitute (2), Handicapped (6), Oriental (7), Transgender (10), Muslim (12)
 which is a Destitute Handicapped Muslim Oriental Transgender

Player 1 wins!

What does this prove? Nothing really, it was kinda fun to write, but I don't see any earthshaking philosophical insights beyond the fact that it's a rather silly game. But then, that's true for its real life analogue as well.

Programming challenge: build a function to instantiate a Hand() from a string e.g. "black east-indian handicapped female" and use this to calculate the canonical score. Bonus points if you can handle missing hyphens.

No comments:

Post a Comment

All comments are subject to retrospective moderation. I will only reject spam, gratuitous abuse, and wilful stupidity.