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.