diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..1814fb0 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,9 @@ +[mypy] +files = . +warn_unused_ignores = True +disallow_untyped_calls = True +disallow_untyped_defs = True +disallow_incomplete_defs = True +disallow_untyped_decorators = True +check_untyped_defs = True +no_implicit_optional = True \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 6957ff2..0e919c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,6 @@ -termcolor +termcolor +mypy>=0.981 +mypy-extensions>=0.4.3 +tomli>=2.0.1 +types-setuptools>=65.4.0.0 +typing_extensions>=4.3.0 diff --git a/src/AI.py b/src/AI.py index b8072ca..590ca8c 100644 --- a/src/AI.py +++ b/src/AI.py @@ -1,9 +1,13 @@ +from __future__ import annotations + import copy import random from multiprocessing import Pool +from typing import no_type_check from src.Board import Board from src.InputParser import InputParser +from src.Move import Move from src.MoveNode import MoveNode WHITE = True @@ -13,20 +17,20 @@ class AI: depth = 1 - board = None - side = None movesAnalyzed = 0 - def __init__(self, board, side, depth): + def __init__(self, board: Board, side: bool, depth: int): self.board = board self.side = side self.depth = depth self.parser = InputParser(self.board, self.side) - def getFirstMove(self, side): + def getFirstMove(self, side: bool) -> Move: move = list(self.board.getAllMovesLegal(side))[0] return move + # TODO this method is never used, remove? + @no_type_check def getAllMovesLegalConcurrent(self, side): p = Pool(8) unfilteredMovesWithBoard = \ @@ -38,8 +42,8 @@ def getAllMovesLegalConcurrent(self, side): p.join() return list(filter(None, legalMoves)) - def minChildrenOfNode(self, node): - lowestNodes = [] + def minChildrenOfNode(self, node: MoveNode) -> list[MoveNode]: + lowestNodes: list[MoveNode] = [] for child in node.children: if not lowestNodes: lowestNodes.append(child) @@ -50,8 +54,8 @@ def minChildrenOfNode(self, node): lowestNodes.append(child) return lowestNodes - def maxChildrenOfNode(self, node): - highestNodes = [] + def maxChildrenOfNode(self, node: MoveNode) -> list[MoveNode]: + highestNodes: list[MoveNode] = [] for child in node.children: if not highestNodes: highestNodes.append(child) @@ -62,12 +66,12 @@ def maxChildrenOfNode(self, node): highestNodes.append(child) return highestNodes - def getRandomMove(self): + def getRandomMove(self) -> Move: legalMoves = list(self.board.getAllMovesLegal(self.side)) randomMove = random.choice(legalMoves) return randomMove - def generateMoveTree(self): + def generateMoveTree(self) -> list[MoveNode]: moveTree = [] for move in self.board.getAllMovesLegal(self.side): moveTree.append(MoveNode(move, [], None)) @@ -78,7 +82,7 @@ def generateMoveTree(self): self.board.undoLastMove() return moveTree - def populateNodeChildren(self, node): + def populateNodeChildren(self, node: MoveNode) -> None: node.pointAdvantage = self.board.getPointAdvantageOfSide(self.side) node.depth = node.getDepth() if node.depth == self.depth: @@ -104,7 +108,7 @@ def populateNodeChildren(self, node): self.populateNodeChildren(node.children[-1]) self.board.undoLastMove() - def getOptimalPointAdvantageForNode(self, node): + def getOptimalPointAdvantageForNode(self, node: MoveNode) -> int: if node.children: for child in node.children: child.pointAdvantage = \ @@ -119,18 +123,18 @@ def getOptimalPointAdvantageForNode(self, node): else: return node.pointAdvantage - def getBestMove(self): + def getBestMove(self) -> Move: moveTree = self.generateMoveTree() bestMoves = self.bestMovesWithMoveTree(moveTree) randomBestMove = random.choice(bestMoves) randomBestMove.notation = self.parser.notationForMove(randomBestMove) return randomBestMove - def makeBestMove(self): + def makeBestMove(self) -> None: self.board.makeMove(self.getBestMove()) - def bestMovesWithMoveTree(self, moveTree): - bestMoveNodes = [] + def bestMovesWithMoveTree(self, moveTree: list[MoveNode]) -> list[Move]: + bestMoveNodes: list[MoveNode] = [] for moveNode in moveTree: moveNode.pointAdvantage = \ self.getOptimalPointAdvantageForNode(moveNode) @@ -144,13 +148,13 @@ def bestMovesWithMoveTree(self, moveTree): return [node.move for node in bestMoveNodes] - def isValidMove(self, move, side): + def isValidMove(self, move: Move, side: bool) -> bool: for legalMove in self.board.getAllMovesLegal(side): if move == legalMove: return True return False - def makeRandomMove(self): + def makeRandomMove(self) -> None: moveToMake = self.getRandomMove() self.board.makeMove(moveToMake) diff --git a/src/Bishop.py b/src/Bishop.py index b87760b..5f2255e 100644 --- a/src/Bishop.py +++ b/src/Bishop.py @@ -1,6 +1,14 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Iterator + from src.Coordinate import Coordinate as C from src.Piece import Piece +if TYPE_CHECKING: + from src.Board import Board + from src.Move import Move + WHITE = True BLACK = False @@ -10,11 +18,11 @@ class Bishop (Piece): stringRep = 'B' value = 3 - def __init__(self, board, side, position, movesMade=0): + def __init__(self, board: Board, side: bool, position: C, movesMade: int = 0): super(Bishop, self).__init__(board, side, position) self.movesMade = movesMade - def getPossibleMoves(self): + def getPossibleMoves(self) -> Iterator[Move]: currentPosition = self.position directions = [C(1, 1), C(1, -1), C(-1, 1), C(-1, -1)] for direction in directions: diff --git a/src/Board.py b/src/Board.py index 93a4271..15031a2 100644 --- a/src/Board.py +++ b/src/Board.py @@ -1,12 +1,18 @@ +from __future__ import annotations + +from typing import Optional, no_type_check + +from termcolor import colored + from src.Bishop import Bishop from src.Coordinate import Coordinate as C from src.King import King from src.Knight import Knight from src.Move import Move from src.Pawn import Pawn +from src.Piece import Piece from src.Queen import Queen from src.Rook import Rook -from termcolor import colored WHITE = True BLACK = False @@ -14,10 +20,10 @@ class Board: - def __init__(self, mateInOne=False, castleBoard=False, - passant=False, promotion=False): - self.pieces = [] - self.history = [] + def __init__(self, mateInOne: bool = False, castleBoard: bool = False, + passant: bool = False, promotion: bool = False): + self.pieces: list[Piece] = [] + self.history: list[tuple[Move, Optional[Piece]]] = [] self.points = 0 self.currentSide = WHITE self.movesMade = 0 @@ -73,7 +79,7 @@ def __init__(self, mateInOne=False, castleBoard=False, def __str__(self): return self.wrapStringRep(self.makeUnicodeStringRep(self.pieces)) - def undoLastMove(self): + def undoLastMove(self) -> None: lastMove, pieceTaken = self.history.pop() if lastMove.queensideCastle or lastMove.kingsideCastle: @@ -81,21 +87,23 @@ def undoLastMove(self): rook = lastMove.specialMovePiece self.movePieceToPosition(king, lastMove.oldPos) - self.movePieceToPosition(rook, lastMove.rookMove.oldPos) - king.movesMade -= 1 - rook.movesMade -= 1 + + if rook: + self.movePieceToPosition(rook, lastMove.rookMove.oldPos) + rook.movesMade -= 1 elif lastMove.passant: pawnMoved = lastMove.piece pawnTaken = pieceTaken - self.pieces.append(pawnTaken) + if pawnTaken: + self.pieces.append(pawnTaken) + if pawnTaken.side == WHITE: + self.points += 1 + if pawnTaken.side == BLACK: + self.points -= 1 self.movePieceToPosition(pawnMoved, lastMove.oldPos) pawnMoved.movesMade -= 1 - if pawnTaken.side == WHITE: - self.points += 1 - if pawnTaken.side == BLACK: - self.points -= 1 elif lastMove.promotion: pawnPromoted = lastMove.piece @@ -128,7 +136,7 @@ def undoLastMove(self): self.currentSide = not self.currentSide - def isCheckmate(self): + def isCheckmate(self) -> bool: #Game continue even after checkmate if len(self.getAllMovesLegal(self.currentSide)) == 0: for move in self.getAllMovesUnfiltered(not self.currentSide): @@ -137,10 +145,10 @@ def isCheckmate(self): return True return False - def isStalemate(self): + def isStalemate(self) -> bool: return len(self.getAllMovesLegal(self.currentSide)) == 0 and not self.isCheckmate() - def noMatingMaterial(self): + def noMatingMaterial(self) -> bool: if len(self.pieces) == 2: return True # just the kings if ( @@ -151,31 +159,31 @@ def noMatingMaterial(self): return True return False - def getLastMove(self): + def getLastMove(self) -> Move: # type: ignore[return] # TODO: add consistent return for else condition if self.history: return self.history[-1][0] - def getLastPieceMoved(self): + def getLastPieceMoved(self) -> Piece: # type: ignore[return] # TODO: add consistent return for else condition if self.history: return self.history[-1][0].piece - def addMoveToHistory(self, move): + def addMoveToHistory(self, move: Move) -> None: pieceTaken = None if move.passant: pieceTaken = move.specialMovePiece - self.history.append([move, pieceTaken]) + self.history.append((move, pieceTaken)) return pieceTaken = move.pieceToCapture if pieceTaken: - self.history.append([move, pieceTaken]) + self.history.append((move, pieceTaken)) return - self.history.append([move, None]) + self.history.append((move, None)) - def getCurrentSide(self): + def getCurrentSide(self) -> bool: return self.currentSide - def makeUnicodeStringRep(self, pieces): + def makeUnicodeStringRep(self, pieces: list[Piece]) -> str: DISPLAY_LOOKUP = { "R": '♜', "N": '♞', @@ -203,7 +211,7 @@ def makeUnicodeStringRep(self, pieces): stringRep += '\n' return stringRep.rstrip() - def wrapStringRep(self, stringRep): + def wrapStringRep(self, stringRep: str) -> str: sRep = '\n'.join( ['%d %s' % (8-r, s.rstrip()) for r, s in enumerate(stringRep.split('\n'))] + @@ -211,24 +219,24 @@ def wrapStringRep(self, stringRep): ).rstrip() return sRep - def rankOfPiece(self, piece): + def rankOfPiece(self, piece: Piece) -> str: return str(piece.position[1] + 1) - def fileOfPiece(self, piece): + def fileOfPiece(self, piece: Piece) -> str: transTable = str.maketrans('01234567', 'abcdefgh') return str(piece.position[0]).translate(transTable) - def getCoordinateNotationOfMove(self, move): + def getCoordinateNotationOfMove(self, move: Move) -> str: notation = "" notation += self.positionToHumanCoord(move.oldPos) notation += self.positionToHumanCoord(move.newPos) if move.promotion: - notation += str(move.specialMovePiece.stringRep) + notation += str(move.specialMovePiece.stringRep) # type: ignore[attr-defined] return notation - def getCaptureNotation(self, move, short=False): + def getCaptureNotation(self, move: Move, short: bool = True) -> str: notation = "" pieceToMove = move.piece pieceToTake = move.pieceToCapture @@ -239,19 +247,19 @@ def getCaptureNotation(self, move, short=False): notation += pieceToMove.stringRep notation += 'x' if short: - notation += pieceToTake.stringRep + notation += pieceToTake.stringRep # type: ignore[union-attr] else: notation += self.positionToHumanCoord(move.newPos) if move.promotion: - notation += str(move.specialMovePiece.stringRep) + notation += str(move.specialMovePiece.stringRep) # type: ignore[attr-defined, operator] return notation - def currentSideRep(self): + def currentSideRep(self) -> str: return "White" if self.currentSide else "Black" - def getAlgebraicNotationOfMove(self, move, short=True): + def getAlgebraicNotationOfMove(self, move: Move, short: bool = True) -> str: notation = "" pieceToMove = move.piece pieceToTake = move.pieceToCapture @@ -273,11 +281,11 @@ def getAlgebraicNotationOfMove(self, move, short=True): notation += self.positionToHumanCoord(move.newPos) if move.promotion: - notation += "=" + str(move.specialMovePiece.stringRep) + notation += "=" + str(move.specialMovePiece.stringRep) # type: ignore[attr-defined] return notation - def getAlgebraicNotationOfMoveWithFile(self, move, short=True): + def getAlgebraicNotationOfMoveWithFile(self, move: Move, short: bool = True) -> str: # TODO: Use self.getAlgebraicNotationOfMove instead of repeating code notation = "" pieceToMove = self.pieceAtPosition(move.oldPos) @@ -293,7 +301,7 @@ def getAlgebraicNotationOfMoveWithFile(self, move, short=True): notation += self.positionToHumanCoord(move.newPos) return notation - def getAlgebraicNotationOfMoveWithRank(self, move, short=True): + def getAlgebraicNotationOfMoveWithRank(self, move: Move, short: bool = True) -> str: # TODO: Use self.getAlgebraicNotationOfMove instead of repeating code notation = "" pieceToMove = self.pieceAtPosition(move.oldPos) @@ -312,7 +320,7 @@ def getAlgebraicNotationOfMoveWithRank(self, move, short=True): notation += self.positionToHumanCoord(move.newPos) return notation - def getAlgebraicNotationOfMoveWithFileAndRank(self, move, short=True): + def getAlgebraicNotationOfMoveWithFileAndRank(self, move: Move, short: bool = True) -> str: # TODO: Use self.getAlgebraicNotationOfMove instead of repeating code notation = "" pieceToMove = self.pieceAtPosition(move.oldPos) @@ -330,6 +338,8 @@ def getAlgebraicNotationOfMoveWithFileAndRank(self, move, short=True): notation += self.positionToHumanCoord(move.newPos) return notation + # TODO this method is never used, remove? + @no_type_check def humanCoordToPosition(self, coord): transTable = str.maketrans('abcdefgh', '12345678') coord = coord.translate(transTable) @@ -337,59 +347,67 @@ def humanCoordToPosition(self, coord): pos = C(coord[0], coord[1]) return pos - def positionToHumanCoord(self, pos): + def positionToHumanCoord(self, pos: C) -> str: transTable = str.maketrans('01234567', 'abcdefgh') notation = str(pos[0]).translate(transTable) + str(pos[1]+1) return notation - def isValidPos(self, pos): + def isValidPos(self, pos: C) -> bool: return 0 <= pos[0] <= 7 and 0 <= pos[1] <= 7 - def getSideOfMove(self, move): + def getSideOfMove(self, move: Move) -> bool: return move.piece.side + # TODO this method is never used, remove? + @no_type_check def getPositionOfPiece(self, piece): for y in range(8): for x in range(8): if self.boardArray[y][x] is piece: return C(x, 7-y) - def pieceAtPosition(self, pos): + def pieceAtPosition(self, pos: C) -> Piece: # type: ignore[return] # TODO: add consistent return for else condition for piece in self.pieces: if piece.position == pos: return piece - def movePieceToPosition(self, piece, pos): + def movePieceToPosition(self, piece: Piece, pos: C) -> None: piece.position = pos - def addPieceToPosition(self, piece, pos): + def addPieceToPosition(self, piece: Piece, pos: C) -> None: piece.position = pos + # TODO this method is never used, remove? + @no_type_check def clearPosition(self, pos): x, y = self.coordToLocationInArray(pos) self.boardArray[x][y] = None + # TODO this method is never used, remove? + @no_type_check def coordToLocationInArray(self, pos): return (7-pos[1], pos[0]) + # TODO this method is never used, remove? + @no_type_check def locationInArrayToCoord(self, loc): return (loc[1], 7-loc[0]) - def makeMove(self, move): + def makeMove(self, move: Move) -> None: self.addMoveToHistory(move) if move.kingsideCastle or move.queensideCastle: kingToMove = move.piece rookToMove = move.specialMovePiece self.movePieceToPosition(kingToMove, move.newPos) - self.movePieceToPosition(rookToMove, move.rookMove.newPos) + self.movePieceToPosition(rookToMove, move.rookMove.newPos) # type: ignore[arg-type, attr-defined] kingToMove.movesMade += 1 - rookToMove.movesMade += 1 + rookToMove.movesMade += 1 # type: ignore[attr-defined] elif move.passant: pawnToMove = move.piece - pawnToTake = move.specialMovePiece + pawnToTake = move.specialMovePiece # TODO fix specialMovePiece default type to be not None pawnToMove.position = move.newPos - self.pieces.remove(pawnToTake) + self.pieces.remove(pawnToTake) # type: ignore[arg-type] pawnToMove.movesMade += 1 elif move.promotion: @@ -401,12 +419,12 @@ def makeMove(self, move): if pieceToTake.side == BLACK: self.points += pieceToTake.value self.pieces.remove(pieceToTake) - - self.pieces.append(move.specialMovePiece) + # TODO fix specialMovePiece default type to be not None + self.pieces.append(move.specialMovePiece) # type: ignore[arg-type] if move.piece.side == WHITE: - self.points += move.specialMovePiece.value - 1 + self.points += move.specialMovePiece.value - 1 # type: ignore[attr-defined] if move.piece.side == BLACK: - self.points -= move.specialMovePiece.value - 1 + self.points -= move.specialMovePiece.value - 1 # type: ignore[attr-defined] move.piece.movesMade += 1 else: @@ -425,17 +443,17 @@ def makeMove(self, move): self.movesMade += 1 self.currentSide = not self.currentSide - def getPointValueOfSide(self, side): + def getPointValueOfSide(self, side: bool) -> int: points = 0 for piece in self.pieces: if piece.side == side: points += piece.value return points - def getPointAdvantageOfSide(self, side): + def getPointAdvantageOfSide(self, side: bool) -> int: return self.getPointValueOfSide(side) - self.getPointValueOfSide(not side) - def getAllMovesUnfiltered(self, side, includeKing=True): + def getAllMovesUnfiltered(self, side: bool, includeKing: bool = True) -> list[Move]: unfilteredMoves = [] for piece in self.pieces: if piece.side == side: @@ -444,14 +462,14 @@ def getAllMovesUnfiltered(self, side, includeKing=True): unfilteredMoves.append(move) return unfilteredMoves - def testIfLegalBoard(self, side): + def testIfLegalBoard(self, side: bool) -> bool: for move in self.getAllMovesUnfiltered(side): pieceToTake = move.pieceToCapture if pieceToTake and pieceToTake.stringRep == 'K': return False return True - def moveIsLegal(self, move): + def moveIsLegal(self, move: Move) -> bool: side = move.piece.side self.makeMove(move) isLegal = self.testIfLegalBoard(not side) @@ -459,7 +477,7 @@ def moveIsLegal(self, move): return isLegal # TODO: remove side parameter, unnecessary - def getAllMovesLegal(self, side): + def getAllMovesLegal(self, side: bool) -> list[Move]: unfilteredMoves = list(self.getAllMovesUnfiltered(side)) legalMoves = [] for move in unfilteredMoves: diff --git a/src/Coordinate.py b/src/Coordinate.py index 80ff238..bb75669 100644 --- a/src/Coordinate.py +++ b/src/Coordinate.py @@ -1,13 +1,18 @@ -class Coordinate(tuple): +from __future__ import annotations - def __new__(cls, *args): - return tuple.__new__(cls, args) +from typing import NamedTuple - def __reduce__(self): - return (self.__class__, tuple(self)) - def __add__(self, other): +class Coordinate(NamedTuple): + rank: int + file: int + + def __add__(self, other: object) -> Coordinate: + if not isinstance(other, Coordinate): + return NotImplemented return Coordinate(self[0] + other[0], self[1] + other[1]) - def __sub__(self, other): + def __sub__(self, other: object) -> Coordinate: + if not isinstance(other, Coordinate): + return NotImplemented return Coordinate(self[0] - other[0], self[1] - other[1]) diff --git a/src/InputParser.py b/src/InputParser.py index 401315e..100e688 100644 --- a/src/InputParser.py +++ b/src/InputParser.py @@ -1,15 +1,19 @@ +from __future__ import annotations + import re +from src.Board import Board +from src.Move import Move from src.Pawn import Pawn class InputParser: - def __init__(self, board, side): + def __init__(self, board: Board, side: bool): self.board = board self.side = side - def parse(self, humanInput): + def parse(self, humanInput: str) -> Move: regexCoordinateNotation = re.compile('(?i)[a-h][1-8][a-h][1-8][QRBN]?') if regexCoordinateNotation.match(humanInput): return self.moveForCoordinateNotation(humanInput) @@ -20,7 +24,7 @@ def parse(self, humanInput): return self.moveForShortAlgebraicNotation(humanInput.upper().replace("O","0")) raise ValueError("Invalid move: %s" % humanInput) - def moveForCoordinateNotation(self, notation): + def moveForCoordinateNotation(self, notation: str) -> Move: for move in self.board.getAllMovesLegal(self.side): if self.board.getCoordinateNotationOfMove(move).lower() == notation.lower(): move.notation = self.notationForMove(move) @@ -28,7 +32,7 @@ def moveForCoordinateNotation(self, notation): raise ValueError("Illegal move: %s" % notation) # Only handles SAN, not long-algebraic or descriptive - def moveForShortAlgebraicNotation(self, notation): + def moveForShortAlgebraicNotation(self, notation: str) -> Move: shortNotation = notation.replace("x","") moves = self.getLegalMovesWithNotation(self.side, False) for move in moves: @@ -62,14 +66,15 @@ def moveForShortAlgebraicNotation(self, notation): return move # ASSUME laziest pawn capture (P)b(x)c is unambiguous raise ValueError("Illegal move: %s" % notation) - def notationForMove(self, move): + def notationForMove(self, move: Move) -> str: side = self.board.getSideOfMove(move) moves = self.getLegalMovesWithNotation(side) for m in moves: if m == move: return m.notation + return "" # return added to make mypy happy - def getLegalMovesWithNotation(self, side, short=True): + def getLegalMovesWithNotation(self, side: bool, short: bool = True) -> list[Move]: moves = [] for legalMove in self.board.getAllMovesLegal(side): moves.append(legalMove) @@ -92,7 +97,7 @@ def getLegalMovesWithNotation(self, side, short=True): return moves - def duplicateMovesFromMoves(self, moves): + def duplicateMovesFromMoves(self, moves: list[Move]) -> list[Move]: return list(filter( lambda move: len([m for m in moves if m.notation == move.notation]) > 1, moves)) diff --git a/src/King.py b/src/King.py index bb2b3f7..53da3b8 100644 --- a/src/King.py +++ b/src/King.py @@ -1,7 +1,14 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Iterator + from src.Coordinate import Coordinate as C from src.Move import Move from src.Piece import Piece +if TYPE_CHECKING: + from src.Board import Board + WHITE = True BLACK = False @@ -11,11 +18,11 @@ class King (Piece): stringRep = 'K' value = 100 - def __init__(self, board, side, position, movesMade=0): + def __init__(self, board: Board, side: bool, position: C, movesMade: int = 0): super(King, self).__init__(board, side, position) self.movesMade = movesMade - def getPossibleMoves(self): + def getPossibleMoves(self) -> Iterator[Move]: currentPos = self.position movements = [C(0, 1), C(0, -1), C(1, 0), C(-1, 0), C(1, 1), C(1, -1), C(-1, 1), C(-1, -1)] @@ -73,7 +80,7 @@ def getPossibleMoves(self): kingsideRookPos = self.position + C(3, 0) kingsideRook = self.board.pieceAtPosition(kingsideRookPos) \ if self.board.isValidPos(kingsideRookPos) \ - else None + else None # TODO kingsideRook should never be None to match Move class requirements if kingsideRook and \ kingsideRook.stringRep == 'R' and \ kingsideRook.movesMade == 0: @@ -82,7 +89,7 @@ def getPossibleMoves(self): queensideRookPos = self.position - C(4, 0) queensideRook = self.board.pieceAtPosition(queensideRookPos) \ if self.board.isValidPos(queensideRookPos) \ - else None + else None # TODO queensideRook should never be None to match Move class requirements if queensideRook and \ queensideRook.stringRep == 'R' and \ queensideRook.movesMade == 0: @@ -93,19 +100,17 @@ def getPossibleMoves(self): not kingsideCastleCheck and \ not kingsideRookMoved: move = Move(self, self.position + C(2, 0)) - rookMove = Move(kingsideRook, self.position + C(1, 0)) - move.specialMovePiece = \ - self.board.pieceAtPosition(kingsideRookPos) + rookMove = Move(kingsideRook, self.position + C(1, 0)) # type: ignore[arg-type] + move.specialMovePiece = self.board.pieceAtPosition(kingsideRookPos) # type: ignore[assignment] move.kingsideCastle = True - move.rookMove = rookMove + move.rookMove = rookMove # type: ignore[assignment] yield move if not queensideCastleBlocked and \ not queensideCastleCheck and \ not queensideRookMoved: move = Move(self, self.position - C(2, 0)) - rookMove = Move(queensideRook, self.position - C(1, 0)) - move.specialMovePiece = \ - self.board.pieceAtPosition(queensideRookPos) + rookMove = Move(queensideRook, self.position - C(1, 0)) # type: ignore[arg-type] + move.specialMovePiece = self.board.pieceAtPosition(queensideRookPos) # type: ignore[assignment] move.queensideCastle = True - move.rookMove = rookMove + move.rookMove = rookMove # type: ignore[assignment] yield move diff --git a/src/Knight.py b/src/Knight.py index 26f1fd6..9d9d3b1 100644 --- a/src/Knight.py +++ b/src/Knight.py @@ -1,7 +1,14 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Iterator + from src.Coordinate import Coordinate as C from src.Move import Move from src.Piece import Piece +if TYPE_CHECKING: + from src.Board import Board + WHITE = True BLACK = False @@ -11,11 +18,11 @@ class Knight(Piece): stringRep = 'N' value = 3 - def __init__(self, board, side, position, movesMade=0): + def __init__(self, board: Board, side: bool, position: C, movesMade: int = 0): super(Knight, self).__init__(board, side, position) self.movesMade = movesMade - def getPossibleMoves(self): + def getPossibleMoves(self) -> Iterator[Move]: board = self.board currentPos = self.position movements = [C(2, 1), C(2, -1), C(-2, 1), C(-2, -1), C(1, 2), diff --git a/src/Move.py b/src/Move.py index 47f087f..4738a48 100644 --- a/src/Move.py +++ b/src/Move.py @@ -1,7 +1,15 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +if TYPE_CHECKING: + from src.Coordinate import Coordinate as C + from src.Piece import Piece + class Move: - def __init__(self, piece, newPos, pieceToCapture=None): - self.notation = None + def __init__(self, piece: Piece, newPos: C, pieceToCapture: Optional[Piece] = None): + self.notation = "" self.check = False self.checkmate = False self.kingsideCastle = False @@ -15,11 +23,11 @@ def __init__(self, piece, newPos, pieceToCapture=None): self.newPos = newPos self.pieceToCapture = pieceToCapture # For en passant and castling - self.specialMovePiece = None + self.specialMovePiece = None # TODO: this should be a 'Piece' type to satisfy mypy # For castling - self.rookMove = None + self.rookMove = None # TODO: this should be a 'Move' type to satisfy mypy - def __str__(self): + def __str__(self) -> str: displayString = 'Old pos : ' + str(self.oldPos) + \ ' -- New pos : ' + str(self.newPos) if self.notation: @@ -31,7 +39,9 @@ def __str__(self): displayString += ' PASSANT' return displayString - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, Move): + return NotImplemented if self.oldPos == other.oldPos and \ self.newPos == other.newPos and \ self.specialMovePiece == other.specialMovePiece: @@ -45,9 +55,9 @@ def __eq__(self, other): else: return False - def __hash__(self): + def __hash__(self) -> int: return hash((self.oldPos, self.newPos)) - def reverse(self): + def reverse(self) -> Move: return Move(self.piece, self.piece.position, pieceToCapture=self.pieceToCapture) diff --git a/src/MoveNode.py b/src/MoveNode.py index 13c96d9..5865398 100644 --- a/src/MoveNode.py +++ b/src/MoveNode.py @@ -1,13 +1,20 @@ +from __future__ import annotations + +from typing import Optional + +from src.Move import Move + + class MoveNode: - def __init__(self, move, children, parent): + def __init__(self, move: Move, children: list[MoveNode], parent: Optional[MoveNode]): self.move = move self.children = children self.parent = parent - self.pointAdvantage = None + self.pointAdvantage = 0 self.depth = 1 - def __str__(self): + def __str__(self) -> str: stringRep = "Move : " + str(self.move) + \ " Point advantage : " + str(self.pointAdvantage) + \ " Checkmate : " + str(self.move.checkmate) @@ -19,7 +26,9 @@ def __str__(self): return stringRep - def __gt__(self, other): + def __gt__(self, other: object) -> bool: + if not isinstance(other, MoveNode): + return NotImplemented if self.move.checkmate and not other.move.checkmate: return True if not self.move.checkmate and other.move.checkmate: @@ -28,7 +37,9 @@ def __gt__(self, other): return False return self.pointAdvantage > other.pointAdvantage - def __lt__(self, other): + def __lt__(self, other: object) -> bool: + if not isinstance(other, MoveNode): + return NotImplemented if self.move.checkmate and not other.move.checkmate: return False if not self.move.checkmate and other.move.checkmate: @@ -37,12 +48,14 @@ def __lt__(self, other): return False return self.pointAdvantage < other.pointAdvantage - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, MoveNode): + return NotImplemented if self.move.checkmate and other.move.checkmate: return True return self.pointAdvantage == other.pointAdvantage - def getHighestNode(self): + def getHighestNode(self) -> MoveNode: highestNode = self while True: if highestNode.parent is not None: @@ -50,7 +63,7 @@ def getHighestNode(self): else: return highestNode - def getDepth(self): + def getDepth(self) -> int: depth = 1 highestNode = self while True: diff --git a/src/Pawn.py b/src/Pawn.py index 345207d..e307f0c 100644 --- a/src/Pawn.py +++ b/src/Pawn.py @@ -1,3 +1,7 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Iterator + from src.Bishop import Bishop from src.Coordinate import Coordinate as C from src.Knight import Knight @@ -6,6 +10,9 @@ from src.Queen import Queen from src.Rook import Rook +if TYPE_CHECKING: + from src.Board import Board + WHITE = True BLACK = False @@ -15,12 +22,12 @@ class Pawn(Piece): stringRep = '▲' value = 1 - def __init__(self, board, side, position, movesMade=0): + def __init__(self, board: Board, side: bool, position: C, movesMade: int = 0): super(Pawn, self).__init__(board, side, position) self.movesMade = movesMade # @profile - def getPossibleMoves(self): + def getPossibleMoves(self) -> Iterator[Move]: currentPosition = self.position # Pawn moves one up @@ -39,7 +46,7 @@ def getPossibleMoves(self): for piece in piecesForPromotion: move = Move(self, advanceOnePosition) move.promotion = True - move.specialMovePiece = piece + move.specialMovePiece = piece # type: ignore[assignment] yield move else: yield Move(self, advanceOnePosition) @@ -73,7 +80,7 @@ def getPossibleMoves(self): for piece in piecesForPromotion: move = Move(self, newPosition, pieceToCapture=pieceToTake) move.promotion = True - move.specialMovePiece = piece + move.specialMovePiece = piece # type: ignore[assignment] yield move else: yield Move(self, newPosition, @@ -103,5 +110,5 @@ def getPossibleMoves(self): move = Move(self, self.position + movement, pieceToCapture=pieceBesidePawn) move.passant = True - move.specialMovePiece = pieceBesidePawn + move.specialMovePiece = pieceBesidePawn # type: ignore[assignment] yield move diff --git a/src/Piece.py b/src/Piece.py index f99635c..1619060 100644 --- a/src/Piece.py +++ b/src/Piece.py @@ -1,6 +1,13 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Iterator + from src.Coordinate import Coordinate as C from src.Move import Move +if TYPE_CHECKING: + from src.Board import Board + WHITE = True BLACK = False X = 0 @@ -9,13 +16,16 @@ class Piece: - def __init__(self, board, side, position, movesMade=0): + stringRep: str + value: int + + def __init__(self, board: Board, side: bool, position: C, movesMade: int = 0): self.board = board self.side = side self.position = position self.movesMade = 0 - def __str__(self): + def __str__(self) -> str: sideString = 'White' if self.side == WHITE else 'Black' return 'Type : ' + type(self).__name__ + \ ' - Position : ' + str(self.position) + \ @@ -23,7 +33,7 @@ def __str__(self): ' -- Value : ' + str(self.value) + \ " -- Moves made : " + str(self.movesMade) - def movesInDirectionFromPos(self, pos, direction, side): + def movesInDirectionFromPos(self, pos: C, direction: C, side: bool) -> Iterator[Move]: for dis in range(1, 8): movement = C(dis * direction[X], dis * direction[Y]) newPos = pos + movement @@ -37,7 +47,9 @@ def movesInDirectionFromPos(self, pos, direction, side): yield Move(self, newPos, pieceToCapture=pieceAtNewPos) return - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, Piece): + return NotImplemented if self.board == other.board and \ self.side == other.side and \ self.position == other.position and \ @@ -45,7 +57,10 @@ def __eq__(self, other): return True return False - def copy(self): + def copy(self) -> Piece: cpy = self.__class__(self.board, self.side, self.position, movesMade=self.movesMade) return cpy + + def getPossibleMoves(self) -> Iterator[Move]: + pass diff --git a/src/Queen.py b/src/Queen.py index c495ad8..d63bcea 100644 --- a/src/Queen.py +++ b/src/Queen.py @@ -1,6 +1,14 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Iterator + from src.Coordinate import Coordinate as C +from src.Move import Move from src.Piece import Piece +if TYPE_CHECKING: + from src.Board import Board + WHITE = True BLACK = False @@ -10,11 +18,11 @@ class Queen(Piece): stringRep = 'Q' value = 9 - def __init__(self, board, side, position, movesMade=0): + def __init__(self, board: Board, side: bool, position: C, movesMade: int = 0): super(Queen, self).__init__(board, side, position) self.movesMade = movesMade - def getPossibleMoves(self): + def getPossibleMoves(self) -> Iterator[Move]: currentPosition = self.position directions = [C(0, 1), C(0, -1), C(1, 0), C(-1, 0), C(1, 1), diff --git a/src/Rook.py b/src/Rook.py index cdec24a..e6fc0a4 100644 --- a/src/Rook.py +++ b/src/Rook.py @@ -1,6 +1,14 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Iterator + from src.Coordinate import Coordinate as C +from src.Move import Move from src.Piece import Piece +if TYPE_CHECKING: + from src.Board import Board + WHITE = True BLACK = False @@ -10,11 +18,11 @@ class Rook (Piece): stringRep = 'R' value = 5 - def __init__(self, board, side, position, movesMade=0): + def __init__(self, board: Board, side: bool, position: C, movesMade: int = 0): super(Rook, self).__init__(board, side, position) self.movesMade = movesMade - def getPossibleMoves(self): + def getPossibleMoves(self) -> Iterator[Move]: currentPosition = self.position directions = [C(0, 1), C(0, -1), C(1, 0), C(-1, 0)] diff --git a/src/main.py b/src/main.py index 4bfb1e4..44bf3fb 100644 --- a/src/main.py +++ b/src/main.py @@ -1,16 +1,21 @@ +from __future__ import annotations + import argparse import random import sys +from typing import Optional from src.AI import AI from src.Board import Board from src.InputParser import InputParser +from src.Move import Move +from src.Piece import Piece WHITE = True BLACK = False -def askForPlayerSide(): +def askForPlayerSide() -> bool: playerChoiceInput = input( "What side would you like to play as [wB]? ").lower() if 'w' in playerChoiceInput: @@ -21,7 +26,7 @@ def askForPlayerSide(): return BLACK -def askForDepthOfAI(): +def askForDepthOfAI() -> int: depthInput = 2 try: depthInput = int(input("How deep should the AI look for moves?\n" @@ -40,7 +45,7 @@ def askForDepthOfAI(): return depthInput -def printCommandOptions(): +def printCommandOptions() -> None: undoOption = 'u : undo last move' printLegalMovesOption = 'l : show all legal moves' randomMoveOption = 'r : make a random move' @@ -52,39 +57,39 @@ def printCommandOptions(): print('\n'.join(options)) -def printAllLegalMoves(board, parser): +def printAllLegalMoves(board: Board, parser: InputParser) -> None: for move in parser.getLegalMovesWithNotation(board.currentSide, short=True): print(move.notation) -def getRandomMove(board, parser): +def getRandomMove(board: Board, parser: InputParser) -> Move: legalMoves = board.getAllMovesLegal(board.currentSide) randomMove = random.choice(legalMoves) randomMove.notation = parser.notationForMove(randomMove) return randomMove -def makeMove(move, board): +def makeMove(move: Move, board: Board) -> None: print("Making move : " + move.notation) board.makeMove(move) -def printPointAdvantage(board): +def printPointAdvantage(board: Board) -> None: print("Currently, the point difference is : " + str(board.getPointAdvantageOfSide(board.currentSide))) -def undoLastTwoMoves(board): +def undoLastTwoMoves(board: Board) -> None: if len(board.history) >= 2: board.undoLastMove() board.undoLastMove() -def printBoard(board): +def printBoard(board: Board) -> None: print() print(board) print() -def printGameMoves(history): +def printGameMoves(history: list[tuple[Move, Optional[Piece]]]) -> None: counter = 0 for num, mv in enumerate(history): if num % 2 == 0: @@ -97,7 +102,7 @@ def printGameMoves(history): print() -def startGame(board, playerSide, ai): +def startGame(board: Board, playerSide: bool, ai: AI) -> None: parser = InputParser(board, playerSide) while True: if board.isCheckmate(): @@ -155,7 +160,7 @@ def startGame(board, playerSide, ai): makeMove(move, board) printBoard(board) -def twoPlayerGame(board): +def twoPlayerGame(board: Board) -> None: parserWhite = InputParser(board, WHITE) parserBlack = InputParser(board, BLACK) while True: @@ -180,7 +185,6 @@ def twoPlayerGame(board): parser = parserWhite else: parser = parserBlack - move = None command = input("It's your move, {}.".format(board.currentSideRep()) + \ " Type '?' for options. ? ") if command.lower() == 'u': @@ -208,7 +212,7 @@ def twoPlayerGame(board): board = Board() -def main(): +def main() -> None: parser = argparse.ArgumentParser( prog="chess", description="A python program to play chess "