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 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_board_place_tile_at_index_with_scissors() -> None: b = Board() assert b.place_tile_at_index_with_scissors(index_to_tile(5), BoardPosition(3, 0)).is_ok() assert b.get_board_state().get_tile(BoardPosition(3, 0)) == index_to_tile(5) r = b.place_tile_at_index_with_scissors(index_to_tile(5), BoardPosition(3, 0)) assert r.is_ok()
def test_validate_place_tile_unnecessary_loop() -> None: rc = RuleChecker() bs = BoardState() bs = bs.with_live_players( bs.live_players.set("red", (BoardPosition(2, 0), Port.RightBottom)) ) bs = bs.with_tile(index_to_tile(4), BoardPosition(2, 0)) bs = bs.with_tile(index_to_tile(4), BoardPosition(2, 1)) bs = bs.with_tile(index_to_tile(4), BoardPosition(3, 1)) r = rc.move_creates_loop(bs, IntermediateMove(index_to_tile(4), "red")) assert r.is_ok() assert r.value() r = rc.move_creates_loop(bs, IntermediateMove(index_to_tile(5), "red")) assert r.is_ok() assert r.value() r = rc.is_move_suicidal(bs, IntermediateMove(index_to_tile(5).rotate(), "red")) assert r.is_ok() assert not r.value() r2 = rc.validate_move( bs, [index_to_tile(5), index_to_tile(4)], IntermediateMove(index_to_tile(4), "red"), ) assert r2.is_error() assert ( r2.error() == "player chose a loopy move when this does not create a loop: Tile(idx=5, edges=[(0, 7), (1, 5), (2, 6), (3, 4)])" )
def test_is_move_suicidal_walk_off_edge() -> None: rc = RuleChecker() bs = BoardState() bs = bs.with_live_players( bs.live_players.set("red", (BoardPosition(2, 0), Port.RightTop)) ) bs = bs.with_tile(index_to_tile(2), BoardPosition(2, 0)) r = rc.is_move_suicidal(bs, IntermediateMove(index_to_tile(4), "red")) assert r.is_ok() assert r.value()
def test_validate_place_tile_inexistent_player() -> None: rc = RuleChecker() bs = BoardState() r = rc.validate_move( bs, [index_to_tile(1), index_to_tile(2)], IntermediateMove(index_to_tile(1), "red"), ) assert r.is_error() assert r.error() == "cannot place a tile for player red since they are not alive"
def test_incorrect_number_tiles_given() -> None: second_s = SecondS() bs = BoardState() r = second_s.generate_move( [index_to_tile(1), index_to_tile(2), index_to_tile(3)], bs) assert r.is_error() assert r.error() == "Strategy.generate_move given 3 (expected 2)" r2 = second_s.generate_first_move([index_to_tile(1), index_to_tile(2)], bs) assert r2.is_error() assert r2.error() == "Strategy.generate_first_move given 2 (expected 3)"
def test_generate_first_move_0_1() -> None: # The first move is just placing a tile at 0,1 third_s = ThirdS() bs = BoardState() r = third_s.generate_first_move( [index_to_tile(22), index_to_tile(23), index_to_tile(24)], bs ) assert r.assert_value() == ( BoardPosition(x=0, y=1), index_to_tile(24), Port.TopLeft, )
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_is_move_suicidal_loop() -> None: rc = RuleChecker() bs = BoardState() bs = bs.with_live_players( bs.live_players.set("red", (BoardPosition(2, 0), Port.RightBottom)) ) bs = bs.with_tile(index_to_tile(4), BoardPosition(2, 0)) bs = bs.with_tile(index_to_tile(4), BoardPosition(2, 1)) bs = bs.with_tile(index_to_tile(4), BoardPosition(3, 1)) r = rc.move_creates_loop(bs, IntermediateMove(index_to_tile(4), "red")) assert r.is_ok() assert r.value()
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_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_generate_first_move_1_0() -> None: # The first move is just placing a tile at 0,1 second_s = SecondS() bs = BoardState() r = second_s.generate_first_move( [index_to_tile(22), index_to_tile(23), index_to_tile(24)], bs) assert r.assert_value() == ( BoardPosition(x=1, y=0), index_to_tile(24), Port.RightTop, )
def handle_intermediate(self, intermediate) -> List: """ Converts the intermediate tile message to a list of Tiles and a BoardState for the player. Returns the tile pattern representing hte move the player made in the format: [tile_index, rotation] """ board = setup_board(intermediate[0]) board_state = board.get_board_state() tiles = [index_to_tile(intermediate[1]), index_to_tile(intermediate[2])] r_move = self._player.generate_move(tiles, board_state) if r_move.is_ok(): return tile_to_tile_pattern(r_move.value()) else: self._tournament_incomplete = False
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 handle_initial(self, initial) -> List: """ Converts the initial tile message to a list of Tiles and a BoardState for the player. Returns the action the player takes in the format: [tile-pat, port, index, index] """ board = setup_board(initial[0]) board_state = board.get_board_state() tiles = [index_to_tile(initial[1]), index_to_tile(initial[2]), index_to_tile(initial[3])] r_move = self._player.generate_first_move(tiles, board_state) if r_move.is_ok(): move = r_move.value() return [tile_to_tile_pattern(move[1]), port_id_to_network_port_id(move[2]), move[0].x, move[0].y] else: self._tournament_incomplete = False
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_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_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 main() -> None: """ Connect to the server at host:port via TCP to render a board state to the GUI via a GraphicalPlayerObserver """ json_stream = StdinStdoutJSONStream() state_pats_json_r = json_stream.receive_message() board = setup_board(state_pats_json_r.assert_value()) turn_pat_json = json_stream.receive_message().assert_value() act_pat = ActionPat.from_json(turn_pat_json[0] # pylint: disable=unsubscriptable-object ) offered_tiles: List[Tile] = [ index_to_tile(tile_idx) for tile_idx in turn_pat_json[1:] # pylint: disable=unsubscriptable-object ] move = IntermediateMove( tile_pattern_to_tile(act_pat.tile_pat.tile_index, act_pat.tile_pat.rotation_angle), act_pat.player, ) rule_checker = RuleChecker() r = rule_checker.validate_move(board.get_board_state(), offered_tiles, move) if r.is_error(): json_stream.send_message("cheating") else: json_stream.send_message("legal") if DEBUG: board.get_board_state().debug_display_board() r = board.intermediate_move(move) if r.is_error(): print("Failed to render #2") else: board.get_board_state().debug_display_board()
def test_get_tiles() -> None: ref = Referee() ref.set_tile_iterator(deterministic_tile_iterator()) assert ref._get_tiles(3) == [ index_to_tile(0), index_to_tile(1), index_to_tile(2) ] assert [tile_to_index(t) for t in ref._get_tiles(35)] == list(range(3, 35)) + [ 0, 1, 2, ] assert len(ref._get_tiles(123)) == 123
def test_index_to_tile() -> None: seen = set() for i in range(0, 35): t = tiles.index_to_tile(cast(TileIndex, i)) assert isinstance(t, tiles.Tile) seen.add(t) assert len(seen) == 35
def test_is_valid_initial_position() -> None: b = Board() assert b.place_tile_at_index_with_scissors( index_to_tile(2), BoardPosition(0, 5) ).is_ok() assert ( PhysicalConstraintChecker.is_valid_initial_port( b._board_state, BoardPosition(0, 5), Port.TopRight ).error() == "cannot place tile at position BoardPosition(x=0, y=5) since there is already a tile at that position" ) assert ( PhysicalConstraintChecker.is_valid_initial_port( b._board_state, BoardPosition(3, 5), Port.TopRight ).error() == "cannot make an initial move at position BoardPosition(x=3, y=5) since it is not on the edge" ) assert ( PhysicalConstraintChecker.is_valid_initial_port( b._board_state, BoardPosition(0, 6), Port.TopRight ).error() == "cannot make an initial move at position BoardPosition(x=0, y=6) since the surrounding tiles are not all empty" ) assert ( PhysicalConstraintChecker.is_valid_initial_port( b._board_state, BoardPosition(0, 4), Port.TopRight ).error() == "cannot make an initial move at position BoardPosition(x=0, y=4) since the surrounding tiles are not all empty" )
def test_is_move_suicidal_inexistent() -> None: rc = RuleChecker() bs = BoardState() r = rc.is_move_suicidal(bs, IntermediateMove(index_to_tile(22), "red")) assert r.is_error() assert r.error() == "player red is not alive thus the move cannot be suicidal"
def main(host: str, port: int) -> None: """ Connect to the server at host:port via TCP to render a board state to the GUI via a GraphicalPlayerObserver :param host: The host to connect to :param port: The port to connect to :return: None """ gpo = GraphicalPlayerObserver() json_stream = NetworkJSONStream.tcp_client_to(host, port) json_stream.send_message(AUTHORS) state_pats_json_r = json_stream.receive_message() board = setup_board(state_pats_json_r.assert_value()) gpo.set_players(list(board.live_players.keys())) turn_pat_json = json_stream.receive_message().assert_value() act_pat = ActionPat.from_json(turn_pat_json[0] # pylint: disable=unsubscriptable-object ) gpo.set_color(act_pat.player) offered_tiles: List[Tile] = [ index_to_tile(tile_idx) for tile_idx in turn_pat_json[1:] ] gpo.intermediate_move_offered(offered_tiles, board.get_board_state()) move = IntermediateMove( tile_pattern_to_tile(act_pat.tile_pat.tile_index, act_pat.tile_pat.rotation_angle), act_pat.player, ) gpo.intermediate_move_played(offered_tiles, board.get_board_state(), move) json_stream.close()
def test_validate_place_tile_not_in_choices() -> None: rc = RuleChecker() bs = BoardState() bs = bs.with_live_players( bs.live_players.set("red", (BoardPosition(2, 0), Port.TopRight)) ) r = rc.validate_move( bs, [index_to_tile(2), index_to_tile(3)], IntermediateMove(index_to_tile(1), "red"), ) assert r.is_error() assert ( r.error() == "tile Tile(idx=1, edges=[(0, 4), (1, 5), (2, 6), (3, 7)]) is not in the list of tiles [Tile(idx=2, edges=[(0, 5), (1, 4), (2, 7), (3, 6)]), Tile(idx=3, edges=[(0, 4), (1, 3), (2, 6), (5, 7)])] the player was given" )
def test_validate_place_tile_required_suicide() -> None: rc = RuleChecker() bs = BoardState() bs = bs.with_live_players( bs.live_players.set("red", (BoardPosition(2, 0), Port.RightTop)) ) bs = bs.with_tile(index_to_tile(2), BoardPosition(2, 0)) r = rc.is_move_suicidal(bs, IntermediateMove(index_to_tile(4), "red")) assert r.is_ok() assert r.value() r2 = rc.validate_move( bs, [index_to_tile(4), index_to_tile(4)], IntermediateMove(index_to_tile(4).rotate(), "red"), ) assert r2.is_ok()
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_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_first_move_2_9() -> None: # The first move is placing a tile at 9,2 since there are tiles blocking the other positions third_s = ThirdS() bs = BoardState() bs = bs.with_tile(index_to_tile(0), BoardPosition(0, 1)) bs = bs.with_tile(index_to_tile(0), BoardPosition(0, 3)) bs = bs.with_tile(index_to_tile(1), BoardPosition(0, 5)) bs = bs.with_tile(index_to_tile(3), BoardPosition(0, 7)) bs = bs.with_tile(index_to_tile(4), BoardPosition(0, 9)) r = third_s.generate_first_move( [index_to_tile(22), index_to_tile(23), index_to_tile(4)], bs ) assert r.assert_value() == (BoardPosition(x=2, y=9), index_to_tile(4), Port.TopLeft)