├── .gitignore ├── README.md ├── ai.py ├── board.py ├── main.py ├── move.py ├── pieces.py └── preview.png /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | venv/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Chess AI 3 | Basic Chess AI written in Python 3. 4 | 5 | Uses the Alpha-Beta algorithm to search for the best move. 6 | 7 | Python Chess AI Program 8 | 9 | ## Getting started 10 | Download the repository and install the numpy dependency: 11 | ``` 12 | git clone git@github.com:Dirk94/ChessAI.git 13 | cd ChessAI 14 | pip3 install numpy 15 | ``` 16 | 17 | Run the program and start playing chess! ♟️ 18 | ``` 19 | python3 main.py 20 | ``` 21 | 22 | ### Example Moves 23 | Moves should have the following format: 24 | ``` 25 | A2 A4 26 | ``` 27 | This will move the piece from position A2 to A4. 28 | 29 | -------------------------------------------------------------------------------- /ai.py: -------------------------------------------------------------------------------- 1 | import board, pieces, numpy 2 | 3 | class Heuristics: 4 | 5 | # The tables denote the points scored for the position of the chess pieces on the board. 6 | 7 | PAWN_TABLE = numpy.array([ 8 | [ 0, 0, 0, 0, 0, 0, 0, 0], 9 | [ 5, 10, 10,-20,-20, 10, 10, 5], 10 | [ 5, -5,-10, 0, 0,-10, -5, 5], 11 | [ 0, 0, 0, 20, 20, 0, 0, 0], 12 | [ 5, 5, 10, 25, 25, 10, 5, 5], 13 | [10, 10, 20, 30, 30, 20, 10, 10], 14 | [50, 50, 50, 50, 50, 50, 50, 50], 15 | [ 0, 0, 0, 0, 0, 0, 0, 0] 16 | ]) 17 | 18 | KNIGHT_TABLE = numpy.array([ 19 | [-50, -40, -30, -30, -30, -30, -40, -50], 20 | [-40, -20, 0, 5, 5, 0, -20, -40], 21 | [-30, 5, 10, 15, 15, 10, 5, -30], 22 | [-30, 0, 15, 20, 20, 15, 0, -30], 23 | [-30, 5, 15, 20, 20, 15, 0, -30], 24 | [-30, 0, 10, 15, 15, 10, 0, -30], 25 | [-40, -20, 0, 0, 0, 0, -20, -40], 26 | [-50, -40, -30, -30, -30, -30, -40, -50] 27 | ]) 28 | 29 | BISHOP_TABLE = numpy.array([ 30 | [-20, -10, -10, -10, -10, -10, -10, -20], 31 | [-10, 5, 0, 0, 0, 0, 5, -10], 32 | [-10, 10, 10, 10, 10, 10, 10, -10], 33 | [-10, 0, 10, 10, 10, 10, 0, -10], 34 | [-10, 5, 5, 10, 10, 5, 5, -10], 35 | [-10, 0, 5, 10, 10, 5, 0, -10], 36 | [-10, 0, 0, 0, 0, 0, 0, -10], 37 | [-20, -10, -10, -10, -10, -10, -10, -20] 38 | ]) 39 | 40 | ROOK_TABLE = numpy.array([ 41 | [ 0, 0, 0, 5, 5, 0, 0, 0], 42 | [-5, 0, 0, 0, 0, 0, 0, -5], 43 | [-5, 0, 0, 0, 0, 0, 0, -5], 44 | [-5, 0, 0, 0, 0, 0, 0, -5], 45 | [-5, 0, 0, 0, 0, 0, 0, -5], 46 | [-5, 0, 0, 0, 0, 0, 0, -5], 47 | [ 5, 10, 10, 10, 10, 10, 10, 5], 48 | [ 0, 0, 0, 0, 0, 0, 0, 0] 49 | ]) 50 | 51 | QUEEN_TABLE = numpy.array([ 52 | [-20, -10, -10, -5, -5, -10, -10, -20], 53 | [-10, 0, 5, 0, 0, 0, 0, -10], 54 | [-10, 5, 5, 5, 5, 5, 0, -10], 55 | [ 0, 0, 5, 5, 5, 5, 0, -5], 56 | [ -5, 0, 5, 5, 5, 5, 0, -5], 57 | [-10, 0, 5, 5, 5, 5, 0, -10], 58 | [-10, 0, 0, 0, 0, 0, 0, -10], 59 | [-20, -10, -10, -5, -5, -10, -10, -20] 60 | ]) 61 | 62 | @staticmethod 63 | def evaluate(board): 64 | material = Heuristics.get_material_score(board) 65 | 66 | pawns = Heuristics.get_piece_position_score(board, pieces.Pawn.PIECE_TYPE, Heuristics.PAWN_TABLE) 67 | knights = Heuristics.get_piece_position_score(board, pieces.Knight.PIECE_TYPE, Heuristics.KNIGHT_TABLE) 68 | bishops = Heuristics.get_piece_position_score(board, pieces.Bishop.PIECE_TYPE, Heuristics.BISHOP_TABLE) 69 | rooks = Heuristics.get_piece_position_score(board, pieces.Rook.PIECE_TYPE, Heuristics.ROOK_TABLE) 70 | queens = Heuristics.get_piece_position_score(board, pieces.Queen.PIECE_TYPE, Heuristics.QUEEN_TABLE) 71 | 72 | return material + pawns + knights + bishops + rooks + queens 73 | 74 | # Returns the score for the position of the given type of piece. 75 | # A piece type can for example be: pieces.Pawn.PIECE_TYPE. 76 | # The table is the 2d numpy array used for the scoring. Example: Heuristics.PAWN_TABLE 77 | @staticmethod 78 | def get_piece_position_score(board, piece_type, table): 79 | white = 0 80 | black = 0 81 | for x in range(8): 82 | for y in range(8): 83 | piece = board.chesspieces[x][y] 84 | if (piece != 0): 85 | if (piece.piece_type == piece_type): 86 | if (piece.color == pieces.Piece.WHITE): 87 | white += table[x][y] 88 | else: 89 | black += table[7 - x][y] 90 | 91 | return white - black 92 | 93 | @staticmethod 94 | def get_material_score(board): 95 | white = 0 96 | black = 0 97 | for x in range(8): 98 | for y in range(8): 99 | piece = board.chesspieces[x][y] 100 | if (piece != 0): 101 | if (piece.color == pieces.Piece.WHITE): 102 | white += piece.value 103 | else: 104 | black += piece.value 105 | 106 | return white - black 107 | 108 | 109 | class AI: 110 | 111 | INFINITE = 10000000 112 | 113 | @staticmethod 114 | def get_ai_move(chessboard, invalid_moves): 115 | best_move = 0 116 | best_score = AI.INFINITE 117 | for move in chessboard.get_possible_moves(pieces.Piece.BLACK): 118 | if (AI.is_invalid_move(move, invalid_moves)): 119 | continue 120 | 121 | copy = board.Board.clone(chessboard) 122 | copy.perform_move(move) 123 | 124 | score = AI.alphabeta(copy, 2, -AI.INFINITE, AI.INFINITE, True) 125 | if (score < best_score): 126 | best_score = score 127 | best_move = move 128 | 129 | # Checkmate. 130 | if (best_move == 0): 131 | return 0 132 | 133 | copy = board.Board.clone(chessboard) 134 | copy.perform_move(best_move) 135 | if (copy.is_check(pieces.Piece.BLACK)): 136 | invalid_moves.append(best_move) 137 | return AI.get_ai_move(chessboard, invalid_moves) 138 | 139 | return best_move 140 | 141 | @staticmethod 142 | def is_invalid_move(move, invalid_moves): 143 | for invalid_move in invalid_moves: 144 | if (invalid_move.equals(move)): 145 | return True 146 | return False 147 | 148 | @staticmethod 149 | def minimax(board, depth, maximizing): 150 | if (depth == 0): 151 | return Heuristics.evaluate(board) 152 | 153 | if (maximizing): 154 | best_score = -AI.INFINITE 155 | for move in board.get_possible_moves(pieces.Piece.WHITE): 156 | copy = board.Board.clone(board) 157 | copy.perform_move(move) 158 | 159 | score = AI.minimax(copy, depth-1, False) 160 | best_score = max(best_score, score) 161 | 162 | return best_score 163 | else: 164 | best_score = AI.INFINITE 165 | for move in board.get_possible_moves(pieces.Piece.BLACK): 166 | copy = board.Board.clone(board) 167 | copy.perform_move(move) 168 | 169 | score = AI.minimax(copy, depth-1, True) 170 | best_score = min(best_score, score) 171 | 172 | return best_score 173 | 174 | @staticmethod 175 | def alphabeta(chessboard, depth, a, b, maximizing): 176 | if (depth == 0): 177 | return Heuristics.evaluate(chessboard) 178 | 179 | if (maximizing): 180 | best_score = -AI.INFINITE 181 | for move in chessboard.get_possible_moves(pieces.Piece.WHITE): 182 | copy = board.Board.clone(chessboard) 183 | copy.perform_move(move) 184 | 185 | best_score = max(best_score, AI.alphabeta(copy, depth-1, a, b, False)) 186 | a = max(a, best_score) 187 | if (b <= a): 188 | break 189 | return best_score 190 | else: 191 | best_score = AI.INFINITE 192 | for move in chessboard.get_possible_moves(pieces.Piece.BLACK): 193 | copy = board.Board.clone(chessboard) 194 | copy.perform_move(move) 195 | 196 | best_score = min(best_score, AI.alphabeta(copy, depth-1, a, b, True)) 197 | b = min(b, best_score) 198 | if (b <= a): 199 | break 200 | return best_score 201 | 202 | -------------------------------------------------------------------------------- /board.py: -------------------------------------------------------------------------------- 1 | import pieces 2 | from move import Move 3 | 4 | class Board: 5 | 6 | WIDTH = 8 7 | HEIGHT = 8 8 | 9 | def __init__(self, chesspieces, white_king_moved, black_king_moved): 10 | self.chesspieces = chesspieces 11 | self.white_king_moved = white_king_moved 12 | self.black_king_moved = black_king_moved 13 | 14 | @classmethod 15 | def clone(cls, chessboard): 16 | chesspieces = [[0 for x in range(Board.WIDTH)] for y in range(Board.HEIGHT)] 17 | for x in range(Board.WIDTH): 18 | for y in range(Board.HEIGHT): 19 | piece = chessboard.chesspieces[x][y] 20 | if (piece != 0): 21 | chesspieces[x][y] = piece.clone() 22 | return cls(chesspieces, chessboard.white_king_moved, chessboard.black_king_moved) 23 | 24 | @classmethod 25 | def new(cls): 26 | chess_pieces = [[0 for x in range(Board.WIDTH)] for y in range(Board.HEIGHT)] 27 | # Create pawns. 28 | for x in range(Board.WIDTH): 29 | chess_pieces[x][Board.HEIGHT-2] = pieces.Pawn(x, Board.HEIGHT-2, pieces.Piece.WHITE) 30 | chess_pieces[x][1] = pieces.Pawn(x, 1, pieces.Piece.BLACK) 31 | 32 | # Create rooks. 33 | chess_pieces[0][Board.HEIGHT-1] = pieces.Rook(0, Board.HEIGHT-1, pieces.Piece.WHITE) 34 | chess_pieces[Board.WIDTH-1][Board.HEIGHT-1] = pieces.Rook(Board.WIDTH-1, Board.HEIGHT-1, pieces.Piece.WHITE) 35 | chess_pieces[0][0] = pieces.Rook(0, 0, pieces.Piece.BLACK) 36 | chess_pieces[Board.WIDTH-1][0] = pieces.Rook(Board.WIDTH-1, 0, pieces.Piece.BLACK) 37 | 38 | # Create Knights. 39 | chess_pieces[1][Board.HEIGHT-1] = pieces.Knight(1, Board.HEIGHT-1, pieces.Piece.WHITE) 40 | chess_pieces[Board.WIDTH-2][Board.HEIGHT-1] = pieces.Knight(Board.WIDTH-2, Board.HEIGHT-1, pieces.Piece.WHITE) 41 | chess_pieces[1][0] = pieces.Knight(1, 0, pieces.Piece.BLACK) 42 | chess_pieces[Board.WIDTH-2][0] = pieces.Knight(Board.WIDTH-2, 0, pieces.Piece.BLACK) 43 | 44 | # Create Bishops. 45 | chess_pieces[2][Board.HEIGHT-1] = pieces.Bishop(2, Board.HEIGHT-1, pieces.Piece.WHITE) 46 | chess_pieces[Board.WIDTH-3][Board.HEIGHT-1] = pieces.Bishop(Board.WIDTH-3, Board.HEIGHT-1, pieces.Piece.WHITE) 47 | chess_pieces[2][0] = pieces.Bishop(2, 0, pieces.Piece.BLACK) 48 | chess_pieces[Board.WIDTH-3][0] = pieces.Bishop(Board.WIDTH-3, 0, pieces.Piece.BLACK) 49 | 50 | # Create King & Queen. 51 | chess_pieces[4][Board.HEIGHT-1] = pieces.King(4, Board.HEIGHT-1, pieces.Piece.WHITE) 52 | chess_pieces[3][Board.HEIGHT-1] = pieces.Queen(3, Board.HEIGHT-1, pieces.Piece.WHITE) 53 | chess_pieces[4][0] = pieces.King(4, 0, pieces.Piece.BLACK) 54 | chess_pieces[3][0] = pieces.Queen(3, 0, pieces.Piece.BLACK) 55 | 56 | return cls(chess_pieces, False, False) 57 | 58 | def get_possible_moves(self, color): 59 | moves = [] 60 | for x in range(Board.WIDTH): 61 | for y in range(Board.HEIGHT): 62 | piece = self.chesspieces[x][y] 63 | if (piece != 0): 64 | if (piece.color == color): 65 | moves += piece.get_possible_moves(self) 66 | 67 | return moves 68 | 69 | def perform_move(self, move): 70 | piece = self.chesspieces[move.xfrom][move.yfrom] 71 | self.move_piece(piece, move.xto, move.yto) 72 | 73 | # If a pawn reaches the end, upgrade it to a queen. 74 | if (piece.piece_type == pieces.Pawn.PIECE_TYPE): 75 | if (piece.y == 0 or piece.y == Board.HEIGHT-1): 76 | self.chesspieces[piece.x][piece.y] = pieces.Queen(piece.x, piece.y, piece.color) 77 | 78 | if (piece.piece_type == pieces.King.PIECE_TYPE): 79 | # Mark the king as having moved. 80 | if (piece.color == pieces.Piece.WHITE): 81 | self.white_king_moved = True 82 | else: 83 | self.black_king_moved = True 84 | 85 | # Check if king-side castling 86 | if (move.xto - move.xfrom == 2): 87 | rook = self.chesspieces[piece.x+1][piece.y] 88 | self.move_piece(rook, piece.x+1, piece.y) 89 | # Check if queen-side castling 90 | if (move.xto - move.xfrom == -2): 91 | rook = self.chesspieces[piece.x-2][piece.y] 92 | self.move_piece(rook, piece.x+1, piece.y) 93 | 94 | def move_piece(self, piece, xto, yto): 95 | self.chesspieces[piece.x][piece.y] = 0 96 | piece.x = xto 97 | piece.y = yto 98 | 99 | self.chesspieces[xto][yto] = piece 100 | 101 | 102 | # Returns if the given color is checked. 103 | def is_check(self, color): 104 | other_color = pieces.Piece.WHITE 105 | if (color == pieces.Piece.WHITE): 106 | other_color = pieces.Piece.BLACK 107 | 108 | for move in self.get_possible_moves(other_color): 109 | copy = Board.clone(self) 110 | copy.perform_move(move) 111 | 112 | king_found = False 113 | for x in range(Board.WIDTH): 114 | for y in range(Board.HEIGHT): 115 | piece = copy.chesspieces[x][y] 116 | if (piece != 0): 117 | if (piece.color == color and piece.piece_type == pieces.King.PIECE_TYPE): 118 | king_found = True 119 | 120 | if (not king_found): 121 | return True 122 | 123 | return False 124 | 125 | # Returns piece at given position or 0 if: No piece or out of bounds. 126 | def get_piece(self, x, y): 127 | if (not self.in_bounds(x, y)): 128 | return 0 129 | 130 | return self.chesspieces[x][y] 131 | 132 | def in_bounds(self, x, y): 133 | return (x >= 0 and y >= 0 and x < Board.WIDTH and y < Board.HEIGHT) 134 | 135 | def to_string(self): 136 | string = " A B C D E F G H\n" 137 | string += " -----------------------\n" 138 | for y in range(Board.HEIGHT): 139 | string += str(8 - y) + " | " 140 | for x in range(Board.WIDTH): 141 | piece = self.chesspieces[x][y] 142 | if (piece != 0): 143 | string += piece.to_string() 144 | else: 145 | string += ".. " 146 | string += "\n" 147 | return string + "\n" 148 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import board, pieces, ai 2 | from move import Move 3 | 4 | # Returns a move object based on the users input. Does not check if the move is valid. 5 | def get_user_move(): 6 | print("Example Move: A2 A4") 7 | move_str = input("Your Move: ") 8 | move_str = move_str.replace(" ", "") 9 | 10 | try: 11 | xfrom = letter_to_xpos(move_str[0:1]) 12 | yfrom = 8 - int(move_str[1:2]) # The board is drawn "upside down", so flip the y coordinate. 13 | xto = letter_to_xpos(move_str[2:3]) 14 | yto = 8 - int(move_str[3:4]) # The board is drawn "upside down", so flip the y coordinate. 15 | return Move(xfrom, yfrom, xto, yto) 16 | except ValueError: 17 | print("Invalid format. Example: A2 A4") 18 | return get_user_move() 19 | 20 | # Returns a valid move based on the users input. 21 | def get_valid_user_move(board): 22 | while True: 23 | move = get_user_move() 24 | valid = False 25 | possible_moves = board.get_possible_moves(pieces.Piece.WHITE) 26 | # No possible moves 27 | if (not possible_moves): 28 | return 0 29 | 30 | for possible_move in possible_moves: 31 | if (move.equals(possible_move)): 32 | valid = True 33 | break 34 | 35 | if (valid): 36 | break 37 | else: 38 | print("Invalid move.") 39 | return move 40 | 41 | # Converts a letter (A-H) to the x position on the chess board. 42 | def letter_to_xpos(letter): 43 | letter = letter.upper() 44 | if letter == 'A': 45 | return 0 46 | if letter == 'B': 47 | return 1 48 | if letter == 'C': 49 | return 2 50 | if letter == 'D': 51 | return 3 52 | if letter == 'E': 53 | return 4 54 | if letter == 'F': 55 | return 5 56 | if letter == 'G': 57 | return 6 58 | if letter == 'H': 59 | return 7 60 | 61 | raise ValueError("Invalid letter.") 62 | 63 | # 64 | # Entry point. 65 | # 66 | board = board.Board.new() 67 | print(board.to_string()) 68 | 69 | while True: 70 | move = get_valid_user_move(board) 71 | if (move == 0): 72 | if (board.is_check(pieces.Piece.WHITE)): 73 | print("Checkmate. Black Wins.") 74 | break 75 | else: 76 | print("Stalemate.") 77 | break 78 | 79 | board.perform_move(move) 80 | 81 | print("User move: " + move.to_string()) 82 | print(board.to_string()) 83 | 84 | ai_move = ai.AI.get_ai_move(board, []) 85 | if (ai_move == 0): 86 | if (board.is_check(pieces.Piece.BLACK)): 87 | print("Checkmate. White wins.") 88 | break 89 | else: 90 | print("Stalemate.") 91 | break 92 | 93 | board.perform_move(ai_move) 94 | print("AI move: " + ai_move.to_string()) 95 | print(board.to_string()) 96 | -------------------------------------------------------------------------------- /move.py: -------------------------------------------------------------------------------- 1 | class Move: 2 | 3 | def __init__(self, xfrom, yfrom, xto, yto): 4 | self.xfrom = xfrom 5 | self.yfrom = yfrom 6 | self.xto = xto 7 | self.yto = yto 8 | 9 | 10 | 11 | # Returns true iff (xfrom,yfrom) and (xto,yto) are the same. 12 | def equals(self, other_move): 13 | return self.xfrom == other_move.xfrom and self.yfrom == other_move.yfrom and self.xto == other_move.xto and self.yto == other_move.yto 14 | 15 | def to_string(self): 16 | return "(" + str(self.xfrom) + ", " + str(self.yfrom) + ") -> (" + str(self.xto) + ", " + str(self.yto) + ")" 17 | -------------------------------------------------------------------------------- /pieces.py: -------------------------------------------------------------------------------- 1 | import ai 2 | from move import Move 3 | 4 | class Piece(): 5 | 6 | WHITE = "W" 7 | BLACK = "B" 8 | 9 | def __init__(self, x, y, color, piece_type, value): 10 | self.x = x 11 | self.y = y 12 | self.color = color 13 | self.piece_type = piece_type 14 | self.value = value 15 | 16 | 17 | 18 | # Returns all diagonal moves for this piece. This should therefore only 19 | # be used by the Bishop and Queen since they are the only pieces that can 20 | # move diagonally. 21 | def get_possible_diagonal_moves(self, board): 22 | moves = [] 23 | 24 | for i in range(1, 8): 25 | if (not board.in_bounds(self.x+i, self.y+i)): 26 | break 27 | 28 | piece = board.get_piece(self.x+i, self.y+i) 29 | moves.append(self.get_move(board, self.x+i, self.y+i)) 30 | if (piece != 0): 31 | break 32 | 33 | for i in range(1, 8): 34 | if (not board.in_bounds(self.x+i, self.y-i)): 35 | break 36 | 37 | piece = board.get_piece(self.x+i, self.y-i) 38 | moves.append(self.get_move(board, self.x+i, self.y-i)) 39 | if (piece != 0): 40 | break 41 | 42 | for i in range(1, 8): 43 | if (not board.in_bounds(self.x-i, self.y-i)): 44 | break 45 | 46 | piece = board.get_piece(self.x-i, self.y-i) 47 | moves.append(self.get_move(board, self.x-i, self.y-i)) 48 | if (piece != 0): 49 | break 50 | 51 | for i in range(1, 8): 52 | if (not board.in_bounds(self.x-i, self.y+i)): 53 | break 54 | 55 | piece = board.get_piece(self.x-i, self.y+i) 56 | moves.append(self.get_move(board, self.x-i, self.y+i)) 57 | if (piece != 0): 58 | break 59 | 60 | return self.remove_null_from_list(moves) 61 | 62 | # Returns all horizontal moves for this piece. This should therefore only 63 | # be used by the Rooks and Queen since they are the only pieces that can 64 | # move horizontally. 65 | def get_possible_horizontal_moves(self, board): 66 | moves = [] 67 | 68 | # Moves to the right of the piece. 69 | for i in range(1, 8 - self.x): 70 | piece = board.get_piece(self.x + i, self.y) 71 | moves.append(self.get_move(board, self.x+i, self.y)) 72 | 73 | if (piece != 0): 74 | break 75 | 76 | # Moves to the left of the piece. 77 | for i in range(1, self.x + 1): 78 | piece = board.get_piece(self.x - i, self.y) 79 | moves.append(self.get_move(board, self.x-i, self.y)) 80 | if (piece != 0): 81 | break 82 | 83 | # Downward moves. 84 | for i in range(1, 8 - self.y): 85 | piece = board.get_piece(self.x, self.y + i) 86 | moves.append(self.get_move(board, self.x, self.y+i)) 87 | if (piece != 0): 88 | break 89 | 90 | # Upward moves. 91 | for i in range(1, self.y + 1): 92 | piece = board.get_piece(self.x, self.y - i) 93 | moves.append(self.get_move(board, self.x, self.y-i)) 94 | if (piece != 0): 95 | break 96 | 97 | return self.remove_null_from_list(moves) 98 | 99 | # Returns a Move object with (xfrom, yfrom) set to the piece current position. 100 | # (xto, yto) is set to the given position. If the move is not valid 0 is returned. 101 | # A move is not valid if it is out of bounds, or a piece of the same color is 102 | # being eaten. 103 | def get_move(self, board, xto, yto): 104 | move = 0 105 | if (board.in_bounds(xto, yto)): 106 | piece = board.get_piece(xto, yto) 107 | if (piece != 0): 108 | if (piece.color != self.color): 109 | move = Move(self.x, self.y, xto, yto) 110 | else: 111 | move = Move(self.x, self.y, xto, yto) 112 | return move 113 | 114 | # Returns the list of moves cleared of all the 0's. 115 | def remove_null_from_list(self, l): 116 | return [move for move in l if move != 0] 117 | 118 | def to_string(self): 119 | return self.color + self.piece_type + " " 120 | 121 | class Rook(Piece): 122 | 123 | PIECE_TYPE = "R" 124 | VALUE = 500 125 | 126 | def __init__(self, x, y, color): 127 | super(Rook, self).__init__(x, y, color, Rook.PIECE_TYPE, Rook.VALUE) 128 | 129 | def get_possible_moves(self, board): 130 | return self.get_possible_horizontal_moves(board) 131 | 132 | def clone(self): 133 | return Rook(self.x, self.y, self.color) 134 | 135 | 136 | class Knight(Piece): 137 | 138 | PIECE_TYPE = "N" 139 | VALUE = 320 140 | 141 | def __init__(self, x, y, color): 142 | super(Knight, self).__init__(x, y, color, Knight.PIECE_TYPE, Knight.VALUE) 143 | 144 | def get_possible_moves(self, board): 145 | moves = [] 146 | 147 | moves.append(self.get_move(board, self.x+2, self.y+1)) 148 | moves.append(self.get_move(board, self.x-1, self.y+2)) 149 | moves.append(self.get_move(board, self.x-2, self.y+1)) 150 | moves.append(self.get_move(board, self.x+1, self.y-2)) 151 | moves.append(self.get_move(board, self.x+2, self.y-1)) 152 | moves.append(self.get_move(board, self.x+1, self.y+2)) 153 | moves.append(self.get_move(board, self.x-2, self.y-1)) 154 | moves.append(self.get_move(board, self.x-1, self.y-2)) 155 | 156 | return self.remove_null_from_list(moves) 157 | 158 | def clone(self): 159 | return Knight(self.x, self.y, self.color) 160 | 161 | 162 | class Bishop(Piece): 163 | 164 | PIECE_TYPE = "B" 165 | VALUE = 330 166 | 167 | def __init__(self, x, y, color): 168 | super(Bishop, self).__init__(x, y, color, Bishop.PIECE_TYPE, Bishop.VALUE) 169 | 170 | def get_possible_moves(self, board): 171 | return self.get_possible_diagonal_moves(board) 172 | 173 | def clone(self): 174 | return Bishop(self.x, self.y, self.color) 175 | 176 | 177 | class Queen(Piece): 178 | 179 | PIECE_TYPE = "Q" 180 | VALUE = 900 181 | 182 | def __init__(self, x, y, color): 183 | super(Queen, self).__init__(x, y, color, Queen.PIECE_TYPE, Queen.VALUE) 184 | 185 | def get_possible_moves(self, board): 186 | diagonal = self.get_possible_diagonal_moves(board) 187 | horizontal = self.get_possible_horizontal_moves(board) 188 | return horizontal + diagonal 189 | 190 | def clone(self): 191 | return Queen(self.x, self.y, self.color) 192 | 193 | 194 | class King(Piece): 195 | 196 | PIECE_TYPE = "K" 197 | VALUE = 20000 198 | 199 | def __init__(self, x, y, color): 200 | super(King, self).__init__(x, y, color, King.PIECE_TYPE, King.VALUE) 201 | 202 | def get_possible_moves(self, board): 203 | moves = [] 204 | 205 | moves.append(self.get_move(board, self.x+1, self.y)) 206 | moves.append(self.get_move(board, self.x+1, self.y+1)) 207 | moves.append(self.get_move(board, self.x, self.y+1)) 208 | moves.append(self.get_move(board, self.x-1, self.y+1)) 209 | moves.append(self.get_move(board, self.x-1, self.y)) 210 | moves.append(self.get_move(board, self.x-1, self.y-1)) 211 | moves.append(self.get_move(board, self.x, self.y-1)) 212 | moves.append(self.get_move(board, self.x+1, self.y-1)) 213 | 214 | moves.append(self.get_castle_kingside_move(board)) 215 | moves.append(self.get_castle_queenside_move(board)) 216 | 217 | return self.remove_null_from_list(moves) 218 | 219 | # Only checks for castle kingside 220 | def get_castle_kingside_move(self, board): 221 | # Are we looking at a valid rook 222 | piece_in_corner = board.get_piece(self.x+3, self.y) 223 | if (piece_in_corner == 0 or piece_in_corner.piece_type != Rook.PIECE_TYPE): 224 | return 0 225 | 226 | # If the rook in the corner is not our color we cannot castle (duh). 227 | if (piece_in_corner.color != self.color): 228 | return 0 229 | 230 | # If the king has moved, we cannot castle 231 | if (self.color == Piece.WHITE and board.white_king_moved): 232 | return 0 233 | 234 | if (self.color == Piece.BLACK and board.black_king_moved): 235 | return 0 236 | 237 | # If there are pieces in between the king and rook we cannot castle 238 | if (board.get_piece(self.x+1, self.y) != 0 or board.get_piece(self.x+2, self.y) != 0): 239 | return 0 240 | 241 | return Move(self.x, self.y, self.x+2, self.y) 242 | 243 | def get_castle_queenside_move(self, board): 244 | # Are we looking at a valid rook 245 | piece_in_corner = board.get_piece(self.x-4, self.y) 246 | if (piece_in_corner == 0 or piece_in_corner.piece_type != Rook.PIECE_TYPE): 247 | return 0 248 | 249 | # If the rook in the corner is not our color we cannot castle (duh). 250 | if (piece_in_corner.color != self.color): 251 | return 0 252 | 253 | # If the king has moved, we cannot castle 254 | if (self.color == Piece.WHITE and board.white_king_moved): 255 | return 0 256 | 257 | if (self.color == Piece.BLACK and board.black_king_moved): 258 | return 0 259 | 260 | # If there are pieces in between the king and rook we cannot castle 261 | if (board.get_piece(self.x-1, self.y) != 0 or board.get_piece(self.x-2, self.y) != 0 or board.get_piece(self.x-3, self.y) != 0): 262 | return 0 263 | 264 | return Move(self.x, self.y, self.x-2, self.y) 265 | 266 | 267 | def clone(self): 268 | return King(self.x, self.y, self.color) 269 | 270 | 271 | class Pawn(Piece): 272 | 273 | PIECE_TYPE = "P" 274 | VALUE = 100 275 | 276 | def __init__(self, x, y, color): 277 | super(Pawn, self).__init__(x, y, color, Pawn.PIECE_TYPE, Pawn.VALUE) 278 | 279 | def is_starting_position(self): 280 | if (self.color == Piece.BLACK): 281 | return self.y == 1 282 | else: 283 | return self.y == 8 - 2 284 | 285 | def get_possible_moves(self, board): 286 | moves = [] 287 | 288 | # Direction the pawn can move in. 289 | direction = -1 290 | if (self.color == Piece.BLACK): 291 | direction = 1 292 | 293 | # The general 1 step forward move. 294 | if (board.get_piece(self.x, self.y+direction) == 0): 295 | moves.append(self.get_move(board, self.x, self.y + direction)) 296 | 297 | # The Pawn can take 2 steps as the first move. 298 | if (self.is_starting_position() and board.get_piece(self.x, self.y+ direction) == 0 and board.get_piece(self.x, self.y + direction*2) == 0): 299 | moves.append(self.get_move(board, self.x, self.y + direction * 2)) 300 | 301 | # Eating pieces. 302 | piece = board.get_piece(self.x + 1, self.y + direction) 303 | if (piece != 0): 304 | moves.append(self.get_move(board, self.x + 1, self.y + direction)) 305 | 306 | piece = board.get_piece(self.x - 1, self.y + direction) 307 | if (piece != 0): 308 | moves.append(self.get_move(board, self.x - 1, self.y + direction)) 309 | 310 | return self.remove_null_from_list(moves) 311 | 312 | def clone(self): 313 | return Pawn(self.x, self.y, self.color) 314 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirk94/ChessAI/6dc8339b271453aeaef95353588b6ff99df7852a/preview.png --------------------------------------------------------------------------------