def test_do(self, roll, from_space, to_space, post_do_from_space,
                post_do_to_space):
        modified_game_state = deepcopy(self.game_state)
        # Put a blue piece on the main board to be sent back to waiting
        modified_game_state.do(
            MoveContainer(
                from_space=BoardSpace(
                    kind='waiting',
                    idx=3,
                    occupied_by='yellow',
                    allowed_occupants=['yellow', EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='main',
                                    idx=0,
                                    occupied_by=EMPTY_SYMBOL,
                                    allowed_occupants=self.player_names +
                                    [EMPTY_SYMBOL])))

        modified_game_state.do(MoveContainer(from_space, to_space))

        assert modified_game_state.get_board_space(
            kind=from_space.kind, idx=from_space.idx,
            player_name='red') == post_do_from_space

        assert modified_game_state.get_board_space(
            kind=to_space.kind, idx=to_space.idx,
            player_name='red') == post_do_to_space
Example #2
0
def test_assert_game_states_equal():
    # Set attributes for repeated use below
    player_names = ['red', 'blue', 'green', 'yellow']
    pieces_per_player = 4
    section_length = 4
    number_of_dice_faces = 6
    game_state = GameState(player_names=player_names,
                           pieces_per_player=pieces_per_player,
                           section_length=section_length,
                           number_of_dice_faces=number_of_dice_faces)
    game_state.initialize()

    assert_game_states_equal(game_state, game_state)

    other = deepcopy(game_state)
    other.do(
        MoveContainer(from_space=BoardSpace(
            kind='waiting',
            idx=0,
            occupied_by='red',
            allowed_occupants=['red', EMPTY_SYMBOL]),
                      to_space=BoardSpace(kind='main',
                                          idx=0,
                                          occupied_by=EMPTY_SYMBOL,
                                          allowed_occupants=player_names +
                                          [EMPTY_SYMBOL])))

    with pytest.raises(AssertionError):
        assert_game_states_equal(game_state, other)
    def test_play_finished(self, monkeypatch):

        played_client = deepcopy(self.client)
        played_game_state = played_client.get_game_state()

        idx_winner = 0
        winner_name = played_game_state.player_names[idx_winner]

        # move all pieces but first to home
        for idx in range(1, played_game_state.pieces_per_player):
            played_game_state.do(
                MoveContainer(from_space=BoardSpace(
                    kind='waiting',
                    idx=idx,
                    occupied_by=winner_name,
                    allowed_occupants=[winner_name, EMPTY_SYMBOL]),
                              to_space=BoardSpace(kind='home',
                                                  idx=idx,
                                                  occupied_by=EMPTY_SYMBOL,
                                                  allowed_occupants=[
                                                      winner_name, EMPTY_SYMBOL
                                                  ])))

        # Move first piece to one before home
        winner_enter_main_idx = played_game_state\
            .get_main_entry_index(winner_name)
        n_players = len(played_client.players)
        winner_prehome_idx = (winner_enter_main_idx - 1) % \
            played_client.main_board_section_length * n_players

        print(winner_prehome_idx)
        played_game_state.do(
            MoveContainer(
                from_space=BoardSpace(
                    kind='waiting',
                    idx=0,
                    occupied_by=winner_name,
                    allowed_occupants=[winner_name, EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='main',
                                    idx=winner_prehome_idx,
                                    occupied_by=EMPTY_SYMBOL,
                                    allowed_occupants=self.player_names +
                                    [EMPTY_SYMBOL])))

        monkeypatch.setattr(played_client, 'roll', lambda: 1)
        # Only one move possible, advance 1 to last open home spot
        monkeypatch.setattr(builtins, 'input', lambda x: 0)

        winner, _ = played_client.play()
        assert winner == played_client.players[idx_winner]
    def test_one_round_of_play(self, mocker, monkeypatch):
        played_client = deepcopy(self.client)
        expected_client = deepcopy(self.client)

        # Set roll values to 6 then 1 for each player turn
        mocker.patch.object(played_client, 'roll', side_effect=4 * [6, 1])
        # For HumanAgent choose_move input, always select 0th
        idx_move_input = 0
        monkeypatch.setattr(builtins, 'input', lambda x: idx_move_input)

        # Play one round with fixed (monkeypatched) dice and move choice
        for _ in range(len(played_client.players)):
            played_client.take_turn()

        played_game_state = played_client.get_game_state()

        # Move 0th piece to main board from waiting for each player
        expected_game_state = expected_client.get_game_state()
        for player_name in expected_client._player_names:
            expected_game_state.do(
                MoveContainer(
                    from_space=BoardSpace(
                        kind='waiting',
                        idx=0,
                        occupied_by=player_name,
                        allowed_occupants=[player_name, EMPTY_SYMBOL]),
                    to_space=BoardSpace(
                        kind='main',
                        idx=expected_game_state.get_main_entry_index(
                            player_name) + 1,
                        occupied_by=EMPTY_SYMBOL,
                        allowed_occupants=self.player_names + [EMPTY_SYMBOL])))

        played_waiting = played_game_state.waiting_areas_to_dict()
        expected_waiting = expected_game_state.waiting_areas_to_dict()

        assert played_waiting == expected_waiting

        played_main_spaces = played_game_state.main_spaces_to_list()
        expected_main_spaces = expected_game_state.main_spaces_to_list()

        assert played_main_spaces == expected_main_spaces

        played_home = played_game_state.home_areas_to_dict()
        expected_home = expected_game_state.home_areas_to_dict()

        assert played_home == expected_home
 def test_get_home_space(self, player_name, idx):
     assert self.game_state.get_board_space(
         kind='home', idx=idx, player_name=player_name
         ) \
         == BoardSpace(
             kind='home', idx=idx, occupied_by=EMPTY_SYMBOL,
             allowed_occupants=[player_name, EMPTY_SYMBOL]
         )
    def test_move_factory_blocked_by_own_piece(self, roll, from_space):
        """These tests use GameState.do, hence appear after the do tests"""
        modified_game_state = deepcopy(self.game_state)

        # Move red from its waiting area to its first home space
        modified_game_state.do(
            MoveContainer(
                from_space=BoardSpace(kind='waiting',
                                      idx=3,
                                      occupied_by='red',
                                      allowed_occupants=['red', EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='home',
                                    idx=0,
                                    occupied_by=EMPTY_SYMBOL,
                                    allowed_occupants=['red', EMPTY_SYMBOL])))

        res = modified_game_state.move_factory(from_space, roll)
        assert res.to_space is None
    def test_is_winner(self, player_name, expected):
        modified_game_state = deepcopy(self.game_state)

        winner_name = modified_game_state.player_names[3]
        for idx in range(self.pieces_per_player):
            modified_game_state.do(
                MoveContainer(from_space=BoardSpace(
                    kind='waiting',
                    idx=idx,
                    occupied_by=winner_name,
                    allowed_occupants=[winner_name, EMPTY_SYMBOL]),
                              to_space=BoardSpace(kind='home',
                                                  idx=idx,
                                                  occupied_by=EMPTY_SYMBOL,
                                                  allowed_occupants=[
                                                      winner_name, EMPTY_SYMBOL
                                                  ])))

        assert modified_game_state.is_winner(player_name) == expected
    def test_move_factory_initial_board(self, roll, from_space,
                                        expected_to_space_kwargs):
        res = self.game_state.move_factory(from_space, roll)

        if expected_to_space_kwargs is None:
            assert res.to_space is None
        else:
            expected_to_space = BoardSpace(**expected_to_space_kwargs)
            assert res == MoveContainer(from_space=from_space,
                                        to_space=expected_to_space)
    def test_get_player_moves(self, player_name, roll, expected):
        modified_game_state = deepcopy(self.game_state)
        # Move one blue piece to main board from waiting
        modified_game_state.do(
            MoveContainer(
                from_space=BoardSpace(kind='waiting',
                                      idx=0,
                                      occupied_by='blue',
                                      allowed_occupants=['blue',
                                                         EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='main',
                                    idx=self.player_enter_main_indices['blue'],
                                    occupied_by=EMPTY_SYMBOL,
                                    allowed_occupants=self.player_names +
                                    [EMPTY_SYMBOL])))
        # Move one green piece to one before where blue enters main from
        # waiting. Note: this is not a valid game move.
        modified_game_state.do(
            MoveContainer(
                from_space=BoardSpace(
                    kind='waiting',
                    idx=0,
                    occupied_by='green',
                    allowed_occupants=['green', EMPTY_SYMBOL]),
                to_space=BoardSpace(
                    kind='main',
                    idx=self.player_enter_main_indices['blue'] - 1,
                    occupied_by=EMPTY_SYMBOL,
                    allowed_occupants=self.player_names + [EMPTY_SYMBOL])))
        # Move one yellow piece to one space before its home area
        modified_game_state.do(
            MoveContainer(
                from_space=BoardSpace(
                    kind='waiting',
                    idx=0,
                    occupied_by='yellow',
                    allowed_occupants=['yellow', EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='main',
                                    idx=self.player_prehome_indices['yellow'],
                                    occupied_by=EMPTY_SYMBOL,
                                    allowed_occupants=self.player_names +
                                    [EMPTY_SYMBOL])))

        player_moves = modified_game_state.get_player_moves(roll, player_name)

        assert player_moves == expected
Example #10
0
    def test_furthest_along_choose_move(self, monkeypatch):
        played_client = deepcopy(self.client)
        expected_client = deepcopy(self.client)

        # Move red players to main board
        played_game_state = played_client.get_game_state()
        idx_main_ahead = 3
        idx_main_behind = 1
        idx_home = played_game_state.pieces_per_player - 2
        played_game_state.do(
            MoveContainer(
                from_space=BoardSpace(kind='waiting',
                                      idx=0,
                                      occupied_by='red',
                                      allowed_occupants=['red', EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='main',
                                    idx=idx_main_ahead,
                                    occupied_by=EMPTY_SYMBOL,
                                    allowed_occupants=self.player_names +
                                    [EMPTY_SYMBOL])))
        played_game_state.do(
            MoveContainer(
                from_space=BoardSpace(kind='waiting',
                                      idx=1,
                                      occupied_by='red',
                                      allowed_occupants=['red', EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='main',
                                    idx=idx_main_behind,
                                    occupied_by=EMPTY_SYMBOL,
                                    allowed_occupants=self.player_names +
                                    [EMPTY_SYMBOL])))
        played_game_state.do(
            MoveContainer(
                from_space=BoardSpace(kind='waiting',
                                      idx=2,
                                      occupied_by='red',
                                      allowed_occupants=['red', EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='home',
                                    idx=idx_home,
                                    occupied_by=EMPTY_SYMBOL,
                                    allowed_occupants=['red', EMPTY_SYMBOL])))

        # Set roll value to 1
        roll = 1
        monkeypatch.setattr(played_client, 'roll', lambda: roll)

        # Play once (red) with fixed (monkeypatched) dice
        played_client.take_turn()

        expected_game_state = expected_client.get_game_state()

        expected_game_state.do(
            MoveContainer(
                from_space=BoardSpace(kind='waiting',
                                      idx=0,
                                      occupied_by='red',
                                      allowed_occupants=['red', EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='main',
                                    idx=idx_main_ahead,
                                    occupied_by='red',
                                    allowed_occupants=self.player_names +
                                    [EMPTY_SYMBOL])))
        expected_game_state.do(
            MoveContainer(
                from_space=BoardSpace(kind='waiting',
                                      idx=1,
                                      occupied_by='red',
                                      allowed_occupants=['red', EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='main',
                                    idx=idx_main_behind,
                                    occupied_by='red',
                                    allowed_occupants=self.player_names +
                                    [EMPTY_SYMBOL])))
        expected_game_state.do(
            MoveContainer(
                from_space=BoardSpace(kind='waiting',
                                      idx=2,
                                      occupied_by='red',
                                      allowed_occupants=['red', EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='home',
                                    idx=idx_home + roll,
                                    occupied_by=EMPTY_SYMBOL,
                                    allowed_occupants=['red', EMPTY_SYMBOL])))

        assert_game_states_equal(played_game_state, expected_game_state)

        # Play again red with fixed (monkeypatched) dice
        played_client.take_turn()

        expected_game_state.do(
            MoveContainer(
                from_space=BoardSpace(kind='main',
                                      idx=idx_main_ahead,
                                      occupied_by='red',
                                      allowed_occupants=self.player_names +
                                      [EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='main',
                                    idx=idx_main_ahead + roll,
                                    occupied_by='red',
                                    allowed_occupants=self.player_names +
                                    [EMPTY_SYMBOL])))

        assert_game_states_equal(played_game_state, expected_game_state)
    def test_send_player_home(self, monkeypatch):
        played_client = deepcopy(self.client)
        expected_client = deepcopy(self.client)

        # Move red player to main 0, yellow to main 1
        played_game_state = played_client.get_game_state()
        played_game_state.do(
            MoveContainer(
                from_space=BoardSpace(kind='waiting',
                                      idx=0,
                                      occupied_by='red',
                                      allowed_occupants=['red', EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='main',
                                    idx=0,
                                    occupied_by=EMPTY_SYMBOL,
                                    allowed_occupants=self.player_names +
                                    [EMPTY_SYMBOL])))
        played_game_state.do(
            MoveContainer(
                from_space=BoardSpace(
                    kind='waiting',
                    idx=0,
                    occupied_by='yellow',
                    allowed_occupants=['yellow', EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='main',
                                    idx=1,
                                    occupied_by=EMPTY_SYMBOL,
                                    allowed_occupants=self.player_names +
                                    [EMPTY_SYMBOL])))

        # Set roll value to 1
        monkeypatch.setattr(played_client, 'roll', lambda: 1)
        # For HumanAgent choose_move input, always select 0th
        idx_move_input = 0
        monkeypatch.setattr(builtins, 'input', lambda x: idx_move_input)

        # Play once (red) with fixed (monkeypatched) dice and move choice
        played_client.take_turn()

        expected_game_state = expected_client.get_game_state()

        # Expect red to be at main index 1, all yellow back in waiting
        expected_game_state.do(
            MoveContainer(
                from_space=BoardSpace(kind='waiting',
                                      idx=0,
                                      occupied_by='red',
                                      allowed_occupants=['red', EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='main',
                                    idx=1,
                                    occupied_by='red',
                                    allowed_occupants=self.player_names +
                                    [EMPTY_SYMBOL])))

        played_waiting = played_game_state.waiting_areas_to_dict()
        expected_waiting = expected_game_state.waiting_areas_to_dict()

        assert played_waiting == expected_waiting

        played_main_spaces = played_game_state.main_spaces_to_list()
        expected_main_spaces = expected_game_state.main_spaces_to_list()

        assert played_main_spaces == expected_main_spaces

        played_home = played_game_state.home_areas_to_dict()
        expected_home = expected_game_state.home_areas_to_dict()

        assert played_home == expected_home
 def test_get_main_board_space(self, idx):
     assert self.game_state.get_board_space(kind='main', idx=idx) \
         == BoardSpace(
             kind='main', idx=idx, occupied_by=EMPTY_SYMBOL,
             allowed_occupants=self.player_names + [EMPTY_SYMBOL]
         )
def test_board_space_errors():
    with pytest.raises(ValueError):
        BoardSpace('yadda', 0, 'red', 'all')
class TestGameState:
    @pytest.mark.parametrize(
        'init_args_dict,Error',
        [
            (
                dict(
                    player_names=['red', 'blue', 'green', 'yellow'],
                    pieces_per_player=0,  # invalid
                    section_length=4,
                    number_of_dice_faces=6),
                ValueError),
            (
                dict(
                    player_names=['red', 'blue', 'green', 'yellow'],
                    pieces_per_player=0.5,  # invalid
                    section_length=4,
                    number_of_dice_faces=6),
                TypeError),
            (
                dict(
                    player_names=['red', 'blue', 'green', 'yellow'],
                    section_length=0,  # invalid
                    pieces_per_player=4,
                    number_of_dice_faces=6),
                ValueError),
            (
                dict(
                    player_names=['red', 'blue', 'green', 'yellow'],
                    section_length=0.5,  # invalid
                    pieces_per_player=4,
                    number_of_dice_faces=6),
                TypeError),
            (
                dict(
                    player_names=['red', 'blue', 'green', 'yellow'],
                    number_of_dice_faces=0,  # invalid
                    pieces_per_player=4,
                    section_length=4),
                ValueError),
            (dict(player_names=['red', 'blue', 'green', 'yellow'],
                  number_of_dice_faces=0.5,
                  pieces_per_player=4,
                  section_length=4), TypeError),
        ])
    def test_game_state_initialization_errors(self, init_args_dict, Error):
        with pytest.raises(Error):
            game_state = GameState(**init_args_dict)
            game_state.initialize()

    # Set attributes for repeated use below
    player_names = ['red', 'blue', 'green', 'yellow']
    pieces_per_player = 4
    section_length = 4
    number_of_dice_faces = 6
    game_state = GameState(player_names=player_names,
                           pieces_per_player=pieces_per_player,
                           section_length=section_length,
                           number_of_dice_faces=6)
    game_state.initialize()
    main_board_length = section_length * len(player_names)

    @pytest.mark.parametrize('player_name,expected', [(player_names[0], 0),
                                                      (player_names[1], 4),
                                                      (player_names[2], 8),
                                                      (player_names[3], 12)])
    def test_get_main_entry_index(self, player_name, expected):
        assert self.game_state.get_main_entry_index(player_name) \
            == expected

    # Set further variables for test cases
    player_enter_main_indices = {}
    for player_name in player_names:
        player_enter_main_indices[player_name] \
            = game_state.get_main_entry_index(player_name)

    @pytest.mark.parametrize("idx", range(main_board_length))
    def test_get_main_board_space(self, idx):
        assert self.game_state.get_board_space(kind='main', idx=idx) \
            == BoardSpace(
                kind='main', idx=idx, occupied_by=EMPTY_SYMBOL,
                allowed_occupants=self.player_names + [EMPTY_SYMBOL]
            )

    @pytest.mark.parametrize("kind,idx", [('yadda', 0), ('main', 42)])
    def test_get_board_space_returns_none(self, kind, idx):
        assert self.game_state.get_board_space(kind=kind, idx=idx) is None

    @pytest.mark.parametrize("player_name", player_names)
    @pytest.mark.parametrize("idx", range(pieces_per_player))
    def test_get_waiting_space(self, player_name, idx):
        assert self.game_state.get_board_space(
            kind='waiting', idx=idx, player_name=player_name
            ) \
            == BoardSpace(
                kind='waiting', idx=idx, occupied_by=player_name,
                allowed_occupants=[player_name, EMPTY_SYMBOL]
            )

    @pytest.mark.parametrize("player_name", player_names)
    @pytest.mark.parametrize("idx", range(pieces_per_player))
    def test_get_home_space(self, player_name, idx):
        assert self.game_state.get_board_space(
            kind='home', idx=idx, player_name=player_name
            ) \
            == BoardSpace(
                kind='home', idx=idx, occupied_by=EMPTY_SYMBOL,
                allowed_occupants=[player_name, EMPTY_SYMBOL]
            )

    def test_waiting_areas_to_dict(self):
        res = self.game_state.waiting_areas_to_dict()
        expected = {
            player_name: self.game_state.pieces_per_player * [player_name]
            for player_name in self.player_names
        }
        assert res == expected

    def test_home_areas_to_dict(self):
        res = self.game_state.home_areas_to_dict()
        expected = {
            player_name: self.game_state.pieces_per_player * [EMPTY_SYMBOL]
            for player_name in self.player_names
        }
        assert res == expected

    def test_main_spaces_to_list(self):
        res = self.game_state.main_spaces_to_list()
        expected = self.main_board_length * [EMPTY_SYMBOL]

        assert res == expected

    # For tests of moves from main to player home
    player_prehome_indices = {}
    for player_name in player_names:
        prehome_idx = ((player_enter_main_indices[player_name] - 1) %
                       main_board_length)
        player_prehome_indices[player_name] = prehome_idx

    # Move tests
    @pytest.mark.parametrize("roll,from_space,expected_to_space_kwargs", [
        (5,
         BoardSpace(kind='waiting',
                    idx=0,
                    occupied_by='red',
                    allowed_occupants=['red', EMPTY_SYMBOL]), None),
        (1,
         BoardSpace(kind='home',
                    idx=3,
                    occupied_by='red',
                    allowed_occupants=['red', EMPTY_SYMBOL]), None),
        (5,
         BoardSpace(kind='main',
                    idx=player_prehome_indices['red'],
                    occupied_by='red',
                    allowed_occupants=player_names + [EMPTY_SYMBOL]), None),
        (6,
         BoardSpace(kind='waiting',
                    idx=0,
                    occupied_by='red',
                    allowed_occupants=['red', EMPTY_SYMBOL]),
         dict(kind='main',
              idx=player_enter_main_indices['red'],
              occupied_by=EMPTY_SYMBOL,
              allowed_occupants=player_names + [EMPTY_SYMBOL])),
        (1,
         BoardSpace(kind='main',
                    idx=0,
                    occupied_by='red',
                    allowed_occupants=player_names + [EMPTY_SYMBOL]),
         dict(kind='main',
              idx=1 + 0,
              occupied_by=EMPTY_SYMBOL,
              allowed_occupants=player_names + [EMPTY_SYMBOL])),
        (1,
         BoardSpace(kind='main',
                    idx=player_prehome_indices['red'],
                    occupied_by='red',
                    allowed_occupants=player_names + [EMPTY_SYMBOL]),
         dict(kind='home',
              idx=0,
              occupied_by=EMPTY_SYMBOL,
              allowed_occupants=['red', EMPTY_SYMBOL])),
        (1,
         BoardSpace(kind='home',
                    idx=0,
                    occupied_by='red',
                    allowed_occupants=['red', EMPTY_SYMBOL]),
         dict(kind='home',
              idx=1 + 0,
              occupied_by=EMPTY_SYMBOL,
              allowed_occupants=['red', EMPTY_SYMBOL])),
    ])
    def test_move_factory_initial_board(self, roll, from_space,
                                        expected_to_space_kwargs):
        res = self.game_state.move_factory(from_space, roll)

        if expected_to_space_kwargs is None:
            assert res.to_space is None
        else:
            expected_to_space = BoardSpace(**expected_to_space_kwargs)
            assert res == MoveContainer(from_space=from_space,
                                        to_space=expected_to_space)

    @pytest.mark.parametrize(
        'roll,from_space,to_space,post_do_from_space,post_do_to_space', [
            (6,
             BoardSpace(kind='waiting',
                        idx=0,
                        occupied_by='red',
                        allowed_occupants=['red', EMPTY_SYMBOL]),
             BoardSpace(kind='main',
                        idx=player_enter_main_indices['red'],
                        occupied_by=EMPTY_SYMBOL,
                        allowed_occupants=player_names + [EMPTY_SYMBOL]),
             BoardSpace(kind='waiting',
                        idx=0,
                        occupied_by=EMPTY_SYMBOL,
                        allowed_occupants=['red', EMPTY_SYMBOL]),
             BoardSpace(kind='main',
                        idx=player_enter_main_indices['red'],
                        occupied_by='red',
                        allowed_occupants=player_names + [EMPTY_SYMBOL])),
            (1,
             BoardSpace(kind='main',
                        idx=0,
                        occupied_by='red',
                        allowed_occupants=player_names + [EMPTY_SYMBOL]),
             BoardSpace(kind='main',
                        idx=1 + 0,
                        occupied_by=EMPTY_SYMBOL,
                        allowed_occupants=player_names + [EMPTY_SYMBOL]),
             BoardSpace(kind='main',
                        idx=0,
                        occupied_by=EMPTY_SYMBOL,
                        allowed_occupants=player_names + [EMPTY_SYMBOL]),
             BoardSpace(kind='main',
                        idx=1 + 0,
                        occupied_by='red',
                        allowed_occupants=player_names + [EMPTY_SYMBOL])),
            (1,
             BoardSpace(kind='main',
                        idx=player_prehome_indices['red'],
                        occupied_by='red',
                        allowed_occupants=player_names + [EMPTY_SYMBOL]),
             BoardSpace(kind='home',
                        idx=0,
                        occupied_by=EMPTY_SYMBOL,
                        allowed_occupants=['red', EMPTY_SYMBOL]),
             BoardSpace(kind='main',
                        idx=player_prehome_indices['red'],
                        occupied_by=EMPTY_SYMBOL,
                        allowed_occupants=player_names + [EMPTY_SYMBOL]),
             BoardSpace(kind='home',
                        idx=0,
                        occupied_by='red',
                        allowed_occupants=['red', EMPTY_SYMBOL])),
            (1,
             BoardSpace(kind='main',
                        idx=player_prehome_indices['red'],
                        occupied_by='red',
                        allowed_occupants=player_names + [EMPTY_SYMBOL]),
             BoardSpace(kind='home',
                        idx=0,
                        occupied_by=EMPTY_SYMBOL,
                        allowed_occupants=['red', EMPTY_SYMBOL]),
             BoardSpace(kind='main',
                        idx=player_prehome_indices['red'],
                        occupied_by=EMPTY_SYMBOL,
                        allowed_occupants=player_names + [EMPTY_SYMBOL]),
             BoardSpace(kind='home',
                        idx=0,
                        occupied_by='red',
                        allowed_occupants=['red', EMPTY_SYMBOL])),
        ])
    def test_do(self, roll, from_space, to_space, post_do_from_space,
                post_do_to_space):
        modified_game_state = deepcopy(self.game_state)
        # Put a blue piece on the main board to be sent back to waiting
        modified_game_state.do(
            MoveContainer(
                from_space=BoardSpace(
                    kind='waiting',
                    idx=3,
                    occupied_by='yellow',
                    allowed_occupants=['yellow', EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='main',
                                    idx=0,
                                    occupied_by=EMPTY_SYMBOL,
                                    allowed_occupants=self.player_names +
                                    [EMPTY_SYMBOL])))

        modified_game_state.do(MoveContainer(from_space, to_space))

        assert modified_game_state.get_board_space(
            kind=from_space.kind, idx=from_space.idx,
            player_name='red') == post_do_from_space

        assert modified_game_state.get_board_space(
            kind=to_space.kind, idx=to_space.idx,
            player_name='red') == post_do_to_space

    @pytest.mark.parametrize("roll,from_space", [
        (1,
         BoardSpace(kind='main',
                    idx=player_prehome_indices['red'],
                    occupied_by='red',
                    allowed_occupants=player_names + [EMPTY_SYMBOL])),
    ])
    def test_move_factory_blocked_by_own_piece(self, roll, from_space):
        """These tests use GameState.do, hence appear after the do tests"""
        modified_game_state = deepcopy(self.game_state)

        # Move red from its waiting area to its first home space
        modified_game_state.do(
            MoveContainer(
                from_space=BoardSpace(kind='waiting',
                                      idx=3,
                                      occupied_by='red',
                                      allowed_occupants=['red', EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='home',
                                    idx=0,
                                    occupied_by=EMPTY_SYMBOL,
                                    allowed_occupants=['red', EMPTY_SYMBOL])))

        res = modified_game_state.move_factory(from_space, roll)
        assert res.to_space is None

    @pytest.mark.parametrize(
        'player_name, roll, expected',
        [
            ('red', 1, []),  # All players in waiting, need roll of 6 to move
            (
                'blue',
                6,  # Own piece blocks enter to main from waiting
                [[
                    MoveContainer(
                        from_space=BoardSpace(
                            kind='main',
                            occupied_by='blue',
                            idx=player_enter_main_indices['blue'],
                            allowed_occupants=player_names + [EMPTY_SYMBOL]),
                        to_space=BoardSpace(
                            kind='main',
                            occupied_by=EMPTY_SYMBOL,
                            idx=player_enter_main_indices['blue'] + 6,
                            allowed_occupants=player_names + [EMPTY_SYMBOL]))
                ]]),
            (
                'yellow',
                1,  # Enter home move
                [[
                    MoveContainer(
                        from_space=BoardSpace(
                            kind='main',
                            occupied_by='yellow',
                            idx=player_prehome_indices['yellow'],
                            allowed_occupants=player_names + [EMPTY_SYMBOL]),
                        to_space=BoardSpace(
                            kind='home',
                            occupied_by=EMPTY_SYMBOL,
                            idx=0,
                            allowed_occupants=['yellow', EMPTY_SYMBOL]))
                ]]),
            (
                'red',
                6,  # Leave waiting
                [[
                    MoveContainer(
                        from_space=BoardSpace(
                            kind='waiting',
                            occupied_by='red',
                            idx=0,
                            allowed_occupants=['red', EMPTY_SYMBOL]),
                        to_space=BoardSpace(
                            kind='main',
                            occupied_by=EMPTY_SYMBOL,
                            idx=player_enter_main_indices['red'],
                            allowed_occupants=player_names + [EMPTY_SYMBOL]))
                ],
                 [
                     MoveContainer(
                         from_space=BoardSpace(
                             kind='waiting',
                             occupied_by='red',
                             idx=1,
                             allowed_occupants=['red', EMPTY_SYMBOL]),
                         to_space=BoardSpace(
                             kind='main',
                             occupied_by=EMPTY_SYMBOL,
                             idx=player_enter_main_indices['red'],
                             allowed_occupants=player_names + [EMPTY_SYMBOL]))
                 ],
                 [
                     MoveContainer(
                         from_space=BoardSpace(
                             kind='waiting',
                             occupied_by='red',
                             idx=2,
                             allowed_occupants=['red', EMPTY_SYMBOL]),
                         to_space=BoardSpace(
                             kind='main',
                             occupied_by=EMPTY_SYMBOL,
                             idx=player_enter_main_indices['red'],
                             allowed_occupants=player_names + [EMPTY_SYMBOL]))
                 ],
                 [
                     MoveContainer(
                         from_space=BoardSpace(
                             kind='waiting',
                             occupied_by='red',
                             idx=3,
                             allowed_occupants=['red', EMPTY_SYMBOL]),
                         to_space=BoardSpace(
                             kind='main',
                             occupied_by=EMPTY_SYMBOL,
                             idx=player_enter_main_indices['red'],
                             allowed_occupants=player_names + [EMPTY_SYMBOL]))
                 ]]),
            (
                'green',
                1,  # Advance and send opponent back to waiting
                [[
                    MoveContainer(from_space=BoardSpace(
                        kind='main',
                        occupied_by='blue',
                        idx=player_enter_main_indices['blue'],
                        allowed_occupants=player_names + [EMPTY_SYMBOL]),
                                  to_space=BoardSpace(kind='waiting',
                                                      occupied_by=EMPTY_SYMBOL,
                                                      idx=0,
                                                      allowed_occupants=[
                                                          'blue', EMPTY_SYMBOL
                                                      ])),
                    MoveContainer(
                        from_space=BoardSpace(
                            kind='main',
                            occupied_by='green',
                            idx=player_enter_main_indices['blue'] - 1,
                            allowed_occupants=player_names + [EMPTY_SYMBOL]),
                        to_space=BoardSpace(
                            kind='main',
                            occupied_by='blue',
                            idx=player_enter_main_indices['blue'],
                            allowed_occupants=player_names + [EMPTY_SYMBOL]))
                ]])
        ])
    def test_get_player_moves(self, player_name, roll, expected):
        modified_game_state = deepcopy(self.game_state)
        # Move one blue piece to main board from waiting
        modified_game_state.do(
            MoveContainer(
                from_space=BoardSpace(kind='waiting',
                                      idx=0,
                                      occupied_by='blue',
                                      allowed_occupants=['blue',
                                                         EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='main',
                                    idx=self.player_enter_main_indices['blue'],
                                    occupied_by=EMPTY_SYMBOL,
                                    allowed_occupants=self.player_names +
                                    [EMPTY_SYMBOL])))
        # Move one green piece to one before where blue enters main from
        # waiting. Note: this is not a valid game move.
        modified_game_state.do(
            MoveContainer(
                from_space=BoardSpace(
                    kind='waiting',
                    idx=0,
                    occupied_by='green',
                    allowed_occupants=['green', EMPTY_SYMBOL]),
                to_space=BoardSpace(
                    kind='main',
                    idx=self.player_enter_main_indices['blue'] - 1,
                    occupied_by=EMPTY_SYMBOL,
                    allowed_occupants=self.player_names + [EMPTY_SYMBOL])))
        # Move one yellow piece to one space before its home area
        modified_game_state.do(
            MoveContainer(
                from_space=BoardSpace(
                    kind='waiting',
                    idx=0,
                    occupied_by='yellow',
                    allowed_occupants=['yellow', EMPTY_SYMBOL]),
                to_space=BoardSpace(kind='main',
                                    idx=self.player_prehome_indices['yellow'],
                                    occupied_by=EMPTY_SYMBOL,
                                    allowed_occupants=self.player_names +
                                    [EMPTY_SYMBOL])))

        player_moves = modified_game_state.get_player_moves(roll, player_name)

        assert player_moves == expected

    @pytest.mark.parametrize('player_name,expected', [
        (player_names[0], False),
        (player_names[1], False),
        (player_names[2], False),
        (player_names[3], True),
    ])
    def test_is_winner(self, player_name, expected):
        modified_game_state = deepcopy(self.game_state)

        winner_name = modified_game_state.player_names[3]
        for idx in range(self.pieces_per_player):
            modified_game_state.do(
                MoveContainer(from_space=BoardSpace(
                    kind='waiting',
                    idx=idx,
                    occupied_by=winner_name,
                    allowed_occupants=[winner_name, EMPTY_SYMBOL]),
                              to_space=BoardSpace(kind='home',
                                                  idx=idx,
                                                  occupied_by=EMPTY_SYMBOL,
                                                  allowed_occupants=[
                                                      winner_name, EMPTY_SYMBOL
                                                  ])))

        assert modified_game_state.is_winner(player_name) == expected

    @pytest.mark.parametrize(
        'board_space,expected',
        [(BoardSpace(kind='main',
                     idx=player_prehome_indices['red'],
                     occupied_by='red',
                     allowed_occupants=player_names + [EMPTY_SYMBOL]), 4),
         (BoardSpace(kind='home',
                     idx=pieces_per_player - 1,
                     occupied_by='red',
                     allowed_occupants=['red', EMPTY_SYMBOL]), 0),
         (BoardSpace(kind='waiting',
                     idx=0,
                     occupied_by='red',
                     allowed_occupants=['red', EMPTY_SYMBOL]),
          pieces_per_player * (section_length + 1))])
    def test_distance_to_end(self, board_space, expected):
        assert self.game_state.distance_to_end(board_space) \
            == expected