Пример #1
0
    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))
Пример #2
0
 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)
Пример #3
0
    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))
Пример #4
0
 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))
Пример #5
0
 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))
Пример #6
0
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) 
        })