Beispiel #1
0
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)
Beispiel #2
0
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)
Beispiel #3
0
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)
                )
            )