Skip to content

Instantly share code, notes, and snippets.

@Torvaney
Last active April 4, 2019 20:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Torvaney/a4670e0c0e47422b721d52a963181f14 to your computer and use it in GitHub Desktop.
Save Torvaney/a4670e0c0e47422b721d52a963181f14 to your computer and use it in GitHub Desktop.
Why did I do this?
'''
How many turns does it take to complete a game of Concentration?
https://en.wikipedia.org/wiki/Concentration_(game)
With perfect memory and strategy, for a game of n pairs, this converges to
(approximately) 1.61n moves.
https://www.jstor.org/stable/10.4169/amer.math.monthly.120.09.787
What about with imperfect memory?
'''
import collections
import random
N_CARDS = 26
RECALL_RATE = 0.5
class Card:
# NOTE: we can't use a namedtuple because we want to retain mutability
__slots__ = ['type', 'seen']
def __init__(self, type, seen):
self.type = type
self.seen = seen
def __repr__(self):
return '{0}{1}'.format(self.type, 's' if self.seen else '')
def remember(self, recall_rate=RECALL_RATE):
if random.random() < recall_rate:
self.seen = True
class Board:
def __init__(self, n_pairs):
self.board = [Card(i, False) for i in list(range(n_pairs))*2]
random.shuffle(self.board)
def __len__(self):
return len(self.board)
def __repr__(self):
return self.board.__repr__()
def __iter__(self):
return self.board.__iter__()
@property
def seen(self):
return [card for card in self.board if card.seen]
@property
def unseen(self):
return [card for card in self.board if not card.seen]
def remove(self, cardtype):
self.board = [card for card in self.board if card.type != cardtype]
def count_seen(self, cardtype):
return len([card for card in self.seen if card.type == cardtype])
if __name__ == "__main__":
# Create the board
board = Board(N_CARDS)
# Go through the board until complete
turns = 0
while len(board) > 0:
remaining_cards = iter(board)
for card in remaining_cards:
# First, check if we can remove any cards
counts = collections.Counter(c.type for c in board.seen)
for cardtype, count in counts.items():
if count == 2:
board.remove(cardtype)
turns += 1
if len(board) == 0:
break
# If the card has already been seen, remove it's partner card
if board.count_seen(card.type) == 1:
board.remove(card.type)
# Otherwise, reveal another card (and remove the pair if we strike lucky)
else:
try:
next_card = next(remaining_cards)
except StopIteration:
# If you run out of cards due to forgetting, return to the top of the deck
next_card = board.unseen[0]
if card.type == next_card.type:
board.remove(card.type)
card.remember()
next_card.remember()
turns += 1
print('Game completed in {0} turns'.format(turns))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment