Upload 5 files
Browse files- .gitignore +4 -0
- LICENSE +21 -0
- README.md +32 -12
- game_arena.py +2191 -0
- 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 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+

|
| 28 |
+
|
| 29 |
+

|
| 30 |
+
|
| 31 |
+

|
| 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
|