Skip to content

Commit 0300b45

Browse files
committed
add chess game tutorial
1 parent 2434430 commit 0300b45

37 files changed

+758
-0
lines changed

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -249,5 +249,6 @@ This is a repository of all the tutorials of [The Python Code](https://www.thepy
249249
- [How to Make an Age Calculator in Python](https://www.thepythoncode.com/article/age-calculator-using-tkinter-python). ([code](gui-programming/age-calculator))
250250
- [How to Create an Alarm Clock App using Tkinter in Python](https://www.thepythoncode.com/article/build-an-alarm-clock-app-using-tkinter-python). ([code](gui-programming/alarm-clock-app))
251251
- [How to Build a GUI Voice Recorder App in Python](https://www.thepythoncode.com/article/make-a-gui-voice-recorder-python). ([code](gui-programming/voice-recorder-app))
252+
- [How to Make a Chess Game with Pygame in Python](https://www.thepythoncode.com/article/make-a-chess-game-using-pygame-in-python). ([code](gui-programming/chess-game))
252253

253254
For any feedback, please consider pulling requests.

Diff for: gui-programming/chess-game/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# [How to Make a Chess Game with Pygame in Python](https://www.thepythoncode.com/article/make-a-chess-game-using-pygame-in-python)
2+
To run this:
3+
- `pip3 install -r requirements.txt`
4+
- `python main.py`

Diff for: gui-programming/chess-game/data/classes/Board.py

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import pygame
2+
3+
from data.classes.Square import Square
4+
from data.classes.pieces.Rook import Rook
5+
from data.classes.pieces.Bishop import Bishop
6+
from data.classes.pieces.Knight import Knight
7+
from data.classes.pieces.Queen import Queen
8+
from data.classes.pieces.King import King
9+
from data.classes.pieces.Pawn import Pawn
10+
11+
12+
# Game state checker
13+
class Board:
14+
def __init__(self, width, height):
15+
self.width = width
16+
self.height = height
17+
self.tile_width = width // 8
18+
self.tile_height = height // 8
19+
self.selected_piece = None
20+
self.turn = 'white'
21+
22+
# try making it chess.board.fen()
23+
self.config = [
24+
['bR', 'bN', 'bB', 'bQ', 'bK', 'bB', 'bN', 'bR'],
25+
['bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP'],
26+
['','','','','','','',''],
27+
['','','','','','','',''],
28+
['','','','','','','',''],
29+
['','','','','','','',''],
30+
['wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP'],
31+
['wR', 'wN', 'wB', 'wQ', 'wK', 'wB', 'wN', 'wR'],
32+
]
33+
34+
self.squares = self.generate_squares()
35+
36+
self.setup_board()
37+
38+
39+
def generate_squares(self):
40+
output = []
41+
for y in range(8):
42+
for x in range(8):
43+
output.append(
44+
Square(x, y, self.tile_width, self.tile_height)
45+
)
46+
return output
47+
48+
49+
def get_square_from_pos(self, pos):
50+
for square in self.squares:
51+
if (square.x, square.y) == (pos[0], pos[1]):
52+
return square
53+
54+
55+
def get_piece_from_pos(self, pos):
56+
return self.get_square_from_pos(pos).occupying_piece
57+
58+
59+
def setup_board(self):
60+
# iterating 2d list
61+
for y, row in enumerate(self.config):
62+
for x, piece in enumerate(row):
63+
if piece != '':
64+
square = self.get_square_from_pos((x, y))
65+
66+
# looking inside contents, what piece does it have
67+
if piece[1] == 'R':
68+
square.occupying_piece = Rook(
69+
(x, y), 'white' if piece[0] == 'w' else 'black', self
70+
)
71+
# as you notice above, we put `self` as argument, or means our class Board
72+
73+
elif piece[1] == 'N':
74+
square.occupying_piece = Knight(
75+
(x, y), 'white' if piece[0] == 'w' else 'black', self
76+
)
77+
78+
elif piece[1] == 'B':
79+
square.occupying_piece = Bishop(
80+
(x, y), 'white' if piece[0] == 'w' else 'black', self
81+
)
82+
83+
elif piece[1] == 'Q':
84+
square.occupying_piece = Queen(
85+
(x, y), 'white' if piece[0] == 'w' else 'black', self
86+
)
87+
88+
elif piece[1] == 'K':
89+
square.occupying_piece = King(
90+
(x, y), 'white' if piece[0] == 'w' else 'black', self
91+
)
92+
93+
elif piece[1] == 'P':
94+
square.occupying_piece = Pawn(
95+
(x, y), 'white' if piece[0] == 'w' else 'black', self
96+
)
97+
98+
99+
def handle_click(self, mx, my):
100+
x = mx // self.tile_width
101+
y = my // self.tile_height
102+
clicked_square = self.get_square_from_pos((x, y))
103+
104+
if self.selected_piece is None:
105+
if clicked_square.occupying_piece is not None:
106+
if clicked_square.occupying_piece.color == self.turn:
107+
self.selected_piece = clicked_square.occupying_piece
108+
109+
elif self.selected_piece.move(self, clicked_square):
110+
self.turn = 'white' if self.turn == 'black' else 'black'
111+
112+
elif clicked_square.occupying_piece is not None:
113+
if clicked_square.occupying_piece.color == self.turn:
114+
self.selected_piece = clicked_square.occupying_piece
115+
116+
117+
def is_in_check(self, color, board_change=None): # board_change = [(x1, y1), (x2, y2)]
118+
output = False
119+
king_pos = None
120+
121+
changing_piece = None
122+
old_square = None
123+
new_square = None
124+
new_square_old_piece = None
125+
126+
if board_change is not None:
127+
for square in self.squares:
128+
if square.pos == board_change[0]:
129+
changing_piece = square.occupying_piece
130+
old_square = square
131+
old_square.occupying_piece = None
132+
for square in self.squares:
133+
if square.pos == board_change[1]:
134+
new_square = square
135+
new_square_old_piece = new_square.occupying_piece
136+
new_square.occupying_piece = changing_piece
137+
138+
pieces = [
139+
i.occupying_piece for i in self.squares if i.occupying_piece is not None
140+
]
141+
142+
if changing_piece is not None:
143+
if changing_piece.notation == 'K':
144+
king_pos = new_square.pos
145+
if king_pos == None:
146+
for piece in pieces:
147+
if piece.notation == 'K' and piece.color == color:
148+
king_pos = piece.pos
149+
for piece in pieces:
150+
if piece.color != color:
151+
for square in piece.attacking_squares(self):
152+
if square.pos == king_pos:
153+
output = True
154+
155+
if board_change is not None:
156+
old_square.occupying_piece = changing_piece
157+
new_square.occupying_piece = new_square_old_piece
158+
159+
return output
160+
161+
162+
def is_in_checkmate(self, color):
163+
output = False
164+
165+
for piece in [i.occupying_piece for i in self.squares]:
166+
if piece != None:
167+
if piece.notation == 'K' and piece.color == color:
168+
king = piece
169+
170+
if king.get_valid_moves(self) == []:
171+
if self.is_in_check(color):
172+
output = True
173+
174+
return output
175+
176+
177+
def draw(self, display):
178+
if self.selected_piece is not None:
179+
self.get_square_from_pos(self.selected_piece.pos).highlight = True
180+
for square in self.selected_piece.get_valid_moves(self):
181+
square.highlight = True
182+
183+
for square in self.squares:
184+
square.draw(display)

Diff for: gui-programming/chess-game/data/classes/Piece.py

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import pygame
2+
3+
class Piece:
4+
def __init__(self, pos, color, board):
5+
self.pos = pos
6+
self.x = pos[0]
7+
self.y = pos[1]
8+
self.color = color
9+
self.has_moved = False
10+
11+
12+
def move(self, board, square, force=False):
13+
for i in board.squares:
14+
i.highlight = False
15+
16+
if square in self.get_valid_moves(board) or force:
17+
prev_square = board.get_square_from_pos(self.pos)
18+
self.pos, self.x, self.y = square.pos, square.x, square.y
19+
20+
prev_square.occupying_piece = None
21+
square.occupying_piece = self
22+
board.selected_piece = None
23+
self.has_moved = True
24+
25+
# Pawn promotion
26+
if self.notation == ' ':
27+
if self.y == 0 or self.y == 7:
28+
from data.classes.pieces.Queen import Queen
29+
square.occupying_piece = Queen(
30+
(self.x, self.y),
31+
self.color,
32+
board
33+
)
34+
35+
# Move rook if king castles
36+
if self.notation == 'K':
37+
if prev_square.x - self.x == 2:
38+
rook = board.get_piece_from_pos((0, self.y))
39+
rook.move(board, board.get_square_from_pos((3, self.y)), force=True)
40+
elif prev_square.x - self.x == -2:
41+
rook = board.get_piece_from_pos((7, self.y))
42+
rook.move(board, board.get_square_from_pos((5, self.y)), force=True)
43+
44+
return True
45+
else:
46+
board.selected_piece = None
47+
return False
48+
49+
50+
def get_moves(self, board):
51+
output = []
52+
for direction in self.get_possible_moves(board):
53+
for square in direction:
54+
if square.occupying_piece is not None:
55+
if square.occupying_piece.color == self.color:
56+
break
57+
else:
58+
output.append(square)
59+
break
60+
else:
61+
output.append(square)
62+
return output
63+
64+
65+
def get_valid_moves(self, board):
66+
output = []
67+
for square in self.get_moves(board):
68+
if not board.is_in_check(self.color, board_change=[self.pos, square.pos]):
69+
output.append(square)
70+
71+
return output
72+
73+
74+
# True for all pieces except pawn
75+
def attacking_squares(self, board):
76+
return self.get_moves(board)

Diff for: gui-programming/chess-game/data/classes/Square.py

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import pygame
2+
3+
class Square:
4+
def __init__(self, x, y, width, height):
5+
self.x = x
6+
self.y = y
7+
self.width = width
8+
self.height = height
9+
10+
self.abs_x = x * width
11+
self.abs_y = y * height
12+
self.abs_pos = (self.abs_x, self.abs_y)
13+
self.pos = (x, y)
14+
self.color = 'light' if (x + y) % 2 == 0 else 'dark'
15+
self.draw_color = (220, 189, 194) if self.color == 'light' else (53, 53, 53)
16+
self.highlight_color = (100, 249, 83) if self.color == 'light' else (0, 228, 10)
17+
self.occupying_piece = None
18+
self.coord = self.get_coord()
19+
self.highlight = False
20+
21+
self.rect = pygame.Rect(
22+
self.abs_x,
23+
self.abs_y,
24+
self.width,
25+
self.height
26+
)
27+
28+
29+
def get_coord(self):
30+
columns = 'abcdefgh'
31+
return columns[self.x] + str(self.y + 1)
32+
33+
34+
def draw(self, display):
35+
if self.highlight:
36+
pygame.draw.rect(display, self.highlight_color, self.rect)
37+
else:
38+
pygame.draw.rect(display, self.draw_color, self.rect)
39+
40+
if self.occupying_piece != None:
41+
centering_rect = self.occupying_piece.img.get_rect()
42+
centering_rect.center = self.rect.center
43+
display.blit(self.occupying_piece.img, centering_rect.topleft)
44+
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import pygame
2+
3+
from data.classes.Piece import Piece
4+
5+
class Bishop(Piece):
6+
def __init__(self, pos, color, board):
7+
super().__init__(pos, color, board)
8+
9+
img_path = 'data/imgs/' + color[0] + '_bishop.png'
10+
self.img = pygame.image.load(img_path)
11+
self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
12+
13+
self.notation = 'B'
14+
15+
16+
def get_possible_moves(self, board):
17+
output = []
18+
19+
moves_ne = []
20+
for i in range(1, 8):
21+
if self.x + i > 7 or self.y - i < 0:
22+
break
23+
moves_ne.append(board.get_square_from_pos(
24+
(self.x + i, self.y - i)
25+
))
26+
output.append(moves_ne)
27+
28+
moves_se = []
29+
for i in range(1, 8):
30+
if self.x + i > 7 or self.y + i > 7:
31+
break
32+
moves_se.append(board.get_square_from_pos(
33+
(self.x + i, self.y + i)
34+
))
35+
output.append(moves_se)
36+
37+
moves_sw = []
38+
for i in range(1, 8):
39+
if self.x - i < 0 or self.y + i > 7:
40+
break
41+
moves_sw.append(board.get_square_from_pos(
42+
(self.x - i, self.y + i)
43+
))
44+
output.append(moves_sw)
45+
46+
moves_nw = []
47+
for i in range(1, 8):
48+
if self.x - i < 0 or self.y - i < 0:
49+
break
50+
moves_nw.append(board.get_square_from_pos(
51+
(self.x - i, self.y - i)
52+
))
53+
output.append(moves_nw)
54+
55+
return output

0 commit comments

Comments
 (0)