Exemple #1
0
    def get_player_actions_from(self, player: Player) -> List[Action]:
        """
        For the given player, find all possible moves for each of this players penguins.
        :param player: The player whose possible moves will be calculated.
        :return: List[Action] representing all possible moves for this Player's penguins
        """

        if player is None:
            return None

        if player == 0:
            return None

        player_actions = []
        for penguin in player.penguins:
            reachable_tiles = self.board.get_reachable_tiles(
                penguin.row, penguin.col, [
                    penguin for _, player in self.players.items()
                    for penguin in player.penguins
                ])
            potential_actions = []
            for reachable_tile in reachable_tiles:
                potential_actions.append(
                    Action(Coordinate(penguin.row, penguin.col),
                           Coordinate(reachable_tile.row, reachable_tile.col),
                           player.color))
            player_actions.extend(potential_actions)
        return player_actions
    def test_notify_player_placement_invalid_movement(self):
        state = self.setup()
        state = state.add_penguin(row=0, col=0, color="red")
        state = state.add_penguin(row=1, col=1, color="white")
        state = state.finalize()

        expected_action = Action(start=Coordinate(row=0, col=0),
                                 end=Coordinate(row=3, col=2),
                                 player_color="red")

        not_expected_action = Action(start=Coordinate(row=1, col=1),
                                     end=Coordinate(row=2, col=2),
                                     player_color="white")

        mocked_strategy_player1 = GenericStrategyComponent()
        mocked_strategy_player1.choose_action_from_state = MagicMock(
            return_value=expected_action)

        mocked_strategy_player2 = GenericStrategyComponent()
        mocked_strategy_player2.choose_action_from_state = MagicMock(
            return_value=not_expected_action)

        player_1_component = PlayerComponent(strategy=mocked_strategy_player1)
        player_2_component = PlayerComponent(strategy=mocked_strategy_player2)

        ref = Referee(players=[player_1_component, player_2_component],
                      num_rows=4,
                      num_cols=3)
        expected = state.kick_player(player_to_kick=state.current_player)
        ref.state = state

        ref.end_penguin_placement()
        ref.notify_players_movement()
        actual = ref.state
        self.assertTrue(actual.is_equal(expected))
 def test_get_reachable_tile_coordinates_by_direction(self):
     model = FishBoard(num_rows=4, num_cols=3)
     tile = model.board[1][2]
     direction = 'bottom_left_coordinate'
     expected = [Coordinate(row=2, col=2), Coordinate(row=3, col=1)]
     self.assertEqual(
         model.get_reachable_tile_coordinates_by_direction(tile, direction),
         expected)
    def test_take_action_failure(self):
        state, *_ = self.setup()

        tree = GameStateTree(state)
        self.assertEqual(
            tree.take_action(
                Action(start=Coordinate(0, 1),
                       end=Coordinate(2, 2),
                       player_color="red")),
            "Player color red does not own penguin at row 0, col 1")
 def test_get_reachable_tiles(self):
     model = FishBoard(num_rows=5, num_cols=4)
     expected_reachable = [
         Coordinate(row=0, col=1),
         Coordinate(row=1, col=1),
         Coordinate(row=0, col=2),
         Coordinate(row=3, col=1),
         Coordinate(row=4, col=2),
         Coordinate(row=4, col=1),
         Coordinate(row=3, col=0),
         Coordinate(row=4, col=0),
         Coordinate(row=1, col=0),
         Coordinate(row=0, col=0)
     ]
     self.assertEqual(model.get_reachable_tiles(2, 1), expected_reachable)
    def __init__(self, num_rows, num_cols, board: List[List[Tile]] = None):
        """
        :param num_rows: the number of rows
        :param num_cols: the number of cols
        """
        if num_rows <= 0 or num_cols <= 0:
            raise ValueError(
                f"[FishGame] : invalid board dimensions: ({num_cols}, {num_rows})"
            )

        self.num_rows = num_rows
        self.num_cols = num_cols

        if board is None:
            self.board: List[List[Tile]] = []
            for r in range(self.num_rows):
                row = []
                for c in range(self.num_cols):
                    tile = Tile.from_coordinate(coordinate=Coordinate(row=r,
                                                                      col=c),
                                                num_fish=0,
                                                num_rows=num_rows,
                                                num_cols=self.num_cols)
                    row.append(tile)
                self.board.append(row)
        else:
            self.board = board

        self.neighboring_coordinates = [
            'top_coordinate', 'top_right_coordinate',
            'bottom_right_coordinate', 'bottom_coordinate',
            'bottom_left_coordinate', 'top_left_coordinate'
        ]
