def test_player_move_fails_not_in_progress(self): """Test exception is thrown if the game is over and a player tries to move""" game = Game() game._status = constants.CHECKMATE with pytest.raises(InvalidMoveError): game.player_move(Position(0, 1), Position(0, 2))
def test_player_move_fails_wrong_turn(self, board): rook_position = Position(1, 1) board.get_piece.return_value = Rook(board, constants.BLACK, rook_position) game = Game() game._board = board game._turn = constants.WHITE with pytest.raises(InvalidMoveError): game.player_move(rook_position, Position)
def test_player_move_fails_invalid_move(self, board, mock_get_moves): rook_position = Position(1, 1) rook = Rook(board, constants.WHITE, rook_position) board.get_piece.return_value = rook mock_get_moves.return_value = { rook: { constants.MOVES: [Position(0, 1), Position(1, 2)], constants.SPECIAL_MOVES: {} } } game = Game() game._board = board with pytest.raises(InvalidMoveError): game.player_move(rook.position, Position(2, 2))
def test_player_move_success(self, mock_get_moves): rook_position = Position(1, 1) rook = Rook(None, constants.WHITE, rook_position) test_board = {'black_pieces': [rook]} mock_get_moves.return_value = { rook: { constants.MOVES: [Position(0, 1), Position(1, 2), Position(2, 2)], constants.SPECIAL_MOVES: {} } } board = set_up_test_board(new_game=False, test_board=test_board) game = Game() game._board = board game.player_move(rook.position, Position(2, 2)) self.assertEqual(rook.position, Position(2, 2))
def test_player_move_fails_no_piece(self, board): board.get_piece.return_value = None game = Game() game._board = board with pytest.raises(InvalidMoveError): game.player_move(Position(1, 1), Position(2, 2))
class GameController: def __init__(self, game, user, new_game=True): """ Initialize game controller """ self.game = game self.user = user self.chess_engine = ChessEngine() self.my_player = Player.objects.get(game=self.game, user=self.user) self.opponent = Player.objects.filter(game=self.game).exclude(user=self.user)[0] if new_game: self.start_game() def send_game(self, message_type): """ Send down a start game message to client """ serializer = serializers.GameSerializer(self.game) my_player_serializer = serializers.PlayerSerializer(self.my_player) opponent_serializer = serializers.PlayerSerializer(self.opponent) async_to_sync(get_channel_layer().send)(self.user.channel_name, { constants.TYPE: constants.CLIENT_SEND, constants.CONTENT: { constants.TYPE: message_type, constants.GAME: serializer.data, constants.ME: my_player_serializer.data, constants.OPPONENT: opponent_serializer.data } }) def start_game(self, **kwargs): """ Handle start of the game, send down start game message to the client """ self.send_game(constants.CLIENT_TYPE_START_GAME) if self.my_player.colour is WHITE: self.my_player.turn = True self.my_player.save() def load_game(self, **kwargs): """ Send game details down to client """ async_to_sync(get_channel_layer().group_add)( self.game.group_channel_name, self.user.channel_name ) self.send_game(constants.CLIENT_TYPE_LOAD_GAME) self.load_moves() self.load_board() self.load_messages() if self.my_player.turn: cancel_timeout(self.game.id, self.my_player.id) self.start_turn() def load_board(self, **kwargs): """ Send down the positions of pieces on board to client """ async_to_sync(get_channel_layer().send)(self.user.channel_name, { constants.TYPE: constants.CLIENT_SEND, constants.CONTENT: { constants.TYPE: constants.CLIENT_TYPE_LOAD_BOARD, constants.BOARD: self.chess_engine.board.format_board(), constants.GAME_ID: str(self.game.id) } }) def load_moves(self, **kwargs): """ Load moves from db into chess_engine and send each move down to client """ moves = Move.objects.filter(game=self.game).order_by('move_number') move_list = [] for m in moves: self.chess_engine.load_move(m) move_from, move_to = m.get_move() move_list.append({ constants.NOTATION: m.notation }) async_to_sync(get_channel_layer().send)(self.user.channel_name, { constants.TYPE: constants.CLIENT_SEND, constants.CONTENT: { constants.TYPE: constants.CLIENT_TYPE_LOAD_MOVES, constants.MOVE_LIST: move_list, constants.GAME_ID: str(self.game.id) } }) def load_messages(self, **kwargs): """ Load all chat messages from the db, and send down to client """ messages = ChatMessage.objects.filter(game=self.game) if not messages: return serializer = serializers.ChatMessageSerializer(messages, many=True) async_to_sync(get_channel_layer().send)(self.user.channel_name, { constants.TYPE: constants.CLIENT_SEND, constants.CONTENT: { constants.TYPE: constants.CLIENT_TYPE_LOAD_MESSAGES, constants.CHAT_MESSAGE: serializer.data } }) def start_turn(self, **kwargs): """ Send down valid moves from chess engine to the client """ self.my_player.update_time() self.my_player.save() self.my_player.refresh_from_db() self.opponent.refresh_from_db() self.turn_timer = threading.Timer( int(self.my_player.time), game_timeout_callback, args=[self.game.id, self.my_player.id] ) self.turn_timer.game_id = self.game.id self.turn_timer.player_id = self.my_player.id self.turn_timer.start() async_to_sync(get_channel_layer().send)(self.user.channel_name, { constants.TYPE: constants.CLIENT_SEND, constants.CONTENT: { constants.TYPE: constants.CLIENT_TYPE_START_TURN, constants.VALID_MOVES: self.chess_engine.format_moves(), constants.GAME_ID: str(self.game.id), constants.MY_TIME: int(self.my_player.time), constants.OPPONENT_TIME: int(self.opponent.time) } }) def my_message(self, **kwargs): """ Add message to the db. """ new_message = ChatMessage( user=self.user, game=self.game, message=kwargs[constants.CHAT_MESSAGE] ) new_message.save() def my_move(self, **kwargs): """ Update the chess engine with the clients move, Update the DB with the move """ move_values = kwargs.get(constants.MOVE, None) move_from, move_to = parse_move(move_values) try: self.chess_engine.player_move(move_from, move_to, colour=self.my_player.colour) except InvalidMoveError as ex: async_to_sync(get_channel_layer().send)(self.user.channel_name, { constants.TYPE: constants.CLIENT_SEND, constants.CONTENT: { constants.TYPE: constants.CLIENT_TYPE_ERROR, constants.CONTENT: f"Error: {ex.message}" } }) else: self.turn_timer.cancel() self.my_player.refresh_from_db() self.my_player.turn = False self.my_player.save() my_move = Move.objects.save_move(self.chess_engine.history[-1], self.game) async_to_sync(get_channel_layer().send)(self.user.channel_name, { constants.TYPE: constants.CLIENT_SEND, constants.CONTENT: { constants.TYPE: constants.CLIENT_TYPE_MOVE_RESPONSE, constants.NOTATION: my_move.notation, constants.GAME_ID: str(self.game.id) } }) if self.chess_engine.status != IN_PROGRESS: self.game.refresh_from_db() self.game.status = self.chess_engine.status self.game.save() def opponent_message(self, **kwargs): """ Retrieve last message from DB and send down to client """ message_id = kwargs.get(constants.ID, None) try: new_message = ChatMessage.objects.get(id=message_id) except ChatMessage.DoesNotExist: print(f'Error: message with id: {message_id} not found') return serializer = serializers.ChatMessageSerializer(new_message) async_to_sync(get_channel_layer().send)(self.user.channel_name, { constants.TYPE: constants.CLIENT_SEND, constants.CONTENT: { constants.TYPE: constants.CLIENT_TYPE_NEW_CHAT_MESSAGE, constants.CHAT_MESSAGE: serializer.data } }) def opponent_move(self, **kwargs): """ Update chess_engine with the opponents move Send valid moves over the channel """ move_id = kwargs.get(constants.ID, None) try: last_move = Move.objects.get(id=move_id) except Move.DoesNotExist: print(f"Error: move with id: {move_id} not found.") return move_from, move_to = last_move.get_move() async_to_sync(get_channel_layer().send)(self.user.channel_name, { constants.TYPE: constants.CLIENT_SEND, constants.CONTENT: { constants.TYPE: constants.CLIENT_TYPE_OPPONENT_MOVE, constants.MOVE: { constants.MOVE_FROM: move_from, constants.MOVE_TO: move_to, constants.NOTATION: last_move.notation, constants.MOVE_TYPE: last_move.move_type }, constants.GAME_ID: str(self.game.id) } }) self.chess_engine.load_move(last_move) if self.chess_engine.status == IN_PROGRESS: self.my_player.turn = True self.my_player.save() def update_time(self, **kwargs): """ Send down updated time to client """ self.my_player.refresh_from_db() self.my_player.update_time() self.my_player.save() self.opponent.refresh_from_db() self.opponent.update_time() self.opponent.save() async_to_sync(get_channel_layer().send)(self.user.channel_name, { constants.TYPE: constants.CLIENT_SEND, constants.CONTENT: { constants.CLIENT_TYPE: constants.CLIENT_TYPE_UPDATE_TIME, constants.MY_TIME: int(self.my_player.time), constants.OPPONENT_TIME: int(self.opponent.time), constants.GAME_ID: str(self.game.id) } }) def resign(self, **kwargs): """ Handle client sending a resign message """ self.my_player.resigned = True self.my_player.save() self.game.status = Game.GameStatus.RESIGN self.game.save() def status_update(self, **kwargs): """ Fetch game results from DB and send over channel for client """ self.game.refresh_from_db() self.my_player.refresh_from_db() cancel_timeout(self.game.id, self.my_player.id) async_to_sync(get_channel_layer().send)(self.user.channel_name, { constants.TYPE: constants.CLIENT_SEND, constants.CONTENT: { constants.CLIENT_TYPE: constants.CLIENT_TYPE_STATUS_UPDATE, constants.CLIENT_STATUS: self.game.status, constants.CLIENT_WINNER: self.my_player.winner, constants.GAME_ID: str(self.game.id) } }) async_to_sync(get_channel_layer().send)(self.user.channel_name, { constants.TYPE: constants.REMOVE_GAME, constants.GAME_ID: str(self.game.id) })