diff --git a/Controller.py b/Controller.py index 8c28cc9..cb140c9 100644 --- a/Controller.py +++ b/Controller.py @@ -57,7 +57,7 @@ def isCheck(end, piece, board, opposite, canPassant, text, computer): moveList = computeAll(-piece, board, 0, opposite, canPassant, computer) if kingCoord(-piece, board) in moveList: inCheck = " White king in check" if -piece < 0 else " Black king in check" - addText(text, inCheck, 0, 0) + addText(text, inCheck, 0, 0, 0) # computes the board position of the piece @@ -80,14 +80,16 @@ def kingCoord(piece, board): # determine if move is legal during check or if it prevents check def checkMove(piece, newY, newX, oldY, oldX, board, canPassant, computer): - tempBoard = [row[:] for row in board] - tempBoard[oldY][oldX] = 0 - tempBoard[newY][newX] = piece - moveList = computeAll(piece, tempBoard, 0, 0, canPassant, computer) + newPiece = board[newY][newX] + board[oldY][oldX] = 0 + board[newY][newX] = piece + moveList = computeAll(piece, board, 0, 0, canPassant, computer) - if (kingCoord(piece, tempBoard) in moveList): + if (kingCoord(piece, board) in moveList): + undo(board, piece, newPiece, newY, newX, oldY, oldX) return False else: + undo(board, piece, newPiece, newY, newX, oldY, oldX) return True @@ -231,7 +233,7 @@ def enPassantCapture(piece, board, newY, newX, oldY, oldX, isPawn, canPassant, t if isPawn and coord in canPassant and piece in {-1, 11, -11, 1} and spaceCheck(piece, board, newY, newX) and newCoord == canPassant[0]: board[newY + add][newX] = 0 if not noPrint: - addText(text, PIECE[piece] + " en passant capture", 0, 0) + addText(text, PIECE[piece] + " en passant capture", 0, 0, 0) # determines if en passant is a possible move @@ -387,14 +389,14 @@ def castle(piece, board, oldY, oldX, pSize, size, moveList, text, count): firstMove(piece, board, oldY, kingX) firstMove(rook, board, oldY, rookX) board[mY][tempX] = 0 - count = addText(text, str(PIECE[piece]) + str(side), count, 0) + count = addText(text, str(PIECE[piece]) + str(side), count, 0, 0, 0) return True, count return False, count # evaluates if a button is pressed # also dictates pawn promotion behaviour -def button(selection, info, promotedPiece, board, text, count, length): +def button(selection, info, promotedPiece, board, text, count, length, font): pressed = pygame.mouse.get_pos()[0] in range(info[0], info[2] + info[0]) and pygame.mouse.get_pos()[1] in range(info[1], info[3] + info[1]) pawn = "Black" @@ -408,8 +410,8 @@ def button(selection, info, promotedPiece, board, text, count, length): newPiece = -newPiece pawn = "White" board[y][x] = newPiece - addText(text, pawn + " pawn promoted to ", 0, 0) - addText(text, " " + str(PIECE[newPiece]), 0, length) + addText(text, pawn + " pawn promoted to", 0, 0, font) + addText(text, " " + str(PIECE[newPiece]), 0, length, font) if count == 0: return pressed @@ -421,29 +423,35 @@ def button(selection, info, promotedPiece, board, text, count, length): def checkmate(king, board, canPassant, opposite, computer): canMove = False opposite = 1 if (opposite == 0 and computer == None) else 0 - moveList = specificCompute(-king, board, canPassant, computer, opposite)[0] - tempBoard = [row[:] for row in board] # goes through each list in moveList for i, _ in enumerate(moveList): n = moveList[i][0] oldY, oldX = moveList[i][1] - tempBoard = [row[:] for row in board] - tempBoard[oldY][oldX] = 0 + board[oldY][oldX] = 0 # goes through each sublist for (newY, newX) in moveList[i][2]: - movedBoard = [row[:] for row in tempBoard] - if spaceCheck(n, movedBoard, newY, newX): - movedBoard[newY][newX] = n - tempMoveList = computeAll(king, movedBoard, 0, opposite, canPassant, computer) - if kingCoord(king, movedBoard) not in tempMoveList: # king not in check after move + if spaceCheck(n, board, newY, newX): + newPiece = board[newY][newX] + board[newY][newX] = n + tempMoveList = computeAll(king, board, 0, opposite, canPassant, computer) + if kingCoord(king, board) not in tempMoveList: # king not in check after move canMove = True + undo(board, 0, newPiece, newY, newX, oldY, oldX) break + undo(board, 0, newPiece, newY, newX, oldY, oldX) + board[oldY][oldX] = n return not canMove +# undos the move to prevent deep copying the list +def undo(board, oldPiece, newPiece, newY, newX, oldY, oldX): + board[newY][newX] = newPiece + board[oldY][oldX] = oldPiece + + # determines if the game has ended through checkmate or stalemate def gameEnd(board, turn, pieceMoving, start, outline, canPassant, opposite, text, computer): if outline or not start or pieceMoving: @@ -471,29 +479,29 @@ def gameEnd(board, turn, pieceMoving, start, outline, canPassant, opposite, text # stalemate cases if emptySpace == 62: # king vs king - addText(text, "Stalemate: Insufficient material.", 0, 0) - addText(text, "Press restart or exit the game.", 0, 0) + addText(text, "Stalemate: Insufficient material.", 0, 0, 0) + addText(text, "Press restart or exit the game.", 0, 0, 0) return True elif emptySpace == 61 and ((len(bishop) == 1 and knight == 0) or (knight == 1 and len(bishop) == 0)): # 1 bishop/knight vs king - addText(text, "Stalemate: Insufficient material.", 0, 0) - addText(text, "Press restart or exit the game.", 0, 0) + addText(text, "Stalemate: Insufficient material.", 0, 0, 0) + addText(text, "Press restart or exit the game.", 0, 0, 0) return True elif emptySpace == 60 and len(bishop) == 2: # 1 bishop vs 1 bishop, same colour b1, b2 = bishop[0][0], bishop[1][0] i1, i2 = (bishop[0][1] * 8) + bishop[0][2], (bishop[1][1] * 8) + bishop[1][2] y1, y2, = bishop[0][1], bishop[1][1] if b1 != b2 and y1 % 2 == i1 % 2 and y2 % 2 == i2 % 2: - addText(text, "Stalemate: Insufficient material.", 0, 0) - addText(text, "Press restart or exit the game.", 0, 0) + addText(text, "Stalemate: Insufficient material.", 0, 0, 0) + addText(text, "Press restart or exit the game.", 0, 0, 0) return True if checkmate(king, board, canPassant, opposite, computer): # cannot move winner = {-9: "Black", -99: "Black", 99: "White", 9: "White"} if kingCoord(king, board) in moveList: # king in check - addText(text, "Checkmate: " + str(winner[king]) + " won!", 0, 0) + addText(text, "Checkmate: " + str(winner[king]) + " won!", 0, 0, 0) else: # no possible moves, stalemate - addText(text, "Stalemate: " + str(winner[-king]) + " cannot move.", 0, 0) - addText(text, "Press restart or exit the game.", 0, 0) + addText(text, "Stalemate: " + str(winner[-king]) + " cannot move.", 0, 0, 0) + addText(text, "Press restart or exit the game.", 0, 0, 0) return True else: return False diff --git a/Engine.py b/Engine.py index 8e7ebf9..f73112b 100644 --- a/Engine.py +++ b/Engine.py @@ -144,7 +144,6 @@ def minimax(board, canPassant, computer, depth, turn, isPawn, alpha, beta): opposite = 1 if computer == turn else 0 moves, i = Controller.specificCompute(turn, board, canPassant, computer, opposite) # all possible moves moves = Controller.computerCastle(turn, board, moves, i, canPassant, computer) # includes castle - tempBoard = [row[:] for row in board] bestMove = None if depth == 0 or Controller.gameOver(computer, turn, board, canPassant): # reached end of tree or game ended @@ -157,21 +156,20 @@ def minimax(board, canPassant, computer, depth, turn, isPawn, alpha, beta): if subList[0] != 10 and Controller.checkMove(subList[0], newMove[0], newMove[1], subList[1][0], subList[1][1], board, canPassant, computer): oldY, oldX = subList[1] newY, newX = newMove - tempBoard[oldY][oldX] = 0 - tempBoard[newY][newX] = subList[0] + newPiece = board[newY][newX] + board[oldY][oldX] = 0 + board[newY][newX] = subList[0] piece = subList[0] - Controller.enPassantCapture(subList[0], tempBoard, newY, newX, oldY, oldX, isPawn, canPassant, [], computer, turn, True) + Controller.enPassantCapture(subList[0], board, newY, newX, oldY, oldX, isPawn, canPassant, [], computer, turn, True) - eval = minimax(tempBoard, canPassant, computer, depth - 1, -turn, isPawn, alpha, beta)[0] + eval = minimax(board, canPassant, computer, depth - 1, -turn, isPawn, alpha, beta)[0] maxEval = max(maxEval, eval) bestMove = (oldY, oldX, newY, newX, piece) if maxEval == eval else bestMove + Controller.undo(board, subList[0], newPiece, newY, newX, oldY, oldX) alpha = max(alpha, eval) if beta <= alpha: - tempBoard = [row[:] for row in board] break - - tempBoard = [row[:] for row in board] return maxEval, bestMove else: # black turn @@ -181,21 +179,20 @@ def minimax(board, canPassant, computer, depth, turn, isPawn, alpha, beta): if subList[0] != 10 and Controller.checkMove(subList[0], newMove[0], newMove[1], subList[1][0], subList[1][1], board, canPassant, computer): oldY, oldX = subList[1] newY, newX = newMove - tempBoard[oldY][oldX] = 0 - tempBoard[newY][newX] = subList[0] + newPiece = board[newY][newX] + board[oldY][oldX] = 0 + board[newY][newX] = subList[0] piece = subList[0] - Controller.enPassantCapture(subList[0], tempBoard, newY, newX, oldY, oldX, isPawn, canPassant, [], computer, turn, True) + Controller.enPassantCapture(subList[0], board, newY, newX, oldY, oldX, isPawn, canPassant, [], computer, turn, True) - eval = minimax(tempBoard, canPassant, computer, depth - 1, -turn, isPawn, alpha, beta)[0] + eval = minimax(board, canPassant, computer, depth - 1, -turn, isPawn, alpha, beta)[0] minEval = min(minEval, eval) bestMove = (oldY, oldX, newY, newX, piece) if minEval == eval else bestMove + Controller.undo(board, subList[0], newPiece, newY, newX, oldY, oldX) beta = min(beta, eval) if beta <= alpha: - tempBoard = [row[:] for row in board] break - - tempBoard = [row[:] for row in board] return minEval, bestMove @@ -256,7 +253,6 @@ def pieceSquare(piece, y, x, flip, board): # incporporates the values of the remaining pieces on the board, their positioning and if the king is in check def boardEvaluation(board, computer, canPassant): eval = 0 - tempBoard = [row[:] for row in board] for y, row in enumerate(board): for x, n in enumerate(row): # evaluates the number of pieces on each side @@ -265,10 +261,9 @@ def boardEvaluation(board, computer, canPassant): table = pieceSquare(n, y, x, flip, board) if n < 0 else -pieceSquare(n, y, x, flip, board) eval += temp + table - # computeAll gave an index error in pawnMove opposite = 1 if computer == BLACK else 0 - blackList = Controller.computeAll(1, tempBoard, 0, opposite + 1, canPassant, computer) - whiteList = Controller.computeAll(-1, tempBoard, 0, opposite, canPassant, computer) + blackList = Controller.computeAll(1, board, 0, opposite + 1, canPassant, computer) + whiteList = Controller.computeAll(-1, board, 0, opposite, canPassant, computer) if Controller.kingCoord(1, board) in blackList: eval -= 100 # black king in check @@ -278,8 +273,11 @@ def boardEvaluation(board, computer, canPassant): return eval -# defines the end game state +# defines the end game state, when most pieces are gone # used to determine which piece square table to use def endGameState(board): - # define end game here - return True + empty = 0 + for row in board: + empty += row.count(0) + + return True if empty < 11 else False diff --git a/GUI.py b/GUI.py index ac60204..4507f73 100644 --- a/GUI.py +++ b/GUI.py @@ -1,4 +1,5 @@ import pygame +import math # dictionary constant of the paths of the image of the pieces IMAGEPATH = { @@ -116,16 +117,26 @@ def promoOutline(screen, size, toggle): # adds additional dialouge into the list # recursively calls itself after deleting the last text dialouge -def addText(array, text, count, length): +def addText(array, text, count, length, font): + concateText = "" + index = 999 + reverseArray = reversed(tuple(enumerate(array))) + + for i, string in reverseArray: # goes through dialouge array in reverse, finding the most recently added line that contains one of the target values + if string in {"Black pawn promoted to", "White pawn promoted to", "Invalid move:"}: + index = i + changedText = text[3:] + changedText = changedText.lower() if array[index] != "Invalid move:" else changedText + concateText = array[index] + changedText + break + try: - index = array.index("") - if length >= 850: - for string in {"Black pawn promoted to ", "White pawn promoted to ", "Invalid move:"}: - if string in array: - index = array.index(string) - text = text[3:] if array[0] == "Invalid move:" else text - array[index] = array[index] + text - return count + textLength = math.inf if (concateText == "" or font == 0) else font.size(concateText)[0] + index = array.index("") if (index == 999 or textLength + 25 >= length) else index + + if textLength + 25 < length: + array[index] = concateText + return count elif count == 0: # two line text array[index] = text return count @@ -135,7 +146,7 @@ def addText(array, text, count, length): except ValueError: array.pop(0) array.append("") - return addText(array, text, count, length) + return addText(array, text, count, length, font) # clears the dialouge list of unecessary dialouge @@ -147,7 +158,7 @@ def clearText(array, num): else: newArray = [] for n in array: - if n not in {"Invalid move:", " White king in check", " Black king in check", "Invalid Move"}: + if n not in {"Invalid move:", " White king in check", " Black king in check", "Invalid Move", "Invalid move: White king in check", "Invalid move: Black king in check"}: newArray.append(n) while len(newArray) != num: newArray.append("") @@ -180,7 +191,7 @@ def dialouge(size, text): height = 130 + 2 * buttonHeight + buttonHeight/3 + ((40/27)*num) * i dialougeSurf.blit(render, (size + 30, height)) - return dialougeSurf + return dialougeSurf, font # side window and button control diff --git a/Main.py b/Main.py index 1e0f1a7..472f75f 100644 --- a/Main.py +++ b/Main.py @@ -3,7 +3,6 @@ import GUI import Controller from Engine import minimax -import os BLACK = -1 WHITE = 1 @@ -25,10 +24,8 @@ def main(): # automatically changes window dimensions according to monitor size pygame.init() - # x, y = -1300, -1000 - # os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (x, y) size = pygame.display.Info().current_h - size = size # /0.585 + size = size SIZE = (size - 120) PSIZE = (SIZE-50)/8 @@ -75,11 +72,10 @@ def main(): # draw board, pieces and side bar buttonSurface = GUI.buttons(SIZE) boardSurface = GUI.createBoard(SIZE, PSIZE) - dialougeSurf = GUI.dialouge(SIZE, text) + dialougeSurf, font = GUI.dialouge(SIZE, text) piecesSurface = GUI.drawPieces(board, PSIZE, SIZE) oldX, oldY = 0, 0 - # main loop while True: for event in pygame.event.get(): @@ -101,12 +97,12 @@ def main(): computer = Controller.randomTurn() player = -computer GUI.clearText(text, NUMTEXT) - dialougeSurf = GUI.dialouge(SIZE, text) + dialougeSurf = GUI.dialouge(SIZE, text)[0] # checks if game has ended if start and not outline and not end: end = Controller.gameEnd(board, turn, pieceMoving, start, outline, canPassant, opposite, text, computer) - dialougeSurf = GUI.dialouge(SIZE, text) + dialougeSurf = GUI.dialouge(SIZE, text)[0] # computer move if start and computer != None and turn == computer and not end and not outline: @@ -120,10 +116,10 @@ def main(): board[oldY][newX[1]] = rook Controller.firstMove(newY[1], board, oldY, newX[0]) Controller.firstMove(rook, board, oldY, newX[1]) - count = Controller.addText(text, str(PIECE[newY[1]]) + str(newY[0]), 0) + count = Controller.addText(text, str(PIECE[newY[1]]) + str(newY[0]), 0, font) else: num, alph = Controller.computePos(piece, computer, player, newY, newX) - count = Controller.addText(text, str(PIECE[piece]) + " to " + str(ALPH[alph]) + str(num), count, 0) + count = Controller.addText(text, str(PIECE[piece]) + " to " + str(ALPH[alph]) + str(num), count, 0, font) Controller.enPassantCapture(piece, board, newY, newX, oldY, oldX, isPawn, canPassant, text, computer, turn, False) board[newY][newX] = piece isPawn = Controller.pawnFirst(piece, newY, newX, oldY, oldX, computer, turn) @@ -132,13 +128,13 @@ def main(): if piece in {-1, 1} and newY == 7: # computer promotion board[newY][newX] = piece * Controller.choice((5, 3, 4, 7)) - Controller.addText(text, PIECE[piece] + " promoted to ", 0, 0) - Controller.addText(text, " " + str(PIECE[board[newY][newX]]), 0, DIALOUGEINFO[0]) + Controller.addText(text, PIECE[piece] + " promoted to", 0, 0, font) + Controller.addText(text, " " + str(PIECE[board[newY][newX]]), 0, DIALOUGEINFO[0], font) turn = -turn text = GUI.clearText(text, NUMTEXT) Controller.isCheck(end, piece, board, 0, canPassant, text, computer) piecesSurface = GUI.drawPieces(board, PSIZE, SIZE) - dialougeSurf = GUI.dialouge(SIZE, text) + dialougeSurf = GUI.dialouge(SIZE, text)[0] oldY, oldX = 0, 0 tempPiece = None @@ -149,37 +145,38 @@ def main(): # pawn promotion is happening if outline: for i, p in enumerate(promoInfo): - promoPressed, count = Controller.button(i + 1, p, promotion, board, text, count, DIALOUGEINFO[0]) + promoPressed, count = Controller.button(i + 1, p, promotion, board, text, count, DIALOUGEINFO[0], font) if promoPressed: outline = False board = GUI.rotate(board, computer) turn = -turn text = GUI.clearText(text, NUMTEXT) end = Controller.gameEnd(board, turn, pieceMoving, start, outline, canPassant, opposite, text, computer) + Controller.isCheck(end, piece, board, opposite, canPassant, text, computer) boardSurface = GUI.createBoard(SIZE, PSIZE) buttonSurface = GUI.buttons(SIZE) piecesSurface = GUI.drawPieces(board, PSIZE, SIZE) - dialougeSurf = GUI.dialouge(SIZE, text) + dialougeSurf = GUI.dialouge(SIZE, text)[0] break # restarts program - elif Controller.button(0, restartInfo, None, None, text, 0, 0): + elif Controller.button(0, restartInfo, None, None, text, 0, 0, 0): pygame.quit() main() return # two player mode - elif not colourChoose and Controller.button(0, twoPlayerInfo, None, None, text, 0, 0): + elif not colourChoose and Controller.button(0, twoPlayerInfo, None, None, text, 0, 0, 0): start = True # Computer mode - elif not start and Controller.button(0, computerInfo, None, None, text, 0, 0) and not colourChoose: - Controller.addText(text, "Press the Key to Choose a Colour:", 0, 0) - Controller.addText(text, "W for White", 0, 0) - Controller.addText(text, "B for Black", 0, 0) - Controller.addText(text, "R for Random", 0, 0) + elif not start and Controller.button(0, computerInfo, None, None, text, 0, 0, 0) and not colourChoose: + Controller.addText(text, "Press the Key to Choose a Colour:", 0, 0, font) + Controller.addText(text, "W for White", 0, 0, font) + Controller.addText(text, "B for Black", 0, 0, font) + Controller.addText(text, "R for Random", 0, 0, font) colourChoose = True - dialougeSurf = GUI.dialouge(SIZE, text) + dialougeSurf = GUI.dialouge(SIZE, text)[0] # checks if a piece has been selected elif start and tempPiece != 10 and Controller.playerTurn(Controller.pieceColour(tempPiece), turn, player) and not end: @@ -222,12 +219,12 @@ def main(): if Controller.kingCoord(piece, tempBoard) in moveList: board[oldY][oldX] = piece inCheck = " White king in check" if piece < 0 else " Black king in check" - Controller.addText(text, "Invalid move:", 0, 0) - Controller.addText(text, inCheck, 0, DIALOUGEINFO[0]) + Controller.addText(text, "Invalid move:", 0, 0, font) + Controller.addText(text, inCheck, 0, DIALOUGEINFO[0], font) else: # legal move num, alph = Controller.computePos(piece, computer, player, newY, newX) - count = Controller.addText(text, str(PIECE[piece]) + " to " + str(ALPH[alph]) + str(num), count, 0) + count = Controller.addText(text, str(PIECE[piece]) + " to " + str(ALPH[alph]) + str(num), count, 0, font) Controller.enPassantCapture(piece, board, newY, newX, oldY, oldX, isPawn, canPassant, text, computer, turn, False) board[newY][newX] = piece isPawn = Controller.pawnFirst(piece, newY, newX, oldY, oldX, computer, turn) @@ -254,18 +251,18 @@ def main(): else: # within bounds, invalid move board[oldY][oldX] = piece if not (newY == oldY and newX == oldX): - Controller.addText(text, "Invalid Move", 0, 0) + Controller.addText(text, "Invalid Move", 0, 0, font) else: # piece placed outside of boards board[oldY][oldX] = piece if not (newY == oldY and newX == oldX): - Controller.addText(text, "Invalid Move", 0, 0) + Controller.addText(text, "Invalid Move", 0, 0, font) # redraw surfaces, reset temp variables boardSurface = GUI.createBoard(SIZE, PSIZE) buttonSurface = GUI.buttons(SIZE) piecesSurface = GUI.drawPieces(board, PSIZE, SIZE) - dialougeSurf = GUI.dialouge(SIZE, text) + dialougeSurf = GUI.dialouge(SIZE, text)[0] selected = None oldY, oldX = 0, 0 diff --git a/Package.py b/Package.py index 00f5223..35583f3 100644 --- a/Package.py +++ b/Package.py @@ -21,16 +21,21 @@ def testBoard(board): print("--------------------------------")""" NEWIMPORT = """import pygame +import math import os import sys """ +OLDIMPORT = """import pygame +import math +""" + OLD = [ "'Assets\Restart.png'", "'Assets\TPlayer.png'", "'Assets\Computer.png'", "'Assets\BishopIcon.png'", "'Assets\KnightIcon.png'", "'Assets\RookIcon.png'", "'Assets\QueenIcon.png'", "(path)", "'Assets\Pawn.png'", "'Assets\Knight.png'", "'Assets\Bishop.png'", "'Assets\Rook.png'", "'Assets\Queen.png'", "'Assets\King.png'", "'Assets\wPawn.png'", "'Assets\wKnight.png'", "'Assets\wBishop.png'", "'Assets\wRook.png'", "'Assets\wQueen.png'", - "'Assets\wKing.png'", "import pygame", REPLACE, + "'Assets\wKing.png'", OLDIMPORT, REPLACE, ] NEW = [ @@ -93,12 +98,12 @@ def main(): # copies files to a new folder called 'temp' shutil.copytree('Assets', 'temp') - for fileName in ('Main.py', 'GUI.py', 'Controller.py'): + for fileName in ('Main.py', 'GUI.py', 'Controller.py', 'Engine.py'): shutil.copy(fileName, 'temp') os.chdir('temp') # replace text in files - readWrite('Main.py', "'Assets\icon.png'", "resource_path('icon.png')", False) + readWrite('Main.py', "'Assets\icon.png'", "GUI.resource_path('icon.png')", False) readWrite('GUI.py', OLD, NEW, True) readWrite('Controller.py', 'from GUI import getPiece, getPos, addText, testBoard', 'from GUI import getPiece, getPos, addText', False) open('version.txt', 'a')