Exemple #7
0
    def add_penguin(self, row, col, color):
        """
        Add a penguin for the given player id at the given row and column
        :param row: the row of the tile to add penguin on
        :param col: the col of the tile to add penguin on
        :param color: the color who will be adding the penguin
        :return: Updated GameState with added penguin
        """

        is_valid, err = self.can_add_penguin(row=row, col=col, color=color)
        if not is_valid:
            raise ValueError(err)

        players = []
        for player in self.players.values():
            if player.color == color:
                players.append(
                    Player.from_dict(
                        {
                            **player._asdict(),
                            **{
                                "penguins":
                                player.penguins + [(Coordinate(row, col))]
                            }
                        }))
            else:
                players.append(player)

        return FishGameState(board=self.board,
                             num_players=self.num_players,
                             players=players,
                             current_player=self.next_player(),
                             phase=self.phase)
Exemple #8
0
    def test_create_choose_action_base_case_no_children_for_maximizing_player(
            self):
        """
        If the player has turns > 0 but has no children - end-game - then
        return the current's player's previous action
        """
        state, *_ = self.setup()
        production_tree = GameStateTree(state, previous_action=None)
        expected = Action(
            player_color=production_tree.state.current_player.color,
            start=Coordinate(0, 0),
            end=Coordinate(1, 0))

        production_tree.previous_action = expected
        actual = GenericStrategyComponent.choose_action(tree=production_tree,
                                                        num_turns=2)
        self.assertEqual(expected, actual)
Exemple #9
0
    def test_create_choose_action_base_case_0_turns(self):
        """
        If the player has 0 turns and it is the currents player turn,
        then the previous action is return.
        """
        state, *_ = self.setup()
        production_tree = GameStateTree(state, previous_action=None)
        production_tree.get_children = MagicMock(return_value=None)
        expected = Action(
            player_color=production_tree.state.current_player.color,
            start=Coordinate(0, 0),
            end=Coordinate(1, 0))

        production_tree.previous_action = expected
        actual = GenericStrategyComponent.choose_action(tree=production_tree,
                                                        num_turns=0)
        self.assertEqual(expected, actual)
Exemple #10
0
 def test_add_penguin_success(self):
     board = FishBoard(4, 3)
     factory = FishGameState(board=board,
                             num_players=2,
                             players=TestGameState.player_list,
                             phase=GameStatePhase.INITIAL,
                             current_player=TestGameState.player_list[0])
     factory = factory.add_penguin(0, 0, "red")
     expected_penguins = [Coordinate(0, 0)]
     self.assertEqual(factory.players["red"].penguins, expected_penguins)
Exemple #11
0
    def test_create_choose_action_base_case_no_children_for_non_maximizing_player(
            self):
        """
        If the player has turns > 0 but has no children - end-game - and this is not
        the maximizing player, then we return None.
        """
        state, *_ = self.setup()
        production_tree = GameStateTree(state, previous_action=None)
        production_tree.get_children = MagicMock(return_value=[])
        production_tree.previous_action = Action(
            player_color=production_tree.state.current_player.color,
            start=Coordinate(0, 0),
            end=Coordinate(1, 0))

        expected = None
        _, actual = GenericStrategyComponent.minimax(tree=production_tree,
                                                     num_rounds=2,
                                                     maximizing_player=Player(
                                                         "white", 0, 0, []))
        self.assertEqual(expected, actual)
