def __init__(self, player1_name='', player2_name='', log_filename='', overwrite_log=False): self._player1 = Player(player1_name) self._player2 = Player(player2_name) self._winner = None self._state = None self._log_filename = log_filename self._overwrite_log = overwrite_log self._logger = GameLogger() self._logger_enabled = True
class GameController(object): MOVER = 'mover' RESPONDER = 'responder' PLAYER1 = 'player1' PLAYER2 = 'player2' class States: DEALING = 'dealing' MOVING = 'moving' RESPONDING = 'responding' GIVING_MORE = 'giving_more' def __init__(self, player1_name='', player2_name='', log_filename='', overwrite_log=False): self._player1 = Player(player1_name) self._player2 = Player(player2_name) self._winner = None self._state = None self._log_filename = log_filename self._overwrite_log = overwrite_log self._logger = GameLogger() self._logger_enabled = True def start_new_game(self, ignore_winner=True): self._deck = list(DurakCard.all()) random.shuffle(self._deck) self._trump = self._deck[-1] self._logger.reset() if self._logger_enabled: self._logger.log_before_game(self._player1.name, self._player2.name, self._deck, self._trump) self._player1.cards = CardSet(cards=self._deck[:6], trump=self._trump) self._player2.cards = CardSet(cards=self._deck[6:12], trump=self._trump) self._deck = self._deck[12:] if not ignore_winner and self._winner is not None: self._to_move = self._winner else: self._to_move = self._get_first_to_move_by_trump() self._winner = None self._discarded = [] self._on_table = Table() self._state = self.States.MOVING self._no_response = False if self._logger_enabled: self._logger.log_before_move( set(self._player1.cards), set(self._player2.cards), self.to_move, self.deck_count, ) return { 'player1_cards': CardSet(self._player1.cards, trump=self._trump), 'player2_cards': CardSet(self._player2.cards, trump=self._trump), 'trump': DurakCard(self._trump), } def _get_first_to_move_by_trump(self): lowest_trump1 = self._player1.cards.lowest_trump() lowest_trump2 = self._player2.cards.lowest_trump() if lowest_trump1 is not None and lowest_trump2 is not None: if lowest_trump1 < lowest_trump2: return self._player1 else: return self._player2 elif lowest_trump1 is None and lowest_trump2 is not None: return self._player2 elif lowest_trump1 is not None and lowest_trump2 is None: return self._player1 else: return random.choice([self._player1, self._player2]) def _get_game_data_for(self, player): return { 'trump': str(DurakCard(self._trump)), 'deck_count': self.deck_count, 'enemy_count': len(self._get_enemy_of(player).cards), 'on_table': map(str, self._on_table), 'discarded': map(str, self._discarded), } def get_game_data_for(self, player): assert player in (self.MOVER, self.RESPONDER, self.PLAYER1, self.PLAYER2) if player == self.MOVER: return self._get_game_data_for(self._to_move) elif player == self.RESPONDER: return self._get_game_data_for(self._to_respond) elif player == self.PLAYER1: return self._get_game_data_for(self._player1) elif player == self.PLAYER2: return self._get_game_data_for(self._player2) def _get_enemy_of(self, player): assert player in (self._player1, self._player2) if player is self._player1: return self._player2 else: return self._player1 @property def _to_respond(self): return self._get_enemy_of(self._to_move) def is_player1_to_move(self): return (self._to_move is self._player1) def register_move(self, card): if self._state != self.States.MOVING: raise exes.InvalidAction(expected=self._state, got=self.States.MOVING) if card is None: if not self._on_table: raise exes.CardIsExpected self._state = self.States.DEALING return card = DurakCard(card) if card not in self._to_move.cards: raise exes.PlayerDoesNotHaveCard(card) if card not in (self._to_move.cards.cards_that_can_be_added_to( self._on_table)): raise exes.InvalidCard('Can not move with card %s (on table: %s)' % (card, self._on_table)) self._to_move.cards.remove(card) self._on_table.append(card) self._state = self.States.RESPONDING self._check_for_game_over() def register_response(self, card): if self._state != self.States.RESPONDING: raise exes.InvalidAction(expected=self._state, got=self.States.RESPONDING) if card is None: self._no_response = True if self._to_move.cards: self._state = self.States.GIVING_MORE else: self._state = self.States.DEALING return card = DurakCard(card) card_to_beat = self._on_table[-1] if card not in self._to_respond.cards: raise exes.PlayerDoesNotHaveCard(card) if card not in ( self._to_respond.cards.cards_that_can_beat(card_to_beat)): raise exes.InvalidCard( 'Card %s can not beat card %s (trump is %s)' % (card, card_to_beat, self._trump)) self._to_respond.cards.remove(card) self._on_table.append(card) if not (self._to_respond.cards and self._to_move.cards): self._state = self.States.DEALING else: self._state = self.States.MOVING self._check_for_game_over() def register_give_more(self, cards): assert self._no_response if self._state != self.States.GIVING_MORE: raise exes.InvalidAction(expected=self._state, got=self.States.GIVING_MORE) if not cards: self._state = self.States.DEALING return # нельзя давать больше карт, чем есть у отбивающегося max_cards = len(self._to_respond.cards) - 1 cards = set(map(DurakCard, cards)) if len(cards) > max_cards: raise exes.TooMuchGiveMoreCards(len(cards), max_cards) invalid_cards = cards - self._to_move.cards if invalid_cards: raise exes.PlayerDoesNotHaveCard(*invalid_cards) allowed_cards = self._to_move.cards.cards_that_can_be_added_to( self._on_table) invalid_cards = cards - set(allowed_cards) if invalid_cards: raise exes.InvalidCard( 'Can not give more cards %s (on table: %s)' % (invalid_cards, self._on_table)) self._to_move.cards.difference_update(cards) self._on_table.give_more(cards) self._state = self.States.DEALING self._check_for_game_over() def deal(self): if self._state != self.States.DEALING: raise exes.InvalidAction(expected=self._state, got=self.States.DEALING) if self._logger_enabled: self._logger.log_after_move(self._on_table, self._on_table.given_more) player1_cards_before = CardSet(self._player1.cards, trump=self._trump) player2_cards_before = CardSet(self._player2.cards, trump=self._trump) if self._no_response: self._to_respond.cards.update(self._on_table) self._to_respond.cards.update(self._on_table.given_more) self._no_response = False else: self._to_move = self._to_respond self._discarded.extend(self._on_table) self._on_table.clear() self._state = self.States.MOVING for player in (self._to_respond, self._to_move): if not self._deck: break cards_needed = 6 - len(player.cards) if cards_needed > 0: player.cards.update(self._deck[:cards_needed]) self._deck = self._deck[cards_needed:] self._check_for_game_over() if not self.is_game_over(): if self._logger_enabled: self._logger.log_before_move( set(self._player1.cards), set(self._player2.cards), self.to_move, self.deck_count, ) return { 'player1_cards': (CardSet(self._player1.cards, trump=self._trump) - player1_cards_before), 'player2_cards': (CardSet(self._player2.cards, trump=self._trump) - player2_cards_before), } def is_game_over(self): return (self.state is None) def _check_for_game_over(self): if self._deck: return if self._player1.cards and self._player2.cards: return if self._state == self.States.RESPONDING: if not self._to_move.cards and len(self._to_respond.cards) == 1: can_beat = self._to_respond.cards.cards_that_can_beat( self._on_table[-1]) if can_beat: return if not (self._player1.cards or self._player2.cards): self._winner = None elif not self._player1.cards: self._winner = self._player1 elif not self._player2.cards: self._winner = self._player2 self._state = None if self._logger_enabled: if self._on_table: self._logger.log_after_move(self._on_table, self._on_table.given_more) self._logger.log_after_game(self.winner) if self._log_filename: self._logger.write_to_file(self._log_filename, self._overwrite_log) @property def state(self): return self._state @property def on_table(self): return self._on_table[:] @property def winner(self): if self._winner == self._player1: return self.PLAYER1 elif self._winner == self._player2: return self.PLAYER2 return None @property def to_move(self): if self._to_move == self._player1: return self.PLAYER1 return self.PLAYER2 @property def deck_count(self): return len(self._deck)
class GameController(object): MOVER = 'mover' RESPONDER = 'responder' PLAYER1 = 'player1' PLAYER2 = 'player2' class States: DEALING = 'dealing' MOVING = 'moving' RESPONDING = 'responding' GIVING_MORE = 'giving_more' def __init__(self, player1_name='', player2_name='', log_filename='', overwrite_log=False): self._player1 = Player(player1_name) self._player2 = Player(player2_name) self._winner = None self._state = None self._log_filename = log_filename self._overwrite_log = overwrite_log self._logger = GameLogger() self._logger_enabled = True def start_new_game(self, ignore_winner=True): self._deck = list(DurakCard.all()) random.shuffle(self._deck) self._trump = self._deck[-1] self._logger.reset() if self._logger_enabled: self._logger.log_before_game( self._player1.name, self._player2.name, self._deck, self._trump ) self._player1.cards = CardSet(cards=self._deck[:6], trump=self._trump) self._player2.cards = CardSet( cards=self._deck[6:12], trump=self._trump ) self._deck = self._deck[12:] if not ignore_winner and self._winner is not None: self._to_move = self._winner else: self._to_move = self._get_first_to_move_by_trump() self._winner = None self._discarded = [] self._on_table = Table() self._state = self.States.MOVING self._no_response = False if self._logger_enabled: self._logger.log_before_move( set(self._player1.cards), set(self._player2.cards), self.to_move, self.deck_count, ) return { 'player1_cards': CardSet(self._player1.cards, trump=self._trump), 'player2_cards': CardSet(self._player2.cards, trump=self._trump), 'trump': DurakCard(self._trump), } def _get_first_to_move_by_trump(self): lowest_trump1 = self._player1.cards.lowest_trump() lowest_trump2 = self._player2.cards.lowest_trump() if lowest_trump1 is not None and lowest_trump2 is not None: if lowest_trump1 < lowest_trump2: return self._player1 else: return self._player2 elif lowest_trump1 is None and lowest_trump2 is not None: return self._player2 elif lowest_trump1 is not None and lowest_trump2 is None: return self._player1 else: return random.choice([self._player1, self._player2]) def _get_game_data_for(self, player): return { 'trump': str(DurakCard(self._trump)), 'deck_count': self.deck_count, 'enemy_count': len(self._get_enemy_of(player).cards), 'on_table': map(str, self._on_table), 'discarded': map(str, self._discarded), } def get_game_data_for(self, player): assert player in ( self.MOVER, self.RESPONDER, self.PLAYER1, self.PLAYER2 ) if player == self.MOVER: return self._get_game_data_for(self._to_move) elif player == self.RESPONDER: return self._get_game_data_for(self._to_respond) elif player == self.PLAYER1: return self._get_game_data_for(self._player1) elif player == self.PLAYER2: return self._get_game_data_for(self._player2) def _get_enemy_of(self, player): assert player in (self._player1, self._player2) if player is self._player1: return self._player2 else: return self._player1 @property def _to_respond(self): return self._get_enemy_of(self._to_move) def is_player1_to_move(self): return (self._to_move is self._player1) def register_move(self, card): if self._state != self.States.MOVING: raise exes.InvalidAction( expected=self._state, got=self.States.MOVING ) if card is None: if not self._on_table: raise exes.CardIsExpected self._state = self.States.DEALING return card = DurakCard(card) if card not in self._to_move.cards: raise exes.PlayerDoesNotHaveCard(card) if card not in (self._to_move.cards .cards_that_can_be_added_to(self._on_table)): raise exes.InvalidCard( 'Can not move with card %s (on table: %s)' % ( card, self._on_table ) ) self._to_move.cards.remove(card) self._on_table.append(card) self._state = self.States.RESPONDING self._check_for_game_over() def register_response(self, card): if self._state != self.States.RESPONDING: raise exes.InvalidAction( expected=self._state, got=self.States.RESPONDING ) if card is None: self._no_response = True if self._to_move.cards: self._state = self.States.GIVING_MORE else: self._state = self.States.DEALING return card = DurakCard(card) card_to_beat = self._on_table[-1] if card not in self._to_respond.cards: raise exes.PlayerDoesNotHaveCard(card) if card not in (self._to_respond.cards .cards_that_can_beat(card_to_beat)): raise exes.InvalidCard( 'Card %s can not beat card %s (trump is %s)' % ( card, card_to_beat, self._trump ) ) self._to_respond.cards.remove(card) self._on_table.append(card) if not (self._to_respond.cards and self._to_move.cards): self._state = self.States.DEALING else: self._state = self.States.MOVING self._check_for_game_over() def register_give_more(self, cards): assert self._no_response if self._state != self.States.GIVING_MORE: raise exes.InvalidAction( expected=self._state, got=self.States.GIVING_MORE ) if not cards: self._state = self.States.DEALING return # нельзя давать больше карт, чем есть у отбивающегося max_cards = len(self._to_respond.cards) - 1 cards = set(map(DurakCard, cards)) if len(cards) > max_cards: raise exes.TooMuchGiveMoreCards(len(cards), max_cards) invalid_cards = cards - self._to_move.cards if invalid_cards: raise exes.PlayerDoesNotHaveCard(*invalid_cards) allowed_cards = self._to_move.cards.cards_that_can_be_added_to( self._on_table ) invalid_cards = cards - set(allowed_cards) if invalid_cards: raise exes.InvalidCard( 'Can not give more cards %s (on table: %s)' % ( invalid_cards, self._on_table ) ) self._to_move.cards.difference_update(cards) self._on_table.give_more(cards) self._state = self.States.DEALING self._check_for_game_over() def deal(self): if self._state != self.States.DEALING: raise exes.InvalidAction( expected=self._state, got=self.States.DEALING ) if self._logger_enabled: self._logger.log_after_move( self._on_table, self._on_table.given_more ) player1_cards_before = CardSet(self._player1.cards, trump=self._trump) player2_cards_before = CardSet(self._player2.cards, trump=self._trump) if self._no_response: self._to_respond.cards.update(self._on_table) self._to_respond.cards.update(self._on_table.given_more) self._no_response = False else: self._to_move = self._to_respond self._discarded.extend(self._on_table) self._on_table.clear() self._state = self.States.MOVING for player in (self._to_respond, self._to_move): if not self._deck: break cards_needed = 6 - len(player.cards) if cards_needed > 0: player.cards.update(self._deck[:cards_needed]) self._deck = self._deck[cards_needed:] self._check_for_game_over() if not self.is_game_over(): if self._logger_enabled: self._logger.log_before_move( set(self._player1.cards), set(self._player2.cards), self.to_move, self.deck_count, ) return { 'player1_cards': ( CardSet(self._player1.cards, trump=self._trump) - player1_cards_before ), 'player2_cards': ( CardSet(self._player2.cards, trump=self._trump) - player2_cards_before ), } def is_game_over(self): return (self.state is None) def _check_for_game_over(self): if self._deck: return if self._player1.cards and self._player2.cards: return if self._state == self.States.RESPONDING: if not self._to_move.cards and len(self._to_respond.cards) == 1: can_beat = self._to_respond.cards.cards_that_can_beat( self._on_table[-1] ) if can_beat: return if not (self._player1.cards or self._player2.cards): self._winner = None elif not self._player1.cards: self._winner = self._player1 elif not self._player2.cards: self._winner = self._player2 self._state = None if self._logger_enabled: if self._on_table: self._logger.log_after_move( self._on_table, self._on_table.given_more ) self._logger.log_after_game(self.winner) if self._log_filename: self._logger.write_to_file( self._log_filename, self._overwrite_log ) @property def state(self): return self._state @property def on_table(self): return self._on_table[:] @property def winner(self): if self._winner == self._player1: return self.PLAYER1 elif self._winner == self._player2: return self.PLAYER2 return None @property def to_move(self): if self._to_move == self._player1: return self.PLAYER1 return self.PLAYER2 @property def deck_count(self): return len(self._deck)
def setUp(self): self.logger = GameLogger()
class GameLoggerTest(unittest.TestCase): def setUp(self): self.logger = GameLogger() def assertAlmostNow(self, iso_datetime): now = datetime.now() dt = datetime.strptime(iso_datetime, '%Y-%m-%dT%H:%M:%S.%f') self.assertAlmostEqual(dt, now, delta=timedelta(seconds=2)) def test_reset(self): self.logger._log = {'something': True} self.logger.reset() self.assertDictEqual(self.logger._log, {'moves': []}) def test_log_before_game(self): self.logger.log_before_game( 'name1', 'name2', [DurakCard('6H')], DurakCard('7H') ) self.assertDictContainsSubset( { 'player1_name': 'name1', 'player2_name': 'name2', 'deck': ['6H'], 'opened_trump': '7H', }, self.logger._log ) self.assertAlmostNow(self.logger._log['started_at']) def test_log_after_game(self): self.logger.log_after_game(self.logger.PLAYER1) self.assertEqual(self.logger._log['result'], '1-0') self.assertAlmostNow(self.logger._log['ended_at']) def test_log_before_move(self): self.logger.log_before_move( [DurakCard('6H')], [DurakCard('7H')], self.logger.PLAYER1, 1 ) self.assertEqual(len(self.logger._log['moves']), 1) self.assertDictEqual( self.logger._log['moves'][0], { 'player1_cards': ['6H'], 'player2_cards': ['7H'], 'to_move': self.logger.PLAYER1, 'deck_count': 1, } ) def test_log_after_move(self): self.logger._log['moves'].append({}) self.logger.log_after_move( [DurakCard('6H'), DurakCard('7H')], {DurakCard('QH')} ) self.assertEqual(len(self.logger._log['moves']), 1) self.assertDictEqual( self.logger._log['moves'][0], {'moves_and_responds': ['6H', '7H'], 'given_more': ['QH']} ) def test_get_result(self): self.assertEqual(self.logger._get_result(self.logger.PLAYER1), '1-0') self.assertEqual(self.logger._get_result(self.logger.PLAYER2), '0-1') self.assertEqual(self.logger._get_result(None), 's-s') def test_log_title(self): self.logger.log_before_game( 'name1', 'name2', [DurakCard('6H')], DurakCard('7H') ) self.logger.log_after_game(self.logger.PLAYER2) self.assertEqual( self.logger._log_title, '%s, name1 - name2 (0-1)' % self.logger._log['started_at'].split('.')[0] ) def test_write_to_file(self): filename = 'filename' self.logger.log_before_game( 'name1', 'name2', [DurakCard('6H')], DurakCard('7H') ) self.logger.log_after_game(self.logger.PLAYER2) open_mock = mock_open() with patch('__builtin__.open', open_mock, create=True): self.logger.write_to_file(filename) open_mock.assert_called_once_with(filename, 'a') open_mock().write.assert_called_once_with( '####%s####\n%s\n' % ( self.logger._log_title, json.dumps(self.logger._log) ) ) def test_write_to_file_with_overwrite(self): filename = 'filename' self.logger.log_before_game( 'name1', 'name2', [DurakCard('6H')], DurakCard('7H') ) self.logger.log_after_game(self.logger.PLAYER2) open_mock = mock_open() with patch('__builtin__.open', open_mock, create=True): self.logger.write_to_file(filename, overwrite=True) open_mock.assert_called_once_with(filename, 'w') open_mock().write.assert_called_once_with( '####%s####\n%s\n' % ( self.logger._log_title, json.dumps(self.logger._log) ) )