예제 #1
0
    def setUp(self):
        self.open_mock = MagicMock(spec=open)
        self._set_file_contents(self.LOG_FILE_CONTENTS)
        self.open_patcher = patch(
            '__builtin__.open', self.open_mock, create=True
        )
        self.open_patcher.start()

        self.log_viewer = LogViewer(self.FILENAME)

        self.game0 = json.loads(self.GAME0)
예제 #2
0
파일: view_log.py 프로젝트: drtyrsa/durak
    def _menu_on_open(self, event=None):
        last_directory = get_setting(self.LAST_DIR_SETTING,
                                     os.path.expanduser('~'))
        dialog = wx.FileDialog(self, u'Выберите файл лога', last_directory, '',
                               '*', wx.OPEN)
        if dialog.ShowModal() != wx.ID_OK:
            self.Close()

        filename = dialog.GetPath()
        dialog.Destroy()

        self._log_viever = LogViewer(filename)
        set_setting(self.LAST_DIR_SETTING, os.path.dirname(filename))

        self._menu_on_select_game()
예제 #3
0
파일: view_log.py 프로젝트: drtyrsa/durak
    def _menu_on_open(self, event=None):
        last_directory = get_setting(
            self.LAST_DIR_SETTING, os.path.expanduser('~')
        )
        dialog = wx.FileDialog(
            self, u'Выберите файл лога', last_directory, '', '*', wx.OPEN
        )
        if dialog.ShowModal() != wx.ID_OK:
            self.Close()

        filename = dialog.GetPath()
        dialog.Destroy()

        self._log_viever = LogViewer(filename)
        set_setting(self.LAST_DIR_SETTING, os.path.dirname(filename))

        self._menu_on_select_game()
