diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..9f31e4c --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 79 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 04b254e..dda3882 100644 --- a/.gitignore +++ b/.gitignore @@ -85,7 +85,7 @@ 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 +.python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. diff --git a/.python-version b/.python-version deleted file mode 100644 index d2577d9..0000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.7.7 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index afa5699..3acb8b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,5 +11,8 @@ Please note we have a code of conduct, please follow it in all your interactions build. 2. Update the README.md with details of changes to the interface, this includes new environment variables, exposed ports, useful file locations and container parameters. +3. Make sure that your code follows the formatting rules enforced within this repository. This can be checked by running +`flake8 src`, assuming that you have required tools installed from `requirements-dev.txt`. Also, make sure that +`mypy --show-error-codes --pretty --strict .` still runs happily against the changed code. **Working on your first Pull Request?** You can learn how from this free series [How to Contribute to an Open Source Project on GitHub](https://kcd.im/pull-request) diff --git a/mypy.ini b/mypy.ini index 1814fb0..ad6a550 100644 --- a/mypy.ini +++ b/mypy.ini @@ -6,4 +6,7 @@ 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 +no_implicit_optional = True + +[mypy-colored] +ignore_missing_imports = True \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..ed28e7c --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,9 @@ +flake8>=5.0.4 +mccabe>=0.7.0 +mypy>=0.981 +mypy-extensions>=0.4.3 +pycodestyle>=2.9.1 +pyflakes>=2.5.0 +tomli>=2.0.1 +types-setuptools>=65.4.0.0 +typing_extensions>=4.3.0 diff --git a/requirements.txt b/requirements.txt index cd8248a..4353f82 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1 @@ colored -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/setup.py b/setup.py index 51b4d4a..ca39542 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -from setuptools import find_packages, setup +from setuptools import setup setup( name='cl-chess', @@ -8,15 +8,9 @@ author='Marcus Buffett', author_email='marcusbuffett@me.com', url='https://github.com/marcusbuffett/command-line-chess', - #download_url='https://github.com/peterldowns/mypackage/tarball/0.1', - entry_points={ - 'console_scripts': [ - 'chess = src.main:main', - ], - }, - install_requires=[ - 'colored', - ], + # download_url='https://github.com/peterldowns/mypackage/tarball/0.1', + entry_points={'console_scripts': ['chess = src.main:main', ], }, + install_requires=['colored', ], keywords=['chess', 'game'], - classifiers=["Programming Language :: Python :: 3"], + classifiers=['Programming Language :: Python :: 3'], ) diff --git a/src/AI.py b/src/AI.py index 590ca8c..1b50e18 100644 --- a/src/AI.py +++ b/src/AI.py @@ -1,9 +1,6 @@ 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 @@ -15,7 +12,6 @@ class AI: - depth = 1 movesAnalyzed = 0 @@ -25,47 +21,6 @@ def __init__(self, board: Board, side: bool, depth: int): self.depth = depth self.parser = InputParser(self.board, 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 = \ - [(move, copy.deepcopy(self.board)) - for move in self.board.getAllMovesUnfiltered(side)] - legalMoves = p.starmap(self.returnMoveIfLegal, - unfilteredMovesWithBoard) - p.close() - p.join() - return list(filter(None, legalMoves)) - - def minChildrenOfNode(self, node: MoveNode) -> list[MoveNode]: - lowestNodes: list[MoveNode] = [] - for child in node.children: - if not lowestNodes: - lowestNodes.append(child) - elif child < lowestNodes[0]: - lowestNodes = [] - lowestNodes.append(child) - elif child == lowestNodes[0]: - lowestNodes.append(child) - return lowestNodes - - def maxChildrenOfNode(self, node: MoveNode) -> list[MoveNode]: - highestNodes: list[MoveNode] = [] - for child in node.children: - if not highestNodes: - highestNodes.append(child) - elif child < highestNodes[0]: - highestNodes = [] - highestNodes.append(child) - elif child == highestNodes[0]: - highestNodes.append(child) - return highestNodes - def getRandomMove(self) -> Move: legalMoves = list(self.board.getAllMovesLegal(self.side)) randomMove = random.choice(legalMoves) @@ -111,15 +66,16 @@ def populateNodeChildren(self, node: MoveNode) -> None: def getOptimalPointAdvantageForNode(self, node: MoveNode) -> int: if node.children: for child in node.children: - child.pointAdvantage = \ - self.getOptimalPointAdvantageForNode(child) + child.pointAdvantage = self.getOptimalPointAdvantageForNode( + child + ) # If the depth is divisible by 2, # it's a move for the AI's side, so return max if node.children[0].depth % 2 == 1: - return(max(node.children).pointAdvantage) + return max(node.children).pointAdvantage else: - return(min(node.children).pointAdvantage) + return min(node.children).pointAdvantage else: return node.pointAdvantage @@ -136,8 +92,9 @@ def makeBestMove(self) -> None: def bestMovesWithMoveTree(self, moveTree: list[MoveNode]) -> list[Move]: bestMoveNodes: list[MoveNode] = [] for moveNode in moveTree: - moveNode.pointAdvantage = \ - self.getOptimalPointAdvantageForNode(moveNode) + moveNode.pointAdvantage = self.getOptimalPointAdvantageForNode( + moveNode + ) if not bestMoveNodes: bestMoveNodes.append(moveNode) elif moveNode > bestMoveNodes[0]: @@ -148,18 +105,8 @@ def bestMovesWithMoveTree(self, moveTree: list[MoveNode]) -> list[Move]: return [node.move for node in bestMoveNodes] - 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) -> None: - moveToMake = self.getRandomMove() - self.board.makeMove(moveToMake) - -if __name__ == "__main__": +if __name__ == '__main__': mainBoard = Board() ai = AI(mainBoard, True, 3) print(mainBoard) diff --git a/src/Bishop.py b/src/Bishop.py index 5f2255e..11fc24b 100644 --- a/src/Bishop.py +++ b/src/Bishop.py @@ -13,12 +13,13 @@ BLACK = False -class Bishop (Piece): - +class Bishop(Piece): stringRep = 'B' value = 3 - def __init__(self, board: Board, side: bool, position: C, movesMade: int = 0): + def __init__( + self, board: Board, side: bool, position: C, movesMade: int = 0 + ) -> None: super(Bishop, self).__init__(board, side, position) self.movesMade = movesMade @@ -26,6 +27,7 @@ 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: - for move in self.movesInDirectionFromPos(currentPosition, - direction, self.side): + for move in self.movesInDirectionFromPos( + currentPosition, direction, self.side + ): yield move diff --git a/src/Board.py b/src/Board.py index 4b4709d..bbb00aa 100644 --- a/src/Board.py +++ b/src/Board.py @@ -1,8 +1,8 @@ from __future__ import annotations -from typing import Optional, no_type_check +from typing import Optional -from termcolor import colored +from colored import attr, bg, fg from src.Bishop import Bishop from src.Coordinate import Coordinate as C @@ -13,21 +13,19 @@ from src.Piece import Piece from src.Queen import Queen from src.Rook import Rook -import colored -from colored import fg, bg, attr WHITE = True BLACK = False -TILES = { - 0:'#769656', - 1:'#EEEED2', -} class Board: - - def __init__(self, mateInOne: bool = False, castleBoard: bool = False, - passant: bool = False, promotion: bool = False): + 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 @@ -38,31 +36,39 @@ def __init__(self, mateInOne: bool = False, castleBoard: bool = False, self.blackColor = 'black' self.isCheckered = False self.tileColors = { - 0:'#769656', - 1:'#BACA44', + 0: '#769656', + 1: '#BACA44', } if not mateInOne and not castleBoard and not passant and not promotion: - self.pieces.extend([Rook(self, BLACK, C(0, 7)), - Knight(self, BLACK, C(1, 7)), - Bishop(self, BLACK, C(2, 7)), - Queen(self, BLACK, C(3, 7)), - King(self, BLACK, C(4, 7)), - Bishop(self, BLACK, C(5, 7)), - Knight(self, BLACK, C(6, 7)), - Rook(self, BLACK, C(7, 7))]) + self.pieces.extend( + [ + Rook(self, BLACK, C(0, 7)), + Knight(self, BLACK, C(1, 7)), + Bishop(self, BLACK, C(2, 7)), + Queen(self, BLACK, C(3, 7)), + King(self, BLACK, C(4, 7)), + Bishop(self, BLACK, C(5, 7)), + Knight(self, BLACK, C(6, 7)), + Rook(self, BLACK, C(7, 7)), + ] + ) for x in range(8): self.pieces.append(Pawn(self, BLACK, C(x, 6))) for x in range(8): self.pieces.append(Pawn(self, WHITE, C(x, 1))) - self.pieces.extend([Rook(self, WHITE, C(0, 0)), - Knight(self, WHITE, C(1, 0)), - Bishop(self, WHITE, C(2, 0)), - Queen(self, WHITE, C(3, 0)), - King(self, WHITE, C(4, 0)), - Bishop(self, WHITE, C(5, 0)), - Knight(self, WHITE, C(6, 0)), - Rook(self, WHITE, C(7, 0))]) + self.pieces.extend( + [ + Rook(self, WHITE, C(0, 0)), + Knight(self, WHITE, C(1, 0)), + Bishop(self, WHITE, C(2, 0)), + Queen(self, WHITE, C(3, 0)), + King(self, WHITE, C(4, 0)), + Bishop(self, WHITE, C(5, 0)), + Knight(self, WHITE, C(6, 0)), + Rook(self, WHITE, C(7, 0)), + ] + ) elif promotion: pawnToPromote = Pawn(self, WHITE, C(1, 6)) @@ -87,7 +93,7 @@ def __init__(self, mateInOne: bool = False, castleBoard: bool = False, self.currentSide = WHITE return - def __str__(self): + def __str__(self) -> str: return self.wrapStringRep(self.makeUnicodeStringRep(self.pieces)) def undoLastMove(self) -> None: @@ -148,33 +154,37 @@ def undoLastMove(self) -> None: self.currentSide = not self.currentSide def isCheckmate(self) -> bool: - #Game continue even after checkmate + # Game continue even after checkmate if len(self.getAllMovesLegal(self.currentSide)) == 0: for move in self.getAllMovesUnfiltered(not self.currentSide): pieceToTake = move.pieceToCapture - if pieceToTake and pieceToTake.stringRep == "K": + if pieceToTake and pieceToTake.stringRep == 'K': return True return False def isStalemate(self) -> bool: - return len(self.getAllMovesLegal(self.currentSide)) == 0 and not self.isCheckmate() + return ( + len(self.getAllMovesLegal(self.currentSide)) == 0 + and not self.isCheckmate() + ) def noMatingMaterial(self) -> bool: if len(self.pieces) == 2: - return True # just the kings - if ( - len(self.pieces) == 3 - and any(piece.stringRep == "B" or piece.stringRep == "N" - for piece in self.pieces) + return True # just the kings + if len(self.pieces) == 3 and any( + piece.stringRep == 'B' or piece.stringRep == 'N' + for piece in self.pieces ): return True return False - def getLastMove(self) -> Move: # type: ignore[return] # TODO: add consistent return for else condition + # TODO: add consistent return for else condition + def getLastMove(self) -> Move: # type: ignore[return] if self.history: return self.history[-1][0] - def getLastPieceMoved(self) -> Piece: # type: ignore[return] # TODO: add consistent return for else condition + # TODO: add consistent return for else condition + def getLastPieceMoved(self) -> Piece: # type: ignore[return] if self.history: return self.history[-1][0].piece @@ -191,17 +201,14 @@ def addMoveToHistory(self, move: Move) -> None: self.history.append((move, None)) - def getCurrentSide(self) -> bool: - return self.currentSide - def makeUnicodeStringRep(self, pieces: list[Piece]) -> str: DISPLAY_LOOKUP = { - "R": '♜', - "N": '♞', - "B": '♝', - "K": '♚', - "Q": '♛', - "▲": '♟', + 'R': '♜', + 'N': '♞', + 'B': '♝', + 'K': '♚', + 'Q': '♛', + '▲': '♟', } stringRep = '' @@ -212,23 +219,33 @@ def makeUnicodeStringRep(self, pieces: list[Piece]) -> str: if p.position == C(x, y): piece = p break - bg_color = bg(self.tileColors[(x+y)%2]) if self.isCheckered else '' + bg_color = ( + bg(self.tileColors[(x + y) % 2]) + if self.isCheckered + else '' + ) pieceRep = bg_color + ' ' + attr(0) if piece: side = piece.side - color = self.whiteColor if side == WHITE else self.blackColor + color = ( + self.whiteColor if side == WHITE else self.blackColor + ) fg_color = fg(color) - pieceRep = fg_color + bg_color + DISPLAY_LOOKUP[piece.stringRep] + ' ' + attr(0) + pieceRep = fg_color + bg_color + \ + DISPLAY_LOOKUP[piece.stringRep] + ' ' + attr(0) + stringRep += pieceRep stringRep += '\n' return stringRep.rstrip() def wrapStringRep(self, stringRep: str) -> str: sRep = '\n'.join( - ['%d %s' % (8-r, s.rstrip()) - for r, s in enumerate(stringRep.split('\n'))] + - [' '*21, ' a b c d e f g h'] - ).rstrip() + [ + '%d %s' % (8 - r, s.rstrip()) + for r, s in enumerate(stringRep.split('\n')) + ] + + [' ' * 21, ' a b c d e f g h'] + ).rstrip() return sRep def rankOfPiece(self, piece: Piece) -> str: @@ -239,17 +256,18 @@ def fileOfPiece(self, piece: Piece) -> str: return str(piece.position[0]).translate(transTable) def getCoordinateNotationOfMove(self, move: Move) -> str: - notation = "" + notation = '' notation += self.positionToHumanCoord(move.oldPos) notation += self.positionToHumanCoord(move.newPos) if move.promotion: - notation += str(move.specialMovePiece.stringRep) # type: ignore[attr-defined] - + notation += str( + move.specialMovePiece.stringRep # type: ignore[attr-defined] # noqa: E501 + ) return notation def getCaptureNotation(self, move: Move, short: bool = True) -> str: - notation = "" + notation = '' pieceToMove = move.piece pieceToTake = move.pieceToCapture @@ -264,23 +282,26 @@ def getCaptureNotation(self, move: Move, short: bool = True) -> str: notation += self.positionToHumanCoord(move.newPos) if move.promotion: - notation += str(move.specialMovePiece.stringRep) # type: ignore[attr-defined, operator] - + notation += str( + move.specialMovePiece.stringRep # type: ignore[attr-defined, operator] # noqa: E501 + ) return notation def currentSideRep(self) -> str: - return "White" if self.currentSide else "Black" + return 'White' if self.currentSide else 'Black' - def getAlgebraicNotationOfMove(self, move: Move, short: bool = True) -> str: - notation = "" + def getAlgebraicNotationOfMove( + self, move: Move, short: bool = True + ) -> str: + notation = '' pieceToMove = move.piece pieceToTake = move.pieceToCapture if move.queensideCastle: - return "0-0-0" + return '0-0-0' if move.kingsideCastle: - return "0-0" + return '0-0' if not short or type(pieceToMove) is not Pawn: notation += pieceToMove.stringRep @@ -293,13 +314,17 @@ def getAlgebraicNotationOfMove(self, move: Move, short: bool = True) -> str: notation += self.positionToHumanCoord(move.newPos) if move.promotion: - notation += "=" + str(move.specialMovePiece.stringRep) # type: ignore[attr-defined] + notation += '=' + str( + move.specialMovePiece.stringRep # type: ignore[attr-defined] # noqa: E501 + ) return notation - def getAlgebraicNotationOfMoveWithFile(self, move: Move, short: bool = True) -> str: + def getAlgebraicNotationOfMoveWithFile( + self, move: Move, short: bool = True + ) -> str: # TODO: Use self.getAlgebraicNotationOfMove instead of repeating code - notation = "" + notation = '' pieceToMove = self.pieceAtPosition(move.oldPos) pieceToTake = self.pieceAtPosition(move.newPos) @@ -313,9 +338,11 @@ def getAlgebraicNotationOfMoveWithFile(self, move: Move, short: bool = True) -> notation += self.positionToHumanCoord(move.newPos) return notation - def getAlgebraicNotationOfMoveWithRank(self, move: Move, short: bool = True) -> str: + def getAlgebraicNotationOfMoveWithRank( + self, move: Move, short: bool = True + ) -> str: # TODO: Use self.getAlgebraicNotationOfMove instead of repeating code - notation = "" + notation = '' pieceToMove = self.pieceAtPosition(move.oldPos) pieceToTake = self.pieceAtPosition(move.newPos) @@ -332,9 +359,11 @@ def getAlgebraicNotationOfMoveWithRank(self, move: Move, short: bool = True) -> notation += self.positionToHumanCoord(move.newPos) return notation - def getAlgebraicNotationOfMoveWithFileAndRank(self, move: Move, short: bool = True) -> str: + def getAlgebraicNotationOfMoveWithFileAndRank( + self, move: Move, short: bool = True + ) -> str: # TODO: Use self.getAlgebraicNotationOfMove instead of repeating code - notation = "" + notation = '' pieceToMove = self.pieceAtPosition(move.oldPos) pieceToTake = self.pieceAtPosition(move.newPos) @@ -350,18 +379,9 @@ def getAlgebraicNotationOfMoveWithFileAndRank(self, move: Move, short: bool = Tr 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) - coord = [int(c)-1 for c in coord] - pos = C(coord[0], coord[1]) - return pos - def positionToHumanCoord(self, pos: C) -> str: transTable = str.maketrans('01234567', 'abcdefgh') - notation = str(pos[0]).translate(transTable) + str(pos[1]+1) + notation = str(pos[0]).translate(transTable) + str(pos[1] + 1) return notation def isValidPos(self, pos: C) -> bool: @@ -370,15 +390,8 @@ def isValidPos(self, pos: C) -> bool: 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: C) -> Piece: # type: ignore[return] # TODO: add consistent return for else condition + # TODO: add consistent return for else condition + def pieceAtPosition(self, pos: C) -> Piece: # type: ignore[return] for piece in self.pieces: if piece.position == pos: return piece @@ -389,35 +402,23 @@ def movePieceToPosition(self, piece: Piece, pos: C) -> None: 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: 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) # type: ignore[arg-type, attr-defined] + self.movePieceToPosition( + rookToMove, # type: ignore[arg-type] + move.rookMove.newPos # type: ignore[attr-defined] + ) kingToMove.movesMade += 1 rookToMove.movesMade += 1 # type: ignore[attr-defined] elif move.passant: pawnToMove = move.piece - pawnToTake = move.specialMovePiece # TODO fix specialMovePiece default type to be not None + # TODO fix specialMovePiece default type to be not None + pawnToTake = move.specialMovePiece pawnToMove.position = move.newPos self.pieces.remove(pawnToTake) # type: ignore[arg-type] pawnToMove.movesMade += 1 @@ -434,9 +435,9 @@ def makeMove(self, move: Move) -> None: # 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 # type: ignore[attr-defined] + self.points += move.specialMovePiece.value - 1 # type: ignore[attr-defined] # noqa: E501 if move.piece.side == BLACK: - self.points -= move.specialMovePiece.value - 1 # type: ignore[attr-defined] + self.points -= move.specialMovePiece.value - 1 # type: ignore[attr-defined] # noqa: E501 move.piece.movesMade += 1 else: @@ -463,9 +464,14 @@ def getPointValueOfSide(self, side: bool) -> int: return points def getPointAdvantageOfSide(self, side: bool) -> int: - return self.getPointValueOfSide(side) - self.getPointValueOfSide(not side) - - def getAllMovesUnfiltered(self, side: bool, includeKing: bool = True) -> list[Move]: + return ( + self.getPointValueOfSide(side) + - self.getPointValueOfSide(not side) + ) + + def getAllMovesUnfiltered( + self, side: bool, includeKing: bool = True + ) -> list[Move]: unfilteredMoves = [] for piece in self.pieces: if piece.side == side: diff --git a/src/InputParser.py b/src/InputParser.py index 100e688..3c8aad4 100644 --- a/src/InputParser.py +++ b/src/InputParser.py @@ -8,7 +8,6 @@ class InputParser: - def __init__(self, board: Board, side: bool): self.board = board self.side = side @@ -17,54 +16,73 @@ 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) - regexAlgebraicNotation = re.compile('(?i)0-0|0-0-0|(?:[KQRBNP]?[a-h]?[1-8]?x?[a-h][1-8]|[Pa-h]x?[a-h])(?:=?[QRBN])?') + regexAlgebraicNotation = re.compile( + '(?i)0-0|0-0-0|(?:[KQRBNP]?[a-h]?[1-8]?x?[a-h][1-8]|[Pa-h]x?[a-h])(?:=?[QRBN])?' # noqa: E501 + ) if regexAlgebraicNotation.match(humanInput): return self.moveForShortAlgebraicNotation(humanInput) if re.compile('(?i)O-O|O-O-O').match(humanInput): - return self.moveForShortAlgebraicNotation(humanInput.upper().replace("O","0")) - raise ValueError("Invalid move: %s" % humanInput) + return self.moveForShortAlgebraicNotation( + humanInput.upper().replace('O', '0') + ) + raise ValueError('Invalid move: %s' % humanInput) def moveForCoordinateNotation(self, notation: str) -> Move: for move in self.board.getAllMovesLegal(self.side): - if self.board.getCoordinateNotationOfMove(move).lower() == notation.lower(): + if self.board.getCoordinateNotationOfMove( + move).lower() == notation.lower(): move.notation = self.notationForMove(move) return move - raise ValueError("Illegal move: %s" % notation) + raise ValueError('Illegal move: %s' % notation) # Only handles SAN, not long-algebraic or descriptive def moveForShortAlgebraicNotation(self, notation: str) -> Move: - shortNotation = notation.replace("x","") + shortNotation = notation.replace('x', '') moves = self.getLegalMovesWithNotation(self.side, False) for move in moves: - if move.notation.replace("x","") == shortNotation: # Bxc3 versus bxc3 - return move + if move.notation.replace('x', '') == shortNotation: + return move # Bxc3 versus bxc3 for move in moves: - if move.notation.replace("x","").lower() == shortNotation.lower(): + if move.notation.replace('x', '').lower() == shortNotation.lower(): return move moves = self.getLegalMovesWithNotation(self.side, True) for move in moves: - if move.notation.replace("x","") == shortNotation: # Bxc3 versus bxc3 - return move + if move.notation.replace('x', '') == shortNotation: + return move # Bxc3 versus bxc3 for move in moves: - if move.notation.replace("x","").lower() == shortNotation.lower(): + if move.notation.replace('x', '').lower() == shortNotation.lower(): return move - shortNotation = notation.lower().replace("p","").replace("=","") + shortNotation = notation.lower().replace('p', '').replace('=', '') if re.compile('[a-h][1-8]?[qrbn]?').match(shortNotation): for move in moves: - if type(move.piece) is Pawn and not move.pieceToCapture and self.board.getCoordinateNotationOfMove(move).replace("=","").lower().endswith(shortNotation): + if type(move.piece) is Pawn and not move.pieceToCapture \ + and self.board.getCoordinateNotationOfMove( + move).replace('=', '').lower().endswith(shortNotation): return move for move in moves: - if type(move.piece) is Pawn and not move.pieceToCapture and re.sub("[1-8]", "", self.board.getCoordinateNotationOfMove(move)).replace("=","").lower().endswith(shortNotation): - return move # ASSUME lazy pawn move (P)c is unambiguous - shortNotation = shortNotation.lower().replace("x","") + if type(move.piece) is Pawn and not move.pieceToCapture \ + and re.sub( + '[1-8]', '', self.board.getCoordinateNotationOfMove( + move), ).replace('=', '').lower().endswith( + shortNotation): + return move # ASSUME lazy pawn move (P)c is unambiguous + shortNotation = shortNotation.lower().replace('x', '') if re.compile('[a-h]?[a-h][1-8]?[qrbn]?').match(shortNotation): for move in moves: - if type(move.piece) is Pawn and move.pieceToCapture and self.board.getCaptureNotation(move).replace("x","").lower().endswith(shortNotation): - return move # ASSUME lazier pawn capture (P)b(x)c3 is unambiguous + if type(move.piece) is Pawn and move.pieceToCapture \ + and self.board.getCaptureNotation( + move).replace('x', '').lower().endswith(shortNotation): + # ASSUME lazier pawn capture (P)b(x)c3 is unambiguous + return move for move in moves: - if type(move.piece) is Pawn and move.pieceToCapture and re.sub("[1-8]", "", self.board.getCaptureNotation(move).replace("x","")).lower().endswith(shortNotation): - return move # ASSUME laziest pawn capture (P)b(x)c is unambiguous - raise ValueError("Illegal move: %s" % notation) + if type(move.piece) is Pawn and move.pieceToCapture and re.sub( + '[1-8]', '', + self.board.getCaptureNotation(move).replace('x', + ''), + ).lower().endswith(shortNotation): + # ASSUME laziest pawn capture (P)b(x)c is unambiguous + return move + raise ValueError('Illegal move: %s' % notation) def notationForMove(self, move: Move) -> str: side = self.board.getSideOfMove(move) @@ -72,32 +90,44 @@ def notationForMove(self, move: Move) -> str: for m in moves: if m == move: return m.notation - return "" # return added to make mypy happy + return '' # return added to make mypy happy - def getLegalMovesWithNotation(self, side: bool, short: bool = True) -> list[Move]: + def getLegalMovesWithNotation( + self, side: bool, short: bool = True) -> list[Move]: moves = [] for legalMove in self.board.getAllMovesLegal(side): moves.append(legalMove) - legalMove.notation = self.board.getAlgebraicNotationOfMove(legalMove, short) + legalMove.notation = self.board.getAlgebraicNotationOfMove( + legalMove, short + ) duplicateNotationMoves = self.duplicateMovesFromMoves(moves) for duplicateMove in duplicateNotationMoves: - duplicateMove.notation = \ - self.board.getAlgebraicNotationOfMoveWithFile(duplicateMove, short) + duplicateMove.notation = self.board.getAlgebraicNotationOfMoveWithFile( # noqa: E501 + duplicateMove, short + ) duplicateNotationMoves = self.duplicateMovesFromMoves(moves) for duplicateMove in duplicateNotationMoves: - duplicateMove.notation = \ - self.board.getAlgebraicNotationOfMoveWithRank(duplicateMove, short) + duplicateMove.notation = self.board.getAlgebraicNotationOfMoveWithRank( # noqa: E501 + duplicateMove, short + ) duplicateNotationMoves = self.duplicateMovesFromMoves(moves) for duplicateMove in duplicateNotationMoves: - duplicateMove.notation = \ - self.board.getAlgebraicNotationOfMoveWithFileAndRank(duplicateMove, short) + duplicateMove.notation = self.board.getAlgebraicNotationOfMoveWithFileAndRank( # noqa: E501 + duplicateMove, short + ) return 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)) + 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 53da3b8..1951fe1 100644 --- a/src/King.py +++ b/src/King.py @@ -13,19 +13,28 @@ BLACK = False -class King (Piece): - +class King(Piece): stringRep = 'K' value = 100 - def __init__(self, board: Board, side: bool, position: C, movesMade: int = 0): + def __init__( + self, board: Board, side: bool, position: C, movesMade: int = 0 + ) -> None: super(King, self).__init__(board, side, position) self.movesMade = movesMade 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)] + 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), + ] for movement in movements: newPos = currentPos + movement if self.board.isValidPos(newPos): @@ -45,16 +54,20 @@ def getPossibleMoves(self) -> Iterator[Move]: kingsideRookMoved = True queensideRookMoved = True - kingsideCastlePositions = [self.position + C(1, 0), - self.position + C(2, 0)] + kingsideCastlePositions = [ + self.position + C(1, 0), + self.position + C(2, 0), + ] for pos in kingsideCastlePositions: if self.board.pieceAtPosition(pos): kingsideCastleBlocked = True break - queensideCastlePositions = [self.position - C(1, 0), - self.position - C(2, 0), - self.position - C(3, 0)] + queensideCastlePositions = [ + self.position - C(1, 0), + self.position - C(2, 0), + self.position - C(3, 0), + ] for pos in queensideCastlePositions: if self.board.pieceAtPosition(pos): queensideCastleBlocked = True @@ -63,54 +76,66 @@ def getPossibleMoves(self) -> Iterator[Move]: if kingsideCastleBlocked and queensideCastleBlocked: return - otherSideMoves = \ - self.board.getAllMovesUnfiltered(not self.side, - includeKing=False) + otherSideMoves = self.board.getAllMovesUnfiltered( + not self.side, includeKing=False + ) for move in otherSideMoves: if move.newPos == self.position: inCheck = True break - if move.newPos == self.position + C(1, 0) or \ - move.newPos == self.position + C(2, 0): + if ( + move.newPos == self.position + C(1, 0) + or move.newPos == self.position + C(2, 0) + ): kingsideCastleCheck = True - if move.newPos == self.position - C(1, 0) or \ - move.newPos == self.position - C(2, 0): + if ( + move.newPos == self.position - C(1, 0) + or move.newPos == self.position - C(2, 0) + ): queensideCastleCheck = True kingsideRookPos = self.position + C(3, 0) - kingsideRook = self.board.pieceAtPosition(kingsideRookPos) \ - if self.board.isValidPos(kingsideRookPos) \ - else None # TODO kingsideRook should never be None to match Move class requirements - if kingsideRook and \ - kingsideRook.stringRep == 'R' and \ - kingsideRook.movesMade == 0: + kingsideRook = ( + self.board.pieceAtPosition(kingsideRookPos) + if self.board.isValidPos(kingsideRookPos) + else None + ) # TODO kingsideRook shouldn't be None to match Move class + if ( + kingsideRook and kingsideRook.stringRep == 'R' + and kingsideRook.movesMade == 0 + ): kingsideRookMoved = False queensideRookPos = self.position - C(4, 0) - queensideRook = self.board.pieceAtPosition(queensideRookPos) \ - if self.board.isValidPos(queensideRookPos) \ - else None # TODO queensideRook should never be None to match Move class requirements - if queensideRook and \ - queensideRook.stringRep == 'R' and \ - queensideRook.movesMade == 0: + queensideRook = ( + self.board.pieceAtPosition(queensideRookPos) + if self.board.isValidPos(queensideRookPos) + else None + ) # TODO queensideRook shouldn't be None to match Move class + if ( + queensideRook and queensideRook.stringRep == 'R' + and queensideRook.movesMade == 0 + ): queensideRookMoved = False if not inCheck: - if not kingsideCastleBlocked and \ - not kingsideCastleCheck and \ - not kingsideRookMoved: + if ( + not kingsideCastleBlocked and not kingsideCastleCheck + and not kingsideRookMoved + ): move = Move(self, self.position + C(2, 0)) - rookMove = Move(kingsideRook, self.position + C(1, 0)) # type: ignore[arg-type] - move.specialMovePiece = self.board.pieceAtPosition(kingsideRookPos) # type: ignore[assignment] + rookMove = Move(kingsideRook, self.position + C(1, 0)) # type: ignore[arg-type] # noqa: E501 + move.specialMovePiece = self.board.pieceAtPosition(kingsideRookPos) # type: ignore[assignment] # noqa: E501 move.kingsideCastle = True move.rookMove = rookMove # type: ignore[assignment] yield move - if not queensideCastleBlocked and \ - not queensideCastleCheck and \ - not queensideRookMoved: + 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)) # type: ignore[arg-type] - move.specialMovePiece = self.board.pieceAtPosition(queensideRookPos) # type: ignore[assignment] + rookMove = Move(queensideRook, self.position - C(1, 0)) # type: ignore[arg-type] # noqa: E501 + move.specialMovePiece = self.board.pieceAtPosition(queensideRookPos) # type: ignore[assignment] # noqa: E501 move.queensideCastle = True move.rookMove = rookMove # type: ignore[assignment] yield move diff --git a/src/Knight.py b/src/Knight.py index 9d9d3b1..709561c 100644 --- a/src/Knight.py +++ b/src/Knight.py @@ -14,19 +14,28 @@ class Knight(Piece): - stringRep = 'N' value = 3 - def __init__(self, board: Board, side: bool, position: C, movesMade: int = 0): + def __init__( + self, board: Board, side: bool, position: C, movesMade: int = 0 + ) -> None: super(Knight, self).__init__(board, side, position) self.movesMade = movesMade 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), - C(1, -2), C(-1, -2), C(-1, 2)] + movements = [ + C(2, 1), + C(2, -1), + C(-2, 1), + C(-2, -1), + C(1, 2), + C(1, -2), + C(-1, -2), + C(-1, 2), + ] for movement in movements: newPos = currentPos + movement if board.isValidPos(newPos): diff --git a/src/Move.py b/src/Move.py index 4738a48..c571b36 100644 --- a/src/Move.py +++ b/src/Move.py @@ -6,11 +6,15 @@ from src.Coordinate import Coordinate as C from src.Piece import Piece -class Move: - def __init__(self, piece: Piece, newPos: C, pieceToCapture: Optional[Piece] = None): - self.notation = "" - self.check = False +class Move: + def __init__( + self, + piece: Piece, + newPos: C, + pieceToCapture: Optional[Piece] = None + ) -> None: + self.notation = '' self.checkmate = False self.kingsideCastle = False self.queensideCastle = False @@ -23,9 +27,11 @@ def __init__(self, piece: Piece, newPos: C, pieceToCapture: Optional[Piece] = No self.newPos = newPos self.pieceToCapture = pieceToCapture # For en passant and castling - self.specialMovePiece = None # TODO: this should be a 'Piece' type to satisfy mypy + # TODO: specialMovePiece should be a 'Piece' type to satisfy mypy + self.specialMovePiece = None # For castling - self.rookMove = None # TODO: this should be a 'Move' type to satisfy mypy + # TODO: rookMove should be a 'Move' type to satisfy mypy + self.rookMove = None def __str__(self) -> str: displayString = 'Old pos : ' + str(self.oldPos) + \ @@ -42,13 +48,16 @@ def __str__(self) -> str: 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: + if ( + self.oldPos == other.oldPos and self.newPos == other.newPos + and self.specialMovePiece == other.specialMovePiece + ): if not self.specialMovePiece: return True - if self.specialMovePiece and \ - self.specialMovePiece == other.specialMovePiece: + if ( + self.specialMovePiece + and self.specialMovePiece == other.specialMovePiece + ): return True else: return False @@ -57,7 +66,3 @@ def __eq__(self, other: object) -> bool: def __hash__(self) -> int: return hash((self.oldPos, self.newPos)) - - 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 5865398..4d2f40f 100644 --- a/src/MoveNode.py +++ b/src/MoveNode.py @@ -6,8 +6,12 @@ class MoveNode: - - def __init__(self, move: Move, children: list[MoveNode], parent: Optional[MoveNode]): + def __init__( + self, + move: Move, + children: list[MoveNode], + parent: Optional[MoveNode] + ) -> None: self.move = move self.children = children self.parent = parent @@ -21,7 +25,7 @@ def __str__(self) -> str: stringRep += "\n" for child in self.children: - stringRep += " " * self.getDepth() * 4 + stringRep += ' ' * self.getDepth() * 4 stringRep += str(child) return stringRep @@ -55,14 +59,6 @@ def __eq__(self, other: object) -> bool: return True return self.pointAdvantage == other.pointAdvantage - def getHighestNode(self) -> MoveNode: - highestNode = self - while True: - if highestNode.parent is not None: - highestNode = highestNode.parent - else: - return highestNode - def getDepth(self) -> int: depth = 1 highestNode = self diff --git a/src/Pawn.py b/src/Pawn.py index e307f0c..657d402 100644 --- a/src/Pawn.py +++ b/src/Pawn.py @@ -18,11 +18,12 @@ class Pawn(Piece): - stringRep = '▲' value = 1 - def __init__(self, board: Board, side: bool, position: C, movesMade: int = 0): + def __init__( + self, board: Board, side: bool, position: C, movesMade: int = 0 + ) -> None: super(Pawn, self).__init__(board, side, position) self.movesMade = movesMade @@ -38,15 +39,16 @@ def getPossibleMoves(self) -> Iterator[Move]: if self.board.pieceAtPosition(advanceOnePosition) is None: col = advanceOnePosition[1] if col == 7 or col == 0: - piecesForPromotion = \ - [Rook(self.board, self.side, advanceOnePosition), - Knight(self.board, self.side, advanceOnePosition), - Bishop(self.board, self.side, advanceOnePosition), - Queen(self.board, self.side, advanceOnePosition)] + piecesForPromotion = [ + Rook(self.board, self.side, advanceOnePosition), + Knight(self.board, self.side, advanceOnePosition), + Bishop(self.board, self.side, advanceOnePosition), + Queen(self.board, self.side, advanceOnePosition), + ] for piece in piecesForPromotion: move = Move(self, advanceOnePosition) move.promotion = True - move.specialMovePiece = piece # type: ignore[assignment] + move.specialMovePiece = piece # type: ignore[assignment] # noqa: E501 yield move else: yield Move(self, advanceOnePosition) @@ -56,13 +58,19 @@ def getPossibleMoves(self) -> Iterator[Move]: movement = C(0, 2) if self.side == WHITE else C(0, -2) advanceTwoPosition = currentPosition + movement if self.board.isValidPos(advanceTwoPosition): - if self.board.pieceAtPosition(advanceTwoPosition) is None and \ - self.board.pieceAtPosition(advanceOnePosition) is None: + if ( + self.board.pieceAtPosition(advanceTwoPosition) is None + and self.board.pieceAtPosition( + advanceOnePosition) is None + ): yield Move(self, advanceTwoPosition) # Pawn takes - movements = [C(1, 1), C(-1, 1)] \ - if self.side == WHITE else [C(1, -1), C(-1, -1)] + movements = ( + [C(1, 1), C(-1, 1)] + if self.side == WHITE + else [C(1, -1), C(-1, -1)] + ) for movement in movements: newPosition = self.position + movement @@ -72,22 +80,30 @@ def getPossibleMoves(self) -> Iterator[Move]: col = newPosition[1] # Promotions if col == 7 or col == 0: - piecesForPromotion = \ - [Rook(self.board, self.side, newPosition), - Knight(self.board, self.side, newPosition), - Bishop(self.board, self.side, newPosition), - Queen(self.board, self.side, newPosition)] + piecesForPromotion = [ + Rook(self.board, self.side, newPosition), + Knight(self.board, self.side, newPosition), + Bishop(self.board, self.side, newPosition), + Queen(self.board, self.side, newPosition), + ] for piece in piecesForPromotion: - move = Move(self, newPosition, pieceToCapture=pieceToTake) + move = Move( + self, newPosition, pieceToCapture=pieceToTake + ) move.promotion = True - move.specialMovePiece = piece # type: ignore[assignment] + move.specialMovePiece = piece # type: ignore[assignment] # noqa: E501 yield move else: - yield Move(self, newPosition, - pieceToCapture=pieceToTake) + yield Move( + self, newPosition, pieceToCapture=pieceToTake + ) # En passant - movements = [C(1, 1), C(-1, 1)] if self.side == WHITE else [C(1, -1), C(-1, -1)] + movements = ( + [C(1, 1), C(-1, 1)] + if self.side == WHITE + else [C(1, -1), C(-1, -1)] + ) for movement in movements: posBesidePawn = self.position + C(movement[0], 0) if not self.board.isValidPos(posBesidePawn): @@ -98,17 +114,24 @@ def getPossibleMoves(self) -> Iterator[Move]: lastMove = self.board.getLastMove() if lastMove: - if lastMove.newPos - lastMove.oldPos == C(0, 2) or \ - lastMove.newPos - lastMove.oldPos == C(0, -2): + if ( + lastMove.newPos - lastMove.oldPos == C(0, 2) + or lastMove.newPos - lastMove.oldPos == C(0, -2) + ): lastMoveWasAdvanceTwo = True - if pieceBesidePawn and \ - pieceBesidePawn.stringRep == Pawn.stringRep and \ - pieceBesidePawn.side != self.side and \ - lastPieceMoved is pieceBesidePawn and \ - lastMoveWasAdvanceTwo: - move = Move(self, self.position + movement, - pieceToCapture=pieceBesidePawn) + if ( + pieceBesidePawn + and pieceBesidePawn.stringRep == Pawn.stringRep + and pieceBesidePawn.side != self.side + and lastPieceMoved is pieceBesidePawn + and lastMoveWasAdvanceTwo + ): + move = Move( + self, + self.position + movement, + pieceToCapture=pieceBesidePawn, + ) move.passant = True - move.specialMovePiece = pieceBesidePawn # type: ignore[assignment] + move.specialMovePiece = pieceBesidePawn # type: ignore[assignment] # noqa: E501 yield move diff --git a/src/Piece.py b/src/Piece.py index 1619060..40fa465 100644 --- a/src/Piece.py +++ b/src/Piece.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Iterator +from typing import TYPE_CHECKING, Iterator from src.Coordinate import Coordinate as C from src.Move import Move @@ -15,11 +15,12 @@ class Piece: - stringRep: str value: int - def __init__(self, board: Board, side: bool, position: C, movesMade: int = 0): + def __init__( + self, board: Board, side: bool, position: C, movesMade: int = 0 + ) -> None: self.board = board self.side = side self.position = position @@ -33,7 +34,9 @@ def __str__(self) -> str: ' -- Value : ' + str(self.value) + \ " -- Moves made : " + str(self.movesMade) - def movesInDirectionFromPos(self, pos: C, direction: C, side: bool) -> Iterator[Move]: + 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 @@ -50,17 +53,13 @@ def movesInDirectionFromPos(self, pos: C, direction: C, side: bool) -> Iterator[ 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 \ - self.__class__ == other.__class__: + if ( + self.board == other.board and self.side == other.side + and self.position == other.position + and self.__class__ == other.__class__ + ): return True return False - 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 d63bcea..db268a7 100644 --- a/src/Queen.py +++ b/src/Queen.py @@ -14,20 +14,30 @@ class Queen(Piece): - stringRep = 'Q' value = 9 - def __init__(self, board: Board, side: bool, position: C, movesMade: int = 0): + def __init__( + self, board: Board, side: bool, position: C, movesMade: int = 0 + ) -> None: super(Queen, self).__init__(board, side, position) self.movesMade = movesMade def getPossibleMoves(self) -> Iterator[Move]: currentPosition = self.position - directions = [C(0, 1), C(0, -1), C(1, 0), C(-1, 0), C(1, 1), - C(1, -1), C(-1, 1), C(-1, -1)] + directions = [ + C(0, 1), + C(0, -1), + C(1, 0), + C(-1, 0), + C(1, 1), + C(1, -1), + C(-1, 1), + C(-1, -1), + ] for direction in directions: - for move in self.movesInDirectionFromPos(currentPosition, - direction, self.side): + for move in self.movesInDirectionFromPos( + currentPosition, direction, self.side + ): yield move diff --git a/src/Rook.py b/src/Rook.py index e6fc0a4..84d7a73 100644 --- a/src/Rook.py +++ b/src/Rook.py @@ -13,12 +13,13 @@ BLACK = False -class Rook (Piece): - +class Rook(Piece): stringRep = 'R' value = 5 - def __init__(self, board: Board, side: bool, position: C, movesMade: int = 0): + def __init__( + self, board: Board, side: bool, position: C, movesMade: int = 0 + ) -> None: super(Rook, self).__init__(board, side, position) self.movesMade = movesMade @@ -27,6 +28,7 @@ def getPossibleMoves(self) -> Iterator[Move]: directions = [C(0, 1), C(0, -1), C(1, 0), C(-1, 0)] for direction in directions: - for move in self.movesInDirectionFromPos(currentPosition, - direction, self.side): + for move in self.movesInDirectionFromPos( + currentPosition, direction, self.side + ): yield move diff --git a/src/main.py b/src/main.py index 18c0911..f860979 100644 --- a/src/main.py +++ b/src/main.py @@ -17,31 +17,40 @@ def askForPlayerSide() -> bool: playerChoiceInput = input( - "What side would you like to play as [wB]? ").lower() + 'What side would you like to play as [wB]? ' + ).lower() if 'w' in playerChoiceInput: - print("You will play as white") + print('You will play as white') return WHITE else: - print("You will play as black") + print('You will play as black') return BLACK def askForDepthOfAI() -> int: depthInput = 2 try: - depthInput = int(input("How deep should the AI look for moves?\n" - "Warning : values above 3 will be very slow." - " [2]? ")) - while depthInput<=0: - depthInput = int(input("How deep should the AI look for moves?\n" - "Warning : values above 3 will be very slow. " - "Your input must be above 0." - " [2]? ")) + depthInput = int( + input( + 'How deep should the AI look for moves?\n' + 'Warning : values above 3 will be very slow.' + ' [2]? ' + ) + ) + while depthInput <= 0: + depthInput = int( + input( + 'How deep should the AI look for moves?\n' + 'Warning : values above 3 will be very slow. ' + 'Your input must be above 0.' + ' [2]? ' + ) + ) except KeyboardInterrupt: sys.exit() - except: - print("Invalid input, defaulting to 2") + except Exception: + print('Invalid input, defaulting to 2') return depthInput @@ -52,13 +61,22 @@ def printCommandOptions() -> None: printGameMoves = 'gm: moves of current game in PGN format' quitOption = 'quit : resign' moveOption = 'a3, Nc3, Qxa2, etc : make the move' - options = [undoOption, printLegalMovesOption, randomMoveOption, printGameMoves, - quitOption, moveOption, '', ] + options = [ + undoOption, + printLegalMovesOption, + randomMoveOption, + printGameMoves, + quitOption, + moveOption, + '', + ] print('\n'.join(options)) def printAllLegalMoves(board: Board, parser: InputParser) -> None: - for move in parser.getLegalMovesWithNotation(board.currentSide, short=True): + for move in parser.getLegalMovesWithNotation( + board.currentSide, short=True + ): print(move.notation) @@ -70,35 +88,32 @@ def getRandomMove(board: Board, parser: InputParser) -> Move: def makeMove(move: Move, board: Board) -> None: - print("Making move : " + move.notation) + print('Making move : ' + move.notation) board.makeMove(move) -def printPointAdvantage(board: Board) -> None: - print("Currently, the point difference is : " + - str(board.getPointAdvantageOfSide(board.currentSide))) - - def undoLastTwoMoves(board: Board) -> None: if len(board.history) >= 2: board.undoLastMove() board.undoLastMove() + def printBoard(board: Board) -> None: print() print(board) print() + def printGameMoves(history: list[tuple[Move, Optional[Piece]]]) -> None: counter = 0 for num, mv in enumerate(history): if num % 2 == 0: if counter % 6 == 0: print() - print(f'{counter + 1}.', end=" ") + print(f'{counter + 1}.', end=' ') counter += 1 - print(mv[0].notation, end=" ") + print(mv[0].notation, end=' ') print() @@ -107,27 +122,26 @@ def startGame(board: Board, playerSide: bool, ai: AI) -> None: while True: if board.isCheckmate(): if board.currentSide == playerSide: - print("Checkmate, you lost") + print('Checkmate, you lost') else: - print("Checkmate! You won!") + print('Checkmate! You won!') printGameMoves(board.history) return if board.isStalemate(): - print("Stalemate") + print('Stalemate') printGameMoves(board.history) return if board.noMatingMaterial(): - print("Draw due to no mating material") + print('Draw due to no mating material') printGameMoves(board.history) return if board.currentSide == playerSide: # printPointAdvantage(board) move = None - command = input("It's your move." - " Type '?' for options. ? ") + command = input("It's your move." " Type '?' for options. ? ") if command.lower() == 'u': undoLastTwoMoves(board) printBoard(board) @@ -148,35 +162,36 @@ def startGame(board: Board, playerSide: bool, ai: AI) -> None: if move is None: move = parser.parse(command) except ValueError as error: - print("%s" % error) + print('%s' % error) continue makeMove(move, board) printBoard(board) else: - print("AI thinking...") + print('AI thinking...') move = ai.getBestMove() move.notation = parser.notationForMove(move) makeMove(move, board) printBoard(board) + def twoPlayerGame(board: Board) -> None: parserWhite = InputParser(board, WHITE) parserBlack = InputParser(board, BLACK) while True: printBoard(board) if board.isCheckmate(): - print("Checkmate") + print('Checkmate') printGameMoves(board.history) return if board.isStalemate(): - print("Stalemate") + print('Stalemate') printGameMoves(board.history) return if board.noMatingMaterial(): - print("Draw due to no mating material") + print('Draw due to no mating material') printGameMoves(board.history) return @@ -185,8 +200,10 @@ def twoPlayerGame(board: Board) -> None: parser = parserWhite else: parser = parserBlack - command = input("It's your move, {}.".format(board.currentSideRep()) + \ - " Type '?' for options. ? ") + command = input( + "It's your move, {}.".format(board.currentSideRep()) + + " Type '?' for options. ? " + ) if command.lower() == 'u': undoLastTwoMoves(board) continue @@ -205,27 +222,28 @@ def twoPlayerGame(board: Board) -> None: try: move = parser.parse(command) except ValueError as error: - print("%s" % error) + print('%s' % error) continue makeMove(move, board) board = Board() + def main() -> None: parser = argparse.ArgumentParser( - prog="chess", - description="A python program to play chess " - "against an AI in the terminal.", + prog='chess', + description='A python program to play chess ' + 'against an AI in the terminal.', formatter_class=argparse.ArgumentDefaultsHelpFormatter, - epilog="Enjoy the game!" + epilog='Enjoy the game!', ) parser.add_argument( '-t', '--two', action='store_true', default=False, - help="to play a 2-player game" + help='to play a 2-player game', ) parser.add_argument( '-w', @@ -233,7 +251,7 @@ def main() -> None: action='store', default='white', metavar='W', - help="color for white player" + help='color for white player', ) parser.add_argument( '-b', @@ -241,14 +259,14 @@ def main() -> None: action='store', default='black', metavar='B', - help="color for black player" + help='color for black player', ) parser.add_argument( '-c', '--checkered', action='store_true', default=False, - help="use checkered theme for the chess board", + help='use checkered theme for the chess board', ) args = parser.parse_args() @@ -269,5 +287,6 @@ def main() -> None: except KeyboardInterrupt: sys.exit() -if __name__ == "__main__": + +if __name__ == '__main__': main()