Created base game with working minimax algorithm, now working on reinforcement learning

This commit is contained in:
Rohit Pai 2023-07-28 19:34:53 +01:00
commit 1eb0a04f30
24 changed files with 916 additions and 0 deletions

162
.gitignore vendored Normal file
View File

@ -0,0 +1,162 @@
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

6
.idea/GitLink.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="uk.co.ben_gibson.git.link.SettingsState">
<option name="host" value="e0f86390-1091-4871-8aeb-f534fbc99cf0" />
</component>
</project>

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

10
.idea/draughts.iml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.11 (draughts)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,12 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyStubPackagesAdvertiser" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<list>
<option value="pyspark-stubs==3.0.0.post3" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

4
.idea/misc.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (draughts)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/draughts.iml" filepath="$PROJECT_DIR$/.idea/draughts.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

BIN
Report.pdf Normal file

Binary file not shown.

0
__init__.py Normal file
View File

213
main.py Normal file
View File

@ -0,0 +1,213 @@
import sys
import pygame
from utilities.constants import WIDTH, HEIGHT, SQUARE_SIZE, WHITE, GREEN
from utilities.gameManager import GameManager
from minimax.minimaxAlgo import MiniMax
FPS = 60
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Draughts")
def getRowColFromMouse(pos):
x, y = pos
row = y // SQUARE_SIZE
col = x // SQUARE_SIZE
return row, col
def drawText(text, font, color, surface, x, y):
textobj = font.render(text, 1, color)
textrect = textobj.get_rect()
textrect.topleft = (x, y)
surface.blit(textobj, textrect)
def drawMultiLineText(surface, text, pos, font, color=pygame.Color('black')):
words = [word.split(' ') for word in text.splitlines()] # 2D array where each row is a list of words.
space = font.size(' ')[0] # The width of a space.
max_width, max_height = surface.get_size()
x, y = pos
word_height = None
for line in words:
for word in line:
word_surface = font.render(word, 0, color)
word_width, word_height = word_surface.get_size()
if x + word_width >= max_width:
x = pos[0] # Reset the x.
y += word_height # Start on new row.
surface.blit(word_surface, (x, y))
x += word_width + space
x = pos[0] # Reset the x.
y += word_height # Start on new row.
def main():
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
menuClock = pygame.time.Clock()
click = False
width = screen.get_width()
font = pygame.font.SysFont(None, 25)
difficulty = 0
while True:
# menu
screen.fill((128, 128, 128))
drawText('Main Menu', font, (255, 255, 255), screen, width / 2, 20)
mx, my = pygame.mouse.get_pos()
easy = pygame.Rect(width / 2 - 50, 100, 200, 50)
pygame.draw.rect(screen, (0, 255, 0), easy)
drawText("easy", font, (255, 255, 255), screen, width / 2, 100)
medium = pygame.Rect(width / 2 - 50, 200, 200, 50)
pygame.draw.rect(screen, (255, 125, 0), medium)
drawText("medium", font, (255, 255, 255), screen, width / 2, 200)
hard = pygame.Rect(width / 2 - 50, 300, 200, 50)
pygame.draw.rect(screen, (255, 0, 0), hard)
drawText("hard", font, (255, 255, 255), screen, width / 2, 300)
rules = pygame.Rect(width / 2 - 50, 400, 200, 50)
pygame.draw.rect(screen, (0, 0, 255), rules)
drawText("rules", font, (255, 255, 255), screen, width / 2, 400)
quitGame = pygame.Rect(width / 2 - 50, 500, 200, 50)
pygame.draw.rect(screen, (0, 0, 0), quitGame)
drawText("quit", font, (255, 255, 255), screen, width / 2, 500)
if easy.collidepoint((mx, my)):
if click:
difficulty = 1
break
if medium.collidepoint((mx, my)):
if click:
difficulty = 3
break
if hard.collidepoint((mx, my)):
if click:
difficulty = 5
break
if rules.collidepoint((mx, my)):
if click:
rulesGUI()
break
if quitGame.collidepoint((mx, my)):
if click:
pygame.quit()
sys.exit()
click = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
click = True
pygame.display.update()
menuClock.tick(60)
if difficulty != 0:
game(difficulty)
def rulesGUI():
screen = pygame.display.set_mode((WIDTH, HEIGHT))
menuClock = pygame.time.Clock()
click = False
width = screen.get_width()
titleFont = pygame.font.SysFont(None, 48)
font = pygame.font.SysFont(None, 21)
while True:
screen.fill((128, 128, 128))
drawText("Rules", titleFont, (255, 255, 255), screen, width / 2, 20)
mx, my = pygame.mouse.get_pos()
drawMultiLineText(screen, """Both the player and AI start with 12 pieces on the dark squares of the three rows closest to that
player's side. The row closest to each player is called the kings row or crownhead. The player moves first.
Then turns alternate.
\n
Move rules
\n
There are two different ways to move in utilities:
\n
Simple move: A simple move consists of moving a piece one square diagonally to an adjacent unoccupied dark square.
Uncrowned pieces can move diagonally forward only; kings can move in any diagonal direction. Jump: A jump consists of
moving a piece that is diagonally adjacent an opponent's piece, to an empty square immediately beyond it in the same
direction (thus "jumping over" the opponent's piece front and back ). Pieces can jump diagonally forward only; kings
can jump in any diagonal direction. A jumped piece is considered "captured" and removed from the game. Any piece,
king or piece, can jump a king.
\n
Forced capture, is always mandatory: if a player has the option to jump, he/she must take it, even if doing so
results in disadvantage for the jumping player. For example, a piecedated single jump might set up the player such
that the opponent has a multi-jump in reply.
\n
Multiple jumps are possible, if after one jump, another piece is immediately eligible to be jumped by the moved
pieceeven if that jump is in a different diagonal direction. If more than one multi-jump is available, the player
can choose which piece to jump with, and which sequence of jumps to make. The sequence chosen is not required to be
the one that maximizes the number of jumps in the turn; however, a player must make all available jumps in the
sequence chosen. Kings If a piece moves into the kings row on the opponent's side of the board, it is crowned as a
king and gains the ability to move both forward and backward. If a piece moves into the kings row or if it jumps into
the kings row, the current move terminates; the piece is crowned as a king but cannot jump back out as in a
multi-jump until the next move.""", (50, 50), font)
back = pygame.Rect(width / 2 - 50, 700, 200, 50)
pygame.draw.rect(screen, (0, 0, 0), back)
drawText("back", font, (255, 255, 255), screen, width / 2, 700)
if back.collidepoint((mx, my)):
if click:
main()
break
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
click = True
pygame.display.update()
menuClock.tick(60)
def game(difficulty):
run = True
clock = pygame.time.Clock()
gameManager = GameManager(WIN, GREEN)
while run:
clock.tick(FPS)
if gameManager.turn == WHITE:
mm = MiniMax()
value, newBoard = mm.AI(gameManager.getBoard(), difficulty, WHITE, gameManager)
gameManager.aiMove(newBoard)
# time.sleep(0.15)
if gameManager.turn == GREEN:
mm = MiniMax()
value, newBoard = mm.AI(gameManager.getBoard(), difficulty, GREEN, gameManager)
gameManager.aiMove(newBoard)
# time.sleep(0.15)
if gameManager.winner() != None:
print(gameManager.winner())
run = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN:
pos = pygame.mouse.get_pos()
row, col = getRowColFromMouse(pos)
# if gameManager.turn == GREEN:
gameManager.select(row, col)
gameManager.update()
pygame.display.update()
# pygame.quit()
main()

