def test_last_piece_in_empty_bin_steals_opponents_pieces(player, opponent): """ The idea here is that if a player takes a turn that results in their last piece landing in a previously empty bin on their side, then the player gets to steal the pieces in the same bin on their opponents side. Both the last piece that landed in the previously empty bin and their opponents pieces go into the player's goal. """ board = Board({ player: PlayerRow(bins=[0, 10, 0, 0, 0, 2], goal=12), opponent: PlayerRow(bins=[1, 6, 7, 1, 2, 7], goal=3), }) # In-player row scenario in_player_row_board = take_turn(board, Turn(player, 5)) assert in_player_row_board[player] == PlayerRow(bins=[0, 10, 0, 0, 1, 0], goal=14) assert in_player_row_board[opponent] == PlayerRow(bins=[1, 6, 7, 0, 2, 7], goal=3) # Double-wrap scenario double_wrap_board = take_turn(board, Turn(player, 1)) assert double_wrap_board[player] == PlayerRow(bins=[1, 0, 0, 0, 0, 3], goal=17) assert double_wrap_board[opponent] == PlayerRow(bins=[2, 7, 8, 2, 0, 8], goal=3)
def test_turn_selected_bin_must_exist_on_board(player): with pytest.raises(ValueError): Turn(player, -1) with pytest.raises(ValueError): Turn(player, NUMBER_OF_BINS) for selected_bin in range(NUMBER_OF_BINS): assert Turn(player, selected_bin) is not None
def test_who_gets_next_turn_returns_correct_player(player, opponent): # player makes optimal first game move to get another turn prior_board = Board({ player: PlayerRow(bins=[4, 4, 4, 4, 4, 4], goal=0), opponent: PlayerRow(bins=[4, 4, 4, 4, 4, 4], goal=0), }) turn = Turn(player, 3) new_board = Board({ player: PlayerRow(bins=[5, 5, 5, 0, 4, 4], goal=1), opponent: PlayerRow.get_new_player_row(), }) assert who_gets_next_turn(prior_board, turn, new_board) == player # player makes a move to steal pieces from opponent prior_board = Board({ player: PlayerRow(bins=[0, 10, 0, 0, 0, 2], goal=12), opponent: PlayerRow(bins=[1, 6, 7, 1, 2, 7], goal=3), }) turn = Turn(player, 5) new_board = Board({ player: PlayerRow(bins=[0, 10, 0, 0, 1, 0], goal=14), opponent: PlayerRow(bins=[1, 6, 7, 0, 2, 7], goal=3), }) assert who_gets_next_turn(prior_board, turn, new_board) == opponent # player makes double-wrap move to steal pieces from opponent prior_board = Board({ player: PlayerRow(bins=[0, 10, 0, 0, 0, 2], goal=12), opponent: PlayerRow(bins=[1, 6, 7, 1, 2, 7], goal=3), }) turn = Turn(player, 1) new_board = Board({ player: PlayerRow(bins=[1, 0, 0, 0, 0, 3], goal=17), opponent: PlayerRow(bins=[2, 7, 8, 2, 0, 8], goal=3), }) assert who_gets_next_turn(prior_board, turn, new_board) == opponent # player makes double-wrap move and ends in goal prior_board = Board({ player: PlayerRow(bins=[14, 0, 0, 0, 2, 0], goal=10), opponent: PlayerRow(bins=[1, 6, 4, 0, 1, 7], goal=3), }) turn = Turn(player, 0) new_board = Board({ player: PlayerRow(bins=[1, 1, 1, 1, 3, 1], goal=12), opponent: PlayerRow(bins=[2, 7, 5, 1, 2, 8], goal=3), }) assert who_gets_next_turn(prior_board, turn, new_board) == player
def test_last_piece_in_empty_bin_only_steals_if_opponent_has_pieces( player, opponent): board = Board({ player: PlayerRow(bins=[0, 10, 0, 0, 0, 2], goal=12), opponent: PlayerRow(bins=[1, 6, 7, 0, 3, 7], goal=3), }) new_board = take_turn(board, Turn(player, 5)) assert new_board[player] == PlayerRow(bins=[0, 10, 0, 1, 1, 0], goal=12) assert new_board[opponent] == PlayerRow(bins=[1, 6, 7, 0, 3, 7], goal=3)
def test_first_moves_on_a_new_game_board(selected_bin, players_result_row, opponents_result_row): # Ensure the turn works for both Players, and that the function # does not change the original board player_one_turn_board = get_new_board() player_one_turn = Turn(Player.ONE, selected_bin) new_player_one_turn_board = take_turn(player_one_turn_board, player_one_turn) assert player_one_turn_board == get_new_board() assert new_player_one_turn_board[Player.ONE] == players_result_row assert new_player_one_turn_board[Player.TWO] == opponents_result_row player_two_turn_board = get_new_board() player_two_turn = Turn(Player.TWO, selected_bin) new_player_two_turn_board = take_turn(player_two_turn_board, player_two_turn) assert player_two_turn_board == get_new_board() assert new_player_two_turn_board[Player.ONE] == opponents_result_row assert new_player_two_turn_board[Player.TWO] == players_result_row
def test_triple_wrap_turn(player, opponent): turn = Turn(player, 0) board = Board({ player: PlayerRow(bins=[16, 1, 5, 0, 5, 2], goal=6), opponent: PlayerRow(bins=[0, 2, 1, 2, 1, 0], goal=7), }) new_board = take_turn(board, turn) assert new_board[player] == PlayerRow(bins=[1, 2, 6, 1, 6, 3], goal=8) assert new_board[opponent] == PlayerRow(bins=[1, 3, 2, 3, 3, 2], goal=7)
def test_double_wrap_turn_that_ends_in_goal(player, opponent): turn = Turn(player, 0) board = Board({ player: PlayerRow(bins=[14, 0, 0, 0, 2, 0], goal=10), opponent: PlayerRow(bins=[1, 6, 4, 0, 1, 7], goal=3), }) new_board = take_turn(board, turn) assert new_board[player] == PlayerRow(bins=[1, 1, 1, 1, 3, 1], goal=12) assert new_board[opponent] == PlayerRow(bins=[2, 7, 5, 1, 2, 8], goal=3)
def test_double_wrap_turn(player, opponent): turn = Turn(player, 1) board = Board({ player: PlayerRow(bins=[0, 10, 0, 0, 2, 0], goal=12), opponent: PlayerRow(bins=[1, 6, 8, 0, 2, 7], goal=3), }) new_board = take_turn(board, turn) assert new_board[player] == PlayerRow(bins=[1, 0, 0, 0, 3, 1], goal=13) assert new_board[opponent] == PlayerRow(bins=[2, 7, 9, 1, 3, 8], goal=3)
@pytest.mark.parametrize( "serializable,serialized", [ (None, None), (Player.ONE, "one"), (Player.TWO, "two"), ( PlayerRow.get_new_player_row(), { "bins": PlayerRow.get_new_player_row().bins, "goal": PlayerRow.get_new_player_row().goal, }, ), (Turn(Player.ONE, 4), { "player": "one", "selected_bin": 4 }), (Turn(Player.TWO, 2), { "player": "two", "selected_bin": 2 }), ], ) def test_serialize_returns_correct_serialization(serializable, serialized): assert to_serializable(serializable) == serialized def test_serialization_returns_correct_serialization_for_boards(): p1 = Player.ONE
def run(self, reset_simulation=False) -> None: # pragma: nocover if self.has_run and not reset_simulation: return if reset_simulation: self._reset_simulation() current_player = self._starting_player current_turn = 0 while True: # Set up objects for current player on this turn current_board = self._boards[current_turn] current_player_row = current_board[current_player] current_player_strategy = self._strategies[current_player] current_opponent_row = current_board[ Player.ONE if current_player == Player.TWO else Player.TWO] # Allow current player to select a bin from their row, and ensure it is valid try: selected_bin = current_player_strategy.choose_bin( current_player_row, current_opponent_row) except ValueError: # Assuming ValueError is thrown if player can't move, i.e. all bins are empty if all([b == 0 for b in current_player_row.bins]): player_one_row = current_board[Player.ONE] player_two_row = current_board[Player.TWO] resulting_player_one_goal = (sum(player_one_row.bins) + player_one_row.goal) resulting_player_two_goal = (sum(player_two_row.bins) + player_two_row.goal) new_board = Board({ Player.ONE: PlayerRow(bins=[0, 0, 0, 0, 0, 0], goal=resulting_player_one_goal), Player.TWO: PlayerRow(bins=[0, 0, 0, 0, 0, 0], goal=resulting_player_two_goal), }) # self._turns.append(Turn.GameEndingTurn) TODO self._boards.append(new_board) if resulting_player_one_goal > resulting_player_two_goal: self._winning_player = Player.ONE elif resulting_player_one_goal < resulting_player_two_goal: self._winning_player = Player.TWO else: self._winning_player = None # tie self._has_run = True break if current_player_row.bins[selected_bin] == 0: raise ValueError( "Player strategies need to ensure they pick non-empty bins in 'choose_bin'" ) # Perform turn with selected bin and save simulation data turn = Turn(current_player, selected_bin) new_board = take_turn(current_board, turn) self._turns.append(turn) self._boards.append(new_board) # Check if game has ended; otherwise, update who is up next if self._is_end_of_game(): self._set_winner() self._has_run = True break current_player = who_gets_next_turn(current_board, turn, new_board) current_turn += 1