Esempio n. 1
0
 def test_rook_checkmate(self):
     """
     Test that a rook will put a king of the opposite color in checkmate
     :return:
     """
     board = ChessBoard()
     board['a3'] = King(Color.WHITE)
     board['a5'] = Rook(Color.BLACK)
     board['b8'] = Rook(Color.BLACK)
     self.assertTrue(board.is_checkmate(Color.WHITE),
                     'King should be in checkmate')
Esempio n. 2
0
 def test_bishop_checkmate(self):
     """
     Test that a queen will put a king of the opposite color in checkmate
     :return:
     """
     board = ChessBoard()
     board['a8'] = King(Color.WHITE)
     board['a6'] = Knight(Color.BLACK)
     board['b6'] = King(Color.BLACK)
     board['c6'] = Bishop(Color.BLACK)
     self.assertTrue(board.is_checkmate(Color.WHITE),
                     'King should be in checkmate')
Esempio n. 3
0
 def test_knight_checkmate(self):
     """
     Test that a knight will put a king of the opposite color in checkmate
     :return:
     """
     board = ChessBoard()
     board['b4'] = Bishop(Color.BLACK)
     board['c5'] = Rook(Color.BLACK)
     board['d1'] = King(Color.WHITE)
     board['e3'] = Knight(Color.BLACK)
     board['f3'] = Pawn(Color.BLACK)
     self.assertTrue(board.is_checkmate(Color.WHITE),
                     'King should be in checkmate')
Esempio n. 4
0
 def test_pawn_checkmate(self):
     """
     Test that a pawn will put a king of the opposite color in checkmate
     :return:
     """
     board = ChessBoard()
     board['a1'] = King(Color.WHITE)
     board['a2'] = Pawn(Color.WHITE)
     board['b1'] = Bishop(Color.WHITE)
     board['b2'] = Pawn(Color.BLACK)
     board['c3'] = Pawn(Color.BLACK)
     self.assertTrue(board.is_checkmate(Color.WHITE),
                     'King should be in checkmate')
Esempio n. 5
0
 def test_queen_checkmate(self):
     """
     Test that a queen will put a king of the opposite color in checkmate
     :return:
     """
     board = ChessBoard()
     board['d6'] = King(Color.BLACK)
     board['c5'] = Pawn(Color.BLACK)
     board['e5'] = Pawn(Color.BLACK)
     board['a5'] = Bishop(Color.WHITE)
     board['d5'] = Queen(Color.WHITE)
     board['d2'] = Rook(Color.WHITE)
     board['g6'] = Knight(Color.WHITE)
     self.assertTrue(board.is_checkmate(Color.BLACK),
                     'King should be in checkmate')
