main.py
import pygame
from data.classes.Board import Board
pygame.init()
WINDOW_SIZE = (600, 600)
screen = pygame.display.set_mode(WINDOW_SIZE)
board = Board(WINDOW_SIZE[0], WINDOW_SIZE[1])
def draw(display):
display.fill('white')
board.draw(display)
pygame.display.update()
if __name__ == '__main__':
running = True
while running:
mx, my = pygame.mouse.get_pos()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
board.handle_click(mx, my)
if board.is_in_checkmate('black'):
print('White wins!')
running = False
elif board.is_in_checkmate('white'):
print('Black wins!')
running = False
draw(screen)
classes/Board.py
import pygame
from data.classes.Square import Square
from data.classes.pieces.Rook import Rook
from data.classes.pieces.Bishop import Bishop
from data.classes.pieces.Knight import Knight
from data.classes.pieces.Queen import Queen
from data.classes.pieces.King import King
from data.classes.pieces.Pawn import Pawn
# Game state checker
class Board:
def __init__(self, width, height):
self.width = width
self.height = height
self.tile_width = width // 8
self.tile_height = height // 8
self.selected_piece = None
self.turn = 'white'
# try making it chess.board.fen()
self.config = [
['bR', 'bN', 'bB', 'bQ', 'bK', 'bB', 'bN', 'bR'],
['bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP'],
['','','','','','','',''],
['','','','','','','',''],
['','','','','','','',''],
['','','','','','','',''],
['wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP'],
['wR', 'wN', 'wB', 'wQ', 'wK', 'wB', 'wN', 'wR'],
]
self.squares = self.generate_squares()
self.setup_board()
def generate_squares(self):
output = []
for y in range(8):
for x in range(8):
output.append(
Square(x, y, self.tile_width, self.tile_height)
)
return output
def get_square_from_pos(self, pos):
for square in self.squares:
if (square.x, square.y) == (pos[0], pos[1]):
return square
def get_piece_from_pos(self, pos):
return self.get_square_from_pos(pos).occupying_piece
def setup_board(self):
# iterating 2d list
for y, row in enumerate(self.config):
for x, piece in enumerate(row):
if piece != '':
square = self.get_square_from_pos((x, y))
# looking inside contents, what piece does it have
if piece[1] == 'R':
square.occupying_piece = Rook(
(x, y), 'white' if piece[0] == 'w' else 'black', self
)
# as you notice above, we put `self` as argument, or means our class Board
elif piece[1] == 'N':
square.occupying_piece = Knight(
(x, y), 'white' if piece[0] == 'w' else 'black', self
)
elif piece[1] == 'B':
square.occupying_piece = Bishop(
(x, y), 'white' if piece[0] == 'w' else 'black', self
)
elif piece[1] == 'Q':
square.occupying_piece = Queen(
(x, y), 'white' if piece[0] == 'w' else 'black', self
)
elif piece[1] == 'K':
square.occupying_piece = King(
(x, y), 'white' if piece[0] == 'w' else 'black', self
)
elif piece[1] == 'P':
square.occupying_piece = Pawn(
(x, y), 'white' if piece[0] == 'w' else 'black', self
)
def handle_click(self, mx, my):
x = mx // self.tile_width
y = my // self.tile_height
clicked_square = self.get_square_from_pos((x, y))
if self.selected_piece is None:
if clicked_square.occupying_piece is not None:
if clicked_square.occupying_piece.color == self.turn:
self.selected_piece = clicked_square.occupying_piece
elif self.selected_piece.move(self, clicked_square):
self.turn = 'white' if self.turn == 'black' else 'black'
elif clicked_square.occupying_piece is not None:
if clicked_square.occupying_piece.color == self.turn:
self.selected_piece = clicked_square.occupying_piece
def is_in_check(self, color, board_change=None): # board_change = [(x1, y1), (x2, y2)]
output = False
king_pos = None
changing_piece = None
old_square = None
new_square = None
new_square_old_piece = None
if board_change is not None:
for square in self.squares:
if square.pos == board_change[0]:
changing_piece = square.occupying_piece
old_square = square
old_square.occupying_piece = None
for square in self.squares:
if square.pos == board_change[1]:
new_square = square
new_square_old_piece = new_square.occupying_piece
new_square.occupying_piece = changing_piece
pieces = [
i.occupying_piece for i in self.squares if i.occupying_piece is not None
]
if changing_piece is not None:
if changing_piece.notation == 'K':
king_pos = new_square.pos
if king_pos == None:
for piece in pieces:
if piece.notation == 'K' and piece.color == color:
king_pos = piece.pos
for piece in pieces:
if piece.color != color:
for square in piece.attacking_squares(self):
if square.pos == king_pos:
output = True
if board_change is not None:
old_square.occupying_piece = changing_piece
new_square.occupying_piece = new_square_old_piece
return output
def is_in_checkmate(self, color):
output = False
for piece in [i.occupying_piece for i in self.squares]:
if piece != None:
if piece.notation == 'K' and piece.color == color:
king = piece
if king.get_valid_moves(self) == []:
if self.is_in_check(color):
output = True
return output
def draw(self, display):
if self.selected_piece is not None:
self.get_square_from_pos(self.selected_piece.pos).highlight = True
for square in self.selected_piece.get_valid_moves(self):
square.highlight = True
for square in self.squares:
square.draw(display)
classes/Piece.py
import pygame
class Piece:
def __init__(self, pos, color, board):
self.pos = pos
self.x = pos[0]
self.y = pos[1]
self.color = color
self.has_moved = False
def move(self, board, square, force=False):
for i in board.squares:
i.highlight = False
if square in self.get_valid_moves(board) or force:
prev_square = board.get_square_from_pos(self.pos)
self.pos, self.x, self.y = square.pos, square.x, square.y
prev_square.occupying_piece = None
square.occupying_piece = self
board.selected_piece = None
self.has_moved = True
# Pawn promotion
if self.notation == ' ':
if self.y == 0 or self.y == 7:
from data.classes.pieces.Queen import Queen
square.occupying_piece = Queen(
(self.x, self.y),
self.color,
board
)
# Move rook if king castles
if self.notation == 'K':
if prev_square.x - self.x == 2:
rook = board.get_piece_from_pos((0, self.y))
rook.move(board, board.get_square_from_pos((3, self.y)), force=True)
elif prev_square.x - self.x == -2:
rook = board.get_piece_from_pos((7, self.y))
rook.move(board, board.get_square_from_pos((5, self.y)), force=True)
return True
else:
board.selected_piece = None
return False
def get_moves(self, board):
output = []
for direction in self.get_possible_moves(board):
for square in direction:
if square.occupying_piece is not None:
if square.occupying_piece.color == self.color:
break
else:
output.append(square)
break
else:
output.append(square)
return output
def get_valid_moves(self, board):
output = []
for square in self.get_moves(board):
if not board.is_in_check(self.color, board_change=[self.pos, square.pos]):
output.append(square)
return output
# True for all pieces except pawn
def attacking_squares(self, board):
return self.get_moves(board)
classes/Square.py
import pygame
class Square:
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
self.abs_x = x * width
self.abs_y = y * height
self.abs_pos = (self.abs_x, self.abs_y)
self.pos = (x, y)
self.color = 'light' if (x + y) % 2 == 0 else 'dark'
self.draw_color = (220, 189, 194) if self.color == 'light' else (53, 53, 53)
self.highlight_color = (100, 249, 83) if self.color == 'light' else (0, 228, 10)
self.occupying_piece = None
self.coord = self.get_coord()
self.highlight = False
self.rect = pygame.Rect(
self.abs_x,
self.abs_y,
self.width,
self.height
)
def get_coord(self):
columns = 'abcdefgh'
return columns[self.x] + str(self.y + 1)
def draw(self, display):
if self.highlight:
pygame.draw.rect(display, self.highlight_color, self.rect)
else:
pygame.draw.rect(display, self.draw_color, self.rect)
if self.occupying_piece != None:
centering_rect = self.occupying_piece.img.get_rect()
centering_rect.center = self.rect.center
display.blit(self.occupying_piece.img, centering_rect.topleft)
classes/pieces/Bishop.py
import pygame
from data.classes.Piece import Piece
class Bishop(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'data/imgs/' + color[0] + '_bishop.png'
self.img = pygame.image.load(img_path)
self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
self.notation = 'B'
def get_possible_moves(self, board):
output = []
moves_ne = []
for i in range(1, 8):
if self.x + i > 7 or self.y - i < 0:
break
moves_ne.append(board.get_square_from_pos(
(self.x + i, self.y - i)
))
output.append(moves_ne)
moves_se = []
for i in range(1, 8):
if self.x + i > 7 or self.y + i > 7:
break
moves_se.append(board.get_square_from_pos(
(self.x + i, self.y + i)
))
output.append(moves_se)
moves_sw = []
for i in range(1, 8):
if self.x - i < 0 or self.y + i > 7:
break
moves_sw.append(board.get_square_from_pos(
(self.x - i, self.y + i)
))
output.append(moves_sw)
moves_nw = []
for i in range(1, 8):
if self.x - i < 0 or self.y - i < 0:
break
moves_nw.append(board.get_square_from_pos(
(self.x - i, self.y - i)
))
output.append(moves_nw)
return output
classes/pieces/King.py
import pygame
from data.classes.Piece import Piece
class King(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'data/imgs/' + color[0] + '_king.png'
self.img = pygame.image.load(img_path)
self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
self.notation = 'K'
def get_possible_moves(self, board):
output = []
moves = [
(0,-1), # north
(1, -1), # ne
(1, 0), # east
(1, 1), # se
(0, 1), # south
(-1, 1), # sw
(-1, 0), # west
(-1, -1), # nw
]
for move in moves:
new_pos = (self.x + move[0], self.y + move[1])
if (
new_pos[0] < 8 and
new_pos[0] >= 0 and
new_pos[1] < 8 and
new_pos[1] >= 0
):
output.append([
board.get_square_from_pos(
new_pos
)
])
return output
def can_castle(self, board):
if not self.has_moved:
if self.color == 'white':
queenside_rook = board.get_piece_from_pos((0, 7))
kingside_rook = board.get_piece_from_pos((7, 7))
if queenside_rook != None:
if not queenside_rook.has_moved:
if [
board.get_piece_from_pos((i, 7)) for i in range(1, 4)
] == [None, None, None]:
return 'queenside'
if kingside_rook != None:
if not kingside_rook.has_moved:
if [
board.get_piece_from_pos((i, 7)) for i in range(5, 7)
] == [None, None]:
return 'kingside'
elif self.color == 'black':
queenside_rook = board.get_piece_from_pos((0, 0))
kingside_rook = board.get_piece_from_pos((7, 0))
if queenside_rook != None:
if not queenside_rook.has_moved:
if [
board.get_piece_from_pos((i, 0)) for i in range(1, 4)
] == [None, None, None]:
return 'queenside'
if kingside_rook != None:
if not kingside_rook.has_moved:
if [
board.get_piece_from_pos((i, 0)) for i in range(5, 7)
] == [None, None]:
return 'kingside'
def get_valid_moves(self, board):
output = []
for square in self.get_moves(board):
if not board.is_in_check(self.color, board_change=[self.pos, square.pos]):
output.append(square)
if self.can_castle(board) == 'queenside':
output.append(
board.get_square_from_pos((self.x - 2, self.y))
)
if self.can_castle(board) == 'kingside':
output.append(
board.get_square_from_pos((self.x + 2, self.y))
)
return output
classes/pieces/Knight.py
import pygame
from data.classes.Piece import Piece
class Knight(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'data/imgs/' + color[0] + '_knight.png'
self.img = pygame.image.load(img_path)
self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
self.notation = 'N'
def get_possible_moves(self, board):
output = []
moves = [
(1, -2),
(2, -1),
(2, 1),
(1, 2),
(-1, 2),
(-2, 1),
(-2, -1),
(-1, -2)
]
for move in moves:
new_pos = (self.x + move[0], self.y + move[1])
if (
new_pos[0] < 8 and
new_pos[0] >= 0 and
new_pos[1] < 8 and
new_pos[1] >= 0
):
output.append([
board.get_square_from_pos(
new_pos
)
])
return output
classes/pieces/Pawn.py
import pygame
from data.classes.Piece import Piece
class Pawn(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'data/imgs/' + color[0] + '_pawn.png'
self.img = pygame.image.load(img_path)
self.img = pygame.transform.scale(self.img, (board.tile_width - 35, board.tile_height - 35))
self.notation = ' '
def get_possible_moves(self, board):
output = []
moves = []
# move forward
if self.color == 'white':
moves.append((0, -1))
if not self.has_moved:
moves.append((0, -2))
elif self.color == 'black':
moves.append((0, 1))
if not self.has_moved:
moves.append((0, 2))
for move in moves:
new_pos = (self.x, self.y + move[1])
if new_pos[1] < 8 and new_pos[1] >= 0:
output.append(
board.get_square_from_pos(new_pos)
)
return output
def get_moves(self, board):
output = []
for square in self.get_possible_moves(board):
if square.occupying_piece != None:
break
else:
output.append(square)
if self.color == 'white':
if self.x + 1 < 8 and self.y - 1 >= 0:
square = board.get_square_from_pos(
(self.x + 1, self.y - 1)
)
if square.occupying_piece != None:
if square.occupying_piece.color != self.color:
output.append(square)
if self.x - 1 >= 0 and self.y - 1 >= 0:
square = board.get_square_from_pos(
(self.x - 1, self.y - 1)
)
if square.occupying_piece != None:
if square.occupying_piece.color != self.color:
output.append(square)
elif self.color == 'black':
if self.x + 1 < 8 and self.y + 1 < 8:
square = board.get_square_from_pos(
(self.x + 1, self.y + 1)
)
if square.occupying_piece != None:
if square.occupying_piece.color != self.color:
output.append(square)
if self.x - 1 >= 0 and self.y + 1 < 8:
square = board.get_square_from_pos(
(self.x - 1, self.y + 1)
)
if square.occupying_piece != None:
if square.occupying_piece.color != self.color:
output.append(square)
return output
def attacking_squares(self, board):
moves = self.get_moves(board)
# return the diagonal moves
return [i for i in moves if i.x != self.x]
classes/pieces/Queen.py
import pygame
from data.classes.Piece import Piece
class Queen(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'data/imgs/' + color[0] + '_queen.png'
self.img = pygame.image.load(img_path)
self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
self.notation = 'Q'
def get_possible_moves(self, board):
output = []
moves_north = []
for y in range(self.y)[::-1]:
moves_north.append(board.get_square_from_pos(
(self.x, y)
))
output.append(moves_north)
moves_ne = []
for i in range(1, 8):
if self.x + i > 7 or self.y - i < 0:
break
moves_ne.append(board.get_square_from_pos(
(self.x + i, self.y - i)
))
output.append(moves_ne)
moves_east = []
for x in range(self.x + 1, 8):
moves_east.append(board.get_square_from_pos(
(x, self.y)
))
output.append(moves_east)
moves_se = []
for i in range(1, 8):
if self.x + i > 7 or self.y + i > 7:
break
moves_se.append(board.get_square_from_pos(
(self.x + i, self.y + i)
))
output.append(moves_se)
moves_south = []
for y in range(self.y + 1, 8):
moves_south.append(board.get_square_from_pos(
(self.x, y)
))
output.append(moves_south)
moves_sw = []
for i in range(1, 8):
if self.x - i < 0 or self.y + i > 7:
break
moves_sw.append(board.get_square_from_pos(
(self.x - i, self.y + i)
))
output.append(moves_sw)
moves_west = []
for x in range(self.x)[::-1]:
moves_west.append(board.get_square_from_pos(
(x, self.y)
))
output.append(moves_west)
moves_nw = []
for i in range(1, 8):
if self.x - i < 0 or self.y - i < 0:
break
moves_nw.append(board.get_square_from_pos(
(self.x - i, self.y - i)
))
output.append(moves_nw)
return output
classes/pieces/Rook.py
import pygame
from data.classes.Piece import Piece
class Rook(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'data/imgs/' + color[0] + '_rook.png'
self.img = pygame.image.load(img_path)
self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
self.notation = 'R'
def get_possible_moves(self, board):
output = []
moves_north = []
for y in range(self.y)[::-1]:
moves_north.append(board.get_square_from_pos(
(self.x, y)
))
output.append(moves_north)
moves_east = []
for x in range(self.x + 1, 8):
moves_east.append(board.get_square_from_pos(
(x, self.y)
))
output.append(moves_east)
moves_south = []
for y in range(self.y + 1, 8):
moves_south.append(board.get_square_from_pos(
(self.x, y)
))
output.append(moves_south)
moves_west = []
for x in range(self.x)[::-1]:
moves_west.append(board.get_square_from_pos(
(x, self.y)
))
output.append(moves_west)
return output