Exemple #12
0
    def test_create_choose_action_recursive_case_is_maximizing_twice(self):
        """
        In a recursive case where 1 round is played and the
        all leave nodes are the maximizing players, the minimum of
        these leave nodes is return.
        """
        state, *_ = self.setup()
        production_tree = GameStateTree(state, previous_action=None)
        expected = Action(
            player_color=production_tree.state.current_player.color,
            start=Coordinate(0, 0),
            end=Coordinate(1, 0))

        children = [child for child in production_tree.get_children()]
        for idx, child in enumerate(children):
            second_players_children = [
                second_players_child
                for second_players_child in child.get_children()
            ]
            for jdx, second_players_child in enumerate(
                    second_players_children):
                if jdx == 1 and idx == 1:
                    score = 0
                    child.previous_action = expected
                else:
                    score = 10
                modified_player = Player.from_dict({
                    **second_players_child.state.current_player._asdict(),
                    **{
                        'score': score
                    }
                })
                second_players_child.state.current_player = modified_player
                second_players_child.get_children = MagicMock(return_value=[])
                second_players_child.state.check_any_player_can_move = MagicMock(
                    return_value=False)

        production_tree.get_children = MagicMock(return_value=children)
        actual = GenericStrategyComponent.choose_action(tree=production_tree,
                                                        num_turns=2)
        self.assertEqual(expected, actual)
    def place_penguin(state: FishGameState) -> Coordinate:
        """
        Place a penguin in the next available place in the given state's board according to a zig-zag pattern that begins
        in the top left corner.
        :param state: The state that will have penguins added to it.
        :return: The updated state after adding the penguin in the correct place.
        """
        for r, row in enumerate(state.board.board):
            for c, col in enumerate(row):
                if state.validate_input(state.current_player.color, r, c)[0]:
                    return Coordinate(r, c)

        raise ValueError("No available tiles for penguin placement.")
Exemple #14
0
    def test_create_choose_action_recursive_case_is_maximizing_once(self):
        """
        In a scenario where all the children of the maximizing player
        are leaves - aka end-games - than a tie-breaker decides which
        action to take.
        """
        state, *_ = self.setup()
        production_tree = GameStateTree(state, previous_action=None)
        expected = Action(
            player_color=production_tree.state.current_player.color,
            start=Coordinate(0, 0),
            end=Coordinate(1, 0))

        children = [child for child in production_tree.get_children()]
        for idx, child in enumerate(children):
            if idx == 1:
                child.previous_action = expected

            child.get_children = MagicMock(return_value=[])

        production_tree.get_children = MagicMock(return_value=children)
        actual = GenericStrategyComponent.choose_action(tree=production_tree,
                                                        num_turns=2)
        self.assertEqual(expected, actual)
Exemple #15
0
    def test_move_penguin(self):
        board = FishBoard(4, 3)
        factory = FishGameState(board=board,
                                num_players=3,
                                players=TestGameState.player_list,
                                phase=GameStatePhase.INITIAL,
                                current_player=TestGameState.player_list[0])
        factory = factory.add_penguin(row=0, col=1, color="red")
        factory = factory.add_penguin(row=0, col=0, color="white")
        factory = factory.add_penguin(row=3, col=0, color="brown")

        game_state = factory.finalize()
        game_state = game_state.move_penguin("red", 0, 1, 2, 1)
        expected_penguins = [Coordinate(row=2, col=1)]
        self.assertEqual(game_state.players["red"].penguins, expected_penguins)
    def test_notify_player_placement_invalid(self):
        state = self.setup()

        expected_placement = Coordinate(row=5, col=5)

        mocked_strategy_player1 = GenericStrategyComponent()
        mocked_strategy_player1.place_penguin = MagicMock(
            return_value=expected_placement)

        player_1_component = PlayerComponent(strategy=mocked_strategy_player1)
        player_2_component = PlayerComponent()

        ref = Referee(players=[player_1_component, player_2_component],
                      num_rows=4,
                      num_cols=3)

        expected = state.kick_player(player_to_kick=state.current_player)
        ref.notify_player_placement()
        self.assertTrue(ref.state.is_equal(expected))
 def is_reachable_from(self,
                       start_row,
                       start_col,
                       end_row,
                       end_col,
                       unreachable_tiles: List[Coordinate] = None):
     """
     Given the row,col information, determine whether the ending row and column
     is reachable from the starting row and column. Reachability is determined
     if the end row, col follows a straight path from the starting row and column.
     Holes or the provided unreachable tiles act as barriers meaning that tiles on
     or beyond them are not reachable.
     :param start_row: the starting row
     :param start_col: the starting column
     :param end_row: the ending row
     :param end_col: the ending cool
     :param unreachable_tiles: a list of Coordinate that are unreachable at it and past it.
     :return: a boolean representing if the ending row, col is reachable.
     """
     return Coordinate(end_row, end_col) in self.get_reachable_tiles(
         start_row, start_col, unreachable_tiles)
