I did an assessment task where I had to implement the game snap. The description:
Simulate a simplified game of snap between two computer players using N packs of cards (standard 52 card, 4 suit packs). A game of snap proceeds by shuffling N packs of cards and then drawing cards from the top of the shuffled pile. Each card drawn is compared to the previous card drawn and, if they match, all the cards drawn since the last match are randomly allocated to one of the players. The game continues until all the cards have been drawn from the shuffled pile. Any cards played without ending in a match once all the cards have been drawn are ignored. The winner is the player who has accumulated the most cards at the end of the game.
There are three possible matching conditions:
- The cards have the same suit.
- The cards have the same value.
- The cards have the same suit or the same value.
Your solution should ask:
- How many (N) packs of cards to use.
- Which of the three matching conditions to use.
Then simulate a game being played and display the winner.
My solution:
task/main.py:
from utils import read_options_from_stdin
from snap.game import Game
if __name__ == '__main__':
number_of_packs, matching_condition = read_options_from_stdin()
game = Game.init_game(number_of_packs, matching_condition)
while not game.finished:
game.turn()
print game.winner
task/utils.py:
from snap.consts import MATCHING_CONDITIONS
def read_options_from_stdin():
number_of_packs = None
while type(number_of_packs) != int:
try:
number_of_packs = int(raw_input('Please tell me how many packs to use: ').strip())
except ValueError:
print 'Please provide a number'
matching_condition = None
while matching_condition not in MATCHING_CONDITIONS:
if matching_condition is not None:
print 'wrong matching condition.'
matching_condition = raw_input('''
Please choose a matching condition.
Options are: {mathcing_consitions}
'''.format(mathcing_consitions=MATCHING_CONDITIONS.keys()))
return number_of_packs, matching_condition
task/snap/card.py:
class Card(object):
def __init__(self, value, suit):
self.value = value
self.suit = suit
def __str__(self):
return 'Card {suit} {value}'.format(suit=self.suit, value=self.value)
class CardSameValue(Card):
def __init__(self, *args, **kwargs):
super(CardSameValue, self).__init__(*args, **kwargs)
def __eq__(self, other):
return self.value == other.value
class CardSameSuit(Card):
def __init__(self, *args, **kwargs):
super(CardSameSuit, self).__init__(*args, **kwargs)
def __eq__(self, other):
return self.suit == other.suit
class CardSameValueOrSameSuit(Card):
def __init__(self, *args, **kwargs):
super(CardSameValueOrSameSuit, self).__init__(*args, **kwargs)
def __eq__(self, other):
return self.value == other.value or self.suit == other.suit
task/snap/consts.py:
import card
MATCHING_CONDITION_SAME_SUIT = 'same_suit'
MATCHING_CONDITION_SAME_VALUE = 'same_value'
MATCHING_CONDITION_SAME_SUIT_OR_VALUE = 'same_suit_or_value'
MATCHING_CONDITIONS = {
MATCHING_CONDITION_SAME_SUIT: card.CardSameSuit,
MATCHING_CONDITION_SAME_VALUE: card.CardSameValue,
MATCHING_CONDITION_SAME_SUIT_OR_VALUE: card.CardSameValueOrSameSuit,
}
SUIT_SPADES = 'spades'
SUIT_HEARTS = 'hearts'
SUIT_DIAMONDS = 'diamonds'
SUIT_CLUBS = 'clubs'
SUITS = [
SUIT_SPADES,
SUIT_HEARTS,
SUIT_DIAMONDS,
SUIT_CLUBS,
]
VALUE_ACE = 'ACE'
VALUE_1 = '1'
VALUE_2 = '2'
VALUE_3 = '3'
VALUE_4 = '4'
VALUE_5 = '5'
VALUE_6 = '6'
VALUE_7 = '7'
VALUE_8 = '8'
VALUE_9 = '9'
VALUE_10 = '10'
VALUE_J = 'J'
VALUE_Q = 'Q'
VALUE_K = 'K'
VALUES = [
VALUE_ACE,
VALUE_1,
VALUE_2,
VALUE_3,
VALUE_4,
VALUE_5,
VALUE_6,
VALUE_7,
VALUE_8,
VALUE_9,
VALUE_10,
VALUE_J,
VALUE_Q,
VALUE_K,
]
task/snap/deck.py:
import random
from .consts import SUITS, VALUES, MATCHING_CONDITIONS
class Deck(object):
def __init__(self, cards):
self.cards = cards
def shuffle(self):
random.shuffle(self.cards)
def draw(self):
return self.cards.pop(0)
def is_empty(self):
return len(self.cards) == 0
def size(self):
return len(self.cards)
def __iadd__(self, other):
self.cards += other.cards
return self
@classmethod
def get_empty_deck(cls):
return cls([])
@classmethod
def get_pack(cls, matching_condition):
card_class = MATCHING_CONDITIONS[matching_condition]
cards = []
for value in VALUES:
for suit in SUITS:
cards.append(card_class(value, suit))
return cls(cards)
def __str__(self):
return str([str(card) for card in self.cards])
task/snap/game.py:
import random
from .deck import Deck
from .player import Player
class Game(object):
def __init__(self, deck, players):
self.deck = deck
self.players = players
self.previously_drawn = None
self.pile_size = 0
@property
def finished(self):
return self.deck.is_empty()
@property
def winner(self):
print 'cards left ', self.pile_size
return max(self.players, key=lambda player:player.number_of_cards).player_name
@classmethod
def init_game(cls, number_of_packs, matching_condition):
players = [
Player.player_factory('Player 1'),
Player.player_factory('Player 2'),
]
deck = Deck.get_empty_deck()
for _ in xrange(number_of_packs):
deck += Deck.get_pack(matching_condition)
deck.shuffle()
return cls(deck, players)
def print_state(self, drawn_card, match):
print 'pile_size:{pile_size} cards_left: {cards_left}\tdrawn: {drawn_card},\tPrev. drawn: {previously_drawn_card}\tMatch: {match}\tplayers {players}'.format(
pile_size=self.pile_size,
cards_left=self.deck.size(),
drawn_card=drawn_card,
previously_drawn_card=self.previously_drawn,
match=match,
players=[str(player) for player in self.players]
)
def turn(self):
drawn_card = self.deck.draw()
self.pile_size += 1
match = False
if self.previously_drawn is not None:
match = drawn_card == self.previously_drawn
if match:
random.choice(self.players).add_pile(self.pile_size)
self.pile_size = 0
self.print_state(drawn_card, match)
self.previously_drawn = drawn_card
task/snap/player.py:
class Player(object):
def __init__(self, number_of_cards, player_name):
self.number_of_cards = number_of_cards
self.player_name = player_name
@classmethod
def player_factory(cls, player_name):
return cls(0, player_name)
def add_pile(self, number_of_cards):
self.number_of_cards += number_of_cards
def __str__(self):
return '{name} {pile}'.format(name=self.player_name, pile=self.number_of_cards)
I got a feedback that the quality of this code is not high enough. I'm wondering what should have been done differently?