예제 #4
0
파일: view_log.py 프로젝트: drtyrsa/durak
class ViewLogFrame(wx.Frame):
    WIDTH = 800
    HEIGHT = 500

    LAST_DIR_SETTING = 'last_log_dir'

    def __init__(self):
        super(ViewLogFrame, self).__init__(parent=None,
                                           title='Durak log viewer',
                                           size=(self.WIDTH, self.HEIGHT))

        self._log_viever = None

        self._create_layout()

    def _create_layout(self):
        self.Bind(wx.EVT_CLOSE, self._on_close)

        self._panel = wx.Panel(parent=self)

        self._top_player_sizer = LabeledCardSizer(
            wx.HORIZONTAL,
            parent=self._panel,
        )
        self._bottom_player_sizer = LabeledCardSizer(
            wx.HORIZONTAL,
            parent=self._panel,
        )

        self._table_sizer = wx.FlexGridSizer(cols=2, rows=1)
        self._table_sizer.AddGrowableCol(0)

        self._table = TablePanel(parent=self._panel)
        self._table_sizer.Add(self._table, proportion=1)

        self._deck = DeckPanel(self._panel, size=(100, 130))
        self._table_sizer.Add(self._deck)

        self._control_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self._to_game_start_button = wx.Button(parent=self._panel, label=u'<<')
        self._to_game_start_button.Bind(wx.EVT_BUTTON,
                                        self._menu_on_to_game_start)
        self._prev_button = wx.Button(parent=self._panel, label=u'<')
        self._prev_button.Bind(wx.EVT_BUTTON, self._menu_on_prev)
        self._next_button = wx.Button(parent=self._panel, label=u'>')
        self._next_button.Bind(wx.EVT_BUTTON, self._menu_on_next)
        self._to_game_end_button = wx.Button(parent=self._panel, label=u'>>')
        self._to_game_end_button.Bind(wx.EVT_BUTTON, self._menu_on_to_game_end)
        self._control_sizer.AddMany([
            self._to_game_start_button, self._prev_button, self._next_button,
            self._to_game_end_button
        ])

        self._main_sizer = wx.FlexGridSizer(cols=1, rows=4)
        self._main_sizer.AddGrowableCol(0)
        self._main_sizer.AddGrowableRow(1)
        self._main_sizer.Add(self._top_player_sizer,
                             flag=wx.EXPAND,
                             proportion=1)
        self._main_sizer.Add(self._table_sizer,
                             flag=wx.ALIGN_CENTER_VERTICAL,
                             proportion=1)
        self._main_sizer.Add(self._control_sizer, flag=wx.EXPAND, proportion=1)
        self._main_sizer.Add(self._bottom_player_sizer,
                             flag=wx.EXPAND,
                             proportion=1)

        self._panel.SetSizer(self._main_sizer)

        self.CreateStatusBar()

        self._create_menu()

        self._panel.Layout()
        self.Refresh()
        self.Layout()

        self.PLAYER_SIZER_MAP = {
            LogViewer.PLAYER1: self._bottom_player_sizer,
            LogViewer.PLAYER2: self._top_player_sizer,
        }

        self._menu_on_open()

    def _create_menu(self):
        self.menu = wx.MenuBar()
        menu = wx.Menu()  # Файл
        item = menu.Append(wx.ID_OPEN, u'Открыть файл лога...',
                           u'Открыть файл лога')
        self.Bind(wx.EVT_MENU, self._menu_on_open, item)
        item = menu.Append(wx.ID_ANY, u'Список игр...\tCTRL+L',
                           u'Просмотреть список игр в открытом файле')
        self.Bind(wx.EVT_MENU, self._menu_on_select_game, item)
        item = menu.Append(wx.ID_EXIT, u'Выход', u'Закрыть программу')
        self.Bind(wx.EVT_MENU, self._on_close, item)
        self.menu.Append(menu, u'Файл')

        menu = wx.Menu()  # Текущая игра
        self._to_game_start_menu_item = menu.Append(wx.ID_ANY,
                                                    u'В начало <<\tHOME',
                                                    u'Перейти в начало игры')
        self.Bind(wx.EVT_MENU, self._menu_on_to_game_start,
                  self._to_game_start_menu_item)
        self._prev_menu_item = menu.Append(wx.ID_ANY, u'Назад <\tCTRL+LEFT',
                                           u'Вернуться на ход назад')
        self.Bind(wx.EVT_MENU, self._menu_on_prev, self._prev_menu_item)
        self._next_menu_item = menu.Append(wx.ID_ANY, u'Вперед >\tCTRL+RIGHT',
                                           u'Перейти на ход вперед')
        self.Bind(wx.EVT_MENU, self._menu_on_next, self._next_menu_item)
        self._to_game_end_menu_item = menu.Append(wx.ID_ANY,
                                                  u'В конец >>\tEND',
                                                  u'Перейти в конец игры')
        self.Bind(wx.EVT_MENU, self._menu_on_to_game_end,
                  self._to_game_end_menu_item)
        self.menu.Append(menu, u'Текущая игра')

        self.SetMenuBar(self.menu)

    def _on_close(self, event):
        if event.GetEventType() in wx.EVT_CLOSE.evtType:
            event.Skip()
        else:
            self.Close()

    def _menu_on_open(self, event=None):
        last_directory = get_setting(self.LAST_DIR_SETTING,
                                     os.path.expanduser('~'))
        dialog = wx.FileDialog(self, u'Выберите файл лога', last_directory, '',
                               '*', wx.OPEN)
        if dialog.ShowModal() != wx.ID_OK:
            self.Close()

        filename = dialog.GetPath()
        dialog.Destroy()

        self._log_viever = LogViewer(filename)
        set_setting(self.LAST_DIR_SETTING, os.path.dirname(filename))

        self._menu_on_select_game()

    def _menu_on_select_game(self, event=None):
        choices = [
            '%d. %s' % (i, game['title'])
            for i, game in enumerate(self._log_viever.iterindex(), start=1)
        ]
        dialog = wx.SingleChoiceDialog(
            self,
            u'Выберите игру',
            u'Список игр',
            choices,
            wx.CHOICEDLG_STYLE,
        )
        dialog.SetSize((500, 400))
        dialog.Refresh()
        if dialog.ShowModal() == wx.ID_OK:
            self._log_viever.load_game(dialog.GetSelection())
            self._bottom_player_sizer.set_label(self._log_viever.player1_name)
            self._top_player_sizer.set_label(self._log_viever.player2_name)
            self._menu_on_to_game_start()

        dialog.Destroy()

    def _menu_on_to_game_start(self, event=None):
        self._log_viever.to_game_start()

        if not self._log_viever.has_next:
            return

        self._deck.set_opened_trump(self._log_viever.opened_trump)

        for card_sizer in (self._bottom_player_sizer, self._top_player_sizer):
            card_sizer.trump = self._log_viever.opened_trump
            card_sizer.remove_all()

        log_event = self._log_viever.get_next()
        self._new_move(log_event)

        self._panel.Layout()
        self.Layout()
        self.Refresh()

        self._set_enabled_controls()

    def _menu_on_to_game_end(self, event=None):
        self._log_viever.to_game_end()

        if not self._log_viever.has_prev:
            return

        log_event = self._log_viever.get_prev()
        self._new_move(log_event, with_table=True)

        self._set_enabled_controls()

    def _menu_on_next(self, event):
        if not self._log_viever.has_next:
            return

        log_event = self._log_viever.get_next()
        if log_event['event_type'] == self._log_viever.NEW_MOVE:
            self._new_move(log_event)
            return

        card_sizer = self.PLAYER_SIZER_MAP[log_event['to_move']]
        table_method = getattr(self._table, log_event['event_type'])

        card = log_event['card']
        table_method(card)
        card_sizer.remove_card(card, do_layout=True)

        self._set_enabled_controls()

    def _menu_on_prev(self, event):
        if not self._log_viever.has_prev:
            return

        log_event = self._log_viever.get_prev()
        if log_event['event_type'] == self._log_viever.NEW_MOVE:
            self._new_move(log_event, with_table=True)
            return

        card_sizer = self.PLAYER_SIZER_MAP[log_event['to_move']]

        card = log_event['card']
        self._table.pop()
        card_sizer.add_card(card, do_layout=True)

        self._set_enabled_controls()

    def _new_move(self, log_event, with_table=False):
        assert log_event['event_type'] == self._log_viever.NEW_MOVE

        self._table.remove_all()
        self._deck.set_card_count(log_event['deck_count'])

        if with_table:
            for index, card in enumerate(log_event['moves_and_responds']):
                if index % 2:
                    self._table.respond(card)
                else:
                    self._table.move(card)

            for card in log_event['given_more']:
                self._table.give_more(card)

        card_sizers = (self._bottom_player_sizer, self._top_player_sizer)
        card_sets = (log_event['player1_cards'], log_event['player2_cards'])

        for card_sizer, card_set in zip(card_sizers, card_sets):
            if with_table:
                card_sizer.remove_all()
                cards_to_add = card_set - set(log_event['moves_and_responds'] +
                                              log_event['given_more'])
            else:
                cards_to_add = card_set - card_sizer.cards

            for card in cards_to_add:
                card_sizer.add_card(card)
            card_sizer.Layout()

        if log_event['to_move'] == self._log_viever.PLAYER1:
            self.SetStatusText(u'Атакует игрок снизу')
        else:
            self.SetStatusText(u'Атакует игрок сверху')

        self._set_enabled_controls()

    def _set_enabled_controls(self):
        all_controls = {
            self._to_game_start_button,
            self._to_game_end_button,
            self._next_button,
            self._prev_button,
            self._to_game_end_menu_item,
            self._to_game_start_menu_item,
            self._prev_menu_item,
            self._next_menu_item,
        }
        [control.Enable(False) for control in all_controls]

        if not self._log_viever or not self._log_viever.has_game_loaded:
            return

        if self._log_viever.has_prev:
            self._to_game_start_button.Enable()
            self._prev_button.Enable()
            self._to_game_start_menu_item.Enable()
            self._prev_menu_item.Enable()

        if self._log_viever.has_next:
            self._to_game_end_button.Enable()
            self._next_button.Enable()
            self._to_game_end_menu_item.Enable()
            self._next_menu_item.Enable()