Exemple #18
0
    def move_penguin(self, color, start_row, start_col, end_row, end_col):
        """
        Move penguin for player with color from start position to end position if possible.
        :param color: the color of the player who wants to move the penguin
        :param start_row: the current row of the penguin to be moved
        :param start_col: the current col of the penguin to be moved
        :param end_row: the desired row to which the penguin will be moved
        :param end_col: the desired col to which the penguin will be moved
        :return: a new FishGameState with the action taken.
        """

        is_valid, err = self.can_move_penguin(color, start_row, start_col,
                                              end_row, end_col)
        if not is_valid:
            raise ValueError(err)

        players = [
            Player.from_dict({
                **self.players[color]._asdict(),
                **{
                    "score": (self.players[color].score + self.board.retrieve_num_fish(
                                  start_row, start_col))
                },
                **{
                    "penguins": [
                        Coordinate(end_row, end_col) if penguin.row == start_row and penguin.col == start_col else penguin for penguin in self.players[color].penguins
                    ]
                }
            }) if player.color == color else player
            for player in self.players.values()
        ]
        board = self.board.create_hole(start_row, start_col)
        return FishGameState(board=board,
                             num_players=self.num_players,
                             players=players,
                             current_player=self.next_player(),
                             phase=self.phase)
 def test_create_board(self):
     model = FishBoard(num_rows=4, num_cols=3)
     expected_board = [[
         Tile(coordinate=Coordinate(row=0, col=0),
              is_tile=True,
              num_fish=0,
              top_coordinate=None,
              top_left_coordinate=None,
              top_right_coordinate=None,
              bottom_coordinate=(2, 0),
              bottom_left_coordinate=None,
              bottom_right_coordinate=(1, 0)),
         Tile(coordinate=Coordinate(row=0, col=1),
              is_tile=True,
              num_fish=0,
              top_coordinate=None,
              top_left_coordinate=None,
              top_right_coordinate=None,
              bottom_coordinate=(2, 1),
              bottom_left_coordinate=(1, 0),
              bottom_right_coordinate=(1, 1)),
         Tile(coordinate=Coordinate(row=0, col=2),
              is_tile=True,
              num_fish=0,
              top_coordinate=None,
              top_left_coordinate=None,
              top_right_coordinate=None,
              bottom_coordinate=(2, 2),
              bottom_left_coordinate=(1, 1),
              bottom_right_coordinate=(1, 2))
     ],
                       [
                           Tile(coordinate=Coordinate(row=1, col=0),
                                is_tile=True,
                                num_fish=0,
                                top_coordinate=None,
                                top_left_coordinate=(0, 0),
                                top_right_coordinate=(0, 1),
                                bottom_coordinate=(3, 0),
                                bottom_left_coordinate=(2, 0),
                                bottom_right_coordinate=(2, 1)),
                           Tile(coordinate=Coordinate(row=1, col=1),
                                is_tile=True,
                                num_fish=0,
                                top_coordinate=None,
                                top_left_coordinate=(0, 1),
                                top_right_coordinate=(0, 2),
                                bottom_coordinate=(3, 1),
                                bottom_left_coordinate=(2, 1),
                                bottom_right_coordinate=(2, 2)),
                           Tile(coordinate=Coordinate(row=1, col=2),
                                is_tile=True,
                                num_fish=0,
                                top_coordinate=None,
                                top_left_coordinate=(0, 2),
                                top_right_coordinate=None,
                                bottom_coordinate=(3, 2),
                                bottom_left_coordinate=(2, 2),
                                bottom_right_coordinate=None)
                       ],
                       [
                           Tile(coordinate=Coordinate(row=2, col=0),
                                is_tile=True,
                                num_fish=0,
                                top_coordinate=(0, 0),
                                top_left_coordinate=None,
                                top_right_coordinate=(1, 0),
                                bottom_coordinate=None,
                                bottom_left_coordinate=None,
                                bottom_right_coordinate=(3, 0)),
                           Tile(coordinate=Coordinate(row=2, col=1),
                                is_tile=True,
                                num_fish=0,
                                top_coordinate=(0, 1),
                                top_left_coordinate=(1, 0),
                                top_right_coordinate=(1, 1),
                                bottom_coordinate=None,
                                bottom_left_coordinate=(3, 0),
                                bottom_right_coordinate=(3, 1)),
                           Tile(coordinate=Coordinate(row=2, col=2),
                                is_tile=True,
                                num_fish=0,
                                top_coordinate=(0, 2),
                                top_left_coordinate=(1, 1),
                                top_right_coordinate=(1, 2),
                                bottom_coordinate=None,
                                bottom_left_coordinate=(3, 1),
                                bottom_right_coordinate=(3, 2))
                       ],
                       [
                           Tile(coordinate=Coordinate(row=3, col=0),
                                is_tile=True,
                                num_fish=0,
                                top_coordinate=(1, 0),
                                top_left_coordinate=(2, 0),
                                top_right_coordinate=(2, 1),
                                bottom_coordinate=None,
                                bottom_left_coordinate=None,
                                bottom_right_coordinate=None),
                           Tile(coordinate=Coordinate(row=3, col=1),
                                is_tile=True,
                                num_fish=0,
                                top_coordinate=(1, 1),
                                top_left_coordinate=(2, 1),
                                top_right_coordinate=(2, 2),
                                bottom_coordinate=None,
                                bottom_left_coordinate=None,
                                bottom_right_coordinate=None),
                           Tile(coordinate=Coordinate(row=3, col=2),
                                is_tile=True,
                                num_fish=0,
                                top_coordinate=(1, 2),
                                top_left_coordinate=(2, 2),
                                top_right_coordinate=None,
                                bottom_coordinate=None,
                                bottom_left_coordinate=None,
                                bottom_right_coordinate=None)
                       ]]
     self.assertEqual(model.num_rows, 4)
     self.assertEqual(model.num_cols, 3)
     self.assertEqual(model.board, expected_board)
    parser.add_argument('--board-demo1', dest="board_demo1", action="store_true")
    parser.add_argument('--board-demo2', dest="board_demo2", action="store_true")
    parser.add_argument('--board-demo3', dest="board_demo3", action="store_true")
    parser.add_argument('--state-demo1', dest="state_demo1", action="store_true")
    parser.add_argument('--state-demo2', dest="state_demo2", action="store_true")

    args = parser.parse_args()
    return args


if __name__ == '__main__':

    args = parse_arguments()
    if args.board_demo1:
        controller = board.Demo1()
    elif args.board_demo2:
        controller = board.Demo2(2)
    elif args.board_demo3:
        controller = board.Demo3(holes=[
           Coordinate(row=1, col=0),
           Coordinate(row=0, col=1),
        ])
    elif args.state_demo1:
        controller = state.Demo1()
    elif args.state_demo2:
        controller = state.Demo2()
    else:
        raise ValueError("Must provide demo number")

    controller.run()