A new card game, a simulator and advice for implementing in a casino.

Ben Houghton
Data & Waffles
Published in
6 min readJan 6, 2020

Earlier this year, I was sitting in a B&B in Western Cuba with a group of friends. With it being our first evening in the country, and jetlag really starting to kick in, we decided to play a quick card game then head to bed. After realising that none of us actually knew any card games (aside from Snap), I made one up. The premise is simple:

Take a full deck of 52 cards and two people — a dealer and a player.

  1. The dealer lays 10 (or indeed a variable number of) cards face down on a table
  2. The player tries to guess each card in order (both value and suit). They win if they guess any of the cards correctly
  3. If the player gets the suit right at any point, then a new card is added to the table. If the player gets the value of the card right, 2 new cards are added.
  4. (optional but interesting rule) The player cannot guess the same suit or card value twice in a row

Simple right? I’ve named this game ‘Yakelin’ after the owner of the B&B we were staying in at the time. Give it a go — it’s surprisingly fun to play.

As a statistician, a question popped straight into my head: what’s the probability of winning Yakelin? As always, there are two ways of starting with this question: approximate using a Monte Carlo simulation or try and evaluate analytically. In this post, I will talk through some of the details of the simulation solution I built and give a few tips for these kinds of problems*. The code is on GitHub: https://github.com/BenHoughton5/yakelin

For the simulation, I used a standard Monte Carlo approach. I won’t go into too much theory on Monte Carlo here, but this article gives a thorough and entertaining overview.

Building the game

We first need to decide what this game looks like in code form. It needs to be built in such a way that we can simulate playing it easily. In addition, there are a lot of parameters we might want to play with (the number of cards on the table; the number of cards you get as a reward for guessing the right suit or even the number of cards in the pack). A careful modular approach enables this.

We start with the basics — coding up what a card actually is and how a deck is created:

class Card(object):
def __init__(self, value, suit):
self.value = value
self.suit = suit

def __str__(self):
return str(self.value) + " of " + self.suit

def __eq__(self,other):
return self.value == other.value and self.suit == other.suit

def __ne__(self, other):
return self.value != other.value or self.suit != other.suit

This may sound trivial but, given cards are the essential building block of this game, it is essential that these are constructed with the right properties and functions. The equality function in particular will be important as this will be used throughout this game.

We next need to consider the groups of cards which are important during the game. We can think of there being 2 important (overlapping) sets of cards which matter to us: the cards which we have on the table (which will each be guessed) and the set of cards which are yet to be seen (which we can guess from).

class CardsNotSeen(object):

def __init__(self):
self.__cards_remaining = card_utilities.create_deck()

def cards_remaining(self):
return self.__cards_remaining

def num_cards_remaining(self):
return len(self.cards_remaining())

def done(self):
if self.__num_cards_remaining == 0:
return True
else
:
return False
# + other functions

These two sets of cards will have their own objects in the code base (again, with a variety of functions which represent the core mechanics of the game, such as guessing a card, adding to the table — to be used when the right suit or value is guessed — and determining if the table is empty, and thus the player has lost).

We then need to bring everything together for when the game itself is played. I wrote the main flow of the game in the ‘Yakelin’ object, instantiating or extending all of the other objects as required. I built it in such a way that this can be played on the terminal (I previously had an idea of making this into an app but questions how many downloads this would actually get).

Deciding how an imaginary agent would play the game

There are multiple ways you might choose which card to suggest in each turn. Naively, you may want to pick a random card that hasn’t been seen yet. A more sophisticated approach would be to choose a card whose suit and value has appeared least frequently so far, maximising the chance of getting the suit or number correct. For this approach, you would need to decide which to prioritise (least frequent suit or least frequent number) as the least frequent number seen might not be available for the least frequent suit and vice versa.

To realise these strategies, I created three ways of choosing a card (each as a separate utility) which can be called during the game itself (within the Yakelin object), giving the simulator the flexibility to experiment with these.

For example:

def num_first(cards_playable):
numbers = [x.value for x in cards_playable]
most_common_number = max(set(numbers), key=numbers.count)

suits_for_number = [x.suit for x in cards_playable if x.value == most_common_number]
suits_overall = [x.suit for x in cards_playable]

suit_to_play = find_most_frequent(suits_overall, suits_for_number)

return Card(most_common_number,suit_to_play)

Collecting the data

Here is another example of why building this game with a modular (even object-oriented) approach is important — there is a large amount of data you may want to collect whilst playing this game, for instance the number of cards it took before a correct guess; the number of correct guesses of the suit or value or niche statistics like the number of picture cards turned over. Modularising your code makes it much more straightforward to find the entry points where this data can be collected.

Simulation

I naively took a Monte Carlo approach to simulate this game, effectively getting the computer to play Yakelin thousands of times and averaging out the results. With some clever resource allocation and use of multithreading / parallel computing, you can make this process very quick indeed. The core purpose of this is to collect data to analyse.

Analysing the Results

The initial question I asked is — for the traditional 10 card game, what is the actual probability of winning. The simulation gives you an estimate of this immediately once you have counted the number of wins and divided by the number of trials. The result came out as 28.5% if you guess a random unseen card each time and just over 29% if you choose the least frequent card and suit combination (with a slight advantage when prioritising the least frequent value, which makes sense given this strategy gives you two extra cards instead of one). So if you are looking to implement this in a casino (and by all means, please feel free to), then giving a 3 fold return for a win would be a highly profitable strategy, even if your clientele decide to play entirely rationally, something which casinos rely on their punters not doing.

A more interesting question is what happens if we vary the number of starting cards on the table. This gives some very interesting results:

The probabilities of winning using the random choice strategy start to considerably diverge from those for the less naïve strategies after 10 cards and don’t start to converge again until we have nearly 40 cards on the table (when you are pretty much certain to win the game). It’s also interesting to note that the probability of winning is not 100%, even when there are 52 cards on the table. It is rule 4 which causes this — if you end up in a state where you have one card left and you have guessed the suit or value of that card in the turn before, then you cannot guess it again so you lose. This is another useful property of the game that a casino owner may want to take advantage of.

There is a whole lot more analytics that can be done here (e.g. looking at the expected number of cards it would take to win) but I will leave this out for now. Do feel free to fork / contribute to the code if you do anything exciting with the game!

*In case you’re wondering, I never did find the probabilities analytically and am keen to buy lunch for the first person to message me with a solution. The most promising approach I tried was to model the game as a Markov chain but the state space gets very messy with all of the rules, even if you exclude rule number 4.

--

--

Ben Houghton
Data & Waffles

Principal Data Scientist for Analytical Innovation at Quantexa