예제 #5
0
class LogViewerTest(unittest.TestCase):
    FILENAME = 'filename'

    GAME0 = '{"moves": [{"to_move": "player1", "player1_cards": ["AS", "QS", "AH", "9D", "8D", "7H"], "moves_and_responds": ["7H", "TH"], "deck_count": 24, "player2_cards": ["JH", "TH", "QH", "6H", "8C", "TD"], "given_more": []}, {"to_move": "player2", "player1_cards": ["AS", "QC", "AH", "QS", "8D", "9D"], "moves_and_responds": ["6H"], "deck_count": 22, "player2_cards": ["6C", "QH", "6H", "8C", "TD", "JH"], "given_more": ["6C"]}], "ended_at": "2014-10-19T01:41:23.161425", "deck": ["QS", "8D", "AH", "AS", "7H", "9D", "TD", "JH", "QH", "TH", "8C", "6H", "QC", "6C", "JD", "9C", "9S", "KH", "AC", "8H", "KC", "8S", "TC", "7C", "TS", "9H", "6S", "6D", "7D", "JS", "KS", "AD", "KD", "JC", "QD", "7S"], "player2_name": "ENGINE", "result": "1-0", "player1_name": "HUMAN", "started_at": "2014-10-19T01:40:51.473966", "opened_trump": "7S"}'

    LOG_FILE_CONTENTS = '''\
####2014-10-19T01:40:51, HUMAN - ENGINE (1-0)####
%s
####2014-11-19T01:40:51, VOVAN - KOLYAN (1-0)####
{"moves": [{"to_move": "player1", "player1_cards": ["AS", "QS", "AH", "9D", "8D", "7H"], "moves_and_responds": ["7H", "TH"], "deck_count": 24, "player2_cards": ["JH", "TH", "QH", "6H", "8C", "TD"], "given_more": []}], "ended_at": "2014-10-19T01:41:23.161425", "deck": ["QS", "8D", "AH", "AS", "7H", "9D", "TD", "JH", "QH", "TH", "8C", "6H", "QC", "7C", "JD", "9C", "9S", "KH", "AC", "8H", "KC", "8S", "TC", "6C", "TS", "9H", "6S", "6D", "7D", "JS", "KS", "AD", "KD", "JC", "QD", "7S"], "player2_name": "KOLYAN", "result": "1-0", "player1_name": "VOVAN", "started_at": "2014-10-19T01:40:51.473966", "opened_trump": "7S"}
''' % GAME0

    def setUp(self):
        self.open_mock = MagicMock(spec=open)
        self._set_file_contents(self.LOG_FILE_CONTENTS)
        self.open_patcher = patch(
            '__builtin__.open', self.open_mock, create=True
        )
        self.open_patcher.start()

        self.log_viewer = LogViewer(self.FILENAME)

        self.game0 = json.loads(self.GAME0)

    def tearDown(self):
        self.open_patcher.stop()

    def _set_file_contents(self, contents):
        self.open_mock.return_value.__enter__.return_value = StringIO(contents)

    def test_fill_game_index_parsing(self):
        self.assertEqual(self.log_viewer._game_index, [
            {
                'prev_end_offset': 0,
                'start_offset': 50,
                'title': '2014-10-19T01:40:51, HUMAN - ENGINE (1-0)'
            },
            {
                'prev_end_offset': 874,
                'start_offset': 924,
                'title': '2014-11-19T01:40:51, VOVAN - KOLYAN (1-0)'
            },
        ])

    def test_io_error_in_fill_game_index_is_invalid_log_format_error(self):
        self.open_mock.side_effect = IOError

        with self.assertRaises(InvalidLogFormat):
            self.log_viewer._fill_game_index()

    def test_no_games_in_file_is_invalid_log_format_error(self):
        self._set_file_contents('no games for you')

        with self.assertRaises(InvalidLogFormat):
            self.log_viewer._fill_game_index()

    def test_iterindex_returns_iterator_over_game_index(self):
        self.assertItemsEqual(
            self.log_viewer.iterindex(), iter(self.log_viewer._game_index)
        )

    def test_load_game_loads_game_according_to_index(self):
        self.log_viewer.load_game(0)
        self.assertEqual(self.log_viewer.player1_name, 'HUMAN')

        self.log_viewer.load_game(1)
        self.assertEqual(self.log_viewer.player1_name, 'VOVAN')

    def test_io_error_in_load_game_is_invalid_log_format_error(self):
        self.open_mock.side_effect = IOError

        with self.assertRaises(InvalidLogFormat):
            self.log_viewer.load_game(0)

    def test_value_error_in_load_game_is_invalid_log_format_error(self):
        self._set_file_contents('no games for you')

        with self.assertRaises(InvalidLogFormat):
            self.log_viewer.load_game(0)

    def test_has_game_loaded_property(self):
        self.assertFalse(self.log_viewer.has_game_loaded)

        self.log_viewer.load_game(0)

        self.assertTrue(self.log_viewer.has_game_loaded)

    def test_get_new_move(self):
        self.log_viewer.load_game(0)

        move = self.game0['moves'][0]
        result = self.log_viewer._get_new_move(move)
        self.assertItemsEqual(result.keys(), move.keys() + ['event_type'])
        self.assertEqual(result['event_type'], self.log_viewer.NEW_MOVE)

    def test_opened_trump_property(self):
        self.log_viewer.load_game(0)

        self.assertEqual(
            self.log_viewer.opened_trump,
            DurakCard(self.game0['opened_trump'])
        )

    def test_to_game_start(self):
        self.log_viewer.load_game(0)

        self.log_viewer._current_move = 666
        self.log_viewer._current_table_index = 777

        self.log_viewer.to_game_start()

        self.assertEqual(
            self.log_viewer._current_move,
            self.log_viewer.INITIAL_CURRENT_MOVE
        )
        self.assertEqual(
            self.log_viewer._current_table_index,
            self.log_viewer.INITIAL_CURRENT_TABLE_INDEX
        )

    def test_to_game_end(self):
        self.log_viewer.load_game(0)

        self.log_viewer._current_move = 666
        self.log_viewer._current_table_index = 777

        self.log_viewer.to_game_end()

        self.assertEqual(
            self.log_viewer._current_move, len(self.game0['moves'])
        )
        self.assertEqual(
            self.log_viewer._current_table_index,
            self.log_viewer.INITIAL_CURRENT_TABLE_INDEX + 1
        )

    def test_player1_name_property(self):
        self.log_viewer.load_game(0)

        self.assertEqual(
            self.log_viewer.player1_name, self.game0['player1_name']
        )

    def test_player2_name_property(self):
        self.log_viewer.load_game(0)

        self.assertEqual(
            self.log_viewer.player2_name, self.game0['player2_name']
        )

    def test_get_opposite_player(self):
        self.assertEqual(
            self.log_viewer._get_opposite_player(self.log_viewer.PLAYER1),
            self.log_viewer.PLAYER2
        )
        self.assertEqual(
            self.log_viewer._get_opposite_player(self.log_viewer.PLAYER2),
            self.log_viewer.PLAYER1
        )

    def test_to_card_set(self):
        self.log_viewer.load_game(0)

        cards = ['AS', 'QS', 'AH', '9D', '8D', '7H']

        result = self.log_viewer._to_card_set(cards)
        self.assertTrue(isinstance(result, CardSet))
        self.assertItemsEqual(result, map(DurakCard, cards))
        self.assertEqual(result._trump, DurakCard(self.game0['opened_trump']))

    def test_has_next(self):
        self.log_viewer.load_game(0)
        self.assertTrue(self.log_viewer.has_next)

        self.log_viewer._current_move = 1
        self.log_viewer._current_table_index = 1
        self.assertTrue(self.log_viewer.has_next)

        self.log_viewer._current_table_index = 2
        self.assertFalse(self.log_viewer.has_next)

        self.log_viewer._current_move = 3
        self.log_viewer._current_table_index = (
            self.log_viewer.INITIAL_CURRENT_TABLE_INDEX
        )
        self.assertFalse(self.log_viewer.has_next)

    def test_has_prev(self):
        self.log_viewer._current_move = 666
        self.log_viewer._current_table_index = 777
        self.assertTrue(self.log_viewer.has_prev)

        self.log_viewer._current_move = self.log_viewer.INITIAL_CURRENT_MOVE
        self.assertTrue(self.log_viewer.has_prev)

        self.log_viewer._current_table_index = (
            self.log_viewer.INITIAL_CURRENT_TABLE_INDEX
        )
        self.assertFalse(self.log_viewer.has_prev)

    def test_get_next_and_get_prev(self):
        # проверяю оба метода в одном тесте, чтобы протестировать в условиях,
        # приближенных к реальным, а не городить моки
        EXPECTED_EVENTS = [
            {
                'to_move': self.log_viewer.PLAYER1,
                'event_type': self.log_viewer.NEW_MOVE,
                'moves_and_responds': [DurakCard('7H'), DurakCard('TH')],
                'deck_count': 24,
                'given_more': [],
                'player2_cards': set(map(DurakCard, (
                    '6H', '8C', 'JH', 'TH', 'QH', 'TD'
                ))),
                'player1_cards': set(map(DurakCard, (
                    'QS', 'AH', '9D', '8D', '7H', 'AS'
                ))),
            },
            {
                'to_move': self.log_viewer.PLAYER1,
                'event_type': self.log_viewer.MOVE,
                'card': DurakCard('7H'),
            },
            {
                'to_move': self.log_viewer.PLAYER2,
                'event_type': self.log_viewer.RESPOND,
                'card': DurakCard('TH'),
            },
            {
                'to_move': self.log_viewer.PLAYER2,
                'event_type': self.log_viewer.NEW_MOVE,
                'moves_and_responds': [DurakCard('6H')],
                'deck_count': 22,
                'given_more': [DurakCard('6C')],
                'player2_cards': set(map(DurakCard, (
                    '6H', '8C', 'JH', '6C', 'QH', 'TD'
                ))),
                'player1_cards': set(map(DurakCard, (
                    'QS', 'AH', '9D', '8D', 'QC', 'AS'
                ))),
            },
            {
                'to_move': self.log_viewer.PLAYER2,
                'event_type': self.log_viewer.MOVE,
                'card': DurakCard('6H'),
            },
            {
                'to_move': self.log_viewer.PLAYER2,
                'event_type': self.log_viewer.GIVE_MORE,
                'card': DurakCard('6C'),
            },
        ]

        self.log_viewer.load_game(0)

        expected = iter(EXPECTED_EVENTS)
        while self.log_viewer.has_next:
            log_event = self.log_viewer.get_next()
            self.assertEqual(log_event, next(expected))

        first_event = EXPECTED_EVENTS.pop(0)
        EXPECTED_EVENTS[2] = first_event
        expected = iter(reversed(EXPECTED_EVENTS))
        while self.log_viewer.has_prev:
            log_event = self.log_viewer.get_prev()
            self.assertEqual(log_event, next(expected))
