Last active
April 4, 2019 20:24
-
-
Save Torvaney/a4670e0c0e47422b721d52a963181f14 to your computer and use it in GitHub Desktop.
Why did I do this?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
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