def test_validate_initial_move_facing_edge() -> None: rc = RuleChecker() bs = BoardState() r = rc.validate_initial_move( bs, [index_to_tile(1), index_to_tile(2), index_to_tile(3)], InitialMove(BoardPosition(0, 3), index_to_tile(2), Port.LeftTop, "red"), ) assert r.is_error() assert ( r.error() == "cannot make an initial move at position BoardPosition(x=0, y=3), port 7 since it does not face the interior of the board" ) r = rc.validate_initial_move( bs, [index_to_tile(1), index_to_tile(2), index_to_tile(3)], InitialMove(BoardPosition(3, 0), index_to_tile(2), Port.TopRight, "red"), ) assert r.is_error() assert ( r.error() == "cannot make an initial move at position BoardPosition(x=3, y=0), port 1 since it does not face the interior of the board" )
def test_board_to_html() -> None: # Just test that it returns a string and rely on manual testing to verify that it # looks roughly correct b = Board() assert isinstance(b.get_board_state().to_html(), str) board_r = Board.create_board_from_moves( [ InitialMove(BoardPosition(5, 0), index_to_tile(2), Port.BottomRight, "blue"), InitialMove(BoardPosition(9, 2), index_to_tile(3), Port.TopLeft, "white"), InitialMove(BoardPosition(9, 4), index_to_tile(4), Port.TopLeft, "green"), ], [ IntermediateMove(index_to_tile(5), "blue"), IntermediateMove(index_to_tile(6), "white"), ], ) assert board_r.is_ok() b2 = board_r.value().get_board_state() assert isinstance(b2.to_html(), str) assert b2.to_html() != b.get_board_state().to_html() assert b2.to_html(automatic_refresh=True) != b2.to_html( automatic_refresh=False)
def test_validate_initial_move_allowed() -> None: rc = RuleChecker() bs = BoardState() r = rc.validate_initial_move( bs, [index_to_tile(1), index_to_tile(2), index_to_tile(3)], InitialMove(BoardPosition(2, 0), index_to_tile(2), Port.BottomLeft, "red"), ) assert r.is_ok() assert bs == BoardState() # board state is unchanged r = rc.validate_initial_move( bs, [index_to_tile(1), index_to_tile(2), index_to_tile(3)], InitialMove(BoardPosition(0, 2), index_to_tile(2), Port.BottomLeft, "red"), ) assert r.is_ok() assert bs == BoardState() # board state is unchanged r = rc.validate_initial_move( bs, [index_to_tile(2), index_to_tile(3), index_to_tile(4)], InitialMove(BoardPosition(9, 2), index_to_tile(2), Port.BottomLeft, "white"), ) assert r.is_ok() assert bs == BoardState() # board state is unchanged
def test_board_intermediate_move_single_player() -> None: b = Board() logging_observer = LoggingObserver() b.add_observer(logging_observer) # Can't place a tile if they haven't moved r = b.intermediate_move(IntermediateMove(make_tiles()[2], "green")) assert r.is_error() assert r.error( ) == "cannot place a tile for player green since they are not alive" # An initial move for green r = b.initial_move( InitialMove(BoardPosition(9, 4), index_to_tile(0), Port.TopLeft, "green")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(9, 4)) == index_to_tile(0) assert b._board_state.live_players["green"] == (BoardPosition(9, 4), Port.TopLeft) # Add another tile and they move r = b.intermediate_move(IntermediateMove(index_to_tile(1), "green")) assert r.is_ok() assert b._board_state.live_players["green"] == ( BoardPosition(x=9, y=3), Port.TopRight, ) # And place a tile that will draw them off the edge of the board assert logging_observer.all_messages() == [] r = b.intermediate_move(IntermediateMove(index_to_tile(4), "green")) assert r.is_ok() assert "green" not in b._board_state.live_players assert logging_observer.all_messages() == ["exited_board: green"]
def generate_first_move( self, tiles: List[Tile], board_state: BoardState ) -> Result[Tuple[BoardPosition, Tile, PortID]]: """ Generate the first move by choosing from the given list of tiles. Returns the move along with the port that the player's token should be placed on. `set_color` and `set_rule_checker` will be called prior to this method. :param tiles: The set of tile options for the first move :param board_state: The state of the current board :return: A result containing a tuple containing the board position, tile, and port ID for the player's initial move """ for observer in self.observers: observer.initial_move_offered(tiles, board_state) r_move = self.strategy.generate_first_move(tiles, board_state) if r_move.is_error(): return r_move board_position, tile, port = r_move.value() for observer in self.observers: observer.initial_move_played( tiles, board_state, InitialMove(board_position, tile, port, self.color)) return r_move
def _get_check_initial_move(self, board: Board, color: ColorString, player: PlayerInterface, tiles: List[Tile]) -> Result[InitialMove]: """ Gets the initial move from the player and checks whether it is valid based on the rulechecker. If any errors, the player is added as a cheater. Returns an error if cheating, or the chosen move if it is valid """ r_initial_move = self._handle_player_timeout( color, lambda: player.generate_first_move(deepcopy(tiles), board.get_board_state())) if r_initial_move.is_error(): self._cheaters.add(color) return error(r_initial_move.error()) pos, tile, port = r_initial_move.value() initial_move = InitialMove(pos, tile, port, color) for observer in self._observers: observer.initial_move_played(color, tiles, board.get_board_state(), initial_move) r_rule = self._rule_checker.validate_initial_move( board.get_board_state(), tiles, initial_move) if r_rule.is_error(): self._cheaters.add(color) return error(r_initial_move.error()) return ok(initial_move)
def test_generate_move_no_valid_moves() -> None: # No possible moves, chooses first option without rotation second_s = SecondS() second_s.set_color(AllColors[0]) second_s.set_rule_checker(RuleChecker()) b = Board() assert b.initial_move( InitialMove(BoardPosition(1, 0), index_to_tile(34), Port.BottomRight, second_s.color)).is_ok() tiles = [ Tile( cast( List[Tuple[PortID, PortID]], [ tuple(Port.all()[i:i + 2]) for i in range(0, len(Port.all()), 2) ], )), Tile( cast( List[Tuple[PortID, PortID]], [ tuple(Port.all()[i:i + 2]) for i in range(0, len(Port.all()), 2) ], )), ] r = second_s.generate_move(tiles, b.get_board_state()) assert id(r.assert_value()) == id(tiles[0])
def test_board_intermediate_move_loop_moves_no_one_else() -> None: # If player A places a tile that sends them into a loop and player B is next to the would be placed tile, # player B does not move b = Board() logging_observer = LoggingObserver() b.add_observer(logging_observer) # An initial move for green r = b.initial_move( InitialMove(BoardPosition(0, 1), index_to_tile(4), Port.RightBottom, "green")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(0, 1)) == index_to_tile(4) assert b.live_players["green"] == (BoardPosition(0, 1), Port.RightBottom) # More moves for green to setup what we need to make a loop r = b.intermediate_move(IntermediateMove(index_to_tile(4), "green")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(1, 1)) == index_to_tile(4) assert b._board_state.live_players["green"] == ( BoardPosition(1, 1), Port.BottomLeft, ) r = b.intermediate_move(IntermediateMove(index_to_tile(4), "green")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(1, 2)) == index_to_tile(4) assert b._board_state.live_players["green"] == (BoardPosition(1, 2), Port.LeftTop) # And set up white next to the where the would-be loop would be created assert b.place_tile_at_index_with_scissors(index_to_tile(4), BoardPosition(0, 3)).is_ok() b._board_state = b._board_state.with_live_players( b._board_state.live_players.set("white", (BoardPosition(0, 3), Port.TopRight))) # This tile placement will make a loop. Test that it is detected, the player is removed, and a message is broadcast # to all of the defined observers. According to assignment 6 the player should be removed but the tile # should not be on the board. assert logging_observer.all_messages() == [] r = b.intermediate_move(IntermediateMove(index_to_tile(4), "green")) assert r.is_ok() assert "green" not in b.live_players assert b._board_state.get_tile(BoardPosition(0, 2)) is None assert logging_observer.all_messages() == [ "entered_loop: green", "exited_board: green", ] # And white is still in the same place assert b._board_state.live_players["white"] == (BoardPosition(0, 3), Port.TopRight)
def test_generate_move_takes_first_tile_no_rotation() -> None: # Test that the first tile is taken when valid second_s = SecondS() second_s.set_color(AllColors[0]) second_s.set_rule_checker(RuleChecker()) b = Board() b.initial_move( InitialMove(BoardPosition(4, 0), index_to_tile(34), Port.BottomRight, second_s.color)) tiles = [index_to_tile(6), index_to_tile(34)] r = second_s.generate_move(tiles, b.get_board_state()) assert r.assert_value().edges == tiles[0].edges
def test_validate_initial_move_middle() -> None: rc = RuleChecker() bs = BoardState() r = rc.validate_initial_move( bs, [index_to_tile(1), index_to_tile(2), index_to_tile(3)], InitialMove(BoardPosition(2, 3), index_to_tile(2), Port.BottomLeft, "red"), ) assert r.is_error() assert ( r.error() == "cannot make an initial move at position BoardPosition(x=2, y=3) since it is not on the edge" )
def test_wrong_number_tile_choices() -> None: rc = RuleChecker() bs = BoardState() r = rc.validate_initial_move( bs, [index_to_tile(1)], InitialMove(BoardPosition(0, 2), index_to_tile(1), Port.BottomRight, "red"), ) assert r.is_error() assert r.error() == "cannot validate move with 1 tile choices (expected 3)" r = rc.validate_initial_move( bs, [index_to_tile(1), index_to_tile(2), index_to_tile(3), index_to_tile(4)], InitialMove(BoardPosition(0, 2), index_to_tile(1), Port.BottomRight, "red"), ) assert r.is_error() assert r.error() == "cannot validate move with 4 tile choices (expected 3)" r = rc.validate_move( bs, [index_to_tile(1)], IntermediateMove(index_to_tile(1), "red") ) assert r.is_error() assert ( r.error() == "cannot validate move with 1 tile choices (expected 2)" ) r = rc.validate_move( bs, [index_to_tile(1), index_to_tile(3), index_to_tile(2)], IntermediateMove(index_to_tile(1), "red"), ) assert r.is_error() assert ( r.error() == "cannot validate move with 3 tile choices (expected 2)" )
def test_validate_initial_move_on_top_of() -> None: rc = RuleChecker() bs = BoardState() bs = bs.with_tile(index_to_tile(5), BoardPosition(0, 2)) r = rc.validate_initial_move( bs, [index_to_tile(1), index_to_tile(2), index_to_tile(3)], InitialMove(BoardPosition(0, 2), index_to_tile(2), Port.BottomLeft, "red"), ) assert r.is_error() assert ( r.error() == "cannot place tile at position BoardPosition(x=0, y=2) since there is already a tile at that position" )
def test_validate_initial_move_not_in_choices() -> None: rc = RuleChecker() bs = BoardState() r = rc.validate_initial_move( bs, [index_to_tile(1), index_to_tile(3), index_to_tile(4)], InitialMove(BoardPosition(0, 2), index_to_tile(2), Port.BottomLeft, "red"), ) assert r.is_error() assert ( r.error() == "tile Tile(idx=2, edges=[(0, 5), (1, 4), (2, 7), (3, 6)]) is not in the list of tiles [Tile(idx=1, edges=[(0, 4), (1, 5), (2, 6), (3, 7)]), Tile(idx=3, edges=[(0, 4), (1, 3), (2, 6), (5, 7)]), Tile(idx=4, edges=[(0, 7), (1, 2), (3, 4), (5, 6)])] the player was given" ) assert bs == BoardState() # board state is unchanged
def test_generate_move_needs_rotation() -> None: # Test that the first tile is rotated to a valid rotation when the second tile is invalid third_s = ThirdS() third_s.set_color(AllColors[0]) third_s.set_rule_checker(RuleChecker()) b = Board() b.initial_move( InitialMove( BoardPosition(9, 0), index_to_tile(34), Port.BottomRight, third_s.color ) ) tiles = [index_to_tile(11), index_to_tile(34)] r = third_s.generate_move(tiles, b.get_board_state()) assert r.assert_value().edges == tiles[0].rotate().edges
def test_validate_initial_move_double_play() -> None: rc = RuleChecker() bs = BoardState() bs = bs.with_live_players( bs.live_players.set("red", (BoardPosition(5, 0), Port.RightBottom)) ) copied = deepcopy(bs) r = rc.validate_initial_move( copied, [index_to_tile(1), index_to_tile(2), index_to_tile(3)], InitialMove(BoardPosition(0, 2), index_to_tile(2), Port.BottomLeft, "red"), ) assert r.is_error() assert ( r.error() == "cannot place player red since the player is already on the board" ) assert bs == copied # board state is unchanged
def test_create_board_from_initial_placements() -> None: assert (Board.create_board_from_initial_placements( []).assert_value().get_board_state() == BoardState()) board_r = Board.create_board_from_initial_placements([ InitialMove(BoardPosition(5, 0), index_to_tile(2), Port.BottomRight, "blue"), InitialMove(BoardPosition(9, 2), index_to_tile(3), Port.TopLeft, "white"), InitialMove(BoardPosition(9, 4), index_to_tile(4), Port.TopLeft, "green"), ]) assert board_r.is_ok() board_state = board_r.value().get_board_state() for x in range(MIN_BOARD_COORDINATE, MAX_BOARD_COORDINATE + 1): for y in range(MIN_BOARD_COORDINATE, MAX_BOARD_COORDINATE + 1): tile = board_state.get_tile(BoardPosition(x, y)) if (x, y) == (5, 0): assert tile == index_to_tile(2) elif (x, y) == (9, 2): assert tile == index_to_tile(3) elif (x, y) == (9, 4): assert tile == index_to_tile(4) else: assert tile is None assert board_r.value().live_players == pmap({ "blue": (BoardPosition(x=5, y=0), 4), "green": (BoardPosition(x=9, y=4), 0), "white": (BoardPosition(x=9, y=2), 0), }) board_r = Board.create_board_from_initial_placements([ InitialMove(BoardPosition(5, 0), make_tiles()[2], Port.BottomRight, "blue"), InitialMove(BoardPosition(9, 2), make_tiles()[2], Port.TopLeft, "white"), InitialMove(BoardPosition(9, 4), make_tiles()[2], Port.TopLeft, "green"), # And an invalid one InitialMove(BoardPosition(9, 7), make_tiles()[2], Port.TopLeft, "green"), ]) assert board_r.is_error() assert ( board_r.error() == "failed to create board from set of initial placements: cannot place player green since the player is already on the board" )
def test_player_calls_observers() -> None: bs = BoardState() lo = LoggingPlayerObserver() p = Player(FirstS()) p.add_observer(lo) p.set_color("red") assert lo.set_colors == ["red"] p.set_color("green") assert lo.set_colors == ["red", "green"] assert p.generate_first_move( [index_to_tile(3), index_to_tile(4), index_to_tile(5)], bs).is_ok() assert lo.initial_move_offereds == [ ([index_to_tile(3), index_to_tile(4), index_to_tile(5)], bs) ] assert lo.initial_move_playeds == [( [index_to_tile(3), index_to_tile(4), index_to_tile(5)], bs, InitialMove(BoardPosition(1, 0), index_to_tile(5), Port.RightTop, "green"), )] assert p.generate_move([index_to_tile(10), index_to_tile(11)], bs).is_ok() assert lo.intermediate_move_offereds == [ ([index_to_tile(10), index_to_tile(11)], bs) ] assert lo.intermediate_move_playeds == [( [index_to_tile(10), index_to_tile(11)], bs, IntermediateMove(index_to_tile(10), "green"), )] gr = ([{"red"}, {"black", "green"}], {"white"}) p.game_result(gr) # type: ignore assert lo.game_results == [gr]
def setup_board(board_setup_data: JSON) -> Board: """ Setup the board according to the given board setup data. This is the first JSON value read from the integration test which is an array of initial-place or intermediate-place. Raises an exception if the input is invalid in any way. :param board_setup_data: An array containg either initial-place elements or intermediate-place elements :return: A board created from the setup data """ board = Board() for item in board_setup_data: if len(item) == 5: initial_place = InitialPlace.from_json(item) r = board.initial_move_with_scissors( InitialMove( BoardPosition(initial_place.x_index, initial_place.y_index), tile_pattern_to_tile( initial_place.tile_pat.tile_index, initial_place.tile_pat.rotation_angle, ), network_port_id_to_port_id(initial_place.port), initial_place.player, )) if r.is_error(): raise Exception(r.error()) elif len(item) == 3: intermediate_place = IntermediatePlace.from_json(item) r = board.place_tile_at_index_with_scissors( tile_pattern_to_tile( intermediate_place.tile_pat.tile_index, intermediate_place.tile_pat.rotation_angle, ), BoardPosition(intermediate_place.x_index, intermediate_place.y_index), ) if r.is_error(): raise Exception(r.error()) else: raise Exception(f"Failed to parse JSON input: {item}") return board
def test_board_intermediate_move_remove_player_after_loop() -> None: b = Board() logging_observer = LoggingObserver() b.add_observer(logging_observer) # An initial move for green r = b.initial_move( InitialMove(BoardPosition(0, 1), index_to_tile(4), Port.RightBottom, "green")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(0, 1)) == index_to_tile(4) assert b.live_players["green"] == (BoardPosition(0, 1), Port.RightBottom) # More moves for green that would create a loop r = b.intermediate_move(IntermediateMove(index_to_tile(4), "green")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(1, 1)) == index_to_tile(4) assert b._board_state.live_players["green"] == ( BoardPosition(1, 1), Port.BottomLeft, ) r = b.intermediate_move(IntermediateMove(index_to_tile(4), "green")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(1, 2)) == index_to_tile(4) assert b._board_state.live_players["green"] == (BoardPosition(1, 2), Port.LeftTop) assert logging_observer.all_messages() == [] # This tile placement will make a loop. Test that it is detected, the player is removed, and a message is broadcast # to all of the defined observers. According to assignment 6 the player should be removed but the tile # should not be on the board. r = b.intermediate_move(IntermediateMove(index_to_tile(4), "green")) assert r.is_ok() assert "green" not in b.live_players assert b._board_state.get_tile(BoardPosition(0, 2)) is None assert logging_observer.all_messages() == [ "entered_loop: green", "exited_board: green", ]
def test_ignore_invalid_moves(*_: Any) -> None: # Test that giving invalid moves to the observer doesn't crash anything. Mocks out subprocess # so that google-chrome isn't opened. Mocks out start_websocket_distributor so that it doesn't # start another process. b = Board() gpo = GraphicalPlayerObserver() gpo.set_color("red") gpo.set_players(["red", "blue", "green", "black", "white"]) tile_choices = [index_to_tile(2), index_to_tile(3)] gpo.initial_move_played( tile_choices, b.get_board_state(), InitialMove(BoardPosition(2, 3), index_to_tile(2), Port.TopLeft, "red"), ) tile_choices = [index_to_tile(2), index_to_tile(3), index_to_tile(4)] gpo.intermediate_move_played(tile_choices, b.get_board_state(), IntermediateMove(index_to_tile(2), "red"))
def test_allow_collision() -> None: rc = RuleChecker() b = Board() move = InitialMove(BoardPosition(0, 0), index_to_tile(4), Port.BottomRight, "red") assert rc.validate_initial_move( b.get_board_state(), [index_to_tile(4), index_to_tile(5), index_to_tile(6)], move, ).is_ok() r = b.initial_move(move) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(0, 0)) == index_to_tile(4) assert b.live_players["red"] == (BoardPosition(0, 0), Port.BottomRight) move = InitialMove(BoardPosition(2, 0), index_to_tile(22), Port.LeftBottom, "green") assert rc.validate_initial_move( b.get_board_state(), [index_to_tile(22), index_to_tile(5), index_to_tile(6)], move, ).is_ok() r = b.initial_move(move) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(2, 0)) == index_to_tile(22) assert b.live_players["green"] == (BoardPosition(2, 0), Port.LeftBottom) move = InitialMove(BoardPosition(4, 0), index_to_tile(22), Port.LeftBottom, "blue") assert rc.validate_initial_move( b.get_board_state(), [index_to_tile(22), index_to_tile(5), index_to_tile(6)], move, ).is_ok() r = b.initial_move(move) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(4, 0)) == index_to_tile(22) assert b.live_players["blue"] == (BoardPosition(4, 0), Port.LeftBottom) move = InitialMove(BoardPosition(6, 0), index_to_tile(22), Port.LeftBottom, "white") assert rc.validate_initial_move( b.get_board_state(), [index_to_tile(22), index_to_tile(5), index_to_tile(6)], move, ).is_ok() r = b.initial_move(move) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(6, 0)) == index_to_tile(22) assert b.live_players["white"] == (BoardPosition(6, 0), Port.LeftBottom) move = InitialMove(BoardPosition(8, 0), index_to_tile(22), Port.LeftBottom, "black") assert rc.validate_initial_move( b.get_board_state(), [index_to_tile(22), index_to_tile(5), index_to_tile(6)], move, ).is_ok() r = b.initial_move(move) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(8, 0)) == index_to_tile(22) assert b.live_players["black"] == (BoardPosition(8, 0), Port.LeftBottom) intermediate_move = IntermediateMove(index_to_tile(22), "black") assert rc.validate_move( b.get_board_state(), [index_to_tile(22), index_to_tile(5)], intermediate_move ).is_ok() r = b.intermediate_move(intermediate_move) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(7, 0)) == index_to_tile(22) intermediate_move = IntermediateMove(index_to_tile(22), "black") assert rc.validate_move( b.get_board_state(), [index_to_tile(22), index_to_tile(5)], intermediate_move ).is_ok() r = b.intermediate_move(intermediate_move) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(5, 0)) == index_to_tile(22) intermediate_move = IntermediateMove(index_to_tile(22), "black") assert rc.validate_move( b.get_board_state(), [index_to_tile(22), index_to_tile(5)], intermediate_move ).is_ok() r = b.intermediate_move(intermediate_move) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(3, 0)) == index_to_tile(22) intermediate_move = IntermediateMove(index_to_tile(22), "black") assert rc.validate_move( b.get_board_state(), [index_to_tile(22), index_to_tile(5)], intermediate_move ).is_ok() r = b.intermediate_move(intermediate_move) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(1, 0)) == index_to_tile(22) assert len(b._board_state.live_players) == 5 assert len(set(b._board_state.live_players.values())) == 1 assert b.live_players == pmap( { "black": (BoardPosition(x=0, y=0), 4), "blue": (BoardPosition(x=0, y=0), 4), "green": (BoardPosition(x=0, y=0), 4), "red": (BoardPosition(x=0, y=0), 4), "white": (BoardPosition(x=0, y=0), 4), } )
def test_multi_collision() -> None: b = Board() logging_observer = LoggingObserver() b.add_observer(logging_observer) r = b.initial_move( InitialMove(BoardPosition(0, 0), index_to_tile(4), Port.BottomRight, "red")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(0, 0)) == index_to_tile(4) assert b.live_players["red"] == (BoardPosition(0, 0), Port.BottomRight) r = b.initial_move( InitialMove(BoardPosition(2, 0), index_to_tile(22), Port.LeftBottom, "green")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(2, 0)) == index_to_tile(22) assert b.live_players["green"] == (BoardPosition(2, 0), Port.LeftBottom) r = b.initial_move( InitialMove(BoardPosition(4, 0), index_to_tile(22), Port.LeftBottom, "blue")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(4, 0)) == index_to_tile(22) assert b.live_players["blue"] == (BoardPosition(4, 0), Port.LeftBottom) r = b.initial_move( InitialMove(BoardPosition(6, 0), index_to_tile(22), Port.LeftBottom, "white")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(6, 0)) == index_to_tile(22) assert b.live_players["white"] == (BoardPosition(6, 0), Port.LeftBottom) r = b.initial_move( InitialMove(BoardPosition(8, 0), index_to_tile(22), Port.LeftBottom, "black")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(8, 0)) == index_to_tile(22) assert b.live_players["black"] == (BoardPosition(8, 0), Port.LeftBottom) r = b.intermediate_move(IntermediateMove(index_to_tile(22), "black")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(7, 0)) == index_to_tile(22) r = b.intermediate_move(IntermediateMove(index_to_tile(22), "black")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(5, 0)) == index_to_tile(22) r = b.intermediate_move(IntermediateMove(index_to_tile(22), "black")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(3, 0)) == index_to_tile(22) r = b.intermediate_move(IntermediateMove(index_to_tile(22), "black")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(1, 0)) == index_to_tile(22) assert len(b._board_state.live_players) == 5 assert len(set(b._board_state.live_players.values())) == 1 assert b.live_players == pmap({ "black": (BoardPosition(x=0, y=0), 4), "blue": (BoardPosition(x=0, y=0), 4), "green": (BoardPosition(x=0, y=0), 4), "red": (BoardPosition(x=0, y=0), 4), "white": (BoardPosition(x=0, y=0), 4), })
def test_board_intermediate_move() -> None: b = Board() logging_observer = LoggingObserver() b.add_observer(logging_observer) # An initial move for green r = b.initial_move( InitialMove(BoardPosition(0, 1), index_to_tile(0), Port.RightBottom, "green")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(0, 1)) == index_to_tile(0) # An initial move for red r = b.initial_move( InitialMove(BoardPosition(0, 3), index_to_tile(0), Port.RightBottom, "red")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(0, 3)) == index_to_tile(0) assert b._board_state.live_players["green"] == ( BoardPosition(0, 1), Port.RightBottom, ) assert b._board_state.live_players["red"] == (BoardPosition(0, 3), Port.RightBottom) # Add a tile for green r = b.intermediate_move(IntermediateMove(index_to_tile(4), "green")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(1, 1)) == index_to_tile(4) assert b._board_state.live_players["green"] == ( BoardPosition(1, 1), Port.BottomLeft, ) assert b._board_state.live_players["red"] == (BoardPosition(0, 3), Port.RightBottom) # Add another tile for green to get closer to red r = b.intermediate_move( IntermediateMove(index_to_tile(22).rotate(), "green")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(1, 2)) == index_to_tile(22) assert b._board_state.live_players["green"] == ( BoardPosition(1, 2), Port.BottomLeft, ) assert b._board_state.live_players["red"] == (BoardPosition(0, 3), Port.RightBottom) # And another tile that will cause green and red to both traverse over the same tile r = b.intermediate_move(IntermediateMove(index_to_tile(2), "green")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(1, 3)) == index_to_tile(2) assert b._board_state.live_players["green"] == ( BoardPosition(1, 3), Port.BottomLeft, ) assert b._board_state.live_players["red"] == (BoardPosition(1, 3), Port.RightBottom) assert logging_observer.all_messages() == []
def test_create_board_from_moves() -> None: assert (Board.create_board_from_moves( [], []).assert_value().get_board_state() == BoardState()) board_r = Board.create_board_from_moves( [ InitialMove(BoardPosition(5, 0), index_to_tile(2), Port.BottomRight, "blue"), InitialMove(BoardPosition(9, 2), index_to_tile(3), Port.TopLeft, "white"), InitialMove(BoardPosition(9, 4), index_to_tile(4), Port.TopLeft, "green"), ], [ IntermediateMove(index_to_tile(5), "blue"), IntermediateMove(index_to_tile(6), "white"), ], ) assert board_r.is_ok() board_state = board_r.value().get_board_state() for x in range(MIN_BOARD_COORDINATE, MAX_BOARD_COORDINATE + 1): for y in range(MIN_BOARD_COORDINATE, MAX_BOARD_COORDINATE + 1): if (x, y) == (5, 0): assert board_state.get_tile(BoardPosition( x, y)) == index_to_tile(2) elif (x, y) == (5, 1): assert board_state.get_tile(BoardPosition( x, y)) == index_to_tile(5) elif (x, y) == (9, 1): assert board_state.get_tile(BoardPosition( x, y)) == index_to_tile(6) elif (x, y) == (9, 2): assert board_state.get_tile(BoardPosition( x, y)) == index_to_tile(3) elif (x, y) == (9, 4): assert board_state.get_tile(BoardPosition( x, y)) == index_to_tile(4) else: assert board_state.get_tile(BoardPosition(x, y)) is None assert board_r.value().live_players == pmap({ "blue": (BoardPosition(x=5, y=1), 2), "green": (BoardPosition(x=9, y=4), 0), "white": (BoardPosition(x=9, y=1), 7), }) board_r = Board.create_board_from_moves( [ InitialMove(BoardPosition(5, 0), index_to_tile(2), Port.BottomRight, "blue"), InitialMove(BoardPosition(9, 2), index_to_tile(3), Port.TopLeft, "white"), InitialMove(BoardPosition(9, 4), index_to_tile(4), Port.TopLeft, "green"), ], [ IntermediateMove(index_to_tile(5), "blue"), IntermediateMove(index_to_tile(6), "white"), # An invalid second move since black never had an initial move IntermediateMove(index_to_tile(7), "black"), ], ) assert board_r.is_error() assert ( board_r.error() == "failed to create board from moves: cannot place a tile for player black since they are not alive" ) board_r = Board.create_board_from_moves( [ InitialMove(BoardPosition(5, 0), index_to_tile(2), Port.BottomRight, "blue"), InitialMove(BoardPosition(9, 2), index_to_tile(3), Port.TopLeft, "white"), InitialMove(BoardPosition(9, 4), index_to_tile(4), Port.TopLeft, "green"), # An invalid first move since someone placed a tile next to this position InitialMove(BoardPosition(9, 3), index_to_tile(7), Port.TopRight, "black"), ], [ IntermediateMove(index_to_tile(5), "blue"), IntermediateMove(index_to_tile(6), "white"), ], ) assert board_r.is_error() assert ( board_r.error() == "failed to create board from set of initial placements: cannot make an initial move at position BoardPosition(x=9, y=3) since the surrounding tiles are not all empty" )
import time from typing import Any, cast from unittest import mock import pytest from Common.board import Board from Common.board_position import BoardPosition from Common.board_state import BoardState from Common.moves import InitialMove, IntermediateMove from Common.tiles import Port, Tile, index_to_tile from Common.tsuro_types import TileIndex from Player.player_observer import GraphicalPlayerObserver TEST_RUN_MOVES = [ InitialMove(BoardPosition(5, 0), index_to_tile(5), Port.BottomLeft, "red"), InitialMove(BoardPosition(2, 0), index_to_tile(3), Port.BottomLeft, "blue"), InitialMove(BoardPosition(9, 2), index_to_tile(3), Port.TopRight, "green"), IntermediateMove(index_to_tile(2), "red"), IntermediateMove(index_to_tile(2), "red"), IntermediateMove(index_to_tile(2), "red"), IntermediateMove(index_to_tile(4), "green"), IntermediateMove(index_to_tile(2), "red"), IntermediateMove(index_to_tile(2), "red"), ] def rand_tile() -> Tile: return index_to_tile(cast(TileIndex, random.randint(0, 34)))
def test_board_initial_move() -> None: b = Board() logging_observer = LoggingObserver() b.add_observer(logging_observer) pos = BoardPosition(1, 1) r = b.initial_move( InitialMove(pos, make_tiles()[0], Port.BottomRight, "red")) assert r.is_error() assert ( r.error() == f"cannot make an initial move at position {pos} since it is not on the edge" ) pos = BoardPosition(3, 0) port = Port.TopLeft r = b.initial_move(InitialMove(pos, make_tiles()[0], port, "red")) assert r.is_error() assert ( r.error() == f"cannot make an initial move at position {pos}, port {port} since it does not face the interior of the " f"board") b.place_tile_at_index_with_scissors(make_tiles()[0], BoardPosition(2, 0)) r = b.initial_move( InitialMove(BoardPosition(2, 0), make_tiles()[1], Port.BottomRight, "blue")) assert r.is_error() assert ( r.error() == "cannot place tile at position BoardPosition(x=2, y=0) since there is already a tile at that position" ) r = b.initial_move( InitialMove(BoardPosition(3, 0), make_tiles()[1], Port.BottomRight, "blue")) assert r.is_error() assert ( r.error() == "cannot make an initial move at position BoardPosition(x=3, y=0) since the surrounding tiles are not all " "empty") r = b.initial_move( InitialMove(BoardPosition(1, 0), make_tiles()[1], Port.BottomRight, "blue")) assert r.is_error() assert ( r.error() == "cannot make an initial move at position BoardPosition(x=1, y=0) since the surrounding tiles are not all " "empty") b._board_state = b._board_state.with_live_players( b._board_state.live_players.set( "red", (BoardPosition(2, 0), Port.BottomRight))) r = b.initial_move( InitialMove(BoardPosition(5, 0), make_tiles()[1], Port.BottomRight, "red")) assert r.is_error() assert (r.error() == "cannot place player red since the player is already on the board") r = b.initial_move( InitialMove(BoardPosition(5, 0), make_tiles()[2], Port.BottomRight, "blue")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(5, 0)) == make_tiles()[2] assert b._board_state.live_players["blue"] == ( BoardPosition(5, 0), Port.BottomRight, ) r = b.initial_move( InitialMove(BoardPosition(9, 2), make_tiles()[2], Port.TopLeft, "white")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(9, 2)) == make_tiles()[2] b._board_state = b._board_state.with_live_players( b._board_state.live_players.set("white", (BoardPosition(9, 2), Port.TopLeft))) r = b.initial_move( InitialMove(BoardPosition(9, 4), make_tiles()[2], Port.TopLeft, "green")) assert r.is_ok() assert b._board_state.get_tile(BoardPosition(9, 4)) == make_tiles()[2] assert b._board_state.live_players["green"] == (BoardPosition(9, 4), Port.TopLeft) assert logging_observer.all_messages() == []