예제 #6
0
파일: view_log.py 프로젝트: drtyrsa/durak
class ViewLogFrame(wx.Frame):
    WIDTH = 800
    HEIGHT = 500

    LAST_DIR_SETTING = 'last_log_dir'

    def __init__(self):
        super(ViewLogFrame, self).__init__(
            parent=None,
            title='Durak log viewer',
            size=(self.WIDTH, self.HEIGHT)
        )

        self._log_viever = None

        self._create_layout()

    def _create_layout(self):
        self.Bind(wx.EVT_CLOSE, self._on_close)

        self._panel = wx.Panel(parent=self)

        self._top_player_sizer = LabeledCardSizer(
            wx.HORIZONTAL,
            parent=self._panel,
        )
        self._bottom_player_sizer = LabeledCardSizer(
            wx.HORIZONTAL,
            parent=self._panel,
        )

        self._table_sizer = wx.FlexGridSizer(cols=2, rows=1)
        self._table_sizer.AddGrowableCol(0)

        self._table = TablePanel(parent=self._panel)
        self._table_sizer.Add(self._table, proportion=1)

        self._deck = DeckPanel(self._panel, size=(100, 130))
        self._table_sizer.Add(self._deck)

        self._control_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self._to_game_start_button = wx.Button(parent=self._panel, label=u'<<')
        self._to_game_start_button.Bind(
            wx.EVT_BUTTON, self._menu_on_to_game_start
        )
        self._prev_button = wx.Button(parent=self._panel, label=u'<')
        self._prev_button.Bind(wx.EVT_BUTTON, self._menu_on_prev)
        self._next_button = wx.Button(parent=self._panel, label=u'>')
        self._next_button.Bind(wx.EVT_BUTTON, self._menu_on_next)
        self._to_game_end_button = wx.Button(parent=self._panel, label=u'>>')
        self._to_game_end_button.Bind(wx.EVT_BUTTON, self._menu_on_to_game_end)
        self._control_sizer.AddMany([
            self._to_game_start_button,
            self._prev_button,
            self._next_button,
            self._to_game_end_button
        ])

        self._main_sizer = wx.FlexGridSizer(cols=1, rows=4)
        self._main_sizer.AddGrowableCol(0)
        self._main_sizer.AddGrowableRow(1)
        self._main_sizer.Add(
            self._top_player_sizer, flag=wx.EXPAND, proportion=1
        )
        self._main_sizer.Add(
            self._table_sizer, flag=wx.ALIGN_CENTER_VERTICAL, proportion=1
        )
        self._main_sizer.Add(self._control_sizer, flag=wx.EXPAND, proportion=1)
        self._main_sizer.Add(
            self._bottom_player_sizer, flag=wx.EXPAND, proportion=1
        )

        self._panel.SetSizer(self._main_sizer)

        self.CreateStatusBar()

        self._create_menu()

        self._panel.Layout()
        self.Refresh()
        self.Layout()

        self.PLAYER_SIZER_MAP = {
            LogViewer.PLAYER1: self._bottom_player_sizer,
            LogViewer.PLAYER2: self._top_player_sizer,
        }

        self._menu_on_open()

    def _create_menu(self):
        self.menu = wx.MenuBar()
        menu = wx.Menu()  # Файл
        item = menu.Append(
            wx.ID_OPEN, u'Открыть файл лога...', u'Открыть файл лога'
        )
        self.Bind(wx.EVT_MENU, self._menu_on_open, item)
        item = menu.Append(
            wx.ID_ANY,
            u'Список игр...\tCTRL+L',
            u'Просмотреть список игр в открытом файле'
        )
        self.Bind(wx.EVT_MENU, self._menu_on_select_game, item)
        item = menu.Append(wx.ID_EXIT, u'Выход', u'Закрыть программу')
        self.Bind(wx.EVT_MENU, self._on_close, item)
        self.menu.Append(menu, u'Файл')

        menu = wx.Menu()  # Текущая игра
        self._to_game_start_menu_item = menu.Append(
            wx.ID_ANY, u'В начало <<\tHOME', u'Перейти в начало игры'
        )
        self.Bind(
            wx.EVT_MENU,
            self._menu_on_to_game_start,
            self._to_game_start_menu_item
        )
        self._prev_menu_item = menu.Append(
            wx.ID_ANY, u'Назад <\tCTRL+LEFT', u'Вернуться на ход назад'
        )
        self.Bind(wx.EVT_MENU, self._menu_on_prev, self._prev_menu_item)
        self._next_menu_item = menu.Append(
            wx.ID_ANY, u'Вперед >\tCTRL+RIGHT', u'Перейти на ход вперед'
        )
        self.Bind(wx.EVT_MENU, self._menu_on_next, self._next_menu_item)
        self._to_game_end_menu_item = menu.Append(
            wx.ID_ANY, u'В конец >>\tEND', u'Перейти в конец игры'
        )
        self.Bind(
            wx.EVT_MENU,
            self._menu_on_to_game_end,
            self._to_game_end_menu_item
        )
        self.menu.Append(menu, u'Текущая игра')

        self.SetMenuBar(self.menu)

    def _on_close(self, event):
        if event.GetEventType() in wx.EVT_CLOSE.evtType:
            event.Skip()
        else:
            self.Close()

    def _menu_on_open(self, event=None):
        last_directory = get_setting(
            self.LAST_DIR_SETTING, os.path.expanduser('~')
        )
        dialog = wx.FileDialog(
            self, u'Выберите файл лога', last_directory, '', '*', wx.OPEN
        )
        if dialog.ShowModal() != wx.ID_OK:
            self.Close()

        filename = dialog.GetPath()
        dialog.Destroy()

        self._log_viever = LogViewer(filename)
        set_setting(self.LAST_DIR_SETTING, os.path.dirname(filename))

        self._menu_on_select_game()

    def _menu_on_select_game(self, event=None):
        choices = [
            '%d. %s' % (i, game['title']) for i, game
            in enumerate(self._log_viever.iterindex(), start=1)
        ]
        dialog = wx.SingleChoiceDialog(
            self,
            u'Выберите игру',
            u'Список игр',
            choices,
            wx.CHOICEDLG_STYLE,
        )
        dialog.SetSize((500, 400))
        dialog.Refresh()
        if dialog.ShowModal() == wx.ID_OK:
            self._log_viever.load_game(dialog.GetSelection())
            self._bottom_player_sizer.set_label(self._log_viever.player1_name)
            self._top_player_sizer.set_label(self._log_viever.player2_name)
            self._menu_on_to_game_start()

        dialog.Destroy()

    def _menu_on_to_game_start(self, event=None):
        self._log_viever.to_game_start()

        if not self._log_viever.has_next:
            return

        self._deck.set_opened_trump(self._log_viever.opened_trump)

        for card_sizer in (self._bottom_player_sizer, self._top_player_sizer):
            card_sizer.trump = self._log_viever.opened_trump
            card_sizer.remove_all()

        log_event = self._log_viever.get_next()
        self._new_move(log_event)

        self._panel.Layout()
        self.Layout()
        self.Refresh()

        self._set_enabled_controls()

    def _menu_on_to_game_end(self, event=None):
        self._log_viever.to_game_end()

        if not self._log_viever.has_prev:
            return

        log_event = self._log_viever.get_prev()
        self._new_move(log_event, with_table=True)

        self._set_enabled_controls()

    def _menu_on_next(self, event):
        if not self._log_viever.has_next:
            return

        log_event = self._log_viever.get_next()
        if log_event['event_type'] == self._log_viever.NEW_MOVE:
            self._new_move(log_event)
            return

        card_sizer = self.PLAYER_SIZER_MAP[log_event['to_move']]
        table_method = getattr(self._table, log_event['event_type'])

        card = log_event['card']
        table_method(card)
        card_sizer.remove_card(card, do_layout=True)

        self._set_enabled_controls()

    def _menu_on_prev(self, event):
        if not self._log_viever.has_prev:
            return

        log_event = self._log_viever.get_prev()
        if log_event['event_type'] == self._log_viever.NEW_MOVE:
            self._new_move(log_event, with_table=True)
            return

        card_sizer = self.PLAYER_SIZER_MAP[log_event['to_move']]

        card = log_event['card']
        self._table.pop()
        card_sizer.add_card(card, do_layout=True)

        self._set_enabled_controls()

    def _new_move(self, log_event, with_table=False):
        assert log_event['event_type'] == self._log_viever.NEW_MOVE

        self._table.remove_all()
        self._deck.set_card_count(log_event['deck_count'])

        if with_table:
            for index, card in enumerate(log_event['moves_and_responds']):
                if index % 2:
                    self._table.respond(card)
                else:
                    self._table.move(card)


            for card in log_event['given_more']:
                self._table.give_more(card)


        card_sizers = (self._bottom_player_sizer, self._top_player_sizer)
        card_sets = (log_event['player1_cards'], log_event['player2_cards'])

        for card_sizer, card_set in zip(card_sizers, card_sets):
            if with_table:
                card_sizer.remove_all()
                cards_to_add = card_set - set(
                    log_event['moves_and_responds'] + log_event['given_more']
                )
            else:
                cards_to_add = card_set - card_sizer.cards

            for card in cards_to_add:
                card_sizer.add_card(card)
            card_sizer.Layout()

        if log_event['to_move'] == self._log_viever.PLAYER1:
            self.SetStatusText(u'Атакует игрок снизу')
        else:
            self.SetStatusText(u'Атакует игрок сверху')

        self._set_enabled_controls()

    def _set_enabled_controls(self):
        all_controls = {
            self._to_game_start_button,
            self._to_game_end_button,
            self._next_button,
            self._prev_button,
            self._to_game_end_menu_item,
            self._to_game_start_menu_item,
            self._prev_menu_item,
            self._next_menu_item,
        }
        [control.Enable(False) for control in all_controls]

        if not self._log_viever or not self._log_viever.has_game_loaded:
            return

        if self._log_viever.has_prev:
            self._to_game_start_button.Enable()
            self._prev_button.Enable()
            self._to_game_start_menu_item.Enable()
            self._prev_menu_item.Enable()

        if self._log_viever.has_next:
            self._to_game_end_button.Enable()
            self._next_button.Enable()
            self._to_game_end_menu_item.Enable()
            self._next_menu_item.Enable()