Esempio n. 6
0
class ChessGame(db.Model):
    """
    Create new chess game
    """
    __tablename__ = 'game'

    id = db.Column(db.Integer, primary_key=True)
    fen = db.Column(db.String(100))
    white_player_id = db.Column(db.Integer, db.ForeignKey('players.id'))
    black_player_id = db.Column(db.Integer, db.ForeignKey('players.id'))
    is_over = db.Column(db.Boolean, default=False)

    white_player = db.relationship("Player", foreign_keys=white_player_id)
    black_player = db.relationship("Player", foreign_keys=black_player_id)
    score = db.relationship("GameScore", uselist=False, back_populates="game")

    def __init__(self, fen=None, **kwargs):
        """
        Generate a ChessGame object.

        :param fen: string
            Fen notation string to initialize game.
        :param kwargs:
        """
        super().__init__(**kwargs)

        self.fen = fen if fen else 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -'
        fen = Fen(self.fen, validate=False)
        self._board = ChessBoard(fen)

    @property
    def board(self):
        return self._board

    @board.setter
    def board(self, board):
        self._board = board

    def get_legal_moves(self, position):
        """
        Retrieve possible legal moves for a piece on a position.

        :param position: string
            Algebraic notation position.
        :return:
        """
        try:
            ChessHelper.validate_position(position)
        except InvalidPositionError as e:
            # Maybe log error here
            # Reraise exception either way
            raise
        else:
            return self._board.get_legal_moves(position)

    def move_piece(self, start_position, end_position):
        """
        Move a piece from start_position to end_position.

        :param start_position:
        :param end_position:
        :return:
        """
        try:
            ChessHelper.validate_position(start_position)
            ChessHelper.validate_position(end_position)
        except InvalidPositionError as e:
            # Maybe log error here
            # Reraise exception either way
            raise
        else:
            current_fen = Fen(self.fen)
            current_player = current_fen.current_player
            next_player = Color.WHITE if current_player == Color.BLACK else Color.BLACK
            move_result = MoveResult()

            # If moving a pawn to the end of the board, dont update anything on the board.
            # Instead, just return the pawn promote info.
            if self.can_promote_pawn(start_position, end_position):
                player = self._get_player_by_color(current_player)
                promote_info = {
                    'player': player,
                    'promote_types': self.get_pawn_promote_types()
                }
                move_result.pawn_promote_info = promote_info
                return move_result

            move_result.update_positions = self._board.move_piece(
                start_position, end_position)

            # Determine which directions castling is possible for.
            castle_info = {Color.BLACK: [], Color.WHITE: []}
            for color in [Color.WHITE, Color.BLACK]:
                for direction in [MoveDirection.LEFT, MoveDirection.RIGHT]:
                    if self._board.can_castle(color, direction):
                        castle_info[color].append(direction)

            # Generate and save fen string after move
            next_fen = Fen()
            next_fen_str = next_fen.generate_fen(
                self._board.get_board_pieces(), next_player,
                castle_info[Color.WHITE], castle_info[Color.BLACK],
                self._board.get_enpassant_position())
            self.fen = next_fen_str

            # If checkmate or draw, set game over flag. Also create game_score object and fill
            # the move results object.
            is_checkmate = self._board.is_checkmate(next_player)
            is_stalemate = self._board.is_stalemate(next_player)
            if is_checkmate or is_stalemate:
                self.is_over = True
                if is_checkmate:
                    if current_player == Color.WHITE:
                        self.score = GameScore(game=self, white_score=1)
                        player_in_checkmate = self.black_player
                        player_in_checkmate.color = Color.BLACK
                    else:
                        self.score = GameScore(game=self, black_score=1)
                        player_in_checkmate = self.white_player
                        player_in_checkmate.color = Color.WHITE
                    move_result.king_in_checkmate = player_in_checkmate
                else:
                    self.score = GameScore(game=self,
                                           white_score=0.5,
                                           black_score=0.5)
                    move_result.draw = True

            # If it is check, add info to move_result.
            is_check = self._board.is_check(next_player)
            if is_check:
                if current_player == Color.WHITE:
                    player_in_check = self._get_player_by_color(Color.BLACK)
                else:
                    player_in_check = self._get_player_by_color(Color.WHITE)
                move_result.king_in_check = player_in_check

            return move_result

    @property
    def current_player(self):
        """
        Retrieve the current player

        :return Player:
            Player object with color set.
        """
        fen = Fen(self.fen)
        current_player_color = fen.current_player
        current_player = self.white_player if current_player_color == Color.WHITE else self.black_player
        # Dynamically add color field so UI can know player info and color.
        if current_player:
            current_player.color = Color.WHITE if current_player_color == Color.WHITE else Color.BLACK

        return current_player

    def promote_pawn(self, start_position, end_position, piece_type):
        """
        Promote a pawn to another piece type.

        :param start_position: string
            Algebraic notation for pawn position.
        :param end_position: string
            Algebraic notation for destination position.
        :param piece_type: Type
            Value from Type enum
        :return:
        """
        try:
            ChessHelper.validate_position(start_position)
            ChessHelper.validate_position(end_position)
        except InvalidPositionError as e:
            # Maybe log error here
            # Reraise exception either way
            raise
        else:
            if piece_type not in self.get_pawn_promote_types():
                raise PieceTypeError(
                    piece_type, 'Cannot promote pawn to supplied piece type')
            if self._board[start_position] is None:
                raise EmptyPositionError(start_position)
            # TODO confirm pawn on second to last row

            piece = self._board[start_position]
            piece_class = {
                Type.ROOK: Rook,
                Type.KNIGHT: Knight,
                Type.BISHOP: Bishop,
                Type.QUEEN: Queen
            }
            self._board[start_position] = piece_class[piece_type](piece.color)

            return self.move_piece(start_position, end_position)

    def get_winner(self):
        """
        Return winner of game.

        :return:
        """
        # Check if score obj exist. If so, return winner
        pass

    @classmethod
    def get_pawn_promote_types(cls):
        """
        Retrieve the piece types a pawn can promote to.

        :return: Type[]
        """
        return [Type.ROOK, Type.KNIGHT, Type.BISHOP, Type.QUEEN]

    def can_promote_pawn(self, start_position, end_position):
        """
        Test if pawn promotion is possible for the provided position.

        :param start_position: string
            Algebraic notation position.
        :param end_position: string
            Algebraic notation position.
        :return:
        """
        try:
            ChessHelper.validate_position(start_position)
            ChessHelper.validate_position(end_position)
        except InvalidPositionError as e:
            # Maybe log error here
            # Reraise exception either way
            raise
        else:
            if self._board[start_position] is None:
                return False

            piece = self._board[start_position]
            if piece.type != Type.PAWN:
                return False

            _, start_row = self._board.position_to_row_and_column(
                start_position, piece.color)
            _, end_row = self._board.position_to_row_and_column(
                end_position, piece.color)
            if start_row == self._board.get_dimension(
            ) - 2 and end_row == self._board.get_dimension() - 1:
                return True

            return False

    def save_to_db(self):
        """
        Save game to db.

        :return:
        """
        db.session.add(self)
        db.session.commit()

    def delete_from_db(self):
        """
        Delete this game from the db.

        :return:
        """
        db.session.delete(self)
        db.session.commit()

    def to_dict(self):
        """
        Return dictionary for game.
        :return:
        """
        current_player = self.current_player
        white_player = self.white_player
        black_player = self.black_player

        return {
            'game_id': self.id,
            'current_player':
            current_player.to_dict() if current_player else None,
            'white_player': white_player.to_dict() if white_player else None,
            'black_player': black_player.to_dict() if black_player else None,
            'game_over': self.is_over,
            'board': {
                position: piece.to_dict()
                for position, piece in self.board.get_board_pieces().items()
                if piece
            }
        }

    @classmethod
    def load_by_id(cls, game_id):
        game = cls.query.get(game_id)
        if game:
            game.board = ChessBoard(Fen(game.fen))
        return game

    def _get_player_by_color(self, color):
        """
        Retrieve the player associated with the provided color.

        :param color: Color
        :return: Player
        """
        if color == Color.WHITE:
            player = self.white_player
            player.color = Color.WHITE
        else:
            player = self.black_player
            player.color = Color.BLACK

        return player

    def __str__(self):
        return str(self._board)