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_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_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 main() -> None: """ Main function for the xboard integration test. Creates a board, applies action pats, and prints data to stdout as described in assignment 4: http://www.ccs.neu.edu/home/matthias/4500-f19/4.html :return: None """ stdin_stream = StdinStdoutJSONStream() board = setup_board(stdin_stream.receive_message().assert_value()) logging_observer = LoggingObserver() board.add_observer(logging_observer) if "red" not in board.live_players: print_and_exit("red never played", board) for act_pat_json in stdin_stream.message_iterator(): act_pat = ActionPat.from_json(act_pat_json.assert_value()) r = board.intermediate_move( IntermediateMove( tile_pattern_to_tile(act_pat.tile_pat.tile_index, act_pat.tile_pat.rotation_angle), act_pat.player, )) if r.is_error(): raise Exception(r.error()) if logging_observer.entered_loop: print_and_exit("infinite", board) seen_pos_port: Set[Tuple[BoardPosition, PortID]] = set() for (pos, port) in board.live_players.values(): if (pos, port) in seen_pos_port: print_and_exit("collision", board) seen_pos_port.add((pos, port)) if "red" not in board.live_players: print_and_exit("red died", board) red_pos, red_port = board.live_players["red"] til = board.get_board_state().get_tile(red_pos) if til is None: raise Exception( "There is no tile at red's position - this should never happen") tile_pat = TilePat(tile_to_index(til), tile_to_rotation_angle(til)) print_and_exit( [ tile_pat.to_json(), "red", port_id_to_network_port_id(red_port), red_pos.x, red_pos.y, ], board, )
def intermediate_move(self, move: IntermediateMove) -> Result[None]: """ Place the given tile at the given position on this board. :param move: The intermediate move to be placed :return: A result containing an error if the move is invalid or a result containing the value None if there was no error and the tile was placed successfully """ validate_result = self.validate_intermediate_move(move) if validate_result.is_error(): return validate_result pos_r = self._board_state.calculate_adjacent_position_of_player(move.player) if pos_r.is_error(): return error( "cannot place tile for player %s: %s" % (move.player, pos_r.error()) ) pos = pos_r.value() if pos is None: return error( f"cannot place tile for player {move.player} since it would go off the edge of the board (this should " f"never happen!)" ) if self._board_state.get_tile(pos) is not None: return error( f"cannot place for player {move.player} since it would be on top of an existing tile (this should" f"never happen!)" ) # Assignment 6 states that if a move causes a loop it is legal (and the board must support it) # but that the expected behavior is to remove the players on the loop, not the place tile, # and accept the move. orig_board_state = deepcopy(self._board_state) temp_logging_observer = LoggingObserver() self.add_observer(temp_logging_observer) self._board_state = self._board_state.with_tile(move.tile, pos) r = self._move_all_players_along_paths() if r.is_error(): return r if temp_logging_observer.entered_loop: # Placing this tile caused someone to enter a loop. So we undo any changes, delete the # people that were in the loop, and return ok self._board_state = orig_board_state for player in temp_logging_observer.entered_loop: self.remove_player(player) self.remove_observer(temp_logging_observer) return ok(None)
def move_creates_loop(self, board_state: BoardState, move: IntermediateMove) -> Result[bool]: """ Returns whether or not the given move creates a loop for anyone on the board. Note that this returns False if a loop is created that no player is on. :param board_state: The board state to apply the move to :param move: The intermediate move being applied :return: A Result containing whether or not the move creates a loop for anyone on the baord """ logging_observer = LoggingObserver() board = Board(deepcopy(board_state)) board.add_observer(logging_observer) r = board.intermediate_move(move) if r.is_error(): return error(r.error()) return ok(len(logging_observer.entered_loop) > 0)
def is_move_suicidal(self, board_state: BoardState, move: IntermediateMove) -> Result[bool]: """ Returns whether the given intermediate move is suicidal. A suicidal move is a move that results in the player who placed the move leaving the board but NOT if a loop is caused. :param board_state: The board state the move is being applied to :param move: The intermediate move to check for suicideness :return: A result containing a boolean or an error. If it contains a value the boolean specifies whether or not the move is suicidal. """ logging_observer = LoggingObserver() if move.player not in board_state.live_players: return error( f"player {move.player} is not alive thus the move cannot be suicidal" ) board = Board(deepcopy(board_state)) board.add_observer(logging_observer) r = board.intermediate_move(move) if r.is_error(): return error(r.error()) return ok(move.player not in board.live_players and len(logging_observer.entered_loop) <= 0)
def test_logging_observer() -> None: lo = LoggingObserver() assert lo.entered_loop == [] assert lo.exited_board == [] lo.player_entered_loop("red") assert lo.entered_loop == ["red"] assert lo.exited_board == [] lo.player_entered_loop("green") assert lo.entered_loop == ["red", "green"] assert lo.exited_board == [] lo.player_entered_loop("red") assert lo.entered_loop == ["red", "green", "red"] assert lo.exited_board == [] lo.player_exited_board("white") assert lo.entered_loop == ["red", "green", "red"] assert lo.exited_board == ["white"] lo.player_exited_board("black") assert lo.entered_loop == ["red", "green", "red"] assert lo.exited_board == ["white", "black"] assert lo.all_messages() == [ "entered_loop: red", "entered_loop: green", "entered_loop: red", "exited_board: white", "exited_board: black", ]
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_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() == []
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() == []