AbhayVG commited on
Commit
ddbd60d
·
verified ·
1 Parent(s): 7de96c8

Upload 5 files

Browse files
Files changed (5) hide show
  1. .gitignore +4 -0
  2. LICENSE +21 -0
  3. README.md +32 -12
  4. game_arena.py +2191 -0
  5. requirements.txt +2 -0
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ .spyproject/
2
+ # .gitignore
3
+ __pycache__/
4
+ *.pyc
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Bokhtiar-Adil
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,12 +1,32 @@
1
- ---
2
- title: Tablut
3
- emoji: 📊
4
- colorFrom: pink
5
- colorTo: blue
6
- sdk: gradio
7
- sdk_version: 5.25.2
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hnefatafl
2
+
3
+ Hnefatafl originated in Scandinavia many centuries ago. It was developed from a Roman game called Ludus Latrunculorum. This game flourished until the arrival of chess. It was revived back in nineteenth century.
4
+
5
+ ## Algorithm and approach
6
+
7
+ This project used minmax algorithm with alpha-beta pruning. The heuristic evaluation function considers king position, number of attackers and defenders remaining, how close it is to capture king etc. Based on the values provided by this function for all possible valid moves at a state, ai decides which move to commit.
8
+
9
+ ## Rules
10
+ - Turn based board game.
11
+ - Two board sizes: 'large' - 11x11 and 'small' - 9x9.
12
+ - Center cell and four corner cells are called restricted cells.
13
+ - Excluding king, a-d count is 24-12 on large board and 16-8 on small board.
14
+ - All pieces except king can move any number of cells horizontally or vertically.
15
+ - King can move only one cell at a time.
16
+ - Only king can move to any of the restricted cells.
17
+ - Pieces, except king, can be captured by sandwitching them from both sides.
18
+ - Restricted cells can be used to sandwitch opponent.
19
+ - Only one opponent piece can be captured in single line with single move.
20
+ - Multiple pieces can be captured with a single move on cardinal points.
21
+ - To capture king, attackers need to sorround him on all four cardinal points.
22
+ - If king is captured, attackers win.
23
+ - If king escapes to any of the four corner cells, defenders win.
24
+ - If all attackers are captured, defenders win.
25
+
26
+ ## Screenshots
27
+ ![Screenshot (6)](https://github.com/Bokhtiar-Adil/Vikings-chess-Hnefatafl/assets/103052177/9d4e8d28-6149-4fad-903e-87b953ea9d97)
28
+
29
+ ![Screenshot (12)](https://github.com/Bokhtiar-Adil/Vikings-chess-Hnefatafl/assets/103052177/3e6367e9-0fdb-42ff-8aa0-7f67ce12e08b)
30
+
31
+ ![Screenshot (13)](https://github.com/Bokhtiar-Adil/Vikings-chess-Hnefatafl/assets/103052177/3a350494-636c-4a56-a264-76afedbe0327)
32
+
game_arena.py ADDED
@@ -0,0 +1,2191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ AI project by Bokhtiar Adil and Sadman Sakib
3
+ '''
4
+
5
+
6
+ import os
7
+ import sys
8
+ import pygame as pg
9
+ import time
10
+
11
+
12
+ WINDOW_HEIGHT = 700
13
+ WINDOW_WIDTH = 1000
14
+ GAME_NAME = TITLE = "VIKINGS_CHESS"
15
+ GAME_ICON = pg.image.load("images/viking_anime_style.jpg")
16
+ MAIN_MENU_TOP_BUTTON_x = 400
17
+ MAIN_MENU_TOP_BUTTON_y = 400
18
+ BOARD_TOP = 200
19
+ BOARD_LEFT = 125
20
+ CELL_WIDTH = 50
21
+ CELL_HEIGHT = 50
22
+ PIECE_RADIUS = 20
23
+ VALID_MOVE_INDICATOR_RADIUS = 10
24
+ SETTINGS_TEXT_GAP_VERTICAL = 50
25
+ SETTINGS_TEXT_GAP_HORIZONTAL = 100
26
+
27
+ bg = (204, 102, 0)
28
+ bg2 = (40, 40, 40)
29
+ red = (255, 0, 0)
30
+ black = (0, 0, 0)
31
+ yellow = (255, 255, 1)
32
+ golden = (255, 215, 0)
33
+ white = (255, 255, 255)
34
+ pink_fuchsia = (255, 0, 255)
35
+ green_neon = (15, 255, 80)
36
+ green_dark = (2, 48, 32)
37
+ green_teal = (0, 128, 128)
38
+ blue_indigo = (63, 0, 255)
39
+ blue_zaffre = (8, 24, 168)
40
+
41
+ ATTACKER_PIECE_COLOR = pink_fuchsia
42
+ DEFENDER_PIECE_COLOR = green_teal
43
+ KING_PIECE_COLOR = golden
44
+ VALID_MOVE_INDICATOR_COLOR = green_neon
45
+ BORDER_COLOR = blue_zaffre
46
+
47
+ GAME_ICON_resized = pg.image.load("images/viking_anime_style.jpg")
48
+
49
+ click_snd = os.path.join("sounds", "click_1.wav")
50
+ move_snd_1 = os.path.join("sounds", "move_1.mp3")
51
+ kill_snd_1 = os.path.join("sounds", "kill_1.mp3")
52
+ win_snd_1 = os.path.join("sounds", "win_1.wav")
53
+ lose_snd_1 = os.path.join("sounds", "lose_1.wav")
54
+
55
+ clicked = False
56
+
57
+
58
+ def write_text(text, screen, position, color, font, new_window=True):
59
+ '''
60
+ This function writes the given text at the given position on given surface applying th e given color and font.
61
+
62
+ Parameters
63
+ ----------
64
+ text : string
65
+ This string will be #printed.
66
+ screen : a pygame display or surface
67
+ The text wiil be written on this suface.
68
+ position : a pair of values e.g. (x,y)
69
+ The text wiil be written at this position.
70
+ color : rgb coolor code e.g. (255,255,255)
71
+ The text wiil be written in this color.
72
+ font : a pygame font (pg.font.SysFont)
73
+ The text wiil be written in this font.
74
+ new_window : a boolean value, optional
75
+ This parameter wiil determine whether the text wil be #printed in a new window or current window.
76
+ If the former, all current text and graphics on this surface will be overwritten with background color.
77
+ The default is True.
78
+
79
+ Returns
80
+ -------
81
+ None.
82
+
83
+ '''
84
+
85
+ if new_window:
86
+ screen.fill(bg2)
87
+ txtobj = font.render(text, True, (255, 255, 255))
88
+ txtrect = txtobj.get_rect()
89
+ txtrect.topleft = position
90
+ screen.blit(txtobj, txtrect)
91
+
92
+
93
+ class Custom_button:
94
+
95
+ '''
96
+ This class holds the ncessary part of a custom button operation.
97
+
98
+
99
+ '''
100
+
101
+ button_col = (26, 117, 255)
102
+ hover_col = red
103
+ click_col = (50, 150, 255)
104
+ text_col = yellow
105
+
106
+ def __init__(self, x, y, text, screen, font, width=200, height=70):
107
+ self.x = x
108
+ self.y = y
109
+ self.text = text
110
+ self.screen = screen
111
+ self.font = font
112
+ self.width = width
113
+ self.height = height
114
+
115
+ def draw_button(self):
116
+
117
+ global clicked
118
+ action = False
119
+
120
+ # get mouse position
121
+ pos = pg.mouse.get_pos()
122
+
123
+ # create pg Rect object for the button
124
+ button_rect = pg.Rect(self.x, self.y, self.width, self.height)
125
+
126
+ # check mouseover and clicked conditions
127
+ if button_rect.collidepoint(pos):
128
+ if pg.mouse.get_pressed()[0] == 1:
129
+ clicked = True
130
+ pg.draw.rect(self.screen, self.click_col, button_rect)
131
+ elif pg.mouse.get_pressed()[0] == 0 and clicked == True:
132
+ clicked = False
133
+ action = True
134
+ else:
135
+ pg.draw.rect(self.screen, self.hover_col, button_rect)
136
+ else:
137
+ pg.draw.rect(self.screen, self.button_col, button_rect)
138
+
139
+ # add text to button
140
+ text_img = self.font.render(self.text, True, self.text_col)
141
+ text_len = text_img.get_width()
142
+ self.screen.blit(text_img, (self.x + int(self.width / 2) -
143
+ int(text_len / 2), self.y + 15))
144
+ return action
145
+
146
+
147
+ class ChessBoard:
148
+ '''
149
+ This class contains all properties of a chess board.
150
+
151
+ Properties:
152
+
153
+ 1. initial_pattern: this parameter holds the position of pieces at the start of the match.
154
+ 2. rows: n(rows) on board
155
+ 3. columns: n(columns) on board
156
+ 4. cell_width: width of each cell on surface
157
+ 5. cell_height: height of each cell on surface
158
+ 6. screen: where the board will be #printed
159
+ 7. restricted_cell: holds the (row, column) value of restricted cells
160
+
161
+ Methods:
162
+
163
+ 1. draw_empty_board(): this method draws an empty board with no piece on given surface
164
+ 2. initiate_board_pieces(): this method initiates all the sprite instances of different types of pieces
165
+
166
+ '''
167
+
168
+ def __init__(self, screen, board_size="large"):
169
+
170
+ # board size large means 11x11, small measn 9x9
171
+ self.initial_pattern11 = ["x..aaaaa..x",
172
+ ".....a.....",
173
+ "...........",
174
+ "a....d....a",
175
+ "a...ddd...a",
176
+ "aa.ddcdd.aa",
177
+ "a...ddd...a",
178
+ "a....d....a",
179
+ "...........",
180
+ ".....a.....",
181
+ "x..aaaaa..x"]
182
+
183
+ self.initial_pattern9 = ["...aaa...",
184
+ "....a....",
185
+ "....d....",
186
+ "a...d...a",
187
+ "aaddcddaa",
188
+ "a...d...a",
189
+ "....d....",
190
+ "....a....",
191
+ "...aaa..."]
192
+
193
+ if board_size == "large":
194
+ self.initial_pattern = self.initial_pattern11
195
+ else:
196
+ self.initial_pattern = self.initial_pattern9
197
+
198
+ self.rows = len(self.initial_pattern)
199
+ self.columns = len(self.initial_pattern[0])
200
+ self.cell_width = CELL_WIDTH
201
+ self.cell_height = CELL_HEIGHT
202
+ self.screen = screen
203
+ self.restricted_cells = [(4,4)]
204
+
205
+ def draw_empty_board(self):
206
+ '''
207
+ This method draws an empty board with no piece on given surface
208
+
209
+ Returns
210
+ -------
211
+ None.
212
+
213
+ '''
214
+
215
+ border_top = pg.Rect(BOARD_LEFT - 10, BOARD_TOP -
216
+ 10, self.columns*CELL_WIDTH + 20, 10)
217
+ pg.draw.rect(self.screen, BORDER_COLOR, border_top)
218
+ border_down = pg.Rect(BOARD_LEFT - 10, BOARD_TOP +
219
+ self.rows*CELL_HEIGHT, self.columns*CELL_WIDTH + 20, 10)
220
+ pg.draw.rect(self.screen, BORDER_COLOR, border_down)
221
+ border_left = pg.Rect(BOARD_LEFT - 10, BOARD_TOP -
222
+ 10, 10, self.rows*CELL_HEIGHT + 10)
223
+ pg.draw.rect(self.screen, BORDER_COLOR, border_left)
224
+ border_right = pg.Rect(BOARD_LEFT+self.columns*CELL_WIDTH,
225
+ BOARD_TOP - 10, 10, self.rows*CELL_HEIGHT + 10)
226
+ pg.draw.rect(self.screen, BORDER_COLOR, border_right)
227
+
228
+ color_flag = True
229
+ for row in range(self.rows):
230
+ write_text(str(row), self.screen, (BOARD_LEFT - 30, BOARD_TOP + row*CELL_HEIGHT +
231
+ PIECE_RADIUS), (255, 255, 255), pg.font.SysFont("Arial", 15), False)
232
+ write_text(str(row), self.screen, (BOARD_LEFT + row*CELL_WIDTH +
233
+ PIECE_RADIUS, BOARD_TOP - 30), (255, 255, 255), pg.font.SysFont("Arial", 15), False)
234
+ for column in range(self.columns):
235
+
236
+ cell_rect = pg.Rect(BOARD_LEFT + column * self.cell_width, BOARD_TOP +
237
+ row * self.cell_height, self.cell_width, self.cell_height)
238
+
239
+ if (row == 0 or row == self.rows-1) and (column == 0 or column == self.columns-1):
240
+ pg.draw.rect(self.screen, red, cell_rect)
241
+ elif row == int(self.rows / 2) and column == int(self.columns / 2):
242
+ pg.draw.rect(self.screen, blue_indigo, cell_rect)
243
+ elif color_flag:
244
+ pg.draw.rect(self.screen, white, cell_rect)
245
+ else:
246
+ pg.draw.rect(self.screen, black, cell_rect)
247
+
248
+ color_flag = not color_flag
249
+
250
+ def initiate_board_pieces(self):
251
+ '''
252
+ This method initiates all the sprite instances of different types of pieces
253
+
254
+ Returns
255
+ -------
256
+ None.
257
+
258
+ '''
259
+ att_cnt, def_cnt = 1, 1
260
+ # for more effective use, this dict maps piece ids and pieces -> {pid : piece}
261
+ global piece_pid_map
262
+ piece_pid_map = {}
263
+
264
+ for row in range(self.rows):
265
+ for column in range(self.columns):
266
+ if self.initial_pattern[row][column] == 'a':
267
+ pid = "a" + str(att_cnt)
268
+ AttackerPiece(pid, row, column)
269
+ att_cnt += 1
270
+ elif self.initial_pattern[row][column] == 'd':
271
+ pid = "d" + str(def_cnt)
272
+ DefenderPiece(pid, row, column)
273
+ def_cnt += 1
274
+ elif self.initial_pattern[row][column] == 'c':
275
+ pid = "k"
276
+ KingPiece(pid, row, column)
277
+ else:
278
+ pass
279
+
280
+ for piece in All_pieces:
281
+ piece_pid_map[piece.pid] = piece
282
+
283
+
284
+ class ChessPiece(pg.sprite.Sprite):
285
+ '''
286
+ This class contains information about each piece.
287
+
288
+ Properties:
289
+ 1. pid: holds a unique id for currnet piece instance
290
+ 2. row: holds the row index of current piece instance
291
+ 3. column: holds the column index of current piece instance
292
+ 4. center: center position of corresponding piece instance
293
+
294
+ Methods:
295
+ 1. update_piece_position(row, column): if the corresponding piece instance is moved, this method updates row and column value of that piece.
296
+
297
+ '''
298
+
299
+ def __init__(self, pid, row, column):
300
+
301
+ pg.sprite.Sprite.__init__(self, self.groups)
302
+ self.pid = pid
303
+ self.row, self.column = (row, column)
304
+ self.center = (BOARD_LEFT + int(CELL_WIDTH / 2) + self.column*CELL_WIDTH,
305
+ BOARD_TOP + int(CELL_HEIGHT / 2) + self.row*CELL_HEIGHT)
306
+
307
+ def draw_piece(self, screen):
308
+ '''
309
+ Draws a piece on board.
310
+
311
+ Parameters
312
+ ----------
313
+ screen : surface
314
+
315
+ Returns
316
+ -------
317
+ None.
318
+
319
+ '''
320
+
321
+ pg.draw.circle(screen, self.color, self.center, PIECE_RADIUS)
322
+
323
+ def update_piece_position(self, row, column):
324
+ '''
325
+ This updates the position of all pieces on board.
326
+
327
+ Parameters
328
+ ----------
329
+ row : row number
330
+ column : column number
331
+
332
+ Returns
333
+ -------
334
+ None.
335
+
336
+ '''
337
+
338
+ self.row, self.column = (row, column)
339
+ self.center = (BOARD_LEFT + int(CELL_WIDTH / 2) + self.column*CELL_WIDTH,
340
+ BOARD_TOP + int(CELL_HEIGHT / 2) + self.row*CELL_HEIGHT)
341
+ if (row, column) == (4,4) and self.ptype != "k":
342
+ return # Block non-kings from castle
343
+ if self.ptype == "k" and (self.row, self.column) != (4,4):
344
+ if (row, column) == (4,4):
345
+ return
346
+
347
+
348
+ class AttackerPiece(ChessPiece):
349
+ '''
350
+ This class holds information about attacker pieces. It's a child of ChessPiece class.
351
+
352
+ Properties:
353
+ 1. color: a rgb color code. e.g. (255,255,255)
354
+ color of the attacker piece that will be drawn on board.
355
+ 2. ptype: type of piece. values:
356
+ i. "a" means attacker
357
+ ii. "d" means defender
358
+ ii. "k" means king.
359
+ here it's "a".
360
+ 3. permit_to_res_sp: a boolean value.
361
+ tells whether the current piece is allowed on a restricted cell or not. here it's false.
362
+ '''
363
+
364
+ def __init__(self, pid, row, column):
365
+ ChessPiece.__init__(self, pid, row, column)
366
+ pg.sprite.Sprite.__init__(self, self.groups)
367
+ self.color = ATTACKER_PIECE_COLOR
368
+ self.permit_to_res_sp = False
369
+ self.ptype = "a"
370
+
371
+
372
+ class DefenderPiece(ChessPiece):
373
+ '''
374
+ This class holds information about defender pieces. It's a child of ChessPiece class.
375
+
376
+ Properties:
377
+ 1. color: a rgb color code. e.g. (255,255,255)
378
+ color of the attacker piece that will be drawn on board.
379
+ 2. ptype: type of piece. values:
380
+ i. "a" means attacker
381
+ ii. "d" means defender
382
+ ii. "k" means king.
383
+ here it's "d".
384
+ 3. permit_to_res_sp: a boolean value.
385
+ tells whether the current piece is allowed on a restricted cell or not. here it's false.
386
+ '''
387
+
388
+ def __init__(self, pid, row, column):
389
+ ChessPiece.__init__(self, pid, row, column)
390
+ pg.sprite.Sprite.__init__(self, self.groups)
391
+ self.color = DEFENDER_PIECE_COLOR
392
+ self.permit_to_res_sp = False
393
+ self.ptype = "d"
394
+
395
+
396
+ class KingPiece(DefenderPiece):
397
+ '''
398
+ This class holds information about attacker pieces. It's a child of DefenderPiece class.
399
+
400
+ Properties:
401
+ 1. color: a rgb color code. e.g. (255,255,255)
402
+ color of the attacker piece that will be drawn on board.
403
+ 2. ptype: type of piece. values:
404
+ i. "a" means attacker
405
+ ii. "d" means defender
406
+ ii. "k" means king.
407
+ here it's "k".
408
+ 3. permit_to_res_sp: a boolean value.
409
+ tells whether the current piece is allowed on a restricted cell or not. here it's true.
410
+ '''
411
+
412
+ def __init__(self, pid, row, column):
413
+ DefenderPiece.__init__(self, pid, row, column)
414
+ pg.sprite.Sprite.__init__(self, self.groups)
415
+ self.color = KING_PIECE_COLOR
416
+ self.permit_to_res_sp = True
417
+ self.ptype = "k"
418
+
419
+
420
+ def match_specific_global_data():
421
+ '''
422
+ This function declares and initiates all sprite groups.
423
+
424
+ Global Properties:
425
+ 1. All_pieces: a srpite group containing all pieces.
426
+ 2. Attacker_pieces: a srpite group containing all attacker pieces.
427
+ 3. Defender_pieces: a srpite group containing all defender pieces.
428
+ 4. King_pieces: a srpite group containing all king piece.
429
+
430
+ Returns
431
+ -------
432
+ None.
433
+
434
+ '''
435
+
436
+ global All_pieces, Attacker_pieces, Defender_pieces, King_pieces
437
+
438
+ All_pieces = pg.sprite.Group()
439
+ Attacker_pieces = pg.sprite.Group()
440
+ Defender_pieces = pg.sprite.Group()
441
+ King_pieces = pg.sprite.Group()
442
+
443
+ ChessPiece.groups = All_pieces
444
+ AttackerPiece.groups = All_pieces, Attacker_pieces
445
+ DefenderPiece.groups = All_pieces, Defender_pieces
446
+ KingPiece.groups = All_pieces, Defender_pieces, King_pieces
447
+
448
+
449
+ class Game_manager:
450
+ '''
451
+ This class handles all the events within the game.
452
+
453
+ Properties:
454
+
455
+ 1. screen: a pygame display or surface.
456
+ holds the current screen where the game is played on.
457
+ 2. board: a ChessBoard object.
458
+ this board is used in current game.
459
+ 3. turn: a boolean value. default is True.
460
+ this value decides whose turn it is - attackers' or defenders'.
461
+ 4. king_escape: a boolean value. dafult is false.
462
+ this variable tells whether the king is captured or not.
463
+ 5. king_captured: a boolean value. default is false.
464
+ this variable tells whether the king escaped or not.
465
+ 6. all_attackers_killed: a boolean value. default is false.
466
+ this variable tells if all attackers are killed or not.
467
+ 7. finish: a boolean value. default is false.
468
+ this variable tells whether a match finishing condition is reached or not.
469
+ 8. already_selected: a ChessPiece object, or any of it's child class object.
470
+ this varaible holds currenlty selected piece.
471
+ 9. is_selected: a boolean value. default is false.
472
+ this variable tells whether any piece is selected or not.
473
+ 10. valid_moves: a list of pair.
474
+ this list contains all the valid move indices- (row, column) of currently selected piece.
475
+ 11. valid_moves_positions: a list of pair.
476
+ this list contains all the valid move pixel positions- (x_pos, y_pos) of currently selected piece.
477
+ 12. current_board_status: a list of lists.
478
+ this holds current positions of all pieces i.e. current board pattern.
479
+ 13. current_board_status_with_border: a list of lists.
480
+ this holds current positions of all pieces i.e. current board pattern along with border index.
481
+ (this is redundent I know, but, it's needed for avoiding complexity)
482
+ 14. mode: 0 means p-vs-p, 1 means p-vs-ai
483
+ this variable holds game mode.
484
+ 15. last_move: pair of pairs of indecies - ((prev_row, prev_col), (curr_row, curr_col))
485
+ this variable holds the 'from' and 'to' of last move.
486
+ 16. board_size: "large" means 11x11, "small" means 9x9, default is "large"
487
+ this variable holds board sizes.
488
+
489
+ Methods:
490
+
491
+ 1. select_piece(selected_piece):
492
+ to select a piece.
493
+ 2. find_valid_moves():
494
+ finds valid moves of selected piece.
495
+ 3. show_valid_moves():
496
+ draws the indicator of valid moves on board.
497
+ 4. deselect():
498
+ deselects currently selected piece.
499
+ 5. update_board_status():
500
+ updates board status after each move.
501
+ 6. capture_check():
502
+ contains capture related logics.
503
+ 7. king_capture_check():
504
+ contains caturing-king related logics.
505
+ 8. escape_check():
506
+ contains king-escape related logics.
507
+ 9. attackers_count_check():
508
+ counts currently unkilled attacker pieces.
509
+ 10. match_finished():
510
+ performs necessary tasks when match ends.
511
+ 11. mouse_click_analyzer(msx, msy):
512
+ analyzes current mouse click action and performs necessary functionalites.
513
+ 12. turn_msg():
514
+ displays info about whose turn it is.
515
+ 13. ai_move_manager(piece, row, column):
516
+ handles ai moves
517
+
518
+ '''
519
+
520
+ def __init__(self, screen, board, mode, board_size="large"):
521
+ self.screen = screen
522
+ self.board = board
523
+ self.turn = True
524
+ self.king_escaped = False
525
+ self.king_captured = False
526
+ self.all_attackers_killed = False
527
+ self.finish = False
528
+ self.already_selected = None
529
+ self.is_selected = False
530
+ self.valid_moves = []
531
+ self.valid_moves_positions = []
532
+ self.current_board_status = []
533
+ self.current_board_status_with_border = []
534
+ self.mode = mode
535
+ self.last_move = None
536
+ self.board_size = board_size
537
+
538
+ # initiating current_board_status and current_board_status_with_border.
539
+ # initially board is in initial_pattern
540
+ # appending top border row
541
+ border = []
542
+ for column in range(self.board.columns + 2):
543
+ border.append("=")
544
+ self.current_board_status_with_border.append(border)
545
+
546
+ # appending according to initial_pattern
547
+ for row in self.board.initial_pattern:
548
+ bordered_row = ["="] # to add a left border
549
+ one_row = []
550
+ for column in row:
551
+ one_row.append(column)
552
+ bordered_row.append(column)
553
+ self.current_board_status.append(one_row)
554
+ bordered_row.append("=") # to add a right border
555
+ self.current_board_status_with_border.append(bordered_row)
556
+
557
+ # appending bottom border row
558
+ self.current_board_status_with_border.append(border)
559
+
560
+ def select_piece(self, selected_piece):
561
+ '''
562
+ This method selects a piece.
563
+
564
+ Parameters
565
+ ----------
566
+ selected_piece : a ChessPiece or it's child class object.
567
+ assigns this piece to already_selected variable.
568
+
569
+ Returns
570
+ -------
571
+ None.
572
+
573
+ '''
574
+
575
+ self.is_selected = True
576
+ self.already_selected = selected_piece
577
+ self.find_valid_moves()
578
+
579
+ def find_valid_moves(self):
580
+ '''
581
+ This method finds valid moves of selected piece.
582
+
583
+ Returns
584
+ -------
585
+ None.
586
+
587
+ '''
588
+
589
+
590
+ self.valid_moves = []
591
+ tempr = self.already_selected.row
592
+ tempc = self.already_selected.column
593
+
594
+
595
+ # finding valid moves in upwards direction
596
+ tempr -= 1
597
+ while tempr >= 0:
598
+
599
+ # stores current row and column
600
+ thispos = self.current_board_status[tempr][tempc]
601
+ # if finds any piece, no move left in this direction anymore
602
+ if thispos == "a" or thispos == "d" or thispos == "k" or (thispos == "x" and self.already_selected.ptype != "k"):
603
+ break
604
+ else:
605
+ # if selected piece is king, only one move per direction is allowed
606
+ # if self.already_selected.ptype == "k":
607
+ # if tempr < self.already_selected.row - 1 or tempr > self.already_selected.row + 1:
608
+ # break
609
+ # self.valid_moves.append((tempr, tempc))
610
+ # else:
611
+ # "." means empty cell
612
+ if thispos == ".":
613
+ self.valid_moves.append((tempr, tempc))
614
+
615
+ tempr -= 1
616
+
617
+ tempr = self.already_selected.row
618
+ tempc = self.already_selected.column
619
+
620
+ # finding valid moves in downwards direction
621
+ tempr += 1
622
+ while tempr < self.board.rows:
623
+
624
+ # stores current row and column
625
+ thispos = self.current_board_status[tempr][tempc]
626
+ # if finds any piece, no move left in this direction anymore
627
+ if thispos == "a" or thispos == "d" or thispos == "k" or (thispos == "x" and self.already_selected.ptype != "k"):
628
+ break
629
+ else:
630
+ # if selected piece is king, only one move per direction is allowed
631
+ # if self.already_selected.ptype == "k":
632
+ # if tempr < self.already_selected.row - 1 or tempr > self.already_selected.row + 1:
633
+ # break
634
+ # self.valid_moves.append((tempr, tempc))
635
+ # else:
636
+ # # "." means empty cell
637
+ if thispos == ".":
638
+ self.valid_moves.append((tempr, tempc))
639
+
640
+ tempr += 1
641
+
642
+ tempr = self.already_selected.row
643
+ tempc = self.already_selected.column
644
+
645
+ # finding valid moves in left direction
646
+ tempc -= 1
647
+ while tempc >= 0:
648
+
649
+ # stores current row and column
650
+ thispos = self.current_board_status[tempr][tempc]
651
+ # if finds any piece, no move left in this direction anymore
652
+ if thispos == "a" or thispos == "d" or thispos == "k" or (thispos == "x" and self.already_selected.ptype != "k"):
653
+ break
654
+ else:
655
+ # if selected piece is king, only one move per direction is allowed
656
+ # if self.already_selected.ptype == "k":
657
+ # if tempc < self.already_selected.column - 1 or tempc > self.already_selected.column + 1:
658
+ # break
659
+ # self.valid_moves.append((tempr, tempc))
660
+ # else:
661
+ # "." means empty cell
662
+ if thispos == ".":
663
+ self.valid_moves.append((tempr, tempc))
664
+
665
+ tempc -= 1
666
+
667
+ tempr = self.already_selected.row
668
+ tempc = self.already_selected.column
669
+
670
+ # finding valid moves in right direction
671
+ tempc += 1
672
+ while tempc < self.board.columns:
673
+
674
+ # stores current row and column
675
+ thispos = self.current_board_status[tempr][tempc]
676
+ # if finds any piece, no move left in this direction anymore
677
+ if thispos == "a" or thispos == "d" or thispos == "k" or (thispos == "x" and self.already_selected.ptype != "k"):
678
+ break
679
+ else:
680
+ # if selected piece is king, only one move per direction is allowed
681
+ # if self.already_selected.ptype == "k":
682
+ # if tempc < self.already_selected.column - 1 or tempc > self.already_selected.column + 1:
683
+ # break
684
+ # self.valid_moves.append((tempr, tempc))
685
+ # else:
686
+ # "." means empty cell
687
+ if thispos == ".":
688
+ self.valid_moves.append((tempr, tempc))
689
+
690
+ tempc += 1
691
+
692
+ # for each (row, column) index of each valid move, corresponding pixel position is stored
693
+ for position in self.valid_moves:
694
+ self.valid_moves_positions.append((BOARD_LEFT + int(CELL_WIDTH / 2) + position[1]*CELL_WIDTH,
695
+ BOARD_TOP + int(CELL_HEIGHT / 2) + position[0]*CELL_HEIGHT))
696
+
697
+ def show_valid_moves(self):
698
+ '''
699
+ This method draws the indicator of valid moves on board.
700
+
701
+ Returns
702
+ -------
703
+ None.
704
+
705
+ '''
706
+
707
+ # iterating over valid moves positions and drawing them on board
708
+ for index in self.valid_moves_positions:
709
+
710
+ pg.draw.circle(self.screen, VALID_MOVE_INDICATOR_COLOR,
711
+ index, VALID_MOVE_INDICATOR_RADIUS)
712
+
713
+ def deselect(self):
714
+ '''
715
+ This method deselects currently selected piece.
716
+
717
+ Returns
718
+ -------
719
+ None.
720
+
721
+ '''
722
+
723
+ self.is_selected = False
724
+ self.already_selected = None
725
+ self.valid_moves = []
726
+ self.valid_moves_positions = []
727
+
728
+ def update_board_status(self):
729
+ '''
730
+ This method updates board status after each move.
731
+
732
+ Returns
733
+ -------
734
+ None.
735
+
736
+ '''
737
+
738
+ self.current_board_status = []
739
+ self.current_board_status_with_border = []
740
+
741
+ # adding top border row
742
+ border = []
743
+ for column in range(self.board.columns + 2):
744
+ border.append("=")
745
+ self.current_board_status_with_border.append(border)
746
+
747
+ # first setting all cells as empty cells, then making changes where necessary
748
+ for row in range(self.board.rows):
749
+ bordered_row = ["="] # left border
750
+ one_row = []
751
+ for column in range(self.board.columns):
752
+ one_row.append(".")
753
+ bordered_row.append(".")
754
+
755
+ # if row == 0 or row == self.board.rows - 1:
756
+ # one_row[0] = "x"
757
+ # one_row[self.board.columns-1] = "x"
758
+ # bordered_row[1] = "x"
759
+ # bordered_row[self.board.columns] = "x"
760
+ self.current_board_status.append(one_row)
761
+ bordered_row.append("=") # right border
762
+ self.current_board_status_with_border.append(bordered_row)
763
+
764
+ # adding bottom border
765
+ self.current_board_status_with_border.append(border)
766
+
767
+ # according to each piece's positions, updating corresponding (row, column) value
768
+ for piece in All_pieces:
769
+ self.current_board_status[piece.row][piece.column] = piece.ptype
770
+ # adding an extra 1 because 0th row and 0th column is border
771
+ self.current_board_status_with_border[piece.row +
772
+ 1][piece.column+1] = piece.ptype
773
+
774
+ # initial pattern set middle cell as empty cell. but, if it is actually an restricted cell.
775
+ # if it doesn't contain king, it's marked as "x'.
776
+ if self.current_board_status[int(self.board.rows/2)][int(self.board.columns/2)] != "k":
777
+ self.current_board_status[int(
778
+ self.board.rows/2)][int(self.board.columns/2)] = "x"
779
+ self.current_board_status_with_border[int(
780
+ self.board.rows/2)+1][int(self.board.columns/2)+1] = "x"
781
+
782
+
783
+ def capture_check(self):
784
+ '''
785
+ This method contains capture related logics.
786
+
787
+ Returns
788
+ -------
789
+ None.
790
+
791
+ '''
792
+ # storing current piece's type and index
793
+ ptype, prow, pcol = self.already_selected.ptype, self.already_selected.row + \
794
+ 1, self.already_selected.column+1
795
+
796
+ # indices of sorrounding one hop cells and two hops cells.
797
+ sorroundings = [(prow, pcol+1), (prow, pcol-1),
798
+ (prow-1, pcol), (prow+1, pcol)]
799
+ two_hop_away = [(prow, pcol+2), (prow, pcol-2),
800
+ (prow-2, pcol), (prow+2, pcol)]
801
+
802
+ # iterating over each neighbour cells and finding out if the piece of this cell is captured or not
803
+ for pos, item in enumerate(sorroundings):
804
+
805
+ # currently selected cell's piece, if any
806
+ opp = self.current_board_status_with_border[item[0]][item[1]]
807
+ # if index is 1, which means it's right beside border, which means there's no two-hop cell in thi direction
808
+ # it may overflow the list index, so it will be set as empty cell instead to avoid error
809
+ try:
810
+ opp2 = self.current_board_status_with_border[two_hop_away[pos]
811
+ [0]][two_hop_away[pos][1]]
812
+ except:
813
+ opp2 = "."
814
+
815
+ # if next cell is empty or has same type of piece or has border, no capturing is possible
816
+ # if two hop cell is empty, then also no capturing is possible
817
+ if ptype == opp or ptype == "x" or ptype == "=" or opp == "." or opp2 == ".":
818
+ continue
819
+
820
+ elif opp == "k":
821
+ # king needs 4 enemies on 4 cardinal points to be captured. so, handled in another function.
822
+ self.king_capture_check(item[0], item[1])
823
+ # #print(self.king_captured)
824
+
825
+ elif ptype != opp:
826
+ # neghbour cell's piece is of different type
827
+ if (ptype == "a" and (opp2 == self.already_selected.ptype or opp2 == "x")) or \
828
+ (ptype in ["d","k"] and (opp2 == "a" or opp2 == "x")):
829
+
830
+ # Capture logic for both attackers and defenders
831
+ for piece in All_pieces:
832
+ if piece.ptype == opp and piece.row == item[0]-1 and piece.column == item[1]-1:
833
+ pg.mixer.Sound.play(pg.mixer.Sound(kill_snd_1))
834
+ piece.kill()
835
+ self.update_board_status()
836
+ break
837
+
838
+ elif ptype != "a" and opp2 != "a" and opp2 != "=" and opp == "a":
839
+ # d-a-d or k-a-d or d-a-k or d-a-res_cell or k-a-res_cell situation
840
+ for piece in All_pieces:
841
+ if piece.ptype == opp and piece.row == sorroundings[pos][0]-1 and piece.column == sorroundings[pos][1]-1:
842
+ pg.mixer.Sound.play(pg.mixer.Sound(kill_snd_1))
843
+ piece.kill()
844
+ self.update_board_status()
845
+ break
846
+ # if self.already_selected.ptype == "k":
847
+ # for dx, dy in [(0,1),(0,-1),(1,0),(-1,0)]:
848
+ # adj_piece = self.current_board_status_with_border[prow+dx][pcol+dy]
849
+ # if adj_piece[0] == "d":
850
+ # opposite_pos = self.current_board_status_with_border[prow-dx][pcol-dy]
851
+ # if opposite_pos[0] == "a":
852
+ # # Capture attacker between king and defender
853
+ # for piece in All_pieces:
854
+ # if piece.ptype == "a" and piece.row == (prow-dx)-1 and piece.column == (pcol-dy)-1:
855
+ # pg.mixer.Sound.play(pg.mixer.Sound(kill_snd_1))
856
+ # piece.kill()
857
+ # self.update_board_status()
858
+ if self.king_captured:
859
+ self.finish = True
860
+ pg.mixer.Sound.play(pg.mixer.Sound(lose_snd_1))
861
+
862
+ def king_capture_check(self, kingr, kingc):
863
+ '''
864
+ This method contains caturing-king related logics.
865
+
866
+ Parameters
867
+ ----------
868
+ kingr : integer
869
+ row index of king piece.
870
+ kingc : integer
871
+ column index of king piece.
872
+
873
+ Returns
874
+ -------
875
+ None.
876
+
877
+ '''
878
+ # store all four neighbor cells' pieces
879
+ king_pos = (kingr-1, kingc-1) # Convert to board coordinates
880
+ attackers = 0
881
+ castle_adjacent = False
882
+
883
+ # Check if king is adjacent to castle
884
+ if abs(king_pos[0]-4) + abs(king_pos[1]-4) == 1:
885
+ castle_adjacent = True
886
+
887
+ # Check four cardinal directions
888
+ for dx, dy in [(0,1),(0,-1),(1,0),(-1,0)]:
889
+ adj = self.current_board_status_with_border[kingr+dx][kingc+dy]
890
+ if adj[0] == "a":
891
+ attackers += 1
892
+ elif adj == "x" and (kingr+dx-1, kingc+dy-1) == (4,4):
893
+ attackers += 1 # Castle counts as attacker for adjacent king
894
+
895
+ # Determine capture conditions
896
+ if king_pos == (4,4): # Center
897
+ self.king_captured = attackers >= 4
898
+ elif castle_adjacent: # Next to castle
899
+ self.king_captured = attackers >= 3
900
+ else: # Regular position
901
+ self.king_captured = attackers >= 2
902
+
903
+ def escape_check(self):
904
+ '''
905
+ This method checks if the king has escaped or not.
906
+
907
+ Returns
908
+ -------
909
+ None.
910
+
911
+ '''
912
+ king = next(p for p in King_pieces)
913
+ # Check all edge cells
914
+ edge_cells = (
915
+ any(row[0] == 'k' for row in self.current_board_status) or # First column
916
+ any(row[-1] == 'k' for row in self.current_board_status) or # Last column
917
+ 'k' in self.current_board_status[0] or # First row
918
+ 'k' in self.current_board_status[self.board.rows-1] # Last row
919
+ )
920
+
921
+ if edge_cells:
922
+ self.king_escaped = True
923
+ self.finish = True
924
+ pg.mixer.Sound.play(pg.mixer.Sound(win_snd_1))
925
+
926
+ def attackers_count_check(self):
927
+ '''
928
+ This method checks if all attackers are killed or not.
929
+
930
+ Returns
931
+ -------
932
+ None.
933
+
934
+ '''
935
+ # only way attackers would win is by capturing king, so it's not necessary to check defenders' count
936
+ # Attacker_pieces sprite group holds all attackers
937
+ if len(Attacker_pieces) == 0:
938
+ self.all_attackers_killed = True
939
+ self.finish = True
940
+ pg.mixer.Sound.play(pg.mixer.Sound(win_snd_1))
941
+
942
+
943
+ def match_finished(self):
944
+ '''
945
+ This method displays necessary messages when the match finishes.
946
+
947
+ Returns
948
+ -------
949
+ None.
950
+
951
+ '''
952
+ consolas = pg.font.SysFont("consolas", 22)
953
+ if self.king_captured:
954
+ if self.mode == 0:
955
+ write_text(">>> KING CAPTURED !! ATTACKERS WIN !!", self.screen, (20, BOARD_TOP - 80), white,
956
+ consolas, False)
957
+ else:
958
+ write_text(">>> KING CAPTURED !! AI WINS !!", self.screen, (20, BOARD_TOP - 80), white,
959
+ consolas, False)
960
+
961
+ elif self.king_escaped:
962
+ write_text(">>> KING ESCAPED !! DEFENDERS WIN !!", self.screen, (20, BOARD_TOP - 80), white,
963
+ consolas, False)
964
+
965
+
966
+ elif self.all_attackers_killed:
967
+ write_text(">>> ALL ATTACKERS DEAD !! DEFENDERS WIN !!", self.screen, (20, BOARD_TOP - 80), white,
968
+ consolas, False)
969
+
970
+
971
+ else:
972
+ pass
973
+
974
+ def mouse_click_analyzer(self, msx, msy):
975
+ '''
976
+ This method analyzes a mouse click event. This is the heart of Game_manager class.
977
+
978
+ Parameters
979
+ ----------
980
+ msx : integer
981
+ the row index of mouse clicked position.
982
+ msy : integer
983
+ the column index of mouse clicked position.
984
+
985
+ Returns
986
+ -------
987
+ None.
988
+
989
+ '''
990
+ # if no piece is selected before and the player selects a piece, the valid moves of that piece is displayed
991
+ if not self.is_selected:
992
+ for piece in All_pieces:
993
+ # collidepoint was not working (dunno why...). so, instead, made a custom logic
994
+ # if mouse click position is within a distant of radius of piece from the center of so, it means it is clicked
995
+ # iterates over all pieces to find out which piece satiefies such condition
996
+ if (msx >= piece.center[0] - PIECE_RADIUS) and (msx < piece.center[0] + PIECE_RADIUS):
997
+ if (msy >= piece.center[1] - PIECE_RADIUS) and (msy < piece.center[1] + PIECE_RADIUS):
998
+ if (piece.ptype == "a" and self.turn) or (piece.ptype != "a" and not self.turn):
999
+ self.select_piece(piece)
1000
+ break
1001
+
1002
+ elif (self.already_selected.ptype != "a" and self.turn) or (self.already_selected.ptype == "a" and not self.turn):
1003
+ # opponent piece is selected, so previously selected piece will be deselected
1004
+ self.deselect()
1005
+
1006
+ else:
1007
+ # some piece was selected previously
1008
+ # gonna check multiple scenerioes serially; if any meets requirement, 'done' flag will stop checking more
1009
+ done = False
1010
+
1011
+ for piece in All_pieces:
1012
+ if (msx >= piece.center[0] - PIECE_RADIUS) and (msx < piece.center[0] + PIECE_RADIUS):
1013
+ if (msy >= piece.center[1] - PIECE_RADIUS) and (msy < piece.center[1] + PIECE_RADIUS):
1014
+ done = True
1015
+ if piece == self.already_selected:
1016
+ # previously selected piece is selected again, so it will be deselected
1017
+ self.deselect()
1018
+ break
1019
+ else:
1020
+ # some other piece of same side is selected
1021
+ # so previous one will be deselected and current will be selected
1022
+ self.deselect()
1023
+ if (piece.ptype == "a" and self.turn) or (piece.ptype != "a" and not self.turn):
1024
+ self.select_piece(piece)
1025
+ break
1026
+
1027
+ if not done:
1028
+ # a valid move was selected for previously selected piece, so it will move to that new cell position
1029
+ for ind, pos in enumerate(self.valid_moves_positions):
1030
+ if (msx >= pos[0] - PIECE_RADIUS) and (msx < pos[0] + PIECE_RADIUS):
1031
+ if (msy >= pos[1] - PIECE_RADIUS) and (msy < pos[1] + PIECE_RADIUS):
1032
+ # updating piece's position
1033
+ prev = (self.already_selected.row,
1034
+ self.already_selected.column)
1035
+ self.already_selected.update_piece_position(
1036
+ self.valid_moves[ind][0], self.valid_moves[ind][1])
1037
+ curr = (self.already_selected.row,
1038
+ self.already_selected.column)
1039
+ self.last_move = (prev, curr)
1040
+ # updating board status
1041
+ self.update_board_status()
1042
+ # playing a sound effect
1043
+ pg.mixer.Sound.play(pg.mixer.Sound(move_snd_1))
1044
+ # checking if any opponent piece was captured or not
1045
+ self.capture_check()
1046
+ # checking if selected piece is king or not
1047
+ # if it is, then checking if it's escaped or not
1048
+ if self.already_selected.ptype == "k":
1049
+ self.escape_check()
1050
+ # if it was defender's turn, checking if all of the attackers are captured or not
1051
+ if self.already_selected.ptype != "a":
1052
+ self.attackers_count_check()
1053
+ # altering turn; a to d or d to a
1054
+ self.turn = not self.turn
1055
+ done = True
1056
+ break
1057
+
1058
+ self.deselect()
1059
+
1060
+ def ai_move_manager(self, piece, row, column):
1061
+ '''
1062
+ This function handles functionalities after AI chooses which piece to move
1063
+
1064
+ Parameters
1065
+ ----------
1066
+ piece : AI's choosen piece
1067
+ row : row index
1068
+ column : column index
1069
+
1070
+ Returns
1071
+ -------
1072
+ None.
1073
+
1074
+ '''
1075
+
1076
+ # updating piece's position
1077
+ self.already_selected = piece
1078
+ prev = (self.already_selected.row, self.already_selected.column)
1079
+ self.already_selected.update_piece_position(row-1, column-1)
1080
+ curr = (row-1, column-1)
1081
+ self.last_move = (prev, curr)
1082
+ # updating board status
1083
+ self.update_board_status()
1084
+ # playing a sound effect
1085
+ pg.mixer.Sound.play(pg.mixer.Sound(move_snd_1))
1086
+ # self.already_selected = self.ai_selected
1087
+ # checking if any opponent piece was captured or not
1088
+ self.capture_check()
1089
+ # checking if selected piece is king or not
1090
+ # if it is, then checking if it's escaped or not
1091
+ if self.already_selected.ptype == "k":
1092
+ self.escape_check()
1093
+ # if it was defender's turn, checking if all of the attackers are captured or not
1094
+ if self.already_selected.ptype != "a":
1095
+ self.attackers_count_check()
1096
+ # altering turn; a to d or d to a
1097
+ self.turn = not self.turn
1098
+ self.deselect()
1099
+
1100
+ def turn_msg(self, game_started):
1101
+ '''
1102
+ This method shows message saying whose turn it is now.
1103
+
1104
+ Returns
1105
+ -------
1106
+ None.
1107
+
1108
+ '''
1109
+ consolas = pg.font.SysFont("consolas", 22)
1110
+ if not game_started:
1111
+ if self.mode == 0:
1112
+ write_text(">>> Click 'New Game' to start a new game.", self.screen,
1113
+ (20, BOARD_TOP - 80), white, consolas, False)
1114
+ else:
1115
+ write_text(">>> Click 'New Game' to start a new game. AI is attacker and you are defender.", self.screen,
1116
+ (20, BOARD_TOP - 80), white, consolas, False)
1117
+
1118
+ elif self.mode == 0 and self.turn:
1119
+ write_text(">>> Attacker's Turn", self.screen, (20, BOARD_TOP - 80), white,
1120
+ consolas, False)
1121
+
1122
+ elif self.mode == 1 and self.turn:
1123
+ write_text(">>> AI is thinking...", self.screen, (20, BOARD_TOP - 80), white,
1124
+ consolas, False)
1125
+
1126
+ else:
1127
+ write_text(">>> Defender's Turn", self.screen, (20, BOARD_TOP - 80), white,
1128
+ consolas, False)
1129
+
1130
+
1131
+ class AI_manager:
1132
+
1133
+ def __init__(self, manager, screen):
1134
+
1135
+ self.manager = manager
1136
+ self.screen = screen
1137
+
1138
+ def move(self):
1139
+ '''
1140
+ AI uses this function to move a piece.
1141
+
1142
+ Returns
1143
+ -------
1144
+ None.
1145
+
1146
+ '''
1147
+
1148
+ current_board = []
1149
+ rows = self.manager.board.rows
1150
+ columns = self.manager.board.columns
1151
+ self.rows = rows
1152
+ self.columns = columns
1153
+
1154
+ # creating pattern such as
1155
+ # [['x', '.', '.', 'a1', 'a2', 'a3', 'a4', 'a5', '.', '.', 'x'],
1156
+ # ['.', '.', '.', '.', '.', 'a6', '.', '.', '.', '.', '.'],
1157
+ # ['.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
1158
+ # ['a7', '.', '.', '.', '.', 'd1', '.', '.', '.', '.', 'a8'],
1159
+ # ['a9', '.', '.', '.', 'd2', 'd3', 'd4', '.', '.', '.', 'a10'],
1160
+ # ['a11', 'a12', '.', 'd5', 'd6', 'k', 'd7', 'd8', '.', 'a13', 'a14'],
1161
+ # ['a15', '.', '.', '.', 'd9', 'd10', 'd11', '.', '.', '.', 'a16'],
1162
+ # ['a17', '.', '.', '.', '.', 'd12', '.', '.', '.', '.', 'a18'],
1163
+ # ['.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
1164
+ # ['.', '.', '.', '.', '.', 'a19', '.', '.', '.', '.', '.'],
1165
+ # ['x', '.', '.', 'a20', 'a21', 'a22', 'a23', 'a24', '.', '.', 'x']]
1166
+
1167
+ current_board = []
1168
+
1169
+ border_row = []
1170
+ for column in range(columns+2):
1171
+ border_row.append("=") #adding border
1172
+ current_board.append(border_row)
1173
+
1174
+ for row in range(rows):
1175
+ one_row = ["="]
1176
+ for column in range(columns):
1177
+ one_row.append('.')
1178
+ one_row.append("=") #adding border
1179
+ current_board.append(one_row)
1180
+
1181
+ current_board.append(border_row)
1182
+
1183
+ for piece in All_pieces:
1184
+ current_board[piece.row+1][piece.column+1] = piece.pid #corresponding id mapping for all the pieces in board
1185
+
1186
+ current_board[1][1] = current_board[1][rows] = current_board[rows][1] = current_board[rows][columns] = 'x' #creating these positions as restricted for all except king
1187
+ if current_board[int((self.rows+1)/2)][int((self.columns+1)/2)] != 'k':
1188
+ current_board[int((self.rows+1)/2)][int((self.columns+1)/2)] = 'x'
1189
+
1190
+ # find all possible valid move and return -> list[piece, (pair of indices)]
1191
+ piece, best_move = self.find_best_move(current_board)
1192
+ row, col = best_move
1193
+
1194
+ # perform the move
1195
+ self.manager.ai_move_manager(piece, row, col)
1196
+
1197
+ def find_all_possible_valid_moves(self, board_status_at_this_state, fake_turn):
1198
+ '''
1199
+ AI uses this fucntion to finds out all valid moves of all pieces of a type.
1200
+
1201
+ Parameters
1202
+ ----------
1203
+ board_status_at_this_state : a 2d matrix
1204
+ at any state of evaluation, ai feeds that state's board status here to calculate moves
1205
+ fake_turn : boolean
1206
+ True - attackers' turn, False - defenders' turn
1207
+
1208
+ Returns
1209
+ -------
1210
+ valid_moves : a list of pairs - [(str, (int, int))]
1211
+ (piece_pid, (row, column))
1212
+
1213
+ '''
1214
+
1215
+ valid_moves = []
1216
+ piece_pos_this_state = {}
1217
+ for row_ind, row in enumerate(board_status_at_this_state):
1218
+ for col_ind, column in enumerate(row):
1219
+ if column != "." and column != "x" and column != "=":
1220
+ piece_pos_this_state[column] = (row_ind, col_ind)
1221
+
1222
+ for each in piece_pos_this_state.keys():
1223
+ piece = each[0]
1224
+
1225
+ # find moves for a side only if it's their turn
1226
+ if (fake_turn and not piece[0] == "a") or (not fake_turn and piece[0] == "a"):
1227
+ continue
1228
+
1229
+ tempr = piece_pos_this_state[each][0]
1230
+ tempc = piece_pos_this_state[each][1]
1231
+
1232
+ # finding valid moves in upwards direction
1233
+ tempr -= 1
1234
+ while tempr >= 0:
1235
+ # stores current row and column
1236
+ thispos = board_status_at_this_state[tempr][tempc][0]
1237
+ # if finds any piece, no move left in this direction anymore
1238
+ if thispos == "a" or thispos == "d" or thispos == "k" or thispos == "=" or (thispos == "x" and piece != "k"):
1239
+ break
1240
+ else:
1241
+ pass
1242
+ # this part is commented out because so far ai is only attacker and this part checks both 'a' or 'd'
1243
+ # # if selected piece is king, only one move per direction is allowed
1244
+ if piece == "k":
1245
+ if tempr < piece_pos_this_state[each][0] - 1 or tempr > piece_pos_this_state[each][0] + 1:
1246
+ break
1247
+ valid_moves.append(
1248
+ (piece_pid_map[each], (tempr, tempc)))
1249
+ else:
1250
+ # "." means empty cell
1251
+ if thispos == ".":
1252
+ valid_moves.append(
1253
+ (piece_pid_map[each], (tempr, tempc)))
1254
+
1255
+ tempr -= 1
1256
+
1257
+ tempr = piece_pos_this_state[each][0]
1258
+ tempc = piece_pos_this_state[each][1]
1259
+
1260
+ # finding valid moves in downwards direction
1261
+ tempr += 1
1262
+ while tempr < self.manager.board.rows+2:
1263
+ # stores current row and column
1264
+ thispos = board_status_at_this_state[tempr][tempc][0]
1265
+ # if finds any piece, no move left in this direction anymore
1266
+ if thispos == "a" or thispos == "d" or thispos == "k" or thispos == "=" or (thispos == "x" and piece != "k"):
1267
+ break
1268
+ else:
1269
+ # # if selected piece is king, only one move per direction is allowed
1270
+ if piece == "k":
1271
+ if tempr < piece_pos_this_state[each][0] - 1 or tempr > piece_pos_this_state[each][0] + 1:
1272
+ break
1273
+ valid_moves.append(
1274
+ (piece_pid_map[each], (tempr, tempc)))
1275
+ else:
1276
+ # "." means empty cell
1277
+ if thispos == ".":
1278
+ valid_moves.append(
1279
+ (piece_pid_map[each], (tempr, tempc)))
1280
+
1281
+ tempr += 1
1282
+
1283
+ tempr = piece_pos_this_state[each][0]
1284
+ tempc = piece_pos_this_state[each][1]
1285
+
1286
+ # finding valid moves in left direction
1287
+ tempc -= 1
1288
+ while tempc >= 0:
1289
+ # stores current row and column
1290
+ thispos = board_status_at_this_state[tempr][tempc][0]
1291
+ # if finds any piece, no move left in this direction anymore
1292
+ if thispos == "a" or thispos == "d" or thispos == "k" or thispos == "=" or (thispos == "x" and piece != "k"):
1293
+ break
1294
+ else:
1295
+ # # if selected piece is king, only one move per direction is allowed
1296
+ if piece == "k":
1297
+ if tempc < piece_pos_this_state[each][1] - 1 or tempc > piece_pos_this_state[each][1] + 1:
1298
+ break
1299
+ valid_moves.append(
1300
+ (piece_pid_map[each], (tempr, tempc)))
1301
+ else:
1302
+ # "." means empty cell
1303
+ if thispos == ".":
1304
+ valid_moves.append(
1305
+ (piece_pid_map[each], (tempr, tempc)))
1306
+
1307
+ tempc -= 1
1308
+
1309
+ tempr = piece_pos_this_state[each][0]
1310
+ tempc = piece_pos_this_state[each][1]
1311
+
1312
+ # finding valid moves in right direction
1313
+ tempc += 1
1314
+ while tempc < self.manager.board.columns+2:
1315
+ # stores current row and column
1316
+ thispos = board_status_at_this_state[tempr][tempc][0]
1317
+ # if finds any piece, no move left in this direction anymore
1318
+ if thispos == "a" or thispos == "d" or thispos == "k" or thispos == "=" or (thispos == "x" and piece != "k"):
1319
+ break
1320
+ else:
1321
+ # # if selected piece is king, only one move per direction is allowed
1322
+ if piece == "k":
1323
+ if tempc < piece_pos_this_state[each][1] - 1 or tempc > piece_pos_this_state[each][1] + 1:
1324
+ break
1325
+ valid_moves.append(
1326
+ (piece_pid_map[each], (tempr, tempc)))
1327
+ else:
1328
+ # "." means empty cell
1329
+ if thispos == ".":
1330
+ valid_moves.append(
1331
+ (piece_pid_map[each], (tempr, tempc)))
1332
+
1333
+ tempc += 1
1334
+
1335
+ return valid_moves
1336
+
1337
+ def king_mobility(self, fake_board, r, c):
1338
+ '''
1339
+ THis function checks how many cells can king move at current state
1340
+
1341
+ Parameters
1342
+ ----------
1343
+ fake_board : board status at that state
1344
+ r : row of king
1345
+ c : column of king
1346
+
1347
+ Returns
1348
+ -------
1349
+ score : number of cells king can move to
1350
+
1351
+ '''
1352
+ score = 0
1353
+ i = c-1
1354
+ while(i != '='):
1355
+ if fake_board[r][i] == '.' or fake_board[r][i] == 'x':
1356
+ score += 1
1357
+ else:
1358
+ break
1359
+ i -= 1
1360
+
1361
+ i = c+1
1362
+ while(i != '='):
1363
+ if fake_board[r][i] == '.' or fake_board[r][i] == 'x':
1364
+ score += 1
1365
+ else:
1366
+ break
1367
+
1368
+ i += 1
1369
+
1370
+ i = r-1
1371
+ while(i != '='):
1372
+ if fake_board[i][c] == '.' or fake_board[i][c] == 'x':
1373
+ score += 1
1374
+ else:
1375
+ break
1376
+
1377
+ i -= 1
1378
+
1379
+ i = r+1
1380
+ while(i != '='):
1381
+ if fake_board[i][c] == '.' or fake_board[i][c] == 'x':
1382
+ score += 1
1383
+ else:
1384
+ break
1385
+
1386
+ i += 1
1387
+
1388
+ return score
1389
+
1390
+ def king_sorrounded(self, fake_board, r, c):
1391
+ '''
1392
+ Finds out how many attacekrs are sorrounding king at current board state.
1393
+
1394
+ Parameters
1395
+ ----------
1396
+ fake_board : board status at that state
1397
+ r : row of king
1398
+ c : column of king
1399
+
1400
+ Returns
1401
+ -------
1402
+ score : number of sorrounding attackers.
1403
+
1404
+ '''
1405
+ score = 0
1406
+ if fake_board[r][c+1][0] == 'a':
1407
+ score += 1
1408
+
1409
+ if fake_board[r][c-1][0] == 'a':
1410
+ score += 1
1411
+
1412
+ if fake_board[r-1][c][0] == 'a':
1413
+ score += 1
1414
+
1415
+ if fake_board[r+1][c][0] == 'a':
1416
+ score += 1
1417
+
1418
+ return score
1419
+
1420
+ def evaluate(self, fake_board):
1421
+ '''
1422
+ This function evaluates current board state using a predefined heuristic value. Heart of AI...
1423
+
1424
+ Parameters
1425
+ ----------
1426
+ fake_board : current board state.
1427
+
1428
+ Returns
1429
+ -------
1430
+ score : calculated cost/value of this state.
1431
+
1432
+ '''
1433
+ # heuristic values
1434
+ weight_pos = 5
1435
+ # for 11x11 board
1436
+ '''
1437
+ here king position is weighted depending on how close it is to escape points , the closer it gets
1438
+ the larger the points are
1439
+ '''
1440
+ weight_king_pos_11 = [[10000, 10000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 10000, 10000],
1441
+ [10000, 500, 500, 500, 500, 500,
1442
+ 500, 500, 500, 500, 10000],
1443
+ [1000, 500, 200, 200, 200, 200,
1444
+ 200, 200, 200, 500, 1000],
1445
+ [1000, 500, 200, 50, 50, 50, 50, 50, 200, 500, 1000],
1446
+ [1000, 500, 200, 50, 10, 10, 10, 50, 200, 500, 1000],
1447
+ [1000, 500, 200, 50, 10, 0, 10, 50, 200, 500, 1000],
1448
+ [1000, 500, 200, 50, 10, 10, 10, 50, 200, 500, 1000],
1449
+ [1000, 500, 200, 50, 50, 50, 50, 50, 200, 500, 1000],
1450
+ [1000, 500, 200, 200, 200, 200,
1451
+ 200, 200, 200, 500, 1000],
1452
+ [10000, 500, 500, 500, 500, 500,
1453
+ 500, 500, 500, 500, 10000],
1454
+ [10000, 10000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 10000, 10000]]
1455
+
1456
+ # for 9x9 board
1457
+ weight_king_pos_9 = [[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000],
1458
+ [10000, 500, 500, 500, 500, 500, 500, 500, 10000],
1459
+ [10000, 500, 150, 150, 150, 150, 150, 500, 10000],
1460
+ [10000, 500, 150, 30, 30, 30, 150, 500, 10000],
1461
+ [10000, 500, 150, 30, 0, 30, 150, 500, 10000],
1462
+ [10000, 500, 150, 30, 30, 30, 150, 500, 10000],
1463
+ [10000, 500, 150, 150, 150, 150, 150, 500, 10000],
1464
+ [10000, 500, 500, 500, 500, 500, 500, 500, 10000],
1465
+ [10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]]
1466
+
1467
+ if self.manager.board_size == "large":
1468
+ weight_king_pos = weight_king_pos_11
1469
+ weight_attacker = 12 # weight is given because inequal number of attacker and defender
1470
+ weight_defender = 24
1471
+ weight_king_sorrounded = 50000
1472
+ else:
1473
+ weight_king_pos = weight_king_pos_9
1474
+ weight_attacker = 8 # weight is given because inequal number of attacker and defender
1475
+ weight_defender = 12
1476
+ weight_king_sorrounded = 10000
1477
+
1478
+ #weight_king_sorrounded = 50000
1479
+
1480
+
1481
+ attacker = 0 # attacker count
1482
+
1483
+ defender = 0 # defender count
1484
+
1485
+ score = 0
1486
+
1487
+ if self.fake_gameOver(fake_board) == 1: # if 1 then winner is attacker
1488
+ print("c")
1489
+ score += 10000000 #if attacker wins a large score is added as it will help to maximize the score
1490
+ return score
1491
+
1492
+ elif self.fake_gameOver(fake_board) == 2: # if 1 then winner is defender
1493
+ score -= 10000000 #if attacker wins a large score is subtracted as it will help to maximize the score
1494
+ return score
1495
+
1496
+ # finding number of attackers and defenders currently on board
1497
+ # searching king position
1498
+ for row_index, row in enumerate(fake_board):
1499
+ for col_index, col in enumerate(row):
1500
+ if(col == 'k'):
1501
+ r = row_index
1502
+ c = col_index
1503
+ elif(col[0] == 'a'):
1504
+ attacker += 1
1505
+ elif(col[0] == 'd'):
1506
+ defender += 1
1507
+
1508
+ # making dynamic heuristic evaluation to prioritize on restricting movement of king when he is close to corner cells
1509
+ '''
1510
+ this dynamic heuristic helps to make decision for ai when king is obviously escaping in next
1511
+ few turns. Then the attacker will prioritize to prevent the king from escaping above anything
1512
+ else
1513
+ '''
1514
+ if r-3 <= 1 and c-3 <= 1:
1515
+ if fake_board[1][2][0] == 'a':
1516
+ score += 1000
1517
+ if fake_board[2][1][0] == 'a':
1518
+ score += 1000
1519
+ elif r-3 <= 1 and c+3 >=(self.columns):
1520
+ if fake_board[1][self.columns-1][0] == 'a':
1521
+ score += 1000
1522
+ if fake_board[2][self.columns][0] == 'a':
1523
+ score += 1000
1524
+
1525
+ elif r+3 >= (self.rows) and c-3 <= 1:
1526
+ if fake_board[self.rows-1][1][0] == 'a':
1527
+ score += 1000
1528
+ if fake_board[self.rows][2][0] == 'a':
1529
+ score += 1000
1530
+
1531
+ elif r+3 >=(self.rows) and c+3 >=(self.columns):
1532
+ if fake_board[self.rows][self.columns-1][0] == 'a':
1533
+ score += 1000
1534
+ if fake_board[self.rows-1][self.columns][0] == 'a':
1535
+ score += 1000
1536
+
1537
+ score += (attacker*weight_attacker)
1538
+ score -= (defender*weight_defender)
1539
+ score -= (weight_pos*weight_king_pos[r-1][c-1])
1540
+ score += (weight_king_sorrounded *
1541
+ self.king_sorrounded(fake_board, r, c))
1542
+
1543
+ return score
1544
+
1545
+ def fake_move(self, fake_board, commited_move):
1546
+ '''
1547
+ This function performs a fake move - AI's imaginative move in alpha-beta pruning
1548
+ Fake move actually determines the score if this move is executed, it is not any actual move
1549
+
1550
+ Parameters
1551
+ ----------
1552
+ fake_board : this state's board status
1553
+ commited_move : which and where to move - (piece.pid, (row, column))
1554
+
1555
+ Returns
1556
+ -------
1557
+ current_board : board status after commiting that move
1558
+ diff : difference of number of uncaptured pieces on both sides
1559
+
1560
+ '''
1561
+ # fake board = current state fake board, commited move=the move to be executed
1562
+ # (piece, (where to))
1563
+ current_board = []
1564
+ for row in range(len(fake_board)):
1565
+ one_row = []
1566
+ for column in range(len(fake_board[0])):
1567
+ one_row.append(".")
1568
+ current_board.append(one_row)
1569
+
1570
+ for row_index, row in enumerate(fake_board):
1571
+ for col_index, column in enumerate(row):
1572
+ current_board[row_index][col_index] = column
1573
+
1574
+ for row_index, row in enumerate(current_board):
1575
+ f = True
1576
+ for column_index, col in enumerate(row):
1577
+ if(commited_move[0].pid == col):
1578
+ current_board[row_index][column_index] = "."
1579
+ f = False
1580
+ break
1581
+
1582
+ if not f:
1583
+ break
1584
+
1585
+ # row = int((self.rows+1)/2)
1586
+ # column = int((self.columns+1)/2)
1587
+ if current_board[int((self.rows+1)/2)][int((self.columns+1)/2)] == ".":
1588
+ current_board[int((self.rows+1)/2)][int((self.columns+1)/2)] = 'x'
1589
+ current_board[commited_move[1][0]][commited_move[1]
1590
+ [1]] = commited_move[0].pid
1591
+
1592
+ current_board, king_captured = self.fake_capture_check(
1593
+ current_board, commited_move)
1594
+
1595
+ attacker = 0
1596
+ defender = 0
1597
+ for row_index, row in enumerate(current_board):
1598
+ for col_index, col in enumerate(row):
1599
+ if(col[0] == 'a'):
1600
+ attacker += 1
1601
+ elif(col[0] == 'd'):
1602
+ defender += 1
1603
+
1604
+ if current_board[int((self.rows+1)/2)][int((self.columns+1)/2)] == ".":
1605
+ current_board[int((self.rows+1)/2)][int((self.columns+1)/2)] = 'x'
1606
+
1607
+ return current_board, attacker-defender
1608
+
1609
+ def minimax(self, fake_board, alpha, beta, max_depth, turn):
1610
+ '''
1611
+ Implementation of minimax algorithm.
1612
+
1613
+ Parameters
1614
+ ----------
1615
+ fake_board : current fake state's board
1616
+ alpha : integer
1617
+ beta : integer
1618
+ max_depth : number of step to dive into the tree
1619
+ turn : True for attackers, False for defenders
1620
+
1621
+ Returns
1622
+ -------
1623
+ bestvalue: the best value evaluated
1624
+
1625
+ '''
1626
+
1627
+ bestvalue = -10000000000
1628
+ moves = self.find_all_possible_valid_moves(
1629
+ fake_board, turn) # True attacker ,False Defender
1630
+
1631
+ if max_depth <= 0 or self.fake_gameOver(fake_board) == 1 or self.fake_gameOver(fake_board) == 2:
1632
+ return self.evaluate(fake_board)
1633
+
1634
+ # fake board is copied into current board
1635
+ current_board = []
1636
+ for row in range(len(fake_board)):
1637
+ one_row = []
1638
+ for column in range(len(fake_board[0])):
1639
+ one_row.append(".")
1640
+ current_board.append(one_row)
1641
+
1642
+ for row_index, row in enumerate(fake_board):
1643
+ for col_index, column in enumerate(row):
1644
+ current_board[row_index][col_index] = column
1645
+
1646
+ # commit a move from valid moves list -> evaluate -> pick bestvalue -> alpha-beta computing
1647
+ if(turn == True): # attacker maximizer
1648
+ bestvalue = -1000000000000000000
1649
+ for i in moves:
1650
+ tmp_fake_board, diff = self.fake_move(current_board, i)
1651
+ value = self.minimax(tmp_fake_board, alpha,
1652
+ beta, max_depth-1, False) #calling minimax function recursively for next possible moves
1653
+ bestvalue = max(value, bestvalue)
1654
+ alpha = max(alpha, bestvalue)
1655
+ if(beta <= alpha): # alpha beta pruning
1656
+ break
1657
+
1658
+ else: # defender minimizer
1659
+ bestvalue = 1000000000000000000
1660
+ for i in moves:
1661
+ tmp_fake_board, diff = self.fake_move(current_board, i)
1662
+ value = self.minimax(tmp_fake_board, alpha,
1663
+ beta, max_depth-1, True)
1664
+ bestvalue = min(value, bestvalue)
1665
+ beta = min(beta, bestvalue) # alpha beta pruning
1666
+ if(beta <= alpha):
1667
+ break
1668
+
1669
+ return bestvalue
1670
+
1671
+ def strategy(self, current_board):
1672
+ '''
1673
+ Brain of AI...
1674
+
1675
+ Parameters
1676
+ ----------
1677
+ current_board : current state's board
1678
+
1679
+ Returns
1680
+ -------
1681
+ bestmove : best move for this state to be committed by AI
1682
+
1683
+ '''
1684
+ # value to calcaute the move with best minimax value
1685
+ bestvalue = -1000000000000000000
1686
+ max_depth = 3
1687
+ # True attacker,False Defender
1688
+ # moves = (piece_object,(row,col))
1689
+ moves = self.find_all_possible_valid_moves(current_board, True)
1690
+ c = 0
1691
+ diffs = {}
1692
+ for i in moves: # iterate all possible valid moves and their corersponding min max value
1693
+ c += 1
1694
+ fake_board, diff = self.fake_move(current_board, i)
1695
+ value = self.minimax(fake_board, -1000000000000000000,
1696
+ 1000000000000000000, max_depth-1, False)
1697
+ print(value, i[1], diff)
1698
+ if(value > bestvalue):
1699
+ bestmove = i #pick the best move which gives the maximum score for AI
1700
+ bestvalue = value
1701
+ diffs[value] = diff
1702
+
1703
+ elif(value == bestvalue and diff > diffs[value]):
1704
+ bestmove = i
1705
+ bestvalue = value
1706
+ diffs[value] = diff
1707
+
1708
+ if(value == bestvalue and (i[1] == (1, 2) or i[1] == (2, 1) or i[1] == (1, self.columns-1) or i[1] == (2, self.columns) or i[1] == (self.rows-1, 1) or i[1] == (self.rows, 2) or i[1] == (self.rows-1, self.columns) or i[1] == (self.rows, self.columns-1))):
1709
+ bestmove = i
1710
+
1711
+ return bestmove
1712
+
1713
+ def find_best_move(self, current_board):
1714
+ '''
1715
+ Calls algoritm.
1716
+
1717
+ Parameters
1718
+ ----------
1719
+ current_board : current state's board
1720
+
1721
+ Returns
1722
+ -------
1723
+ best_move : best move for this state to be committed by AI
1724
+
1725
+ '''
1726
+
1727
+ best_move = self.strategy(current_board)
1728
+
1729
+ return best_move
1730
+
1731
+ def fake_gameOver(self, fake_board):
1732
+ '''
1733
+ Check AI's minimax tree traversing has reached game over condition or not.
1734
+
1735
+ Parameters
1736
+ ----------
1737
+ fake_board : current fake state's board
1738
+
1739
+ Returns
1740
+ -------
1741
+ int
1742
+ 1 attacker win, 2 defender win, 3 none win
1743
+
1744
+ '''
1745
+ # 1 attacker win,2 defender win,3 none win
1746
+ if self.fake_king_capture_check(fake_board):
1747
+ return 1
1748
+ elif self.fake_king_escape(fake_board) or self.fake_attacker_cnt(fake_board):
1749
+ return 2
1750
+ else:
1751
+ return 3
1752
+
1753
+ def fake_capture_check(self, fake_board_with_border, move):
1754
+ '''
1755
+ This method contains capture related logics at any fake state.
1756
+
1757
+ Parameters
1758
+ ----------
1759
+ fake_board_with_border : current fake state's board
1760
+ move : for which move the capture event might happen
1761
+
1762
+ Returns
1763
+ -------
1764
+ fake_board_with_border : current fake state's board
1765
+ king_captured : whether the king is captured or not - True or False
1766
+
1767
+ '''
1768
+ # storing current piece's type and index
1769
+ ptype, prow, pcol = move[0].pid[0], move[1][0], move[1][1]
1770
+
1771
+ # indices of sorrounding one hop cells and two hops cells.
1772
+ sorroundings = [(prow, pcol+1), (prow, pcol-1),
1773
+ (prow-1, pcol), (prow+1, pcol)]
1774
+ two_hop_away = [(prow, pcol+2), (prow, pcol-2),
1775
+ (prow-2, pcol), (prow+2, pcol)]
1776
+
1777
+ # iterating over each neighbour cells and finding out if the piece of this cell is captured or not
1778
+ for pos, item in enumerate(sorroundings):
1779
+
1780
+ king_captured = False
1781
+ # currently selected cell's piece, if any
1782
+ opp = fake_board_with_border[item[0]][item[1]][0]
1783
+ # if index is 1, which means it's right beside border, which means there's no two-hop cell in thi direction
1784
+ # it may overflow the list index, so it will be set as empty cell instead to avoid error
1785
+ try:
1786
+ opp2 = fake_board_with_border[two_hop_away[pos]
1787
+ [0]][two_hop_away[pos][1]][0]
1788
+ except:
1789
+ opp2 = "."
1790
+
1791
+ # if next cell is empty or has same type of piece or has border, no capturing is possible
1792
+ # if two hop cell is empty, then also no capturing is possible
1793
+ if ptype == opp or ptype == "x" or ptype == "=" or opp == "." or opp2 == ".":
1794
+ continue
1795
+
1796
+ elif opp == "k":
1797
+ # king needs 4 enemies on 4 cardinal points to be captured. so, handled in another function.
1798
+ king_captured = self.fake_king_capture_check(
1799
+ fake_board_with_border)
1800
+
1801
+ elif ptype != opp:
1802
+ # neghbour cell's piece is of different type
1803
+ if ptype == "a" and (ptype == opp2 or opp2 == "x"):
1804
+ # a-d-a or a-d-res_cell situation
1805
+ fake_board_with_border[item[0]][item[1]] = '.'
1806
+
1807
+ elif ptype != "a" and opp2 != "a" and opp2 != "=" and opp == "a":
1808
+ # d-a-d or k-a-d or d-a-k or d-a-res_cell or k-a-res_cell situation
1809
+ fake_board_with_border[item[0]][item[1]] = '.'
1810
+
1811
+ return fake_board_with_border, king_captured
1812
+
1813
+
1814
+ def fake_king_capture_check(self, fake_board_with_border):
1815
+ '''
1816
+ This method contains caturing-king related logics.
1817
+
1818
+ Parameters
1819
+ ----------
1820
+ fake_board_with_border : current fake state's board
1821
+
1822
+ Returns
1823
+ -------
1824
+ bool
1825
+ True if captured, False if not.
1826
+
1827
+ '''
1828
+ # store all four neighbor cells' pieces
1829
+ for row_index, row in enumerate(fake_board_with_border):
1830
+ for col_index, col in enumerate(row):
1831
+ if col == "k":
1832
+ kingr = row_index
1833
+ kingc = col_index
1834
+ break
1835
+
1836
+ front = fake_board_with_border[kingr][kingc+1][0]
1837
+ back = fake_board_with_border[kingr][kingc-1][0]
1838
+ up = fake_board_with_border[kingr-1][kingc][0]
1839
+ down = fake_board_with_border[kingr+1][kingc][0]
1840
+
1841
+ # if all four sides has attackers or a 3-attackers-one-bordercell situation occurs, king is captured
1842
+ # all other possible combos are discarded
1843
+ if front == "x" or back == "x" or up == "x" or down == "x":
1844
+ return False
1845
+
1846
+ elif front == "d" or back == "d" or up == "d" or down == "d":
1847
+ return False
1848
+
1849
+ elif front == "." or back == "." or up == "." or down == ".":
1850
+ return False
1851
+
1852
+ else:
1853
+ return True
1854
+
1855
+ def fake_king_escape(self, fake_board):
1856
+ '''
1857
+ Checks whether king has escaped in this fake state or not.
1858
+
1859
+ Parameters
1860
+ ----------
1861
+ fake_board : current fake state's board
1862
+
1863
+ Returns
1864
+ -------
1865
+ bool
1866
+ True if escaped, False if not.
1867
+
1868
+ '''
1869
+ r = self.manager.board.rows
1870
+ c = self.manager.board.columns
1871
+ if fake_board[1][1] == 'k' or fake_board[1][c] == 'k' or fake_board[r][1] == 'k' or fake_board[r][c] == 'k':
1872
+ return True
1873
+
1874
+ def fake_attacker_cnt(self, fake_board):
1875
+ '''
1876
+ Checks whether all attacekrs are captured in this fake state or not.
1877
+
1878
+ Parameters
1879
+ ----------
1880
+ fake_board : current fake state's board
1881
+
1882
+ Returns
1883
+ -------
1884
+ bool
1885
+ True if all are captured, False if not.
1886
+
1887
+ '''
1888
+
1889
+ for row_index, row in enumerate(fake_board):
1890
+ for col_ind, col in enumerate(row):
1891
+ if col[0] == "a":
1892
+ return False
1893
+ return True
1894
+
1895
+
1896
+ def game_window(screen, mode):
1897
+ '''
1898
+ This handles game.
1899
+
1900
+ Parameters
1901
+ ----------
1902
+ screen : surface
1903
+ on which surface the game will be played.
1904
+ mode : integer
1905
+ 0 means p vs p, 1 means p vs ai.
1906
+
1907
+ Returns
1908
+ -------
1909
+ None.
1910
+
1911
+ '''
1912
+
1913
+ # intializing some needed instances
1914
+ match_specific_global_data()
1915
+ chessboard = ChessBoard(screen)
1916
+ chessboard.draw_empty_board()
1917
+ chessboard.initiate_board_pieces()
1918
+ manager = Game_manager(screen, chessboard, mode)
1919
+ if mode == 1:
1920
+ bot = AI_manager(manager, screen)
1921
+
1922
+ tafle = True
1923
+ game_started = False
1924
+ while tafle:
1925
+ write_text("Play Vikings Chess", screen, (20, 20), (255, 255, 255),
1926
+ pg.font.SysFont("Arial", 40))
1927
+ backbtn = Custom_button(750, 20, "Back", screen,
1928
+ pg.font.SysFont("Arial", 30))
1929
+
1930
+ write_text("Game Settings", screen, (WINDOW_WIDTH - 250, BOARD_TOP), (255, 255, 255),
1931
+ pg.font.SysFont("Arial", 25), False)
1932
+
1933
+ write_text("Board Size:", screen, (WINDOW_WIDTH - 300, BOARD_TOP + SETTINGS_TEXT_GAP_VERTICAL + 10), (255, 255, 255),
1934
+ pg.font.SysFont("Arial", 20), False)
1935
+
1936
+ size9by9btn = Custom_button(WINDOW_WIDTH - 300 + SETTINGS_TEXT_GAP_HORIZONTAL, BOARD_TOP + SETTINGS_TEXT_GAP_VERTICAL, "9x9", screen,
1937
+ pg.font.SysFont("Arial", 20), width=50, height=50)
1938
+
1939
+ size11by11btn = Custom_button(WINDOW_WIDTH - 300 + SETTINGS_TEXT_GAP_HORIZONTAL*1.7, BOARD_TOP + SETTINGS_TEXT_GAP_VERTICAL, "11x11", screen,
1940
+ pg.font.SysFont("Arial", 20), width=50, height=50)
1941
+
1942
+ backbtn = Custom_button(750, 20, "Back", screen,
1943
+ pg.font.SysFont("Arial", 30))
1944
+
1945
+ if game_started:
1946
+ txt = "Restart Game"
1947
+ else:
1948
+ txt = 'New Game'
1949
+
1950
+ newgamebtn = Custom_button(
1951
+ 525, 20, txt, screen, pg.font.SysFont("Arial", 30))
1952
+
1953
+ if backbtn.draw_button():
1954
+ pg.mixer.Sound.play(pg.mixer.Sound(click_snd))
1955
+ main()
1956
+
1957
+ if size9by9btn.draw_button():
1958
+ pg.mixer.Sound.play(pg.mixer.Sound(click_snd))
1959
+ game_started = False
1960
+ match_specific_global_data()
1961
+ chessboard = ChessBoard(screen, "small")
1962
+ chessboard.draw_empty_board()
1963
+ chessboard.initiate_board_pieces()
1964
+ manager = Game_manager(screen, chessboard, mode, "small")
1965
+ if mode == 1:
1966
+ bot = AI_manager(manager, screen)
1967
+
1968
+ if size11by11btn.draw_button():
1969
+ pg.mixer.Sound.play(pg.mixer.Sound(click_snd))
1970
+ game_started = False
1971
+ match_specific_global_data()
1972
+ chessboard = ChessBoard(screen, "large")
1973
+ chessboard.draw_empty_board()
1974
+ chessboard.initiate_board_pieces()
1975
+ manager = Game_manager(screen, chessboard, mode, "large")
1976
+ if mode == 1:
1977
+ bot = AI_manager(manager, screen)
1978
+
1979
+ if newgamebtn.draw_button():
1980
+ last_board = manager.board_size
1981
+ game_started = True
1982
+ match_specific_global_data()
1983
+ chessboard = ChessBoard(screen, last_board)
1984
+ chessboard.draw_empty_board()
1985
+ chessboard.initiate_board_pieces()
1986
+ manager = Game_manager(screen, chessboard, mode, last_board)
1987
+ if mode == 1:
1988
+ bot = AI_manager(manager, screen)
1989
+
1990
+ chessboard.draw_empty_board()
1991
+
1992
+ for event in pg.event.get():
1993
+ if event.type == pg.QUIT:
1994
+ pg.quit()
1995
+ if event.type == pg.KEYDOWN:
1996
+ if event.key == pg.K_ESCAPE:
1997
+ tafle = False
1998
+ if event.type == pg.MOUSEBUTTONDOWN and event.button == 1:
1999
+ msx, msy = pg.mouse.get_pos()
2000
+ if not manager.finish:
2001
+ if mode == 0:
2002
+ manager.mouse_click_analyzer(msx, msy)
2003
+ else:
2004
+ if manager.turn == False:
2005
+ manager.mouse_click_analyzer(msx, msy)
2006
+ chessboard.draw_empty_board()
2007
+ for piece in All_pieces:
2008
+ piece.draw_piece(screen)
2009
+ if manager.finish:
2010
+ manager.match_finished()
2011
+ else:
2012
+ manager.turn_msg(game_started)
2013
+ if manager.last_move is not None:
2014
+ pg.draw.circle(screen, red, (BOARD_LEFT+(manager.last_move[0][1]*CELL_WIDTH)+(
2015
+ CELL_WIDTH/2), BOARD_TOP+(manager.last_move[0][0]*CELL_HEIGHT)+(CELL_HEIGHT/2)), 5)
2016
+ pg.draw.circle(screen, white, (BOARD_LEFT+(manager.last_move[1][1]*CELL_WIDTH)+(
2017
+ CELL_WIDTH/2), BOARD_TOP+(manager.last_move[1][0]*CELL_HEIGHT)+(CELL_HEIGHT/2)), 5)
2018
+ pg.display.update()
2019
+
2020
+ if game_started and mode == 1 and manager.turn and not manager.finish:
2021
+
2022
+ chessboard.draw_empty_board()
2023
+ for piece in All_pieces:
2024
+ piece.draw_piece(screen)
2025
+ if manager.finish:
2026
+ manager.match_finished()
2027
+ else:
2028
+ manager.turn_msg(game_started)
2029
+ if manager.last_move is not None:
2030
+ pg.draw.circle(screen, red, (BOARD_LEFT+(manager.last_move[0][1]*CELL_WIDTH)+(CELL_WIDTH/2), BOARD_TOP+(
2031
+ manager.last_move[0][0]*CELL_HEIGHT)+(CELL_HEIGHT/2)), 5)
2032
+ pg.draw.circle(screen, white, (BOARD_LEFT+(manager.last_move[1][1]*CELL_WIDTH)+(CELL_WIDTH/2), BOARD_TOP+(
2033
+ manager.last_move[1][0]*CELL_HEIGHT)+(CELL_HEIGHT/2)), 5)
2034
+ pg.display.update()
2035
+ print("c")
2036
+ bot.move()
2037
+ for piece in All_pieces:
2038
+ piece.draw_piece(screen)
2039
+
2040
+ manager.show_valid_moves()
2041
+ if manager.finish:
2042
+ manager.match_finished()
2043
+ else:
2044
+ manager.turn_msg(game_started)
2045
+
2046
+ if manager.last_move is not None:
2047
+ pg.draw.circle(screen, red, (BOARD_LEFT+(manager.last_move[0][1]*CELL_WIDTH)+(CELL_WIDTH/2), BOARD_TOP+(
2048
+ manager.last_move[0][0]*CELL_HEIGHT)+(CELL_HEIGHT/2)), 5)
2049
+ pg.draw.circle(screen, white, (BOARD_LEFT+(manager.last_move[1][1]*CELL_WIDTH)+(CELL_WIDTH/2), BOARD_TOP+(
2050
+ manager.last_move[1][0]*CELL_HEIGHT)+(CELL_HEIGHT/2)), 5)
2051
+ pg.display.update()
2052
+
2053
+
2054
+ def rules(screen):
2055
+ tafle = True
2056
+ while tafle:
2057
+ write_text("Rules of Viking Chess", screen, (20, 20), (255, 255, 255),
2058
+ pg.font.SysFont("Arial", 40))
2059
+ backbtn = Custom_button(750, 20, "Back", screen,
2060
+ pg.font.SysFont("Arial", 30))
2061
+
2062
+ if backbtn.draw_button():
2063
+ pg.mixer.Sound.play(pg.mixer.Sound(click_snd))
2064
+ main()
2065
+
2066
+ msgs = []
2067
+ msgs.append("> Turn based board game.")
2068
+ # msgs.append("> Two board sizes: 'large' - 11x11 and 'small' - 9x9.")
2069
+ msgs.append("> Center cell and four corner cells are called restricted cells.")
2070
+ msgs.append("> Excluding king, a-d count is 24-12 on large board and 16-8 on small board.")
2071
+ msgs.append("> All pieces except king can move any number of cells horizontally or vertically.")
2072
+ msgs.append("> King can move only one cell at a time.")
2073
+ msgs.append("> Only king can move to any of the restricted cells.")
2074
+ msgs.append("> Pieces, except king, can be captured by sandwitching them from both sides.")
2075
+ msgs.append("> Restricted cells can be used to sandwitch opponent.")
2076
+ msgs.append("> Only one opponent piece can be captured in single line with single move.")
2077
+ msgs.append("> Multiple pieces can be captured with a single move on cardinal points.")
2078
+ msgs.append("> To capture king, attackers need to sorround him on all four cardinal points.")
2079
+ msgs.append("> If king is captured, attackers win.")
2080
+ msgs.append("> If king escapes to any of the four corner cells, defenders win.")
2081
+ msgs.append("> If all attackers are captured, defenders win.")
2082
+
2083
+ consolas = pg.font.SysFont("consolas", 20)
2084
+ cnt = 0
2085
+ for msg in msgs:
2086
+ write_text(msg, screen, (20, BOARD_TOP - 80 + 40*cnt), white, consolas, False)
2087
+ cnt += 1
2088
+
2089
+ for event in pg.event.get():
2090
+ if event.type == pg.QUIT:
2091
+ pg.quit()
2092
+ if event.type == pg.KEYDOWN:
2093
+ if event.key == pg.K_ESCAPE:
2094
+ tafle = False
2095
+ pg.display.update()
2096
+
2097
+
2098
+ def history(screen):
2099
+ tafle = True
2100
+ while tafle:
2101
+ write_text("History", screen, (20, 20), (255, 255, 255),
2102
+ pg.font.SysFont("Arial", 40))
2103
+ backbtn = Custom_button(750, 20, "Back", screen,
2104
+ pg.font.SysFont("Arial", 30))
2105
+
2106
+ if backbtn.draw_button():
2107
+ pg.mixer.Sound.play(pg.mixer.Sound(click_snd))
2108
+ main()
2109
+
2110
+ msgs = []
2111
+ msgs.append("> Originated in Scandinavia.")
2112
+ msgs.append("> Developed from a Roman game called Ludus Latrunculorum.")
2113
+ msgs.append("> This game flourished until the arrival of chess.")
2114
+ msgs.append("> This game was revived back in nineteenth century.")
2115
+
2116
+
2117
+ consolas = pg.font.SysFont("consolas", 20)
2118
+ cnt = 0
2119
+ for msg in msgs:
2120
+ write_text(msg, screen, (20, BOARD_TOP - 80 + 40*cnt), white, consolas, False)
2121
+ cnt += 1
2122
+
2123
+ for event in pg.event.get():
2124
+ if event.type == pg.QUIT:
2125
+ pg.quit()
2126
+ if event.type == pg.KEYDOWN:
2127
+ if event.key == pg.K_ESCAPE:
2128
+ tafle = False
2129
+ pg.display.update()
2130
+
2131
+
2132
+ def main():
2133
+ pg.init()
2134
+ screen = pg.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
2135
+ pg.display.set_caption(GAME_NAME)
2136
+ pg.display.set_icon(GAME_ICON)
2137
+
2138
+ icon_rect = GAME_ICON_resized.get_rect(
2139
+ center=(500, MAIN_MENU_TOP_BUTTON_y-150))
2140
+
2141
+ game_on = True
2142
+
2143
+ while game_on:
2144
+ for event in pg.event.get():
2145
+ if event.type == pg.QUIT:
2146
+ game_on = False
2147
+ pg.quit()
2148
+
2149
+ screen.fill(bg2)
2150
+ write_text("Welcome To Vikings Chess!", screen, (250, 20),
2151
+ (255, 255, 255), pg.font.SysFont("Arial", 50))
2152
+
2153
+ btn_font = pg.font.SysFont("Arial", 28)
2154
+ gamebtn_1 = Custom_button(
2155
+ MAIN_MENU_TOP_BUTTON_x - 110, MAIN_MENU_TOP_BUTTON_y, "Play vs Human", screen, btn_font)
2156
+ gamebtn_2 = Custom_button(
2157
+ MAIN_MENU_TOP_BUTTON_x + 110, MAIN_MENU_TOP_BUTTON_y, "Play vs AI", screen, btn_font)
2158
+ rulesbtn = Custom_button(
2159
+ MAIN_MENU_TOP_BUTTON_x, MAIN_MENU_TOP_BUTTON_y + 100, "Rules", screen, btn_font)
2160
+ # historybtn = Custom_button(
2161
+ # MAIN_MENU_TOP_BUTTON_x, MAIN_MENU_TOP_BUTTON_y + 200, "History", screen, btn_font)
2162
+ exitbtn = Custom_button(
2163
+ MAIN_MENU_TOP_BUTTON_x, MAIN_MENU_TOP_BUTTON_y + 200, "Exit", screen, btn_font)
2164
+
2165
+ if gamebtn_1.draw_button():
2166
+ pg.mixer.Sound.play(pg.mixer.Sound(click_snd))
2167
+ game_window(screen, mode=0)
2168
+
2169
+ if gamebtn_2.draw_button():
2170
+ pg.mixer.Sound.play(pg.mixer.Sound(click_snd))
2171
+ game_window(screen, mode=1)
2172
+
2173
+ if rulesbtn.draw_button():
2174
+ pg.mixer.Sound.play(pg.mixer.Sound(click_snd))
2175
+ rules(screen)
2176
+
2177
+ # if historybtn.draw_button():
2178
+ # pg.mixer.Sound.play(pg.mixer.Sound(click_snd))
2179
+ # history(screen)
2180
+
2181
+ if exitbtn.draw_button():
2182
+ pg.mixer.Sound.play(pg.mixer.Sound(click_snd))
2183
+ game_on = False
2184
+ pg.quit()
2185
+
2186
+ screen.blit(GAME_ICON_resized, (icon_rect))
2187
+ pg.display.update()
2188
+
2189
+
2190
+ if __name__ == "__main__":
2191
+ main()
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ pygame==2.5.0
2
+ pygbag==0.6.0