Note: There are different files in this project which are separated by different headings throughout this post.
1. Main.py
import pygame
from 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)
2. Board.py
import pygame
from classes.Square import Square
from classes.pieces.Rook import Rook
from classes.pieces.Bishop import Bishop
from classes.pieces.Knight import Knight
from classes.pieces.Queen import Queen
from classes.pieces.King import King
from 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)
3. 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)
4. 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 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)
5. Bishop.py
import pygame
from classes.Piece import Piece
class Bishop(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'assets/' + 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
6. King.py
import pygame
from classes.Piece import Piece
class King(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'assets/' + 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
7. Knight.py
import pygame
from classes.Piece import Piece
class Knight(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'assets/' + 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
8. Pawn.py
import pygame
from classes.Piece import Piece
class Pawn(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'assets/' + 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]
9. Queen.py
import pygame
from classes.Piece import Piece
class Queen(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'assets/' + 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
10. Rook.py
import pygame
from classes.Piece import Piece
class Rook(Piece):
def __init__(self, pos, color, board):
super().__init__(pos, color, board)
img_path = 'assets/' + 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
You can choose any images for the pieces, but make sure to name them in the following format:
[color (w/b)] + [name of piece] + [.png]
For example;
b knight.png
would be the file name for the image of a black knight.
Also, you will have to run only the main file to play the game.
Thank you!
Comments
Post a Comment