0
minimax/__init__.py Normal file
View File

56
minimax/minimaxAlgo.py Normal file
View File

@ -0,0 +1,56 @@
import random
from copy import deepcopy
from math import inf
from utilities.constants import GREEN, WHITE
class MiniMax():
def AI(self, board, depth, maxPlayer, gameManager):
if depth == 0 or board.winner() is not None:
return board.scoreOfTheBoard(), board
if maxPlayer:
maxEval = -inf
bestMove = None
for move in self.getAllMoves(board, maxPlayer):
evaluation = self.AI(move, depth - 1, False, gameManager)[0]
maxEval = max(maxEval, evaluation)
if maxEval > evaluation:
bestMove = move
if maxEval == evaluation:
bestMove = bestMove if random.choice([True, False]) else move
return maxEval, bestMove
else:
minEval = inf
bestMove = None
colour = WHITE if gameManager.turn == GREEN else GREEN
for move in self.getAllMoves(board, colour):
evaluation = self.AI(move, depth - 1, True, gameManager)[0]
minEval = min(minEval, evaluation)
if minEval < evaluation:
bestMove = move
if minEval == evaluation:
bestMove = bestMove if random.choice([True, False]) else move
return minEval, bestMove
def _simulateMove(self, piece, move, board, skip):
board.move(piece, move[0], move[1])
if skip:
board.remove(skip)
return board
def getAllMoves(self, board, colour):
moves = []
for piece in board.getAllPieces(colour):
validMoves = board.getValidMoves(piece)
for move, skip in validMoves.items():
tempBoard = deepcopy(board)
tempPiece = tempBoard.getPiece(piece.row, piece.col)
newBoard = self._simulateMove(tempPiece, move, tempBoard, skip)
moves.append(newBoard)
return moves

