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.

Scentrics worth half a billion quid - and other fiction

Regular readers (both of you) will recall my previous scepticism regarding IT "security" company Scentrics. TL;DR - they're pushing the idea that a key part of "secure" email is sending a copy of every email to a central server, encrypted with a key that only gives access to a trusted party - your local government, for instance. Singapore seemed very interested in their proposals, for reasons one can imagine.

Out of idle curiosity, I thought I'd check the Scentrics accounts for 2016-2017. Well, gosh.

 30 June 2017
£
30 June 2016
£
Fixed assets  
Intangible assets504,014,09220,455
Property, plant and equipment6,4638,618
Investments10-
 504,020,56529,073
Current assets  
Debtors1,051,5561,047,027
Cash at bank893,8152,793,822
 1,945,3713,840,849
Creditors within 1 year(893,718)(893,232)
Net current assets1,051,6532,947,617
Total assets less current liabilities505,072,2182,976,690
Provision for liabilities(99,546,235) 
Net assets405,525,9832,976,690
Capital and reserves  
Called up share capital130130
Share premium5,778,5965,778,596
Retained earnings399,747,257(2,802,036)
 405,525,9832,976,690

How would I read this? They spent £1.9M of their cash on various things during the year; about half of that on medium-to-long term debt servicing, and the rest presumably on overheads (salary, office, patent office fees, other professional service fees). This is clearly not sustainable, and indeed last year they had a net worth (retained earnings) of minus 2.8 million pounds. How could this be fixed?

Well, they've just gained £504 million in intangible assets. The associated notes indicate a "revaluation" of their intangibles happened, which changed from £22K to £560M. There was a 10% amortisation charge ("spreading out") over the year, taking them down to a measly £504M. That's quite a change, what was involved?

Patents and licences were valued on an open market basis on 20 August 2018 by the Directors
There's also the useful information:
Patents and licences are being amortised evenly over their estimated useful life of ten years.
But there's no obvious licence revenue in the company accounts that I can see, and there's still only 4 employees (the directors) so they're not doing anything substantial with the resources, so I'd bet this £560M change is an evaluation of the worth of their patents. Let's look at these, shall we?

The main Scentrics patents pivot around the previously discussed system where a client (mobile, in the most recent patents, but there's nothing specifically "mobile" about them) talks to a centralised mail server to obtain encryption keys to safely send messages to it for routing onwards a destination, and then separately sends a copy of the message (asynchronously! wow, there's some modern thinking) to a "monitoring" server using a different encryption key.

Basically, it's a system for a company or government to enable scanning of email sent by its employees/citizens - as long as they're using its mail application, of course. If the employees use Outlook.com, Gmail, or any number of other public webmail services, they are sunk. So companies will block all the webmail applications by restricting the web browsers in their corporate devices, forcing use of the corporate mail server (Outlook, most likely) which they can snoop on. They don't need Scentrics' patents. Governments would need a willing population to live with the (likely) crappy, unreliable custom email application and not look elsewhere for their email needs. Even China struggles to keep up with restricting their population to approved websites, and they're a gosh-darned communist dictatorship.

It's not impossible that Scentrics reckons they can get a major corporation or government to licence their patents, but I'd have to rate it as unlikely at best. Why would someone pay £500M for it, rather than (say) £5M to get a moderately competent cryptographer to design a better system? The patent is extremely dubious to defend in my personal technical opinion; there are alternative strategies such as encrypting the message with a randomized key, encrypting that key with a) the recipient's key and b) the monitoring service's key, and enclosing both encrypted keys in the message. Then the client only has to send one message, and the monitoring service can store it and decrypt it on demand. But hey, what do I know.

Guru Paran Chandrasekaran and Andrea Bittau - happy to bring you gents up to speed on the state of modern cryptography, if you're interested. No charge!

(They've finally fixed their https problem. Guess it got a bit embarrassing.)

Update: Looks like Andrea Bittau was killed in a motorcycle crash last year. Nothing sinister, just terribly sad - 34 years old.