4
readme.txt Normal file
View File

@ -0,0 +1,4 @@
To run the project
open project folder in terminal or cmd
run pip install pygame or pip3 install pygame if running a mac or linux machine run with sudo for best chance of running the project
then run python main.py

View File

@ -0,0 +1,96 @@
import random
from collections import deque
import numpy as np
import tensorflow as tf
from tensorflow.python.keras import Sequential, regularizers
from tensorflow.python.keras.layers import Dense
class ReinforcementLearning():
def __init__(self, action_space, state_space, env):
self.action_space = action_space
self.state_space = state_space
self.env = env
self.epsilon = 1
self.gamma = .95
self.batch_size = 64
self.epsilon_min = .01
self.epsilon_decay = .995
self.learning_rate = 0.001
self.memory = deque(maxlen=100000)
self.model = self._buildModel()
def AI(self, episode):
loss = []
max_steps = 1000
for e in range(episode):
state = self.env.reset()
state = np.reshape(state, (1, self.state_space))
score = 0
for i in range(max_steps):
action = self.act(state)
reward, next_state, done = self.env.step(action)
score += reward
next_state = np.reshape(next_state, (1, self.state_space))
self.remember(state, action, reward, next_state, done)
state = next_state
self.replay()
if done:
print("episode: {}/{}, score: {}".format(e, episode, score))
break
loss.append(score)
def _buildModel(self):
# Board model
board_model = Sequential()
# input dimensions is 32 board position values
board_model.add(Dense(64, activation='relu', input_dim=32))
# use regularizers, to prevent fitting noisy labels
board_model.add(Dense(32, activation='relu', kernel_regularizer=regularizers.l2(0.01)))
board_model.add(Dense(16, activation='relu', kernel_regularizer=regularizers.l2(0.01))) # 16
board_model.add(Dense(8, activation='relu', kernel_regularizer=regularizers.l2(0.01))) # 8
# output isn't squashed, because it might lose information
board_model.add(Dense(1, activation='linear', kernel_regularizer=regularizers.l2(0.01)))
board_model.compile(optimizer='nadam', loss='binary_crossentropy')
return board_model
def remember(self, state, action, reward, next_state, done):
self.memory.append((state, action, reward, next_state, done))
def replay(self):
if len(self.memory) < self.batch_size:
return
minibatch = random.sample(self.memory, self.batch_size)
states = np.array([i[0] for i in minibatch])
actions = np.array([i[1] for i in minibatch])
rewards = np.array([i[2] for i in minibatch])
next_states = np.array([i[3] for i in minibatch])
dones = np.array([i[4] for i in minibatch])
states = np.squeeze(states)
next_states = np.squeeze(next_states)
targets = rewards + self.gamma * (np.amax(self.model.predict_on_batch(next_states), axis=1)) * (1 - dones)
targets_full = self.model.predict_on_batch(states)
ind = np.array([i for i in range(self.batch_size)])
targets_full[[ind], [actions]] = targets
self.model.fit(states, targets_full, epochs=1, verbose=0)
if self.epsilon > self.epsilon_min:
self.epsilon *= self.epsilon_decay
def act(self, state):
if np.random.rand() <= self.epsilon:
return random.randrange(self.action_space)
act_values = self.model.predict(state)
return np.argmax(act_values[0])

View File

0
utilities/__init__.py Normal file
View File

BIN
utilities/assets/crown.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

185
utilities/board.py Normal file
View File

@ -0,0 +1,185 @@
import pygame
from .constants import BLACK, ROWS, GREEN, SQUARE_SIZE, COLS, WHITE
from .piece import Piece
class Board:
def __init__(self):
self.board = []
self.greenLeft = self.whiteLeft = 12
self.greenKings = self.whiteKings = 0
self.createBoard()
def drawSquares(self, win):
win.fill(BLACK)
for row in range(ROWS):
for col in range(row % 2, ROWS, 2):
pygame.draw.rect(win, GREEN, (row * SQUARE_SIZE, col * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE))
def createBoard(self):
for row in range(ROWS):
self.board.append([])
for col in range(COLS):
if col % 2 == ((row + 1) % 2):
if row < 3:
self.board[row].append(Piece(row, col, WHITE))
elif row > 4:
self.board[row].append(Piece(row, col, GREEN))
else:
self.board[row].append(None)
else:
self.board[row].append(None)
def draw(self, win):
self.drawSquares(win)
for row in range(ROWS):
for col in range(COLS):
piece = self.board[row][col]
if piece is not None:
piece.draw(win)
def move(self, piece, row, col):
self.board[piece.row][piece.col], self.board[row][col] = self.board[row][col], self.board[piece.row][piece.col]
piece.move(row, col)
if row == ROWS - 1 or row == 0:
piece.makeKing()
if piece.colour == WHITE:
self.whiteKings += 1
else:
self.greenKings += 1
def remove(self, skipped):
for piece in skipped:
self.board[piece.row][piece.col] = None
if piece is not None:
if piece.colour == GREEN:
self.greenLeft -= 1
else:
self.whiteLeft -= 1
def getPiece(self, row, col):
return self.board[row][col]
def winner(self):
if self.greenLeft <= 0:
return WHITE
elif self.whiteLeft <= 0:
return GREEN
return None
def getValidMoves(self, piece):
moves = {}
forcedCapture = {}
left = piece.col - 1
right = piece.col + 1
row = piece.row
if piece.colour == GREEN:
moves.update(self._traverseLeft(row - 1, max(row - 3, -1), -1, piece.colour, left))
moves.update(self._traverseRight(row - 1, max(row - 3, -1), -1, piece.colour, right))
if piece.colour == WHITE:
moves.update(self._traverseLeft(row + 1, min(row + 3, ROWS), 1, piece.colour, left))
moves.update(self._traverseRight(row + 1, min(row + 3, ROWS), 1, piece.colour, right))
if piece.king:
moves.update(self._traverseLeft(row - 1, max(row - 3, -1), -1, piece.colour, left))
moves.update(self._traverseRight(row - 1, max(row - 3, -1), -1, piece.colour, right))
moves.update(self._traverseLeft(row + 1, min(row + 3, ROWS), 1, piece.colour, left))
moves.update(self._traverseRight(row + 1, min(row + 3, ROWS), 1, piece.colour, right))
if len(moves.values()) <= 1:
return moves
movesValues = list(moves.values())
movesKeys = list(moves.keys())
forced = {}
for i in range(len(movesKeys)):
if not movesValues[i]:
forced[movesKeys[i]] = moves[movesKeys[i]]
if len(forced) != len(moves):
forced.clear()
for i in range(len(movesKeys)):
if movesValues[i]:
forced[movesKeys[i]] = moves[movesKeys[i]]
if len(forced) != len(moves):
for i in range(len(movesKeys)):
if movesValues[i]:
forcedCapture[movesKeys[i]] = moves[movesKeys[i]]
else:
forcedCapture = forced
else:
forcedCapture = forced
return forcedCapture
def scoreOfTheBoard(self):
return self.whiteLeft - self.greenLeft
def getAllPieces(self, colour):
pieces = []
for row in self.board:
for piece in row:
if piece is not None and piece.colour == colour:
pieces.append(piece)
return pieces
def _traverseLeft(self, start, stop, step, colour, left, skipped=[]):
moves = {}
last = []
for row in range(start, stop, step):
if left < 0:
break
mvs = self._traverse(row, left, skipped, moves, step, last, colour)
if mvs is None:
break
elif isinstance(mvs, list):
last = mvs
else:
moves.update(mvs)
left -= 1
return moves
def _traverseRight(self, start, stop, step, colour, right, skipped=[]):
moves = {}
last = []
for row in range(start, stop, step):
if right >= COLS:
break
mvs = self._traverse(row, right, skipped, moves, step, last, colour)
if mvs is None:
break
elif isinstance(mvs, list):
last = mvs
else:
moves.update(mvs)
right += 1
return moves
def _traverse(self, row, col, skipped, moves, step, last, colour):
current = self.board[row][col]
if current is None:
if skipped and not last:
return None
elif skipped:
moves[(row, col)] = last + skipped
else:
moves[(row, col)] = last
if last:
if step == -1:
rowCalc = max(row - 3, 0)
else:
rowCalc = min(row + 3, ROWS)
moves.update(self._traverseLeft(row + step, rowCalc, step, colour, col - 1, skipped=last))
moves.update(self._traverseRight(row + step, rowCalc, step, colour, col + 1, skipped=last))
return None
elif current.colour == colour:
return None
else:
last = [current]
return last

15
utilities/constants.py Normal file
View File

@ -0,0 +1,15 @@
import pygame
WIDTH, HEIGHT = 800, 800
ROWS, COLS = 8, 8
SQUARE_SIZE = WIDTH // COLS
# RGB color
GREEN = (144, 184, 59)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
GREY = (128, 128, 128)
CROWN = pygame.transform.scale(pygame.image.load("./utilities/assets/crown.png"), (45, 25))

82
utilities/gameManager.py Normal file
View File

@ -0,0 +1,82 @@
import pygame
from utilities.board import Board
from utilities.constants import GREEN, WHITE, BLUE, SQUARE_SIZE
class GameManager:
def __init__(self, win, colour):
self._init(colour)
self.win = win
def _init(self, colour):
self.selected = None
self.board = Board()
self.turn = colour
self.validMoves = {}
self.legCount = 0
def update(self):
self.board.draw(self.win)
self.drawValidMoves(self.validMoves)
pygame.display.update()
def reset(self):
self._init(self.turn)
def select(self, row, col):
if self.selected:
result = self._move(row, col)
if not result:
self.selected = None
self.select(row, col)
piece = self.board.getPiece(row, col)
if piece is not None and piece.colour == self.turn:
self.selected = piece
self.validMoves = self.board.getValidMoves(piece)
return True
def _move(self, row, col):
piece = self.board.getPiece(row, col)
if self.selected and piece is None and (row, col) in self.validMoves:
self.board.move(self.selected, row, col)
skipped = self.validMoves[row, col]
if self.validMoves[list(self.validMoves.keys())[0]]:
if self.validMoves[list(self.validMoves.keys())[0]][0].king:
self.selected.makeKing()
if skipped:
self.board.remove(skipped)
if len(self.validMoves) > 1:
del self.validMoves[list(self.validMoves.keys())[0]]
else:
self.changeTurn()
else:
self.changeTurn()
else:
return False
return True
def changeTurn(self):
self.validMoves = {}
if self.turn == GREEN:
self.turn = WHITE
else:
self.turn = GREEN
def drawValidMoves(self, moves):
for row, col in moves:
pygame.draw.circle(self.win, BLUE,
(col * SQUARE_SIZE + SQUARE_SIZE // 2, row * SQUARE_SIZE + SQUARE_SIZE // 2), 15)
def winner(self):
return self.board.winner()
def getBoard(self):
return self.board
def aiMove(self, board):
if board is None:
# colour = "green" if self.turn == GREEN else "white"
# print("no move left for " + colour + " to make")
self.changeTurn()
return
self.board = board
self.changeTurn()

38
utilities/piece.py Normal file
View File

@ -0,0 +1,38 @@
import pygame.draw
from utilities.constants import SQUARE_SIZE, GREY, CROWN
class Piece:
def __init__(self, row, col, colour):
self.row = row
self.col = col
self.colour = colour
self.king = False
self.x = 0
self.y = 0
self.calcPosition()
self.padding = 20
self.border = 2
def calcPosition(self):
self.x = SQUARE_SIZE * self.col + SQUARE_SIZE // 2
self.y = SQUARE_SIZE * self.row + SQUARE_SIZE // 2
def makeKing(self):
self.king = True
def draw(self, win):
radius = SQUARE_SIZE // 2 - self.padding
pygame.draw.circle(win, GREY, (self.x, self.y), radius + self.border)
pygame.draw.circle(win, self.colour, (self.x, self.y), radius)
if self.king:
win.blit(CROWN, (self.x - CROWN.get_width() // 2, self.y - CROWN.get_height() // 2))
def move(self, row, col):
self.row = row
self.col = col
self.calcPosition()
def __repr__(self):
return str(self.colour)