From e68ab3ea5ed91459a48c302371d7b319ada6f3f9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Nils=20Forss=C3=A9n?= Date: Thu, 18 Sep 2025 02:00:02 +0200 Subject: [PATCH 1/1] initial --- LICENSE | Bin 0 -> 2140 bytes README.md | 12 + build/lib/pawnshop/ChessBoard.py | 554 ++++++++++++++++++ build/lib/pawnshop/ChessVector.py | 200 +++++++ build/lib/pawnshop/Exceptions.py | 42 ++ build/lib/pawnshop/GameNotations.py | 194 ++++++ build/lib/pawnshop/Moves.py | 397 +++++++++++++ build/lib/pawnshop/Pieces.py | 423 +++++++++++++ build/lib/pawnshop/Utils.py | 159 +++++ build/lib/pawnshop/__init__.py | 6 + .../pawnshop/configurations/ClassicConfig.py | 66 +++ .../configurations/DefaultConfig.JSON | 12 + .../configurations/FourPlayerConfig.py | 139 +++++ build/lib/pawnshop/configurations/__init__.py | 0 config.pypirc | 7 + dist/pawnshop-1.0.3-py3-none-any.whl | Bin 0 -> 21143 bytes dist/pawnshop-1.0.3.tar.gz | Bin 0 -> 18053 bytes pawnshop.egg-info/PKG-INFO | 27 + pawnshop.egg-info/SOURCES.txt | 25 + pawnshop.egg-info/dependency_links.txt | 1 + pawnshop.egg-info/top_level.txt | 1 + pawnshop/ChessBoard.py | 554 ++++++++++++++++++ pawnshop/ChessVector.py | 200 +++++++ pawnshop/Exceptions.py | 42 ++ pawnshop/GameNotations.py | 194 ++++++ pawnshop/Moves.py | 397 +++++++++++++ pawnshop/Pieces.py | 423 +++++++++++++ pawnshop/Utils.py | 159 +++++ pawnshop/__init__.py | 6 + .../__pycache__/ChessBoard.cpython-37.pyc | Bin 0 -> 18076 bytes .../__pycache__/ChessBoard.cpython-39.pyc | Bin 0 -> 18510 bytes .../__pycache__/ChessVector.cpython-37.pyc | Bin 0 -> 5833 bytes .../__pycache__/ChessVector.cpython-39.pyc | Bin 0 -> 6772 bytes .../__pycache__/ClassicConfig.cpython-37.pyc | Bin 0 -> 2349 bytes .../__pycache__/ClassicConfig.cpython-39.pyc | Bin 0 -> 2340 bytes .../__pycache__/Exceptions.cpython-37.pyc | Bin 0 -> 2568 bytes .../__pycache__/Exceptions.cpython-39.pyc | Bin 0 -> 2286 bytes .../FourPlayerConfig.cpython-37.pyc | Bin 0 -> 4012 bytes .../FourPlayerConfig.cpython-39.pyc | Bin 0 -> 4003 bytes .../__pycache__/GameNotations.cpython-39.pyc | Bin 0 -> 4902 bytes pawnshop/__pycache__/Moves.cpython-37.pyc | Bin 0 -> 6769 bytes pawnshop/__pycache__/Moves.cpython-39.pyc | Bin 0 -> 12369 bytes pawnshop/__pycache__/Pieces.cpython-37.pyc | Bin 0 -> 8908 bytes pawnshop/__pycache__/Pieces.cpython-39.pyc | Bin 0 -> 12332 bytes pawnshop/__pycache__/Utils.cpython-37.pyc | Bin 0 -> 2361 bytes pawnshop/__pycache__/Utils.cpython-39.pyc | Bin 0 -> 4378 bytes pawnshop/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 148 bytes pawnshop/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 269 bytes pawnshop/configurations/ClassicConfig.py | 66 +++ pawnshop/configurations/DefaultConfig.JSON | 12 + pawnshop/configurations/FourPlayerConfig.py | 139 +++++ pawnshop/configurations/__init__.py | 0 .../__pycache__/ClassicConfig.cpython-39.pyc | Bin 0 -> 2395 bytes .../FourPlayerConfig.cpython-39.pyc | Bin 0 -> 4058 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 167 bytes setup.py | 30 + .../test_1.cpython-39-pytest-6.2.2.pyc | Bin 0 -> 2830 bytes tests/__pycache__/test_1.cpython-39.pyc | Bin 0 -> 2279 bytes .../test_2.cpython-39-pytest-6.2.2.pyc | Bin 0 -> 863 bytes tests/__pycache__/test_2.cpython-39.pyc | Bin 0 -> 754 bytes tests/test_1.py | 128 ++++ tests/test_2.py | 29 + 62 files changed, 4644 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build/lib/pawnshop/ChessBoard.py create mode 100644 build/lib/pawnshop/ChessVector.py create mode 100644 build/lib/pawnshop/Exceptions.py create mode 100644 build/lib/pawnshop/GameNotations.py create mode 100644 build/lib/pawnshop/Moves.py create mode 100644 build/lib/pawnshop/Pieces.py create mode 100644 build/lib/pawnshop/Utils.py create mode 100644 build/lib/pawnshop/__init__.py create mode 100644 build/lib/pawnshop/configurations/ClassicConfig.py create mode 100644 build/lib/pawnshop/configurations/DefaultConfig.JSON create mode 100644 build/lib/pawnshop/configurations/FourPlayerConfig.py create mode 100644 build/lib/pawnshop/configurations/__init__.py create mode 100644 config.pypirc create mode 100644 dist/pawnshop-1.0.3-py3-none-any.whl create mode 100644 dist/pawnshop-1.0.3.tar.gz create mode 100644 pawnshop.egg-info/PKG-INFO create mode 100644 pawnshop.egg-info/SOURCES.txt create mode 100644 pawnshop.egg-info/dependency_links.txt create mode 100644 pawnshop.egg-info/top_level.txt create mode 100644 pawnshop/ChessBoard.py create mode 100644 pawnshop/ChessVector.py create mode 100644 pawnshop/Exceptions.py create mode 100644 pawnshop/GameNotations.py create mode 100644 pawnshop/Moves.py create mode 100644 pawnshop/Pieces.py create mode 100644 pawnshop/Utils.py create mode 100644 pawnshop/__init__.py create mode 100644 pawnshop/__pycache__/ChessBoard.cpython-37.pyc create mode 100644 pawnshop/__pycache__/ChessBoard.cpython-39.pyc create mode 100644 pawnshop/__pycache__/ChessVector.cpython-37.pyc create mode 100644 pawnshop/__pycache__/ChessVector.cpython-39.pyc create mode 100644 pawnshop/__pycache__/ClassicConfig.cpython-37.pyc create mode 100644 pawnshop/__pycache__/ClassicConfig.cpython-39.pyc create mode 100644 pawnshop/__pycache__/Exceptions.cpython-37.pyc create mode 100644 pawnshop/__pycache__/Exceptions.cpython-39.pyc create mode 100644 pawnshop/__pycache__/FourPlayerConfig.cpython-37.pyc create mode 100644 pawnshop/__pycache__/FourPlayerConfig.cpython-39.pyc create mode 100644 pawnshop/__pycache__/GameNotations.cpython-39.pyc create mode 100644 pawnshop/__pycache__/Moves.cpython-37.pyc create mode 100644 pawnshop/__pycache__/Moves.cpython-39.pyc create mode 100644 pawnshop/__pycache__/Pieces.cpython-37.pyc create mode 100644 pawnshop/__pycache__/Pieces.cpython-39.pyc create mode 100644 pawnshop/__pycache__/Utils.cpython-37.pyc create mode 100644 pawnshop/__pycache__/Utils.cpython-39.pyc create mode 100644 pawnshop/__pycache__/__init__.cpython-37.pyc create mode 100644 pawnshop/__pycache__/__init__.cpython-39.pyc create mode 100644 pawnshop/configurations/ClassicConfig.py create mode 100644 pawnshop/configurations/DefaultConfig.JSON create mode 100644 pawnshop/configurations/FourPlayerConfig.py create mode 100644 pawnshop/configurations/__init__.py create mode 100644 pawnshop/configurations/__pycache__/ClassicConfig.cpython-39.pyc create mode 100644 pawnshop/configurations/__pycache__/FourPlayerConfig.cpython-39.pyc create mode 100644 pawnshop/configurations/__pycache__/__init__.cpython-39.pyc create mode 100644 setup.py create mode 100644 tests/__pycache__/test_1.cpython-39-pytest-6.2.2.pyc create mode 100644 tests/__pycache__/test_1.cpython-39.pyc create mode 100644 tests/__pycache__/test_2.cpython-39-pytest-6.2.2.pyc create mode 100644 tests/__pycache__/test_2.cpython-39.pyc create mode 100644 tests/test_1.py create mode 100644 tests/test_2.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..623c1ae716be347bde36b3f38555be4db4959eac GIT binary patch literal 2140 zcmai#OHbQS5QS%LssF)CkPv+=y9?nJHE~c#cq|DdkSgHFPKx@I`zP9dXC@aXY1ImW zedlrJ%$d37*Uz4HY-qW4ZD#j2u@|fPoAJ}O(iZmJDm;(&WJ{vs;eR1cryM-_CN`!*4t`~Kc$Zin>;TKuy%XL_-gD|c z5m6C2asLu~?m5a|I9k1~P=dO|Hv>_>j~*$E=qOubF2M{o%AAt(>NQkVY##h6?j^Z1 zY{4(bBg=~3q?l?g@kf7C-safV}!~X<}0ej43eIz^-OQ7GG(4| z)_l{n7H}BDL~1{|U$Kex*gFZ#`Yp2AM&ZRg-czOGBnER=D>%$tgS0D&{jPQmj`Y?3 z#NI^z>7;4v7NnS07?DXR%seRO(V!pL~reen3js3bq-_pK;+3a~Uy`1dQbERrhGddF6)cfvT-Of^1+R-`j zW5?Otu;@3oH=;87(~WDU6SLDUX!ddAb+3fDAhz1*?|;)gT=#!&CuCn*pD)nRuB~MQ zw{)=Oe1isfuK3CwV?D;w!misWzc%99zkugNjr>a(2m|5@&bLGeS$Tz@z7Su4q}&s` z@d_tksal_ELhfPh@>E5|DWi>L#98lW*pAlq3bNu0tUP^voiD<*9>nB-9STBgXBrCd?dSY7;TYxd} z=NW!6kXnaC$QNZwz8aKxMb zYjwal;;B>9RZL2Xw}|NZ432s4@hiS*R+?BSthpt$P3^QFnm~$5{b?JdN*hlH6rEyE z#9mrxgXGoxHA`t68?sqd+b4w;r8(%7Hbe^doqfhLwliu7Eis9Dn(Zc+wAD%Lzuk~P fNgFnD9)B{-U3E<^cuBjMab)WM+V Game Over! +## Background +This is my first real project to scale (> 1000 lines of code) and the first to be published to [PyPi](https://pypi.org/project/pawnshop/). + +The project has certainly been a product of time with other hobby projects and school often taking priority. This along with multiple mid-project large scale refractorizations, reconsiderations and laziness should explain the unorganized codebase. + +Needless to say, I've attempted to document the code well and create a proper package that I can "proudly" publish, Although I still don't recommend anyone to continue developing or using this outside of simple projects like this. There are definitely better alternatives out there. + +Some central documentation, more extensive testing and usage examples would have been beneficial, but as of now I just want to continue with other projects and leave this as finished for the time being. diff --git a/build/lib/pawnshop/ChessBoard.py b/build/lib/pawnshop/ChessBoard.py new file mode 100644 index 0000000..b945643 --- /dev/null +++ b/build/lib/pawnshop/ChessBoard.py @@ -0,0 +1,554 @@ +# ChessBoard.py + +import json +import os +from copy import deepcopy, copy +from functools import wraps +from typing import Union, List, Dict, Generator + +from .ChessVector import ChessVector +from .Pieces import * +from .Moves import * +from .configurations import ClassicConfig, FourPlayerConfig +from .Utils import countAlpha, getResourcePath +from .Exceptions import * + + +def _defaultColors(func): + @wraps(func) + def wrapper(self, *colors): + if not colors: + colors = self.getColors() + returned = func(self, *colors) + if isinstance(returned, dict) and len(returned) == 1: + returned = returned.pop(*colors) + return returned + return wrapper + + +class Board(): + """Board object for storing and moving pieces + + :param config: Board configuration (defaults to emtpy board) + """ + + def __init__(self, config={}): + + self._board = [] + with open(getResourcePath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "configurations/DefaultConfig.JSON")), "r") as default: + dConfig = json.load(default) + + self._rows = config.get("rows") or dConfig.get("rows") + self._cols = config.get("cols") or dConfig.get("cols") + self._pieces = config.get("pieces") or dConfig.get("pieces") + self._moves = config.get("moves") or dConfig.get("moves") + self._promoteTo = config.get("promoteTo") or dConfig.get("promoteTo") + self._promoteFrom = config.get("promoteFrom") or dConfig.get("promoteFrom") + self._promoteAt = config.get("promoteAt") or dConfig.get("promteAt") + self._turnorder = config.get("turnorder") or dConfig.get("turnorder") + + try: + self.currentTurn = self._turnorder[0] + except IndexError: + self.currentTurn = None + self._board = [[Empty(ChessVector((row, col))) for col in range(self._cols)] for row in range(self._rows)] + + for color, pieceList in self._pieces.items(): + for piece in pieceList: + self[piece.vector] = piece + + for vec in config.get("disabled") or dConfig.get("disabled"): + self[vec] = Disabled(vec) + + self._checks = {key: False for key in self._pieces.keys()} + self._checkmates = copy(self._checks) + self._kings = {key: [piece for piece in self._pieces[key] if isinstance(piece, King)] for key in self._pieces.keys()} + self._history = [] + + self.checkForCheck() + + def __eq__(self, other): + for p1, p2 in zip(self, other): + if type(p1) == type(p2): + continue + else: + break + + else: + return True + return False + + def __ne__(self, other): + return not self == other + + def __iter__(self): + """Iterates through all positions in board + + Use iterPieces() method to iterate through pieces of board. + """ + for p in [p for row in self._board for p in row]: + yield p + + def __str__(self): + string = "\n" + ending = "\t|\n\n\t\t__" + ("\t__" * self._cols) + "\n\n\t\t" + alpha = countAlpha() + + for row in self._board: + + num, char = next(alpha) + + string += str(self._rows - num) + "\t|\t\t" + + for piece in row: + string += str(piece) + "\t" + + string += "\n\n" + + ending += "\t" + char.upper() + + return string + ending + "\n\n" + + def __setitem__(self, index, item): + + try: + iter(item) + except TypeError: + item = [item] + + try: + iter(index) + except TypeError: + index = [index] + + if len(index) != len(item): + raise ValueError("List index expected {0} values to unpack but {1} were given".format( + len(item), len(index))) + + for i, vec in enumerate(index): + + if isinstance(self._board[vec.row][vec.col], Disabled): + raise DisabledError(vec.getStr(self)) + + item1 = self._board[vec.row][vec.col] + item2 = item[i] + + if not isinstance(item2, Disabled): + + if not isinstance(item1, Empty): + self._removePiece(item1) + + if not isinstance(item2, Empty): + + if item2 in self.iterPieces(item2.color): + pass + else: + self._addPiece(item2, vec) + + self._board[vec.row][vec.col] = item2 + + def __getitem__(self, index): + res = [] + + try: + iter(index) + except TypeError: + index = [index] + + for vec in index: + if isinstance(self._board[vec.row][vec.col], Disabled): + raise DisabledError(vec.getStr(self)) + res.append(self._board[vec.row][vec.col]) + + if len(res) == 1: + return res.pop() + else: + return res + + def getRows(self) -> int: + """Get rows in board + + :returns: Number of rows in board + :rtype: ``int`` + """ + return self._rows + + def getCols(self) -> int: + """Get columns in board + + :returns: Number of columns in board + :rtype: ``int`` + """ + return self._cols + + def getHistory(self) -> list: + """Get history list of board + + :returns: History of board + :rtype: ``list`` + """ + return self._history + + + def getTurnorder(self) -> list: + """Get turnorder list of board + + :returns: Turnorder of board + :rtype: ``list`` + """ + return self._turnorder + + @_defaultColors + def getChecks(self, *colors: str) -> Union[bool, Dict[str, bool]]: + """Get checks in board + + If more than one color is given, this returns a ``dict`` + with a ``bool`` corresponding to each color. + + :param *colors: Colors to return + :returns: If colors are in check or not + :rtype: ``bool`` or ``dict`` + """ + return {col: self._checks[col] for col in colors} + + @_defaultColors + def getCheckmates(self, *colors: str) -> Union[bool, Dict[str, bool]]: + """Get checkmates in board + + If more than one color is given, this returns a ``dict`` + with a ``bool`` corresponding to each color. + + :param *colors: Colors to return + :returns: If colors are in checkmate or not + :rtype: ``bool`` or ``dict`` + """ + return {col: self._checkmates[col] for col in colors} + + @_defaultColors + def getKings(self, *colors: str) -> Union[List[King], Dict[str, List[King]]]: + """Get kings in board + + If more than one color is given, this returns a ``dict`` + with a ``list`` of kings corresponding to each color. + + :param *colors: Colors to return + :returns: All kings of colors in board + :rtype: ``list`` or ``dict`` + """ + return {col: self._kings[col] for col in colors} + + @_defaultColors + def getMoves(self, *colors: str) -> Union[List[Move], Dict[str, List[Move]]]: + """Get moves of board + + If more than one color is given, this returns a ``dict`` + with a ``list`` of moves corresponding to each color. + + :param *colors: Colors to return + :returns: All moves of colors in board + :rtype: ``list`` or ``dict`` + """ + return {col: self._moves[col] for col in colors} + + @_defaultColors + def getPromoteAt(self, *colors: str) -> Union[int, Dict[str, int]]: + """Get promotion position of board + + If more than one color is given, this returns a ``dict`` + with a ``int`` corresponding to each color. + + :param *colors: Colors to return + :returns: The promotion position of colors in board + :rtype: ``list`` or ``dict`` + """ + return {col: self._promoteAt[col] for col in colors} + + @_defaultColors + def getPromoteFrom(self, *colors: str) -> Union[List[Piece], Dict[str, List[Piece]]]: + """Get promotion starting pieces of board + + If more than one color is given, this returns a ``dict`` + with a ``list`` corresponding to each color. + + :param *colors: Colors to return + :returns: The promotion starting piece types of colors in board + :rtype: ``list`` or ``dict`` + """ + return {col: self._promoteFrom[col] for col in colors} + + @_defaultColors + def getPromoteTo(self, *colors: str) -> Union[List[Piece], Dict[str, List[Piece]]]: + """Get promotion target pieces of board + + If more than one color is given, this returns a ``dict`` + with a ``list`` corresponding to each color. + + :param *colors: Colors to return + :returns: The promotion target piece types of colors in board + :rtype: ``list`` or ``dict`` + """ + return {col: self._promoteTo[col] for col in colors} + + def getColors(self) -> List[str]: + """Get all colors of board + + :returns: List of colors in board + :rtype: ``list`` + """ + return self._pieces.keys() + + @_defaultColors + def iterPieces(self, *colors: str) -> Generator[Piece, None, None]: + """Iterate through pieces of board + + Use __iter__ to iterate through all positions of the board. + + :param *colors: Colors of pieces to iterate through (default is all colors) + :yields: Every piece in board + :ytype: ``generator`` + """ + for col in colors: + for p in self._pieces[col]: + yield p + + @_defaultColors + def eval(self, *colors: str) -> Dict[str, int]: + """Evaluate board + + Returns the sum of all pieces' values of colors in board + + :param *colors: Colors to evaluate (defaults to all colors of board) + + :returns: Colors with corresponding sum of pieces + :rtype: ``dict`` + """ + return {col: sum(list(map(lambda p: p.value, list(self.iterPieces(col))))) for col in colors} + + def removeColor(self, color: str) -> None: + """Remove color from board + + :param color: Color to remove + """ + vectors = list(map(lambda p: p.vector, list(self.iterPieces(color)))) + self[vectors] = [Empty(vector) for vector in vectors] + self.checkForCheck() + + def swapPositions(self, vec1: ChessVector, vec2: ChessVector) -> None: + """Swap position of two pieces + + :param vec1: Starting position of first piece + :param vec2: Starting position of second piece + """ + self._board[vec1.row][vec1.col].move(vec2) + self._board[vec2.row][vec2.col].move(vec1) + self._board[vec1.row][vec1.col], self._board[vec2.row][vec2.col] = self._board[vec2.row][vec2.col], self._board[vec1.row][vec1.col] + + def isEmpty(self, vec: ChessVector) -> bool: + """Check if position is empty + + :param vec: Position to check + :returns: True if position is empty, else False + :rtype: ``bool`` + """ + return isinstance(self[vec], Empty) + + def isThreatened(self, vec: ChessVector, alliedColor: str) -> bool: + """Check if position is threatened by enemy pieces + + :param vector: Position to check for threats + :param alliedColor: Color to exclude from enemy pieces + :returns: True if position is threatened, else False + :rtype: ``bool`` + """ + hostilePieces = [piece for col in self.getColors() if col != alliedColor for piece in self.iterPieces(col)] + + for hp in hostilePieces: + hostile = hp.getMoves(self, ignoreCheck=True) + if vec in hostile: + return True + else: + return False + + def checkForCheck(self, checkForMate=True) -> None: + """Check for any checks in board + + If checkForMate is True and king is in check, + method checks if any allied pieces can move to + interfere with the threatened check. + + :param checkForMate: Flag False to ignore checkmate (default is True) + :returns: None, stores result in attributes ``checks`` and ``checkmates`` + """ + for color in self.getColors(): + + for alliedKing in self._kings[color]: + + if self.isThreatened(alliedKing.vector, color): + self._checks[color] = True + break + else: + self._checks[color] = False + + if self._checks[color] and checkForMate: + + alliedPiecesPos = map(lambda p: p.vector, list(self.iterPieces(color))) + + for alliedPos in list(alliedPiecesPos): + for move in self[alliedPos].getMoves(self, ignoreCheck=True): + testBoard = deepcopy(self) + for pieceType in [None, *self._promoteTo[color]]: + try: + testBoard.movePiece(alliedPos, move, ignoreMate=True, + checkForMate=False, promote=pieceType, + printout=False, checkMove=False, ignoreOrder=True) + except PromotionError: + continue + else: + break + + if testBoard._checks[color]: + continue + else: + self._checkmates[color] = False + break + else: + continue + break + else: + self._checkmates[color] = True + + def advanceTurn(self) -> None: + """Advance the turn according to turnorder + """ + newidx = self._turnorder.index(self.currentTurn) + 1 + try: + self.currentTurn = self._turnorder[newidx] + except IndexError: + self.currentTurn = self._turnorder[0] + + def movePiece(self, startVec: ChessVector, targetVec: ChessVector, + ignoreOrder=False, ignoreMate=False, ignoreCheck=False, + checkForCheck=True, checkForMate=True, checkMove=True, + printout=True, promote=None) -> str: + """Move piece on board + + :param startVec: Position of moving piece + :param targetVec: Destination of moving piece + :param **Flags: Flags altering move rules, see below + :returns: Notation of move + :rtype: ``str`` + + :**Flags: + :ignoreOrder (False): Ignore the turnorder + :ignoreMate (False): Ignore if any pieces are in checkmate + :ignoreCheck (False): Ignore if any pieces are in check + :checkForCheck (True): Check for any checks after move + :checkForMate (True): Check for any checkmates after move + :checkMove (True): Check if piece is able to move to destination + :printout (True): Print the results of the move; checks, checkmates and move notation + :promote (None): Piece type to promote to + """ + + if self.isEmpty(startVec): + raise EmptyError(startVec.getStr(self)) + + startPiece = self[startVec] + + if not ignoreOrder and self.currentTurn != startPiece.color: + raise TurnError + + if self._checkmates[startPiece.color] and not ignoreMate: + raise CheckMate + + if checkMove and not targetVec.matches(startPiece.getMoves(self, ignoreCheck=ignoreCheck, ignoreMate=ignoreMate)): + raise IllegalMove(startVec.getStr(self), targetVec.getStr(self)) + + for move in self._moves[startPiece.color]: + if move.pieceCondition(startPiece): + if targetVec in move.getDestinations(startPiece, self): + notation = move.action(startPiece, targetVec, self, promote) + if checkForCheck: + self.checkForCheck(checkForMate=checkForMate) + break + + else: + raise IllegalMove(startVec.getStr(self), targetVec.getStr(self)) + + for color in self._checks.keys(): + if self._checkmates[color]: + if printout: + print(f"{color} in Checkmate!") + if not "#" in notation: + notation += "#" + + elif self._checks[color]: + if printout: + print(f"{color} in Check!") + if not "+" in notation: + notation += "+" + + for piece in self.iterPieces(): + + if not piece is startPiece: + piece.postAction(self) + + self._history.append(notation) + if printout: + print(notation) + + self.advanceTurn() + return notation + + def _addPiece(self, piece: Piece, vec: ChessVector) -> None: + if not piece.color in self.getColors(): + self._pieces[piece.color] = [] + self._kings[piece.color] = [] + self._checks[piece.color] = False + self._checkmates[piece.color] = False + + self._pieces[piece.color].append(piece) + + if isinstance(piece, King): + self._kings[piece.color].append(piece) + + piece.vector = vec + + def _removePiece(self, piece: Piece) -> None: + + self._pieces[piece.color].remove(piece) + + if isinstance(piece, King) and piece in self._kings[piece.color]: + self._kings[piece.color].remove(piece) + + if not self._pieces[piece.color]: + del self._pieces[piece.color] + del self._promoteTo[piece.color] + del self._promoteFrom[piece.color] + del self._promoteAt[piece.color] + del self._kings[piece.color] + del self._checks[piece.color] + del self._checkmates[piece.color] + + self._turnorder.remove(piece.color) + + piece.vector = None + + +def initClassic() -> Board: + """Initialize a chessBoard setup for 2 players, classic setup + + :returns: Classic chessboard + :rtype: ``Board`` + """ + board = Board(deepcopy(ClassicConfig.CONFIG)) + return board + + +def init4P() -> Board: + """Initialize a chessboard setup for four players + + :returns 4 player chessboard + :rtype: ``Board`` + """ + board = Board(deepcopy(FourPlayerConfig.CONFIG)) + return board diff --git a/build/lib/pawnshop/ChessVector.py b/build/lib/pawnshop/ChessVector.py new file mode 100644 index 0000000..8aee5bc --- /dev/null +++ b/build/lib/pawnshop/ChessVector.py @@ -0,0 +1,200 @@ +# ChessVector.py + +from typing import Union, Tuple, List, TYPE_CHECKING +from .Utils import toAlpha, inverseIdx, countAlpha +# import pawnshop.ChessBoard + +if TYPE_CHECKING: + from pawnshop.ChessBoard import Board + + +class ChessVector(object): + """ChessVector object + + Object to store position on chessboard + Initialize object with position in (row, col) or string notation format + If a string notation format is given, the board must also be given + The vector supports common operations such as addition, multiplication with other vectors + + :param position: Tuple or string notation position on chessboard + :param board: Board to use when determining position given by string notation (default is None) + """ + + def __init__(self, position: Union[Tuple[int, int], str], board=None): + self._row = 0 + self._col = 0 + + if isinstance(position, tuple): + row, col = position + self.row = int(row) + self.col = int(col) + elif isinstance(position, str) and not board is None: + position = position.lower() + for char in position: + if char.isdigit(): + i = position.find(char) + if i == 0: + raise ValueError("Position does not include column!") + alpha = position[:i] + num = position[i::] + row = board.getRows() - int(num) + for n, a in countAlpha(): + if a == alpha: + col = n + break + else: + continue + break + else: + raise ValueError("position does not include row!") + self.row = row + self.col = col + else: + raise ValueError("Position is not a string or a tuple!") + + @property + def col(self): + return self._col + + @col.setter + def col(self, newCol): + self._col = newCol + + @property + def row(self): + return self._row + + @row.setter + def row(self, newRow): + self._row = newRow + + def __sub__(self, other): + if isinstance(other, ChessVector): + return ChessVector((self.row - other.row, self.col - other.col)) + else: + return ChessVector((self.row - other, self.col - other)) + + def __rsub__(self, other): + if isinstance(other, ChessVector): + return ChessVector((other.row - self.row, other.col - self.col)) + else: + raise ValueError(f"Cannot subtract {type(self)} from non-{type(self)}!") + + def __add__(self, other): + if isinstance(other, ChessVector): + return ChessVector((self.row + other.row, self.col + other.col)) + else: + return ChessVector((self.row + other, self.col + other)) + + def __radd__(self, other): + if isinstance(other, ChessVector): + return ChessVector((other.row + self.row, other.col + self.col)) + else: + raise ValueError(f"Cannot add {type(self)} to non-{type(self)}!") + + def __mul__(self, other): + if isinstance(other, ChessVector): + return ChessVector((self.row * other.row, self.col * other.col)) + else: + return ChessVector((self.row * other, self.col * other)) + + def __rmul__(self, other): + if isinstance(other, ChessVector): + return ChessVector((other.row * self.row, other.col * self.col)) + else: + raise ValueError(f"Cannot multiply non-{type(self)} by {type(self)}!") + + def __div__(self, other): + if isinstance(other, ChessVector): + return ChessVector((self.row / other.row, self.col / other.col)) + else: + return ChessVector((self.row / other, self.col / other)) + + def __rdiv__(self, other): + if isinstance(other, ChessVector): + return ChessVector((other.row / self.row, other.col / self.col)) + else: + raise ValueError(f"Cannot divide non-{type(self)} by {type(self)}!") + + def __neg__(self): + return ChessVector((-self.row, -self.col)) + + def __pos__(self): + return ChessVector((+self.row, +self.col)) + + def __eq__(self, other): + if isinstance(other, ChessVector): + return self.row == other.row and self.col == other.col + else: + raise ValueError(f"Cannot compare {type(self)} with {type(other)}!") + + def __ne__(self, other): + return not self == other + + def __lt__(self, other): + if isinstance(other, ChessVector): + return self.row < other.row and self.col < other.col + else: + raise ValueError(f"Cannot compare {type(self)} with {type(other)}!") + + def __gt__(self, other): + return not self < other + + def __le__(self, other): + return self < other or self == other + + def __ge__(self, other): + return self > other or self == other + + def __repr__(self): + return str((self.row, self.col)) + + def __str__(self): + return str((self.row, self.col)) + + def tuple(self) -> Tuple[int, int]: + """Return tuple format of vector + + :returns: (row, col) tuple + :rType: ´´tuple´´ + """ + return (self._row, self._col) + + def getStr(self, board: "Board") -> str: + """Return string notation format of vector + + :param board: Board to determine string position from + :returns: string notation of vector position + :rType: ´´str´´ + """ + notation = "" + notation += toAlpha(self.col) + notation += inverseIdx(self.row, board) + return notation + + def matches(self, otherVecs: List["ChessVector"]) -> bool: + """Check if vector matches any of other vectors + + :param otherVecs: List of other vectors + :returns: If match is found or not + :rType: ´´bool´´ + """ + for vec in otherVecs: + if self.row == vec.row and self.col == vec.col: + return True + else: + return False + + def copy(self) -> "ChessVector": + """Create a new copy of this vector + + :returns: Copy of this vector + :rType: ´´ChessVector´´ + """ + return ChessVector((self.row, self.col)) + + +if __name__ == "__main__": + + # Do some testing + pass diff --git a/build/lib/pawnshop/Exceptions.py b/build/lib/pawnshop/Exceptions.py new file mode 100644 index 0000000..5b1144d --- /dev/null +++ b/build/lib/pawnshop/Exceptions.py @@ -0,0 +1,42 @@ +# Exceptions.py + +class Illegal(Exception): + """Move is ilegal""" + pass + + +class IllegalMove(Illegal): + def __init__(self, startPos, targetPos, + msg="Piece at {0} cannot move to {1}!"): + super().__init__(msg.format(startPos, targetPos)) + + +class CheckMate(Illegal): + def __init__(self, msg="Your king is in checkmate!"): + super().__init__(msg) + + +class EmptyError(IndexError): + def __init__(self, position, msg="Position {0} is empty!"): + super().__init__(msg.format(position)) + + +class DisabledError(IndexError): + def __init__(self, position, msg="Position {0} is out of bounce!"): + super().__init__(msg.format(position)) + + +class PromotionError(Exception): + def __init__(self, msg="Moved piece needs to be promoted!"): + super().__init__(msg) + + +class TurnError(Exception): + def __init__(self, msg="Wrong player!"): + super().__init__(msg) + + +if __name__ == "__main__": + + # Do some testing + pass diff --git a/build/lib/pawnshop/GameNotations.py b/build/lib/pawnshop/GameNotations.py new file mode 100644 index 0000000..4c011e5 --- /dev/null +++ b/build/lib/pawnshop/GameNotations.py @@ -0,0 +1,194 @@ +# GameNotations.py + +import re +from copy import deepcopy + +from .ChessBoard import ( + initClassic, + Board +) +from .configurations import ClassicConfig +from .ChessVector import ChessVector +from .Pieces import * +from .Utils import toAlpha +from .Moves import ( + CastleK, + CastleQ +) + + +STANDARDTAGS = [ + "Event", + "Site", + "Date", + "Round", + "White", + "Black", + "Result" +] +OPTIONALTAGS = [ + "Annotator", + "PlyCount", + "TimeControl", + "Time", + "Termination", + "Mode", + "FEN" +] +ALLTAGS = [*STANDARDTAGS, *OPTIONALTAGS] + + +def board2PGN(board: Board, **tags) -> str: + """Get Portable Game Notation from board + + :param board: Board to get notation from + :param **tags: Tags added to the notation + :returns: PGN string + :rtype: ``str`` + + :**tags: Tags found in STANDARDTAGS and OPTIONALTAGS + """ + PGNString = "" + tags = {t.lower(): v for t, v in tags.items()} + + for TAG in ALLTAGS: + if TAG.lower() in tags: + PGNString += f"[{TAG} \"{str(tags[TAG.lower()])}\"]\n" + i = 0 + while i * 2 < len(board.history): + i += 1 + PGNString += str(i) + ". " + " ".join(board.history[(i - 1) * 2:i * 2:1]) + "\n" + + return PGNString + + +def PGN2Board(PGNString: str) -> Board: + """Get Board object from Portable Game Notation + + :param PGNString: PGN string + :returns: Board object from PGN + :rtype: ``Board`` + """ + notations = re.finditer(r"\s*(?PO-O-O)|(?PO-O)|(?P[A-Z]*)(?P[a-h]?)(?P[x]?)(?P[a-h]+)(?P\d+)=?(?P[A-Z]?)\+*\#?", PGNString) + + board = initClassic() + for i, notation in enumerate(notations): + color = ["white", "black"][i % 2 == 1] + if (not notation.group("castleK") is None) or (not notation.group("castleQ") is None): + for king in board.kings[color]: + for move in board.moves[color]: + if ((not notation.group("castleK") is None) and move is CastleK) or ((not notation.group("castleQ") is None) and move is CastleQ): + board.movePiece(king.vector, move.getDestinations(king, board).pop(), checkMove=False, ignoreMate=True, checkForCheck=False, printOut=False, ignoreOrder=True) + break + else: + continue + break + else: + for piece in board.pieces[color]: + vector = ChessVector(notation.group("col") + notation.group("rank"), board) + if vector.matches(piece.getMoves(board)): + pType = pieceNotations[notation.group("piece")] + if isinstance(piece, pType): + if notation.group("pcol") == "" or notation.group("pcol") == toAlpha(piece.vector.col): + board.movePiece(piece.vector, vector, checkMove=False, promote=pieceNotations[notation.group("promote")], ignoreMate=True, checkForCheck=False, printOut=False, ignoreOrder=True) + break + else: + continue + else: + continue + + board.checkForCheck() + return board + + +def FEN2Board(FENString: str) -> Board: + """Get Board object from Forsyth-Edwards-Notation + + :param FENString: Forsyth-Edwards-Notation + :returns: Board object from FEN + :rtype: ``Board`` + """ + board = Board() + config = deepcopy(ClassicConfig.CONFIG) + del config["pieces"] + board.setup(config) + + fieldFinder = re.finditer(r"[^ ]+", FENString) + rowFinder = re.finditer(r"([^/]+)", next(fieldFinder).group()) + + for rowi, row in enumerate(rowFinder): + coli = 0 + for chari, char in enumerate(row.group(0)): + if char.isnumeric(): + for coli in range(coli, coli + int(char)): + vector = ChessVector((rowi, coli), board) + board[vector] = Empty(vector) + coli += 1 + else: + vector = ChessVector((rowi, coli), board) + + if char.isupper(): + board[vector] = pieceNotations[char]("white", direction="up") + elif char.islower(): + board[vector] = pieceNotations[char.upper()]("black", direction="down") + coli += 1 + + # No other fields are critical, might implement more later + return board + + +def board2FEN(board: Board) -> str: + """Get Forsyth-Edward-Notation from board + + The notation does not account for: + current turn, castling potential, en-passant or move count + - only the position is notated (I am lazy) + + :param board: Board to get FEN from + :returns: FEN string + :rtype: ``str`` + """ + FENString = "" + for rowi, row in enumerate(board._board): + empty = 0 + for coli, piece in enumerate(row): + if isinstance(piece, Empty) or isinstance(piece, Disabled): + empty += 1 + else: + if empty: + FENString += str(empty) + if piece.color == "white": + ps = piece.symbol.upper() + elif piece.color == "black": + ps = piece.symbol.lower() + FENString += ps + empty = 0 + + if empty: + FENString += str(empty) + if not rowi == board.getRows() - 1: + FENString += "/" + + return FENString + + +def readable(historyList: List[str], players=2) -> str: + """Get printable format of history + + :param historyList: History to be read + :param players: How many players the history includes + :returns: Readable string of history + :rtype: ``str`` + """ + finalString = "" + i = 0 + while i * players < len(historyList): + i += 1 + finalString += str(i) + ". " + " - ".join(historyList[(i - 1) * players:i * players:1]) + "\n" + return finalString.strip() + + +if __name__ == "__main__": + + # Do some testing + pass diff --git a/build/lib/pawnshop/Moves.py b/build/lib/pawnshop/Moves.py new file mode 100644 index 0000000..0784822 --- /dev/null +++ b/build/lib/pawnshop/Moves.py @@ -0,0 +1,397 @@ +# Moves.py + +from typing import List, Union, Tuple, TYPE_CHECKING +from abc import ABC, abstractclassmethod +from .Pieces import * +from .Utils import createNotation +from .Exceptions import PromotionError +from .ChessVector import ChessVector + +if TYPE_CHECKING: + from .ChessBoard import Board + + +class Move(ABC): + """Abstract class for moves in chess + """ + + @abstractclassmethod + def pieceCondition(thisMove, piece: Piece, *args, **kwargs) -> bool: + """Test if piece satisfies move requirement + """ + raise NotImplementedError + + @abstractclassmethod + def getDestinations(thisMove, piece: Piece, board: "Board", *args, **kwargs) -> list: + """Return list of possible destinations + """ + raise NotImplementedError + + @abstractclassmethod + def action(thisMove, startPiece, targetPos, board, *args, **kwargs) -> str: + """Move the piece + """ + raise NotImplementedError + + +class Standard(Move): + """Standard move in chess + + Moves piece according to .getStandardMoves() method + """ + + @classmethod + def pieceCondition(thisMove, *args, **kwargs) -> bool: + """Moving piece must satisfy this condition + + Since there is no condition to standard moves, this will always return True. + + :returns: True + :rtype: ``bool`` + """ + return True + + @classmethod + def getDestinations(thisMove, piece: Piece, board: "Board", *args, **kwargs) -> List[ChessVector]: + """Get possible destinations of piece + + Calls piece.getStandardMoves() method to get all standard moves of piece. + Call pieceCondition() classmethod prior. + + :param piece: Piece to get moves from + :param board: Board which piece is stored in + :returns: list of possible destinations + :rtype: list + """ + return piece.getStandardMoves(board) + + @classmethod + def action(thisMove, startPiece: Piece, targetVec: ChessVector, board: "Board", promote=None, *args, **kwargs) -> str: + """Performs the action of move + + Moves piece according to standard move rules. + Call pieceCondition() and getDestinations() classmethods prior. + + :param startPiece: Piece to be moved + :param targetVec: Destination of piece move + :param board: Board to perform move in + :param promote: Promotion type of piece (default is None) + :returns: Notation of move + :rtype: ``str`` + """ + promo = False + + for pieceType in board.getPromoteFrom(startPiece.color): + if isinstance(startPiece, pieceType): + + if startPiece.rank + abs((startPiece.vector - targetVec).tuple()[startPiece.forwardVec.col]) == board.getPromoteAt(startPiece.color): + if promote is None: + raise PromotionError + + if promote not in board.getPromoteTo(startPiece.color): + raise PromotionError( + f"{startPiece.color} cannot promote to {promote}!") + + promo = True + + break + + targetPiece = board[targetVec] + + notation = createNotation( + board, startPiece, targetVec, + isPawn=isinstance(startPiece, Pawn), capture=not isinstance(targetPiece, Empty)) + + if not isinstance(targetPiece, Empty): + board[targetVec] = Empty(targetVec) + board.swapPositions(startPiece.vector, targetVec) + else: + board.swapPositions(startPiece.vector, targetVec) + if promo: + newPiece = promote(startPiece.color) + newPiece.move(startPiece.vector) + board[startPiece.vector] = newPiece + notation += "=" + newPiece.symbol + + return notation + + +class _Castling(Move): + """Parent class to King-side and Queen-side castling + """ + + @classmethod + def pieceCondition(thisMove, piece: Piece, *args, **kwargs) -> bool: + """Moving piece must satisfy this condition + + Must be pieces first move and piece must be instance of ``King``. + + :param piece: Piece to check + :returns: If piece satisfies requirements + :rtype: ``bool`` + """ + return piece.firstMove and isinstance(piece, King) + + @classmethod + def action(thisMove, startPiece: Piece, targetVec: ChessVector, board: "Board", *args, **kwargs) -> None: + """Performs the action of move + + Moves piece according to move rules. + Returns None as Queen-side and King-side castling are noted differently. + Call pieceCondition() and getDestinations() classmethods prior. + + :param startPiece: Piece to be moved + :param targetVec: Destination of piece move + :param board: Board to perform move in + """ + for rook in thisMove.findRooks(startPiece, board): + between = thisMove.findBetween(startPiece.vector, rook.vector) + if targetVec in between: + kingTarget, rookTarget = thisMove.getTargets(between) + board.swapPositions(startPiece.vector, kingTarget) + board.swapPositions(rook.vector, rookTarget) + break + else: + raise ValueError(f"Piece cannot move to {targetVec}") + + def findBetween(vec1: ChessVector, vec2: ChessVector) -> List[ChessVector]: + """Helper function to find all positions between two positions + + Helper function. + If there are not positions between or given positions + are not in a row, the returned list is empty. + + :param vec1: First position + :param vec2: Second position + :returns: List of possitions betweeen + :rtype: ``list`` + """ + rowStep = vec1.row - vec2.row and (1, -1)[vec1.row - vec2.row < 0] + colStep = vec1.col - vec2.col and (1, -1)[vec1.col - vec2.col < 0] + + if not rowStep: + colRange = range(vec2.col + colStep, vec1.col, colStep) + rowRange = [vec1.row] * len(colRange) + elif not colStep: + rowRange = range(vec2.row + rowStep, vec1.row, rowStep) + colRange = [vec1.col] * len(rowRange) + else: + rowRange = range(0, 0) + colRange = range(0, 0) + + return [ChessVector(idx) for idx in zip(rowRange, colRange)] + + def emptyBetween(board: "Board", between: List[ChessVector]) -> bool: + """Check if all positions are emtpy + + Helper funciton. + Check if all positions between two pieces are empty + + :param board: Board to check positions in + :param between: List of positions to check + :returns: If all positions are empty or not + :rtype: ``bool`` + """ + for vector in between: + if not isinstance(board[vector], Empty): + return False + else: + return True + + def findRooks(piece: Piece, board: "Board") -> List[Piece]: + """Find all rooks in board that are on same lane as piece + + Helper function. + Iterates through all pieces on board looking for + rooks on same lane as piece. + + :param piece: Piece to check for same lane + :param board: Board to check for rooks in + :returns: List of rooks on same lane as piece + :rtype: ``list`` + """ + def vecCondition(vec1, vec2): + return bool(vec2.row - vec1.row) != bool(vec2.col - vec1.col) and (not vec2.row - vec1.row or not vec2.col - vec2.col) + + rookList = [] + for p in board.iterPieces(piece.color): + if isinstance(p, Rook) and p.firstMove and vecCondition(piece.vector, p.vector): + rookList.append(p) + return rookList + + def getTargets(between: list) -> Union[Tuple[ChessVector], None]: + """Get castling targets + + Helper function + Get the two middle squares of list of positions between. + If list is of length 1, this returns None. + Biased towards the start of list. + + + :param between: List of positions between + :returns: Tuple of target positions + :rtype: ``tuple`` or None + """ + if not len(between) > 1: + return None + if not len(between) % 2: + target1 = between[int((len(between) / 2) - 1)] + target2 = between[int((len(between) / 2))] + else: + target1 = between[int((len(between) / 2) - 0.5)] + target2 = between[int((len(between) / 2) + 0.5)] + return (target1, target2) + + +class CastleK(_Castling): + """Castle King-side move + """ + + @classmethod + def getDestinations(thisMove, piece: Piece, board: "Board", *args, **kwargs) -> List[ChessVector]: + """Get possible destinations of piece + + Returns all possible castling moves of piece. + Call pieceCondition() classmethod prior. + + :param piece: Piece to get moves from + :param board: Board which piece is stored in + :returns: list of possible destinations + :rtype: list + """ + destList = [] + if not board.getChecks(piece.color): + for rook in thisMove.findRooks(piece, board): + between = thisMove.findBetween(piece.vector, rook.vector) + if thisMove.emptyBetween(board, between) and not len(between) % 2: + kingTarget, _ = thisMove.getTargets(between) + walked = thisMove.findBetween(piece.vector, kingTarget) + for vec in walked: + if board.isThreatened(vec, piece.color): + break + else: + destList.append(kingTarget) + return destList + + @classmethod + def action(thisMove, *args, **kwargs) -> str: + """Performs the action of move + + Moves piece according to move rules. + Returns the notation of the castling move + Call pieceCondition() and getDestinations() classmethods prior. + + :param startPiece: Piece to be moved + :param targetVec: Destination of piece move + :param board: Board to perform move in + :returns: Notation of move + :rtype: str + """ + super().action(*args, **kwargs) + return "O-O" + + +class CastleQ(_Castling): + + @classmethod + def getDestinations(thisMove, piece: Piece, board: "Board", *args, **kwargs) -> List[ChessVector]: + """Get possible destinations of piece + + Returns all possible castling moves of piece. + Call pieceCondition() classmethod prior. + + :param piece: Piece to get moves from + :param board: Board which piece is stored in + :returns: list of possible destinations + :rtype: list + """ + destList = [] + if not board.getChecks(piece.color): + for rook in thisMove.findRooks(piece, board): + between = thisMove.findBetween(piece.vector, rook.vector) + if thisMove.emptyBetween(board, between) and len(between) % 2: + kingTarget, _ = thisMove.getTargets(between) + walked = thisMove.findBetween(piece.vector, kingTarget) + for vec in walked: + if board.isThreatened(vec, piece.color): + break + else: + destList.append(kingTarget) + return destList + + @classmethod + def action(thisMove, *args, **kwargs) -> str: + """Performs the action of move + + Moves piece according to move rules. + Returns the notation of the castling move + Call pieceCondition() and getDestinations() classmethods prior. + + :param startPiece: Piece to be moved + :param targetVec: Destination of piece move + :param board: Board to perform move in + :returns: Notation of move + :rtype: str + """ + super().action(*args, **kwargs) + return "O-O-O" + + +class EnPassant(Move): + """Special move en-passant + """ + + @classmethod + def pieceCondition(thisMove, piece: Piece, *args, **kwargs) -> bool: + """Moving piece must satisfy this condition + + Piece must be of instance ``Pawn`` + + :param piece: Piece to check + :returns: If piece satisfies requirements + :rtype: ``bool`` + """ + return isinstance(piece, Pawn) + + @classmethod + def getDestinations(thisMove, piece: Piece, board: "Board", *args, **kwargs) -> List[ChessVector]: + """Get possible destinations of piece + + Returns all possible en-passant moves of piece. + Call pieceCondition() classmethod prior. + + :param piece: Piece to get moves from + :param board: Board which piece is stored in + :returns: list of possible destinations + :rtype: list + """ + destList = [] + for diagVec in (piece.lDiagVec, piece.rDiagVec): + checkVec = (piece.vector - piece.forwardVec) + diagVec + try: + if isinstance(board[checkVec], Pawn) and board[checkVec].passed and board[checkVec].forwardVec == -piece.forwardVec: + destList.append(piece.vector + diagVec) + except IndexError: + pass + return destList + + @classmethod + def action(thisMove, piece: Piece, targetVec: ChessVector, board: "Board", *args, **kwargs) -> str: + """Performs the action of move + + Moves piece according to move rules. + Returns the notation of the en-passant move + Call pieceCondition() and getDestinations() classmethods prior. + + :param startPiece: Piece to be moved + :param targetVec: Destination of piece move + :param board: Board to perform move in + :returns: Notation of move + :rtype: str + """ + notation = createNotation(board, piece, targetVec, + isPawn=True, capture=True) + + board[targetVec - piece.forwardVec] = Empty(targetVec - piece.forwardVec) + board.swapPositions(piece.vector, targetVec) + return notation diff --git a/build/lib/pawnshop/Pieces.py b/build/lib/pawnshop/Pieces.py new file mode 100644 index 0000000..fe9b84c --- /dev/null +++ b/build/lib/pawnshop/Pieces.py @@ -0,0 +1,423 @@ +# Pieces.py + +from copy import deepcopy +from abc import ABC, abstractmethod +from typing import List, Tuple, TYPE_CHECKING +from .Utils import _positivePos, _catchOutofBounce, removeDupes +from .ChessVector import ChessVector + +if TYPE_CHECKING: + from .ChessBoard import Board + +_directions = { + "up": ((-1, 0), (-1, -1), (-1, 1)), + "down": ((1, 0), (1, 1), (1, -1)), + "right": ((0, 1), (-1, 1), (1, 1)), + "left": ((0, -1), (1, -1), (-1, -1)) +} +_directions = {key: [ChessVector(offset) for offset in _directions[key]] for key in _directions} + + +class Piece(ABC): + """Abstract base class for pieces + + :param color: Color of piece + :param value: Numerical value of piece + :param symbol: Char symbol of piece + """ + + def __init__(self, color: str, value: int, symbol: str, *args, **kwargs): + self.vector = None + self.color = color + self.value = value + self.symbol = symbol + self.firstMove = True + + def __str__(self): + return self.color[0] + self.symbol + + @abstractmethod + def getStandardMoves(self, board: "Board"): + """Returns standard destinations of piece in board + """ + raise NotImplementedError + + def getMoves(self, board: "Board", ignoreCheck=False, ignoreMate=False) -> List[ChessVector]: + """Returns all moves of piece in board + + Uses board.getMoves() method to check what moves piece is allowed to. + + :param board: Board to move in + :param **Flags: Flags to pass into move + :returns: List of possible moves + :rtype: ``list`` + + :**Flags: + :ignoreCheck (False): Ignore checks when getting moves + :ignoreMate (False): Ignore checkmate when getting moves + """ + destList = [] + for move in board.getMoves(self.color): + if move.pieceCondition(self): + destList.extend(move.getDestinations(self, board)) + + if not ignoreCheck: + remove = [] + + for dest in destList: + testBoard = deepcopy(board) + testBoard.movePiece(self.vector, dest, ignoreMate=ignoreMate, checkForMate=False, printout=False, checkMove=False, promote=Queen, ignoreOrder=True) + if testBoard.getChecks(self.color): + remove.append(dest) + + for dest in remove: + destList.remove(dest) + + return destList + + def move(self, destVector: ChessVector) -> None: + """Move piece to destination + + :param destVector: Destination + """ + self.vector = destVector + self.firstMove = False + + def postAction(self, board: "Board") -> None: + """Do action after piece is moved in board + + Call this after a piece is moved in board + """ + pass + + @_positivePos + @_catchOutofBounce + def canWalk(self, vector: ChessVector, board: "Board") -> bool: + """Check if piece can walk to destination in board + + :param vector: Destination + :param board: Board to check in + :returns: If piece can move + :rtype: ``bool`` + """ + return board.isEmpty(vector) + + @_positivePos + @_catchOutofBounce + def canCapture(self, vector: ChessVector, board: "Board") -> bool: + """Check if piece can capture to destination in board + + :param vector: Destination + :param board: Board to check in + :returns: If piece can capture + :rtype: ``bool`` + """ + destPiece = board[vector] + try: + return destPiece.color != self.color + except AttributeError: + return False + + @_positivePos + @_catchOutofBounce + def canMove(self, vector: ChessVector, board: "Board") -> bool: + """Check if piece can capture to destination in board + + :param vector: Destination + :param board: Board to check in + :returns: If piece can move (capture or walk) + :rtype: ``bool`` + """ + destPiece = board[vector] + try: + return destPiece.color != self.color + except AttributeError: + return board.isEmpty(vector) + + def _getMovesInLine(self, iterVector: ChessVector, board: "Board") -> List[ChessVector]: + """Get moves in one line + + Return all positions piece is can move to iterating with iterVector. + Stops if piece can capture as piece cannot continue moving after capturing. + + :param iterVector: Vector to iterate moves with + :param board: Board to check in + :returns: List of possible destinations + :rtype: ``list`` + """ + moveList = [] + newV = self.vector + while True: + newV += iterVector + if self.canWalk(newV, board): + moveList.append(newV) + elif self.canCapture(newV, board): + moveList.append(newV) + break + else: + break + return moveList + + +class Pawn(Piece): + """Pawn object + + :param color: Color of piece + :param direction: Movement direction of Pawn (default is "up") + :param rank: Starting rank of pawn, used to calc promote + """ + + def __init__(self, color: str, direction="up", rank=2, *args, **kwargs): + super().__init__(color, 1, "P") + + self.passed = False + self.direction = direction.lower() + self.rank = rank + + if direction in _directions.keys(): + self.forwardVec, self.lDiagVec, self.rDiagVec = _directions[direction] + else: + raise ValueError(f"Direction is not any of {_directions.keys()}") + + def getStandardMoves(self, board: "Board") -> List[ChessVector]: + """Returns standard destinations of piece in board + + :param board: Board to check in + :returns: List of standard posssible destinations + :rtype: ``list`` + """ + destList = [] + destVec = self.vector + self.forwardVec + + if self.canWalk(destVec, board): + destList.append(destVec) + if self.firstMove: + destVec += self.forwardVec + if self.canWalk(destVec, board): + destList.append(destVec) + + for destVec in self.getAttacking(board): + if self.canCapture(destVec, board): + destList.append(destVec) + + return destList + + def move(self, newV: ChessVector) -> None: + """Move piece to destination + + If Pawn moves 2 places, it can be captured by en-passant. + + :param newV: Destination + """ + if self.firstMove: + if abs(self.vector.row - newV.row) == 2 or abs(self.vector.col - newV.col) == 2: + self.passed = True + self.rank += 1 + self.rank += 1 + super().move(newV) + + def postAction(self, *args, **kwargs): + """Do action after piece is moved in board + + Call this after a piece is moved in board + """ + self.passed = False + + def getAttacking(self, *args, **kwargs) -> Tuple[ChessVector]: + """Get the threatened positions of piece + + :returns: Tuple of threatened positions + :rType: ´´tuple´´ + """ + return [self.vector + self.lDiagVec, self.vector + self.rDiagVec] + + +class Rook(Piece): + """Rook object + + :param color: Color of piece + """ + + def __init__(self, color: str, *args, **kwargs): + super().__init__(color, 5, "R") + + def getStandardMoves(self, board: "Board") -> List[ChessVector]: + """Returns standard destinations of piece in board + + :param board: Board to check in + :returns: List of standard posssible destinations + :rtype: ``list`` + """ + destList = [] + for vecTuple in _directions.values(): + forwardVec = vecTuple[0] + destList.extend(self._getMovesInLine(forwardVec, board)) + return destList + + +class Knight(Piece): + """Knight object + + :param color: Color of piece + """ + + def __init__(self, color: str, *args, **kwargs): + super().__init__(color, 3, "N") + + def getStandardMoves(self, board: "Board") -> List[ChessVector]: + """Returns standard destinations of piece in board + + :param board: Board to check in + :returns: List of standard posssible destinations + :rtype: ``list`` + """ + destList = [] + offsetList = [ + (1, 2), + (1, -2), + (-1, 2), + (-1, -2), + (2, 1), + (2, -1), + (-2, 1), + (-2, -1) + ] + vecList = [ChessVector(offset) for offset in offsetList] + + for offsetVec in vecList: + destVec = self.vector + offsetVec + if self.canMove(destVec, board): + destList.append(destVec) + return destList + + +class Bishop(Piece): + """Bishop object + + :param color: Color of piece + """ + + def __init__(self, color: str, *args, **kwargs): + super().__init__(color, 3, "B") + + def getStandardMoves(self, board: "Board") -> List[ChessVector]: + """Returns standard destinations of piece in board + + :param board: Board to check in + :returns: List of standard posssible destinations + :rtype: ``list`` + """ + destList = [] + for vecTuple in _directions.values(): + destList.extend(self._getMovesInLine(vecTuple[1], board)) + return destList + + +class King(Piece): + """King object + + :param color: Color of piece + """ + + def __init__(self, color: str, *args, **kwargs): + super().__init__(color, int(1e10), "K") + + def getStandardMoves(self, board: "Board") -> List[ChessVector]: + """Returns standard destinations of piece in board + + :param board: Board to check in + :returns: List of standard posssible destinations + :rtype: ``list`` + """ + destList = [] + for offsetVec in removeDupes([vec for vecList in _directions.values() for vec in vecList]): + destVec = self.vector + offsetVec + if self.canMove(destVec, board): + destList.append(destVec) + return destList + + +class Queen(Piece): + """Queen object + + :param color: Color of piece + """ + + def __init__(self, color: str, *args, **kwargs): + super().__init__(color, 9, "Q") + + def getStandardMoves(self, board: "Board") -> List[ChessVector]: + """Returns standard destinations of piece in board + + :param board: Board to check in + :returns: List of standard posssible destinations + :rtype: ``list`` + """ + destList = [] + for vecTuple in _directions.values(): + destList.extend(self._getMovesInLine(vecTuple[0], board)) + destList.extend(self._getMovesInLine(vecTuple[1], board)) + return destList + + +class Disabled(): + """Disabled object + + Object for representing disabled positions in chessboard + + :param vector: Position of disabled square + """ + + def __init__(self, vector: ChessVector, *args, **kwargs): + self.vector = vector + + def __str__(self): + return " " + + def move(self, vec: ChessVector): + """Move disabled object + + Move the disabled square + + :param vec: New position + """ + self.vector = vec + + +class Empty(): + """Empty object + + Object for representing empty positions in chessboard + + :param vector: Position of empty square + """ + + def __init__(self, vector: ChessVector, *args, **kwargs): + self.vector = vector + + def __str__(self): + return "__" + + def move(self, vec: ChessVector): + """Move empty object + + Move the empty square + + :param vec: New position + """ + self.vector = vec + + +pieceNotations = { + "P": Pawn, + "N": Knight, + "B": Bishop, + "R": Rook, + "Q": Queen, + "K": King +} + +if __name__ == "__main__": + + # Do some testing + pass diff --git a/build/lib/pawnshop/Utils.py b/build/lib/pawnshop/Utils.py new file mode 100644 index 0000000..0eb9da3 --- /dev/null +++ b/build/lib/pawnshop/Utils.py @@ -0,0 +1,159 @@ +# Utils.py + +from typing import List, Generator, TYPE_CHECKING +from string import ascii_lowercase +import sys, os + +if TYPE_CHECKING: + from .ChessBoard import Board + from .ChessVector import ChessVector + from .Pieces import Piece + +def _catchOutofBounce(func): + """Decorator for catching out of bounce ´´IndexError´´""" + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except IndexError: + return False + return wrapper + + +def _positivePos(func): + """Decorator for ensuring a position is not negative""" + def wrapper(pInstance, vector, bInstance, *args, **kwargs): + if not vector.row < 0 and not vector.col < 0: + return func(pInstance, vector, bInstance, *args, **kwargs) + else: + return False + return wrapper + + +def removeDupes(vectorList: List["ChessVector"]) -> List["ChessVector"]: + """Remove duplicate positions + + :param vectorList: List to remove duplicates from + :returns: List without duplicates + :rtype: ``list`` + """ + for i, superVec in enumerate(vectorList): + if superVec.matches(vectorList[i + 1::]): + vectorList.remove(superVec) + return removeDupes(vectorList) + else: + return vectorList + + +def createNotation(board: "Board", startPiece: "Piece", targetVec: "ChessVector", isPawn=False, capture=False) -> str: + """Create a notation for a move + + Creates notation of move according to standard chess notation. + + :param startPiece: Piece to be moved + :param targetVec: Destination of move + :param **Flags: Flags to create notation + :returns: Notation of move + :rtype: ``str`` + + :**Flags: + :isPawn (True): + :capture (True): + """ + notation = "" + targetNot = targetVec.getStr(board) + + if not isPawn: + notation = startPiece.symbol + for piece in board.iterPieces(startPiece.color): + if piece is not startPiece and isinstance(piece, type(startPiece)): + if targetVec.matches(piece.getMoves(board, ignoreCheck=True)): + if piece.vector.col == startPiece.vector.col: + notation += inverseIdx(startPiece.vector.row, board) + else: + notation += toAlpha(startPiece.vector.col) + break + elif capture: + notation = toAlpha(startPiece.vector.col) + + if capture: + notation += "x" + + notation += targetNot + return notation + + +def countAlpha() -> Generator[str, None, None]: + """Generator to count in alphabetical order + + Counts in alphabetical order. + a->b->c->...->aa->ab->...->ba->... + + :yields: Character + :ytype: ``generator`` + """ + stringList = [0] + num = 0 + while True: + yield (num, "".join([ascii_lowercase[num] for num in stringList])) + i = 1 + num += 1 + + while True: + if i > len(stringList): + stringList.insert(0, 0) + break + else: + changeTo = stringList[-i] + 1 + if changeTo >= len(ascii_lowercase): + stringList[-i::] = [0] * (i) + i += 1 + continue + else: + stringList[-i] = changeTo + break + + +def inverseIdx(idx: int, board: "Board") -> str: + """Inverse index + + Inverses idx given board rows and returns string + + :param idx: Index to reverse + :param board: Board to reverse according to rows + :returns: Reversed index + :rtype: ``str`` + """ + return str(board.getRows() - idx) + + +def toAlpha(num: int) -> str: + """Convert number to alphabetical + + Counts through all alpha until reaching number. + (I tried to make it not have to count through all alphas, + however, since my alpha system doesn't match any regular + base number system I was not able to.) + + :param num: Number to convert + :returns: Alphabetical string from num + :rtype: str + """ + for n, notation in countAlpha(): + if num == n: + return notation + +def getResourcePath(relative_path): + """ + Get pyinstaller resource + """ + + if hasattr(sys, '_MEIPASS'): + return os.path.join(sys._MEIPASS, relative_path) + + return os.path.join(os.path.abspath("."), relative_path) + + +if __name__ == "__main__": + # Do some testing + + pass diff --git a/build/lib/pawnshop/__init__.py b/build/lib/pawnshop/__init__.py new file mode 100644 index 0000000..f27784b --- /dev/null +++ b/build/lib/pawnshop/__init__.py @@ -0,0 +1,6 @@ +# import pawnshop.ChessBoard +# import pawnshop.ChessVector +# import pawnshop.Utils +# import pawnshop.GameNotations +__all__ = ["ChessVector", "ChessBoard", "GameNotations", "Utils", "Moves", "Utils", "Pieces", "Exceptions"] +from . import * diff --git a/build/lib/pawnshop/configurations/ClassicConfig.py b/build/lib/pawnshop/configurations/ClassicConfig.py new file mode 100644 index 0000000..039cf47 --- /dev/null +++ b/build/lib/pawnshop/configurations/ClassicConfig.py @@ -0,0 +1,66 @@ +# ClassicConfig.py + +from pawnshop.Pieces import * +from pawnshop.ChessVector import ChessVector +from pawnshop.Moves import * + +_colors = ("black", "white") +_black, _white = _colors +_classicPieces = { + Rook(_black): ChessVector((0, 0)), + Knight(_black): ChessVector((0, 1)), + Bishop(_black): ChessVector((0, 2)), + Queen(_black): ChessVector((0, 3)), + King(_black): ChessVector((0, 4)), + Bishop(_black): ChessVector((0, 5)), + Knight(_black): ChessVector((0, 6)), + Rook(_black): ChessVector((0, 7)), + Pawn(_black, "down"): ChessVector((1, 0)), + Pawn(_black, "down"): ChessVector((1, 1)), + Pawn(_black, "down"): ChessVector((1, 2)), + Pawn(_black, "down"): ChessVector((1, 3)), + Pawn(_black, "down"): ChessVector((1, 4)), + Pawn(_black, "down"): ChessVector((1, 5)), + Pawn(_black, "down"): ChessVector((1, 6)), + Pawn(_black, "down"): ChessVector((1, 7)), + + Rook(_white): ChessVector((7, 0)), + Knight(_white): ChessVector((7, 1)), + Bishop(_white): ChessVector((7, 2)), + Queen(_white): ChessVector((7, 3)), + King(_white): ChessVector((7, 4)), + Bishop(_white): ChessVector((7, 5)), + Knight(_white): ChessVector((7, 6)), + Rook(_white): ChessVector((7, 7)), + Pawn(_white, "up"): ChessVector((6, 0)), + Pawn(_white, "up"): ChessVector((6, 1)), + Pawn(_white, "up"): ChessVector((6, 2)), + Pawn(_white, "up"): ChessVector((6, 3)), + Pawn(_white, "up"): ChessVector((6, 4)), + Pawn(_white, "up"): ChessVector((6, 5)), + Pawn(_white, "up"): ChessVector((6, 6)), + Pawn(_white, "up"): ChessVector((6, 7)) +} + +for piece, vector in _classicPieces.items(): + piece.vector = vector + +_pieceDict = {color: [piece for piece in _classicPieces.keys() if piece.color == color] for color in _colors} +_moveDict = {color: [Standard, CastleK, CastleQ, EnPassant] for color in _colors} +_promoteToDict = {color: [Queen, Rook, Knight, Bishop] for color in _colors} +_promoteFromDict = {color: [Pawn] for color in _colors} +_promoteAtDict = {color: 8 for color in _colors} + +CONFIG = { + "rows": 8, + "cols": 8, + "pieces": _pieceDict, + "moves": _moveDict, + "promoteTo": _promoteToDict, + "promoteFrom": _promoteFromDict, + "promoteAt": _promoteAtDict, + "turnorder": ["white", "black"] +} + +if __name__ == "__main__": + print(CONFIG) diff --git a/build/lib/pawnshop/configurations/DefaultConfig.JSON b/build/lib/pawnshop/configurations/DefaultConfig.JSON new file mode 100644 index 0000000..4de45ae --- /dev/null +++ b/build/lib/pawnshop/configurations/DefaultConfig.JSON @@ -0,0 +1,12 @@ +{ + "rows": 8, + "cols": 8, + "colors": [], + "disabled": [], + "pieces": {}, + "moves": {}, + "promoteTo": {}, + "promoteFrom": {}, + "promoteAt": {}, + "turnorder": [] +} diff --git a/build/lib/pawnshop/configurations/FourPlayerConfig.py b/build/lib/pawnshop/configurations/FourPlayerConfig.py new file mode 100644 index 0000000..56ab996 --- /dev/null +++ b/build/lib/pawnshop/configurations/FourPlayerConfig.py @@ -0,0 +1,139 @@ +# FourPlayerConfig.py + +from pawnshop.Pieces import * +from pawnshop.Moves import * +from pawnshop.ChessVector import ChessVector + +_colors = ("yellow", "green", "red", "blue") +_yellow, _green, _red, _blue = _colors + +_fourPlayerPieces = { + Rook(_yellow): ChessVector((0, 3)), + Knight(_yellow): ChessVector((0, 4)), + Bishop(_yellow): ChessVector((0, 5)), + Queen(_yellow): ChessVector((0, 6)), + King(_yellow): ChessVector((0, 7)), + Bishop(_yellow): ChessVector((0, 8)), + Knight(_yellow): ChessVector((0, 9)), + Rook(_yellow): ChessVector((0, 10)), + Pawn(_yellow, "down"): ChessVector((1, 3)), + Pawn(_yellow, "down"): ChessVector((1, 4)), + Pawn(_yellow, "down"): ChessVector((1, 5)), + Pawn(_yellow, "down"): ChessVector((1, 6)), + Pawn(_yellow, "down"): ChessVector((1, 7)), + Pawn(_yellow, "down"): ChessVector((1, 8)), + Pawn(_yellow, "down"): ChessVector((1, 9)), + Pawn(_yellow, "down"): ChessVector((1, 10)), + + Rook(_green): ChessVector((3, 13)), + Knight(_green): ChessVector((4, 13)), + Bishop(_green): ChessVector((5, 13)), + Queen(_green): ChessVector((6, 13)), + King(_green): ChessVector((7, 13)), + Bishop(_green): ChessVector((8, 13)), + Knight(_green): ChessVector((9, 13)), + Rook(_green): ChessVector((10, 13)), + Pawn(_green, "left"): ChessVector((3, 12)), + Pawn(_green, "left"): ChessVector((4, 12)), + Pawn(_green, "left"): ChessVector((5, 12)), + Pawn(_green, "left"): ChessVector((6, 12)), + Pawn(_green, "left"): ChessVector((7, 12)), + Pawn(_green, "left"): ChessVector((8, 12)), + Pawn(_green, "left"): ChessVector((9, 12)), + Pawn(_green, "left"): ChessVector((10, 12)), + + Rook(_red): ChessVector((13, 3)), + Knight(_red): ChessVector((13, 4)), + Bishop(_red): ChessVector((13, 5)), + Queen(_red): ChessVector((13, 6)), + King(_red): ChessVector((13, 7)), + Bishop(_red): ChessVector((13, 8)), + Knight(_red): ChessVector((13, 9)), + Rook(_red): ChessVector((13, 10)), + Pawn(_red, "up"): ChessVector((12, 3)), + Pawn(_red, "up"): ChessVector((12, 4)), + Pawn(_red, "up"): ChessVector((12, 5)), + Pawn(_red, "up"): ChessVector((12, 6)), + Pawn(_red, "up"): ChessVector((12, 7)), + Pawn(_red, "up"): ChessVector((12, 8)), + Pawn(_red, "up"): ChessVector((12, 9)), + Pawn(_red, "up"): ChessVector((12, 10)), + + Rook(_blue): ChessVector((3, 0)), + Knight(_blue): ChessVector((4, 0)), + Bishop(_blue): ChessVector((5, 0)), + Queen(_blue): ChessVector((6, 0)), + King(_blue): ChessVector((7, 0)), + Bishop(_blue): ChessVector((8, 0)), + Knight(_blue): ChessVector((9, 0)), + Rook(_blue): ChessVector((10, 0)), + Pawn(_blue, "right"): ChessVector((3, 1)), + Pawn(_blue, "right"): ChessVector((4, 1)), + Pawn(_blue, "right"): ChessVector((5, 1)), + Pawn(_blue, "right"): ChessVector((6, 1)), + Pawn(_blue, "right"): ChessVector((7, 1)), + Pawn(_blue, "right"): ChessVector((8, 1)), + Pawn(_blue, "right"): ChessVector((9, 1)), + Pawn(_blue, "right"): ChessVector((10, 1)) +} +_disabled = [ + ChessVector((0, 0)), + ChessVector((0, 1)), + ChessVector((0, 2)), + ChessVector((1, 0)), + ChessVector((1, 1)), + ChessVector((1, 2)), + ChessVector((2, 0)), + ChessVector((2, 1)), + ChessVector((2, 2)), + ChessVector((0, 11)), + ChessVector((0, 12)), + ChessVector((0, 13)), + ChessVector((1, 11)), + ChessVector((1, 12)), + ChessVector((1, 13)), + ChessVector((2, 11)), + ChessVector((2, 12)), + ChessVector((2, 13)), + ChessVector((11, 0)), + ChessVector((11, 1)), + ChessVector((11, 2)), + ChessVector((12, 0)), + ChessVector((12, 1)), + ChessVector((12, 2)), + ChessVector((13, 0)), + ChessVector((13, 1)), + ChessVector((13, 2)), + ChessVector((11, 11)), + ChessVector((11, 12)), + ChessVector((11, 13)), + ChessVector((12, 11)), + ChessVector((12, 12)), + ChessVector((12, 13)), + ChessVector((13, 11)), + ChessVector((13, 12)), + ChessVector((13, 13)) +] +for piece, vector in _fourPlayerPieces.items(): + piece.vector = vector + +_pieceDict = {color: [piece for piece in _fourPlayerPieces.keys() if piece.color == color] for color in _colors} +_moveDict = {color: [Standard, CastleK, CastleQ] for color in _colors} +_promoteToDict = {color: [Queen, Rook, Knight, Bishop] for color in _colors} +_promoteFromDict = {color: [Pawn] for color in _colors} +_promoteAtDict = {color: 8 for color in _colors} + +CONFIG = { + "rows": 14, + "cols": 14, + "pieces": _pieceDict, + "moves": _moveDict, + "promoteTo": _promoteToDict, + "promoteFrom": _promoteFromDict, + "promoteAt": _promoteAtDict, + "disabled": _disabled, + "turnorder": ["red", "blue", "yellow", "green"] +} + +if __name__ == "__main__": + print(CONFIG) diff --git a/build/lib/pawnshop/configurations/__init__.py b/build/lib/pawnshop/configurations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config.pypirc b/config.pypirc new file mode 100644 index 0000000..787305b --- /dev/null +++ b/config.pypirc @@ -0,0 +1,7 @@ +[testpypi] +username = __token__ +password = pypi-AgENdGVzdC5weXBpLm9yZwIkZGVmMzQ2NmItYTRiZS00NDQyLWI0NGMtMjE1YjA2YTdhYTA2AAIleyJwZXJtaXNzaW9ucyI6ICJ1c2VyIiwgInZlcnNpb24iOiAxfQAABiBeZ1fDv4uC4Jd4PqJ6DyIgOIzHaihlRwrxALZuKY5fvA + +[pypi] +username = __token__ +password = pypi-AgEIcHlwaS5vcmcCJDA4OGFhYzUxLWU0ZGQtNDk5YS1hNzlmLTA0OTJmNDVkOTEzNwACJXsicGVybWlzc2lvbnMiOiAidXNlciIsICJ2ZXJzaW9uIjogMX0AAAYgYA0Y_MBPziPX1LkFMOTOyk1Az8oj6qVAhVFdz2OUW34 diff --git a/dist/pawnshop-1.0.3-py3-none-any.whl b/dist/pawnshop-1.0.3-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..b252aa4e0575dd6695e8416839b523e3580a8f05 GIT binary patch literal 21143 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 gKcebiKlkriL05~8teC#i_@% literal 0 HcmV?d00001 diff --git a/dist/pawnshop-1.0.3.tar.gz b/dist/pawnshop-1.0.3.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..673a36251b72301aef11ba37619187a8e99036f2 GIT binary patch literal 18053 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 Game Over! + ## Background + This is my first real project to scale (> 1000 lines of code) and the first to be published to [PyPi](https://pypi.org/project/pawnshop/). + + The project has certainly been a product of time with other hobby projects and school often taking priority. This along with multiple mid-project large scale refractorizations, reconsiderations and laziness should explain the unorganized codebase. + + Needless to say, I've attempted to document the code well and create a proper package that I can "proudly" publish, Although I still don't recommend anyone to continue developing or using this outside of simple projects like this. There are definitely better alternatives out there. + + Some central documentation, more extensive testing and usage examples would have been beneficial, but as of now I just want to continue with other projects and leave this as finished for the time being. + +Platform: UNKNOWN +Classifier: Programming Language :: Python :: 3 +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Requires-Python: >=3.6 +Description-Content-Type: text/markdown diff --git a/pawnshop.egg-info/SOURCES.txt b/pawnshop.egg-info/SOURCES.txt new file mode 100644 index 0000000..2e512d2 --- /dev/null +++ b/pawnshop.egg-info/SOURCES.txt @@ -0,0 +1,25 @@ +.gitattributes +.gitignore +LICENSE +README.md +setup.py +dist/pawnshop-1.0.3-py3-none-any.whl +dist/pawnshop-1.0.3.tar.gz +pawnshop/ChessBoard.py +pawnshop/ChessVector.py +pawnshop/Exceptions.py +pawnshop/GameNotations.py +pawnshop/Moves.py +pawnshop/Pieces.py +pawnshop/Utils.py +pawnshop/__init__.py +pawnshop.egg-info/PKG-INFO +pawnshop.egg-info/SOURCES.txt +pawnshop.egg-info/dependency_links.txt +pawnshop.egg-info/top_level.txt +pawnshop/configurations/ClassicConfig.py +pawnshop/configurations/DefaultConfig.JSON +pawnshop/configurations/FourPlayerConfig.py +pawnshop/configurations/__init__.py +tests/test_1.py +tests/test_2.py \ No newline at end of file diff --git a/pawnshop.egg-info/dependency_links.txt b/pawnshop.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/pawnshop.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/pawnshop.egg-info/top_level.txt b/pawnshop.egg-info/top_level.txt new file mode 100644 index 0000000..de2b74e --- /dev/null +++ b/pawnshop.egg-info/top_level.txt @@ -0,0 +1 @@ +pawnshop diff --git a/pawnshop/ChessBoard.py b/pawnshop/ChessBoard.py new file mode 100644 index 0000000..b945643 --- /dev/null +++ b/pawnshop/ChessBoard.py @@ -0,0 +1,554 @@ +# ChessBoard.py + +import json +import os +from copy import deepcopy, copy +from functools import wraps +from typing import Union, List, Dict, Generator + +from .ChessVector import ChessVector +from .Pieces import * +from .Moves import * +from .configurations import ClassicConfig, FourPlayerConfig +from .Utils import countAlpha, getResourcePath +from .Exceptions import * + + +def _defaultColors(func): + @wraps(func) + def wrapper(self, *colors): + if not colors: + colors = self.getColors() + returned = func(self, *colors) + if isinstance(returned, dict) and len(returned) == 1: + returned = returned.pop(*colors) + return returned + return wrapper + + +class Board(): + """Board object for storing and moving pieces + + :param config: Board configuration (defaults to emtpy board) + """ + + def __init__(self, config={}): + + self._board = [] + with open(getResourcePath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "configurations/DefaultConfig.JSON")), "r") as default: + dConfig = json.load(default) + + self._rows = config.get("rows") or dConfig.get("rows") + self._cols = config.get("cols") or dConfig.get("cols") + self._pieces = config.get("pieces") or dConfig.get("pieces") + self._moves = config.get("moves") or dConfig.get("moves") + self._promoteTo = config.get("promoteTo") or dConfig.get("promoteTo") + self._promoteFrom = config.get("promoteFrom") or dConfig.get("promoteFrom") + self._promoteAt = config.get("promoteAt") or dConfig.get("promteAt") + self._turnorder = config.get("turnorder") or dConfig.get("turnorder") + + try: + self.currentTurn = self._turnorder[0] + except IndexError: + self.currentTurn = None + self._board = [[Empty(ChessVector((row, col))) for col in range(self._cols)] for row in range(self._rows)] + + for color, pieceList in self._pieces.items(): + for piece in pieceList: + self[piece.vector] = piece + + for vec in config.get("disabled") or dConfig.get("disabled"): + self[vec] = Disabled(vec) + + self._checks = {key: False for key in self._pieces.keys()} + self._checkmates = copy(self._checks) + self._kings = {key: [piece for piece in self._pieces[key] if isinstance(piece, King)] for key in self._pieces.keys()} + self._history = [] + + self.checkForCheck() + + def __eq__(self, other): + for p1, p2 in zip(self, other): + if type(p1) == type(p2): + continue + else: + break + + else: + return True + return False + + def __ne__(self, other): + return not self == other + + def __iter__(self): + """Iterates through all positions in board + + Use iterPieces() method to iterate through pieces of board. + """ + for p in [p for row in self._board for p in row]: + yield p + + def __str__(self): + string = "\n" + ending = "\t|\n\n\t\t__" + ("\t__" * self._cols) + "\n\n\t\t" + alpha = countAlpha() + + for row in self._board: + + num, char = next(alpha) + + string += str(self._rows - num) + "\t|\t\t" + + for piece in row: + string += str(piece) + "\t" + + string += "\n\n" + + ending += "\t" + char.upper() + + return string + ending + "\n\n" + + def __setitem__(self, index, item): + + try: + iter(item) + except TypeError: + item = [item] + + try: + iter(index) + except TypeError: + index = [index] + + if len(index) != len(item): + raise ValueError("List index expected {0} values to unpack but {1} were given".format( + len(item), len(index))) + + for i, vec in enumerate(index): + + if isinstance(self._board[vec.row][vec.col], Disabled): + raise DisabledError(vec.getStr(self)) + + item1 = self._board[vec.row][vec.col] + item2 = item[i] + + if not isinstance(item2, Disabled): + + if not isinstance(item1, Empty): + self._removePiece(item1) + + if not isinstance(item2, Empty): + + if item2 in self.iterPieces(item2.color): + pass + else: + self._addPiece(item2, vec) + + self._board[vec.row][vec.col] = item2 + + def __getitem__(self, index): + res = [] + + try: + iter(index) + except TypeError: + index = [index] + + for vec in index: + if isinstance(self._board[vec.row][vec.col], Disabled): + raise DisabledError(vec.getStr(self)) + res.append(self._board[vec.row][vec.col]) + + if len(res) == 1: + return res.pop() + else: + return res + + def getRows(self) -> int: + """Get rows in board + + :returns: Number of rows in board + :rtype: ``int`` + """ + return self._rows + + def getCols(self) -> int: + """Get columns in board + + :returns: Number of columns in board + :rtype: ``int`` + """ + return self._cols + + def getHistory(self) -> list: + """Get history list of board + + :returns: History of board + :rtype: ``list`` + """ + return self._history + + + def getTurnorder(self) -> list: + """Get turnorder list of board + + :returns: Turnorder of board + :rtype: ``list`` + """ + return self._turnorder + + @_defaultColors + def getChecks(self, *colors: str) -> Union[bool, Dict[str, bool]]: + """Get checks in board + + If more than one color is given, this returns a ``dict`` + with a ``bool`` corresponding to each color. + + :param *colors: Colors to return + :returns: If colors are in check or not + :rtype: ``bool`` or ``dict`` + """ + return {col: self._checks[col] for col in colors} + + @_defaultColors + def getCheckmates(self, *colors: str) -> Union[bool, Dict[str, bool]]: + """Get checkmates in board + + If more than one color is given, this returns a ``dict`` + with a ``bool`` corresponding to each color. + + :param *colors: Colors to return + :returns: If colors are in checkmate or not + :rtype: ``bool`` or ``dict`` + """ + return {col: self._checkmates[col] for col in colors} + + @_defaultColors + def getKings(self, *colors: str) -> Union[List[King], Dict[str, List[King]]]: + """Get kings in board + + If more than one color is given, this returns a ``dict`` + with a ``list`` of kings corresponding to each color. + + :param *colors: Colors to return + :returns: All kings of colors in board + :rtype: ``list`` or ``dict`` + """ + return {col: self._kings[col] for col in colors} + + @_defaultColors + def getMoves(self, *colors: str) -> Union[List[Move], Dict[str, List[Move]]]: + """Get moves of board + + If more than one color is given, this returns a ``dict`` + with a ``list`` of moves corresponding to each color. + + :param *colors: Colors to return + :returns: All moves of colors in board + :rtype: ``list`` or ``dict`` + """ + return {col: self._moves[col] for col in colors} + + @_defaultColors + def getPromoteAt(self, *colors: str) -> Union[int, Dict[str, int]]: + """Get promotion position of board + + If more than one color is given, this returns a ``dict`` + with a ``int`` corresponding to each color. + + :param *colors: Colors to return + :returns: The promotion position of colors in board + :rtype: ``list`` or ``dict`` + """ + return {col: self._promoteAt[col] for col in colors} + + @_defaultColors + def getPromoteFrom(self, *colors: str) -> Union[List[Piece], Dict[str, List[Piece]]]: + """Get promotion starting pieces of board + + If more than one color is given, this returns a ``dict`` + with a ``list`` corresponding to each color. + + :param *colors: Colors to return + :returns: The promotion starting piece types of colors in board + :rtype: ``list`` or ``dict`` + """ + return {col: self._promoteFrom[col] for col in colors} + + @_defaultColors + def getPromoteTo(self, *colors: str) -> Union[List[Piece], Dict[str, List[Piece]]]: + """Get promotion target pieces of board + + If more than one color is given, this returns a ``dict`` + with a ``list`` corresponding to each color. + + :param *colors: Colors to return + :returns: The promotion target piece types of colors in board + :rtype: ``list`` or ``dict`` + """ + return {col: self._promoteTo[col] for col in colors} + + def getColors(self) -> List[str]: + """Get all colors of board + + :returns: List of colors in board + :rtype: ``list`` + """ + return self._pieces.keys() + + @_defaultColors + def iterPieces(self, *colors: str) -> Generator[Piece, None, None]: + """Iterate through pieces of board + + Use __iter__ to iterate through all positions of the board. + + :param *colors: Colors of pieces to iterate through (default is all colors) + :yields: Every piece in board + :ytype: ``generator`` + """ + for col in colors: + for p in self._pieces[col]: + yield p + + @_defaultColors + def eval(self, *colors: str) -> Dict[str, int]: + """Evaluate board + + Returns the sum of all pieces' values of colors in board + + :param *colors: Colors to evaluate (defaults to all colors of board) + + :returns: Colors with corresponding sum of pieces + :rtype: ``dict`` + """ + return {col: sum(list(map(lambda p: p.value, list(self.iterPieces(col))))) for col in colors} + + def removeColor(self, color: str) -> None: + """Remove color from board + + :param color: Color to remove + """ + vectors = list(map(lambda p: p.vector, list(self.iterPieces(color)))) + self[vectors] = [Empty(vector) for vector in vectors] + self.checkForCheck() + + def swapPositions(self, vec1: ChessVector, vec2: ChessVector) -> None: + """Swap position of two pieces + + :param vec1: Starting position of first piece + :param vec2: Starting position of second piece + """ + self._board[vec1.row][vec1.col].move(vec2) + self._board[vec2.row][vec2.col].move(vec1) + self._board[vec1.row][vec1.col], self._board[vec2.row][vec2.col] = self._board[vec2.row][vec2.col], self._board[vec1.row][vec1.col] + + def isEmpty(self, vec: ChessVector) -> bool: + """Check if position is empty + + :param vec: Position to check + :returns: True if position is empty, else False + :rtype: ``bool`` + """ + return isinstance(self[vec], Empty) + + def isThreatened(self, vec: ChessVector, alliedColor: str) -> bool: + """Check if position is threatened by enemy pieces + + :param vector: Position to check for threats + :param alliedColor: Color to exclude from enemy pieces + :returns: True if position is threatened, else False + :rtype: ``bool`` + """ + hostilePieces = [piece for col in self.getColors() if col != alliedColor for piece in self.iterPieces(col)] + + for hp in hostilePieces: + hostile = hp.getMoves(self, ignoreCheck=True) + if vec in hostile: + return True + else: + return False + + def checkForCheck(self, checkForMate=True) -> None: + """Check for any checks in board + + If checkForMate is True and king is in check, + method checks if any allied pieces can move to + interfere with the threatened check. + + :param checkForMate: Flag False to ignore checkmate (default is True) + :returns: None, stores result in attributes ``checks`` and ``checkmates`` + """ + for color in self.getColors(): + + for alliedKing in self._kings[color]: + + if self.isThreatened(alliedKing.vector, color): + self._checks[color] = True + break + else: + self._checks[color] = False + + if self._checks[color] and checkForMate: + + alliedPiecesPos = map(lambda p: p.vector, list(self.iterPieces(color))) + + for alliedPos in list(alliedPiecesPos): + for move in self[alliedPos].getMoves(self, ignoreCheck=True): + testBoard = deepcopy(self) + for pieceType in [None, *self._promoteTo[color]]: + try: + testBoard.movePiece(alliedPos, move, ignoreMate=True, + checkForMate=False, promote=pieceType, + printout=False, checkMove=False, ignoreOrder=True) + except PromotionError: + continue + else: + break + + if testBoard._checks[color]: + continue + else: + self._checkmates[color] = False + break + else: + continue + break + else: + self._checkmates[color] = True + + def advanceTurn(self) -> None: + """Advance the turn according to turnorder + """ + newidx = self._turnorder.index(self.currentTurn) + 1 + try: + self.currentTurn = self._turnorder[newidx] + except IndexError: + self.currentTurn = self._turnorder[0] + + def movePiece(self, startVec: ChessVector, targetVec: ChessVector, + ignoreOrder=False, ignoreMate=False, ignoreCheck=False, + checkForCheck=True, checkForMate=True, checkMove=True, + printout=True, promote=None) -> str: + """Move piece on board + + :param startVec: Position of moving piece + :param targetVec: Destination of moving piece + :param **Flags: Flags altering move rules, see below + :returns: Notation of move + :rtype: ``str`` + + :**Flags: + :ignoreOrder (False): Ignore the turnorder + :ignoreMate (False): Ignore if any pieces are in checkmate + :ignoreCheck (False): Ignore if any pieces are in check + :checkForCheck (True): Check for any checks after move + :checkForMate (True): Check for any checkmates after move + :checkMove (True): Check if piece is able to move to destination + :printout (True): Print the results of the move; checks, checkmates and move notation + :promote (None): Piece type to promote to + """ + + if self.isEmpty(startVec): + raise EmptyError(startVec.getStr(self)) + + startPiece = self[startVec] + + if not ignoreOrder and self.currentTurn != startPiece.color: + raise TurnError + + if self._checkmates[startPiece.color] and not ignoreMate: + raise CheckMate + + if checkMove and not targetVec.matches(startPiece.getMoves(self, ignoreCheck=ignoreCheck, ignoreMate=ignoreMate)): + raise IllegalMove(startVec.getStr(self), targetVec.getStr(self)) + + for move in self._moves[startPiece.color]: + if move.pieceCondition(startPiece): + if targetVec in move.getDestinations(startPiece, self): + notation = move.action(startPiece, targetVec, self, promote) + if checkForCheck: + self.checkForCheck(checkForMate=checkForMate) + break + + else: + raise IllegalMove(startVec.getStr(self), targetVec.getStr(self)) + + for color in self._checks.keys(): + if self._checkmates[color]: + if printout: + print(f"{color} in Checkmate!") + if not "#" in notation: + notation += "#" + + elif self._checks[color]: + if printout: + print(f"{color} in Check!") + if not "+" in notation: + notation += "+" + + for piece in self.iterPieces(): + + if not piece is startPiece: + piece.postAction(self) + + self._history.append(notation) + if printout: + print(notation) + + self.advanceTurn() + return notation + + def _addPiece(self, piece: Piece, vec: ChessVector) -> None: + if not piece.color in self.getColors(): + self._pieces[piece.color] = [] + self._kings[piece.color] = [] + self._checks[piece.color] = False + self._checkmates[piece.color] = False + + self._pieces[piece.color].append(piece) + + if isinstance(piece, King): + self._kings[piece.color].append(piece) + + piece.vector = vec + + def _removePiece(self, piece: Piece) -> None: + + self._pieces[piece.color].remove(piece) + + if isinstance(piece, King) and piece in self._kings[piece.color]: + self._kings[piece.color].remove(piece) + + if not self._pieces[piece.color]: + del self._pieces[piece.color] + del self._promoteTo[piece.color] + del self._promoteFrom[piece.color] + del self._promoteAt[piece.color] + del self._kings[piece.color] + del self._checks[piece.color] + del self._checkmates[piece.color] + + self._turnorder.remove(piece.color) + + piece.vector = None + + +def initClassic() -> Board: + """Initialize a chessBoard setup for 2 players, classic setup + + :returns: Classic chessboard + :rtype: ``Board`` + """ + board = Board(deepcopy(ClassicConfig.CONFIG)) + return board + + +def init4P() -> Board: + """Initialize a chessboard setup for four players + + :returns 4 player chessboard + :rtype: ``Board`` + """ + board = Board(deepcopy(FourPlayerConfig.CONFIG)) + return board diff --git a/pawnshop/ChessVector.py b/pawnshop/ChessVector.py new file mode 100644 index 0000000..8aee5bc --- /dev/null +++ b/pawnshop/ChessVector.py @@ -0,0 +1,200 @@ +# ChessVector.py + +from typing import Union, Tuple, List, TYPE_CHECKING +from .Utils import toAlpha, inverseIdx, countAlpha +# import pawnshop.ChessBoard + +if TYPE_CHECKING: + from pawnshop.ChessBoard import Board + + +class ChessVector(object): + """ChessVector object + + Object to store position on chessboard + Initialize object with position in (row, col) or string notation format + If a string notation format is given, the board must also be given + The vector supports common operations such as addition, multiplication with other vectors + + :param position: Tuple or string notation position on chessboard + :param board: Board to use when determining position given by string notation (default is None) + """ + + def __init__(self, position: Union[Tuple[int, int], str], board=None): + self._row = 0 + self._col = 0 + + if isinstance(position, tuple): + row, col = position + self.row = int(row) + self.col = int(col) + elif isinstance(position, str) and not board is None: + position = position.lower() + for char in position: + if char.isdigit(): + i = position.find(char) + if i == 0: + raise ValueError("Position does not include column!") + alpha = position[:i] + num = position[i::] + row = board.getRows() - int(num) + for n, a in countAlpha(): + if a == alpha: + col = n + break + else: + continue + break + else: + raise ValueError("position does not include row!") + self.row = row + self.col = col + else: + raise ValueError("Position is not a string or a tuple!") + + @property + def col(self): + return self._col + + @col.setter + def col(self, newCol): + self._col = newCol + + @property + def row(self): + return self._row + + @row.setter + def row(self, newRow): + self._row = newRow + + def __sub__(self, other): + if isinstance(other, ChessVector): + return ChessVector((self.row - other.row, self.col - other.col)) + else: + return ChessVector((self.row - other, self.col - other)) + + def __rsub__(self, other): + if isinstance(other, ChessVector): + return ChessVector((other.row - self.row, other.col - self.col)) + else: + raise ValueError(f"Cannot subtract {type(self)} from non-{type(self)}!") + + def __add__(self, other): + if isinstance(other, ChessVector): + return ChessVector((self.row + other.row, self.col + other.col)) + else: + return ChessVector((self.row + other, self.col + other)) + + def __radd__(self, other): + if isinstance(other, ChessVector): + return ChessVector((other.row + self.row, other.col + self.col)) + else: + raise ValueError(f"Cannot add {type(self)} to non-{type(self)}!") + + def __mul__(self, other): + if isinstance(other, ChessVector): + return ChessVector((self.row * other.row, self.col * other.col)) + else: + return ChessVector((self.row * other, self.col * other)) + + def __rmul__(self, other): + if isinstance(other, ChessVector): + return ChessVector((other.row * self.row, other.col * self.col)) + else: + raise ValueError(f"Cannot multiply non-{type(self)} by {type(self)}!") + + def __div__(self, other): + if isinstance(other, ChessVector): + return ChessVector((self.row / other.row, self.col / other.col)) + else: + return ChessVector((self.row / other, self.col / other)) + + def __rdiv__(self, other): + if isinstance(other, ChessVector): + return ChessVector((other.row / self.row, other.col / self.col)) + else: + raise ValueError(f"Cannot divide non-{type(self)} by {type(self)}!") + + def __neg__(self): + return ChessVector((-self.row, -self.col)) + + def __pos__(self): + return ChessVector((+self.row, +self.col)) + + def __eq__(self, other): + if isinstance(other, ChessVector): + return self.row == other.row and self.col == other.col + else: + raise ValueError(f"Cannot compare {type(self)} with {type(other)}!") + + def __ne__(self, other): + return not self == other + + def __lt__(self, other): + if isinstance(other, ChessVector): + return self.row < other.row and self.col < other.col + else: + raise ValueError(f"Cannot compare {type(self)} with {type(other)}!") + + def __gt__(self, other): + return not self < other + + def __le__(self, other): + return self < other or self == other + + def __ge__(self, other): + return self > other or self == other + + def __repr__(self): + return str((self.row, self.col)) + + def __str__(self): + return str((self.row, self.col)) + + def tuple(self) -> Tuple[int, int]: + """Return tuple format of vector + + :returns: (row, col) tuple + :rType: ´´tuple´´ + """ + return (self._row, self._col) + + def getStr(self, board: "Board") -> str: + """Return string notation format of vector + + :param board: Board to determine string position from + :returns: string notation of vector position + :rType: ´´str´´ + """ + notation = "" + notation += toAlpha(self.col) + notation += inverseIdx(self.row, board) + return notation + + def matches(self, otherVecs: List["ChessVector"]) -> bool: + """Check if vector matches any of other vectors + + :param otherVecs: List of other vectors + :returns: If match is found or not + :rType: ´´bool´´ + """ + for vec in otherVecs: + if self.row == vec.row and self.col == vec.col: + return True + else: + return False + + def copy(self) -> "ChessVector": + """Create a new copy of this vector + + :returns: Copy of this vector + :rType: ´´ChessVector´´ + """ + return ChessVector((self.row, self.col)) + + +if __name__ == "__main__": + + # Do some testing + pass diff --git a/pawnshop/Exceptions.py b/pawnshop/Exceptions.py new file mode 100644 index 0000000..5b1144d --- /dev/null +++ b/pawnshop/Exceptions.py @@ -0,0 +1,42 @@ +# Exceptions.py + +class Illegal(Exception): + """Move is ilegal""" + pass + + +class IllegalMove(Illegal): + def __init__(self, startPos, targetPos, + msg="Piece at {0} cannot move to {1}!"): + super().__init__(msg.format(startPos, targetPos)) + + +class CheckMate(Illegal): + def __init__(self, msg="Your king is in checkmate!"): + super().__init__(msg) + + +class EmptyError(IndexError): + def __init__(self, position, msg="Position {0} is empty!"): + super().__init__(msg.format(position)) + + +class DisabledError(IndexError): + def __init__(self, position, msg="Position {0} is out of bounce!"): + super().__init__(msg.format(position)) + + +class PromotionError(Exception): + def __init__(self, msg="Moved piece needs to be promoted!"): + super().__init__(msg) + + +class TurnError(Exception): + def __init__(self, msg="Wrong player!"): + super().__init__(msg) + + +if __name__ == "__main__": + + # Do some testing + pass diff --git a/pawnshop/GameNotations.py b/pawnshop/GameNotations.py new file mode 100644 index 0000000..4c011e5 --- /dev/null +++ b/pawnshop/GameNotations.py @@ -0,0 +1,194 @@ +# GameNotations.py + +import re +from copy import deepcopy + +from .ChessBoard import ( + initClassic, + Board +) +from .configurations import ClassicConfig +from .ChessVector import ChessVector +from .Pieces import * +from .Utils import toAlpha +from .Moves import ( + CastleK, + CastleQ +) + + +STANDARDTAGS = [ + "Event", + "Site", + "Date", + "Round", + "White", + "Black", + "Result" +] +OPTIONALTAGS = [ + "Annotator", + "PlyCount", + "TimeControl", + "Time", + "Termination", + "Mode", + "FEN" +] +ALLTAGS = [*STANDARDTAGS, *OPTIONALTAGS] + + +def board2PGN(board: Board, **tags) -> str: + """Get Portable Game Notation from board + + :param board: Board to get notation from + :param **tags: Tags added to the notation + :returns: PGN string + :rtype: ``str`` + + :**tags: Tags found in STANDARDTAGS and OPTIONALTAGS + """ + PGNString = "" + tags = {t.lower(): v for t, v in tags.items()} + + for TAG in ALLTAGS: + if TAG.lower() in tags: + PGNString += f"[{TAG} \"{str(tags[TAG.lower()])}\"]\n" + i = 0 + while i * 2 < len(board.history): + i += 1 + PGNString += str(i) + ". " + " ".join(board.history[(i - 1) * 2:i * 2:1]) + "\n" + + return PGNString + + +def PGN2Board(PGNString: str) -> Board: + """Get Board object from Portable Game Notation + + :param PGNString: PGN string + :returns: Board object from PGN + :rtype: ``Board`` + """ + notations = re.finditer(r"\s*(?PO-O-O)|(?PO-O)|(?P[A-Z]*)(?P[a-h]?)(?P[x]?)(?P[a-h]+)(?P\d+)=?(?P[A-Z]?)\+*\#?", PGNString) + + board = initClassic() + for i, notation in enumerate(notations): + color = ["white", "black"][i % 2 == 1] + if (not notation.group("castleK") is None) or (not notation.group("castleQ") is None): + for king in board.kings[color]: + for move in board.moves[color]: + if ((not notation.group("castleK") is None) and move is CastleK) or ((not notation.group("castleQ") is None) and move is CastleQ): + board.movePiece(king.vector, move.getDestinations(king, board).pop(), checkMove=False, ignoreMate=True, checkForCheck=False, printOut=False, ignoreOrder=True) + break + else: + continue + break + else: + for piece in board.pieces[color]: + vector = ChessVector(notation.group("col") + notation.group("rank"), board) + if vector.matches(piece.getMoves(board)): + pType = pieceNotations[notation.group("piece")] + if isinstance(piece, pType): + if notation.group("pcol") == "" or notation.group("pcol") == toAlpha(piece.vector.col): + board.movePiece(piece.vector, vector, checkMove=False, promote=pieceNotations[notation.group("promote")], ignoreMate=True, checkForCheck=False, printOut=False, ignoreOrder=True) + break + else: + continue + else: + continue + + board.checkForCheck() + return board + + +def FEN2Board(FENString: str) -> Board: + """Get Board object from Forsyth-Edwards-Notation + + :param FENString: Forsyth-Edwards-Notation + :returns: Board object from FEN + :rtype: ``Board`` + """ + board = Board() + config = deepcopy(ClassicConfig.CONFIG) + del config["pieces"] + board.setup(config) + + fieldFinder = re.finditer(r"[^ ]+", FENString) + rowFinder = re.finditer(r"([^/]+)", next(fieldFinder).group()) + + for rowi, row in enumerate(rowFinder): + coli = 0 + for chari, char in enumerate(row.group(0)): + if char.isnumeric(): + for coli in range(coli, coli + int(char)): + vector = ChessVector((rowi, coli), board) + board[vector] = Empty(vector) + coli += 1 + else: + vector = ChessVector((rowi, coli), board) + + if char.isupper(): + board[vector] = pieceNotations[char]("white", direction="up") + elif char.islower(): + board[vector] = pieceNotations[char.upper()]("black", direction="down") + coli += 1 + + # No other fields are critical, might implement more later + return board + + +def board2FEN(board: Board) -> str: + """Get Forsyth-Edward-Notation from board + + The notation does not account for: + current turn, castling potential, en-passant or move count + - only the position is notated (I am lazy) + + :param board: Board to get FEN from + :returns: FEN string + :rtype: ``str`` + """ + FENString = "" + for rowi, row in enumerate(board._board): + empty = 0 + for coli, piece in enumerate(row): + if isinstance(piece, Empty) or isinstance(piece, Disabled): + empty += 1 + else: + if empty: + FENString += str(empty) + if piece.color == "white": + ps = piece.symbol.upper() + elif piece.color == "black": + ps = piece.symbol.lower() + FENString += ps + empty = 0 + + if empty: + FENString += str(empty) + if not rowi == board.getRows() - 1: + FENString += "/" + + return FENString + + +def readable(historyList: List[str], players=2) -> str: + """Get printable format of history + + :param historyList: History to be read + :param players: How many players the history includes + :returns: Readable string of history + :rtype: ``str`` + """ + finalString = "" + i = 0 + while i * players < len(historyList): + i += 1 + finalString += str(i) + ". " + " - ".join(historyList[(i - 1) * players:i * players:1]) + "\n" + return finalString.strip() + + +if __name__ == "__main__": + + # Do some testing + pass diff --git a/pawnshop/Moves.py b/pawnshop/Moves.py new file mode 100644 index 0000000..0784822 --- /dev/null +++ b/pawnshop/Moves.py @@ -0,0 +1,397 @@ +# Moves.py + +from typing import List, Union, Tuple, TYPE_CHECKING +from abc import ABC, abstractclassmethod +from .Pieces import * +from .Utils import createNotation +from .Exceptions import PromotionError +from .ChessVector import ChessVector + +if TYPE_CHECKING: + from .ChessBoard import Board + + +class Move(ABC): + """Abstract class for moves in chess + """ + + @abstractclassmethod + def pieceCondition(thisMove, piece: Piece, *args, **kwargs) -> bool: + """Test if piece satisfies move requirement + """ + raise NotImplementedError + + @abstractclassmethod + def getDestinations(thisMove, piece: Piece, board: "Board", *args, **kwargs) -> list: + """Return list of possible destinations + """ + raise NotImplementedError + + @abstractclassmethod + def action(thisMove, startPiece, targetPos, board, *args, **kwargs) -> str: + """Move the piece + """ + raise NotImplementedError + + +class Standard(Move): + """Standard move in chess + + Moves piece according to .getStandardMoves() method + """ + + @classmethod + def pieceCondition(thisMove, *args, **kwargs) -> bool: + """Moving piece must satisfy this condition + + Since there is no condition to standard moves, this will always return True. + + :returns: True + :rtype: ``bool`` + """ + return True + + @classmethod + def getDestinations(thisMove, piece: Piece, board: "Board", *args, **kwargs) -> List[ChessVector]: + """Get possible destinations of piece + + Calls piece.getStandardMoves() method to get all standard moves of piece. + Call pieceCondition() classmethod prior. + + :param piece: Piece to get moves from + :param board: Board which piece is stored in + :returns: list of possible destinations + :rtype: list + """ + return piece.getStandardMoves(board) + + @classmethod + def action(thisMove, startPiece: Piece, targetVec: ChessVector, board: "Board", promote=None, *args, **kwargs) -> str: + """Performs the action of move + + Moves piece according to standard move rules. + Call pieceCondition() and getDestinations() classmethods prior. + + :param startPiece: Piece to be moved + :param targetVec: Destination of piece move + :param board: Board to perform move in + :param promote: Promotion type of piece (default is None) + :returns: Notation of move + :rtype: ``str`` + """ + promo = False + + for pieceType in board.getPromoteFrom(startPiece.color): + if isinstance(startPiece, pieceType): + + if startPiece.rank + abs((startPiece.vector - targetVec).tuple()[startPiece.forwardVec.col]) == board.getPromoteAt(startPiece.color): + if promote is None: + raise PromotionError + + if promote not in board.getPromoteTo(startPiece.color): + raise PromotionError( + f"{startPiece.color} cannot promote to {promote}!") + + promo = True + + break + + targetPiece = board[targetVec] + + notation = createNotation( + board, startPiece, targetVec, + isPawn=isinstance(startPiece, Pawn), capture=not isinstance(targetPiece, Empty)) + + if not isinstance(targetPiece, Empty): + board[targetVec] = Empty(targetVec) + board.swapPositions(startPiece.vector, targetVec) + else: + board.swapPositions(startPiece.vector, targetVec) + if promo: + newPiece = promote(startPiece.color) + newPiece.move(startPiece.vector) + board[startPiece.vector] = newPiece + notation += "=" + newPiece.symbol + + return notation + + +class _Castling(Move): + """Parent class to King-side and Queen-side castling + """ + + @classmethod + def pieceCondition(thisMove, piece: Piece, *args, **kwargs) -> bool: + """Moving piece must satisfy this condition + + Must be pieces first move and piece must be instance of ``King``. + + :param piece: Piece to check + :returns: If piece satisfies requirements + :rtype: ``bool`` + """ + return piece.firstMove and isinstance(piece, King) + + @classmethod + def action(thisMove, startPiece: Piece, targetVec: ChessVector, board: "Board", *args, **kwargs) -> None: + """Performs the action of move + + Moves piece according to move rules. + Returns None as Queen-side and King-side castling are noted differently. + Call pieceCondition() and getDestinations() classmethods prior. + + :param startPiece: Piece to be moved + :param targetVec: Destination of piece move + :param board: Board to perform move in + """ + for rook in thisMove.findRooks(startPiece, board): + between = thisMove.findBetween(startPiece.vector, rook.vector) + if targetVec in between: + kingTarget, rookTarget = thisMove.getTargets(between) + board.swapPositions(startPiece.vector, kingTarget) + board.swapPositions(rook.vector, rookTarget) + break + else: + raise ValueError(f"Piece cannot move to {targetVec}") + + def findBetween(vec1: ChessVector, vec2: ChessVector) -> List[ChessVector]: + """Helper function to find all positions between two positions + + Helper function. + If there are not positions between or given positions + are not in a row, the returned list is empty. + + :param vec1: First position + :param vec2: Second position + :returns: List of possitions betweeen + :rtype: ``list`` + """ + rowStep = vec1.row - vec2.row and (1, -1)[vec1.row - vec2.row < 0] + colStep = vec1.col - vec2.col and (1, -1)[vec1.col - vec2.col < 0] + + if not rowStep: + colRange = range(vec2.col + colStep, vec1.col, colStep) + rowRange = [vec1.row] * len(colRange) + elif not colStep: + rowRange = range(vec2.row + rowStep, vec1.row, rowStep) + colRange = [vec1.col] * len(rowRange) + else: + rowRange = range(0, 0) + colRange = range(0, 0) + + return [ChessVector(idx) for idx in zip(rowRange, colRange)] + + def emptyBetween(board: "Board", between: List[ChessVector]) -> bool: + """Check if all positions are emtpy + + Helper funciton. + Check if all positions between two pieces are empty + + :param board: Board to check positions in + :param between: List of positions to check + :returns: If all positions are empty or not + :rtype: ``bool`` + """ + for vector in between: + if not isinstance(board[vector], Empty): + return False + else: + return True + + def findRooks(piece: Piece, board: "Board") -> List[Piece]: + """Find all rooks in board that are on same lane as piece + + Helper function. + Iterates through all pieces on board looking for + rooks on same lane as piece. + + :param piece: Piece to check for same lane + :param board: Board to check for rooks in + :returns: List of rooks on same lane as piece + :rtype: ``list`` + """ + def vecCondition(vec1, vec2): + return bool(vec2.row - vec1.row) != bool(vec2.col - vec1.col) and (not vec2.row - vec1.row or not vec2.col - vec2.col) + + rookList = [] + for p in board.iterPieces(piece.color): + if isinstance(p, Rook) and p.firstMove and vecCondition(piece.vector, p.vector): + rookList.append(p) + return rookList + + def getTargets(between: list) -> Union[Tuple[ChessVector], None]: + """Get castling targets + + Helper function + Get the two middle squares of list of positions between. + If list is of length 1, this returns None. + Biased towards the start of list. + + + :param between: List of positions between + :returns: Tuple of target positions + :rtype: ``tuple`` or None + """ + if not len(between) > 1: + return None + if not len(between) % 2: + target1 = between[int((len(between) / 2) - 1)] + target2 = between[int((len(between) / 2))] + else: + target1 = between[int((len(between) / 2) - 0.5)] + target2 = between[int((len(between) / 2) + 0.5)] + return (target1, target2) + + +class CastleK(_Castling): + """Castle King-side move + """ + + @classmethod + def getDestinations(thisMove, piece: Piece, board: "Board", *args, **kwargs) -> List[ChessVector]: + """Get possible destinations of piece + + Returns all possible castling moves of piece. + Call pieceCondition() classmethod prior. + + :param piece: Piece to get moves from + :param board: Board which piece is stored in + :returns: list of possible destinations + :rtype: list + """ + destList = [] + if not board.getChecks(piece.color): + for rook in thisMove.findRooks(piece, board): + between = thisMove.findBetween(piece.vector, rook.vector) + if thisMove.emptyBetween(board, between) and not len(between) % 2: + kingTarget, _ = thisMove.getTargets(between) + walked = thisMove.findBetween(piece.vector, kingTarget) + for vec in walked: + if board.isThreatened(vec, piece.color): + break + else: + destList.append(kingTarget) + return destList + + @classmethod + def action(thisMove, *args, **kwargs) -> str: + """Performs the action of move + + Moves piece according to move rules. + Returns the notation of the castling move + Call pieceCondition() and getDestinations() classmethods prior. + + :param startPiece: Piece to be moved + :param targetVec: Destination of piece move + :param board: Board to perform move in + :returns: Notation of move + :rtype: str + """ + super().action(*args, **kwargs) + return "O-O" + + +class CastleQ(_Castling): + + @classmethod + def getDestinations(thisMove, piece: Piece, board: "Board", *args, **kwargs) -> List[ChessVector]: + """Get possible destinations of piece + + Returns all possible castling moves of piece. + Call pieceCondition() classmethod prior. + + :param piece: Piece to get moves from + :param board: Board which piece is stored in + :returns: list of possible destinations + :rtype: list + """ + destList = [] + if not board.getChecks(piece.color): + for rook in thisMove.findRooks(piece, board): + between = thisMove.findBetween(piece.vector, rook.vector) + if thisMove.emptyBetween(board, between) and len(between) % 2: + kingTarget, _ = thisMove.getTargets(between) + walked = thisMove.findBetween(piece.vector, kingTarget) + for vec in walked: + if board.isThreatened(vec, piece.color): + break + else: + destList.append(kingTarget) + return destList + + @classmethod + def action(thisMove, *args, **kwargs) -> str: + """Performs the action of move + + Moves piece according to move rules. + Returns the notation of the castling move + Call pieceCondition() and getDestinations() classmethods prior. + + :param startPiece: Piece to be moved + :param targetVec: Destination of piece move + :param board: Board to perform move in + :returns: Notation of move + :rtype: str + """ + super().action(*args, **kwargs) + return "O-O-O" + + +class EnPassant(Move): + """Special move en-passant + """ + + @classmethod + def pieceCondition(thisMove, piece: Piece, *args, **kwargs) -> bool: + """Moving piece must satisfy this condition + + Piece must be of instance ``Pawn`` + + :param piece: Piece to check + :returns: If piece satisfies requirements + :rtype: ``bool`` + """ + return isinstance(piece, Pawn) + + @classmethod + def getDestinations(thisMove, piece: Piece, board: "Board", *args, **kwargs) -> List[ChessVector]: + """Get possible destinations of piece + + Returns all possible en-passant moves of piece. + Call pieceCondition() classmethod prior. + + :param piece: Piece to get moves from + :param board: Board which piece is stored in + :returns: list of possible destinations + :rtype: list + """ + destList = [] + for diagVec in (piece.lDiagVec, piece.rDiagVec): + checkVec = (piece.vector - piece.forwardVec) + diagVec + try: + if isinstance(board[checkVec], Pawn) and board[checkVec].passed and board[checkVec].forwardVec == -piece.forwardVec: + destList.append(piece.vector + diagVec) + except IndexError: + pass + return destList + + @classmethod + def action(thisMove, piece: Piece, targetVec: ChessVector, board: "Board", *args, **kwargs) -> str: + """Performs the action of move + + Moves piece according to move rules. + Returns the notation of the en-passant move + Call pieceCondition() and getDestinations() classmethods prior. + + :param startPiece: Piece to be moved + :param targetVec: Destination of piece move + :param board: Board to perform move in + :returns: Notation of move + :rtype: str + """ + notation = createNotation(board, piece, targetVec, + isPawn=True, capture=True) + + board[targetVec - piece.forwardVec] = Empty(targetVec - piece.forwardVec) + board.swapPositions(piece.vector, targetVec) + return notation diff --git a/pawnshop/Pieces.py b/pawnshop/Pieces.py new file mode 100644 index 0000000..fe9b84c --- /dev/null +++ b/pawnshop/Pieces.py @@ -0,0 +1,423 @@ +# Pieces.py + +from copy import deepcopy +from abc import ABC, abstractmethod +from typing import List, Tuple, TYPE_CHECKING +from .Utils import _positivePos, _catchOutofBounce, removeDupes +from .ChessVector import ChessVector + +if TYPE_CHECKING: + from .ChessBoard import Board + +_directions = { + "up": ((-1, 0), (-1, -1), (-1, 1)), + "down": ((1, 0), (1, 1), (1, -1)), + "right": ((0, 1), (-1, 1), (1, 1)), + "left": ((0, -1), (1, -1), (-1, -1)) +} +_directions = {key: [ChessVector(offset) for offset in _directions[key]] for key in _directions} + + +class Piece(ABC): + """Abstract base class for pieces + + :param color: Color of piece + :param value: Numerical value of piece + :param symbol: Char symbol of piece + """ + + def __init__(self, color: str, value: int, symbol: str, *args, **kwargs): + self.vector = None + self.color = color + self.value = value + self.symbol = symbol + self.firstMove = True + + def __str__(self): + return self.color[0] + self.symbol + + @abstractmethod + def getStandardMoves(self, board: "Board"): + """Returns standard destinations of piece in board + """ + raise NotImplementedError + + def getMoves(self, board: "Board", ignoreCheck=False, ignoreMate=False) -> List[ChessVector]: + """Returns all moves of piece in board + + Uses board.getMoves() method to check what moves piece is allowed to. + + :param board: Board to move in + :param **Flags: Flags to pass into move + :returns: List of possible moves + :rtype: ``list`` + + :**Flags: + :ignoreCheck (False): Ignore checks when getting moves + :ignoreMate (False): Ignore checkmate when getting moves + """ + destList = [] + for move in board.getMoves(self.color): + if move.pieceCondition(self): + destList.extend(move.getDestinations(self, board)) + + if not ignoreCheck: + remove = [] + + for dest in destList: + testBoard = deepcopy(board) + testBoard.movePiece(self.vector, dest, ignoreMate=ignoreMate, checkForMate=False, printout=False, checkMove=False, promote=Queen, ignoreOrder=True) + if testBoard.getChecks(self.color): + remove.append(dest) + + for dest in remove: + destList.remove(dest) + + return destList + + def move(self, destVector: ChessVector) -> None: + """Move piece to destination + + :param destVector: Destination + """ + self.vector = destVector + self.firstMove = False + + def postAction(self, board: "Board") -> None: + """Do action after piece is moved in board + + Call this after a piece is moved in board + """ + pass + + @_positivePos + @_catchOutofBounce + def canWalk(self, vector: ChessVector, board: "Board") -> bool: + """Check if piece can walk to destination in board + + :param vector: Destination + :param board: Board to check in + :returns: If piece can move + :rtype: ``bool`` + """ + return board.isEmpty(vector) + + @_positivePos + @_catchOutofBounce + def canCapture(self, vector: ChessVector, board: "Board") -> bool: + """Check if piece can capture to destination in board + + :param vector: Destination + :param board: Board to check in + :returns: If piece can capture + :rtype: ``bool`` + """ + destPiece = board[vector] + try: + return destPiece.color != self.color + except AttributeError: + return False + + @_positivePos + @_catchOutofBounce + def canMove(self, vector: ChessVector, board: "Board") -> bool: + """Check if piece can capture to destination in board + + :param vector: Destination + :param board: Board to check in + :returns: If piece can move (capture or walk) + :rtype: ``bool`` + """ + destPiece = board[vector] + try: + return destPiece.color != self.color + except AttributeError: + return board.isEmpty(vector) + + def _getMovesInLine(self, iterVector: ChessVector, board: "Board") -> List[ChessVector]: + """Get moves in one line + + Return all positions piece is can move to iterating with iterVector. + Stops if piece can capture as piece cannot continue moving after capturing. + + :param iterVector: Vector to iterate moves with + :param board: Board to check in + :returns: List of possible destinations + :rtype: ``list`` + """ + moveList = [] + newV = self.vector + while True: + newV += iterVector + if self.canWalk(newV, board): + moveList.append(newV) + elif self.canCapture(newV, board): + moveList.append(newV) + break + else: + break + return moveList + + +class Pawn(Piece): + """Pawn object + + :param color: Color of piece + :param direction: Movement direction of Pawn (default is "up") + :param rank: Starting rank of pawn, used to calc promote + """ + + def __init__(self, color: str, direction="up", rank=2, *args, **kwargs): + super().__init__(color, 1, "P") + + self.passed = False + self.direction = direction.lower() + self.rank = rank + + if direction in _directions.keys(): + self.forwardVec, self.lDiagVec, self.rDiagVec = _directions[direction] + else: + raise ValueError(f"Direction is not any of {_directions.keys()}") + + def getStandardMoves(self, board: "Board") -> List[ChessVector]: + """Returns standard destinations of piece in board + + :param board: Board to check in + :returns: List of standard posssible destinations + :rtype: ``list`` + """ + destList = [] + destVec = self.vector + self.forwardVec + + if self.canWalk(destVec, board): + destList.append(destVec) + if self.firstMove: + destVec += self.forwardVec + if self.canWalk(destVec, board): + destList.append(destVec) + + for destVec in self.getAttacking(board): + if self.canCapture(destVec, board): + destList.append(destVec) + + return destList + + def move(self, newV: ChessVector) -> None: + """Move piece to destination + + If Pawn moves 2 places, it can be captured by en-passant. + + :param newV: Destination + """ + if self.firstMove: + if abs(self.vector.row - newV.row) == 2 or abs(self.vector.col - newV.col) == 2: + self.passed = True + self.rank += 1 + self.rank += 1 + super().move(newV) + + def postAction(self, *args, **kwargs): + """Do action after piece is moved in board + + Call this after a piece is moved in board + """ + self.passed = False + + def getAttacking(self, *args, **kwargs) -> Tuple[ChessVector]: + """Get the threatened positions of piece + + :returns: Tuple of threatened positions + :rType: ´´tuple´´ + """ + return [self.vector + self.lDiagVec, self.vector + self.rDiagVec] + + +class Rook(Piece): + """Rook object + + :param color: Color of piece + """ + + def __init__(self, color: str, *args, **kwargs): + super().__init__(color, 5, "R") + + def getStandardMoves(self, board: "Board") -> List[ChessVector]: + """Returns standard destinations of piece in board + + :param board: Board to check in + :returns: List of standard posssible destinations + :rtype: ``list`` + """ + destList = [] + for vecTuple in _directions.values(): + forwardVec = vecTuple[0] + destList.extend(self._getMovesInLine(forwardVec, board)) + return destList + + +class Knight(Piece): + """Knight object + + :param color: Color of piece + """ + + def __init__(self, color: str, *args, **kwargs): + super().__init__(color, 3, "N") + + def getStandardMoves(self, board: "Board") -> List[ChessVector]: + """Returns standard destinations of piece in board + + :param board: Board to check in + :returns: List of standard posssible destinations + :rtype: ``list`` + """ + destList = [] + offsetList = [ + (1, 2), + (1, -2), + (-1, 2), + (-1, -2), + (2, 1), + (2, -1), + (-2, 1), + (-2, -1) + ] + vecList = [ChessVector(offset) for offset in offsetList] + + for offsetVec in vecList: + destVec = self.vector + offsetVec + if self.canMove(destVec, board): + destList.append(destVec) + return destList + + +class Bishop(Piece): + """Bishop object + + :param color: Color of piece + """ + + def __init__(self, color: str, *args, **kwargs): + super().__init__(color, 3, "B") + + def getStandardMoves(self, board: "Board") -> List[ChessVector]: + """Returns standard destinations of piece in board + + :param board: Board to check in + :returns: List of standard posssible destinations + :rtype: ``list`` + """ + destList = [] + for vecTuple in _directions.values(): + destList.extend(self._getMovesInLine(vecTuple[1], board)) + return destList + + +class King(Piece): + """King object + + :param color: Color of piece + """ + + def __init__(self, color: str, *args, **kwargs): + super().__init__(color, int(1e10), "K") + + def getStandardMoves(self, board: "Board") -> List[ChessVector]: + """Returns standard destinations of piece in board + + :param board: Board to check in + :returns: List of standard posssible destinations + :rtype: ``list`` + """ + destList = [] + for offsetVec in removeDupes([vec for vecList in _directions.values() for vec in vecList]): + destVec = self.vector + offsetVec + if self.canMove(destVec, board): + destList.append(destVec) + return destList + + +class Queen(Piece): + """Queen object + + :param color: Color of piece + """ + + def __init__(self, color: str, *args, **kwargs): + super().__init__(color, 9, "Q") + + def getStandardMoves(self, board: "Board") -> List[ChessVector]: + """Returns standard destinations of piece in board + + :param board: Board to check in + :returns: List of standard posssible destinations + :rtype: ``list`` + """ + destList = [] + for vecTuple in _directions.values(): + destList.extend(self._getMovesInLine(vecTuple[0], board)) + destList.extend(self._getMovesInLine(vecTuple[1], board)) + return destList + + +class Disabled(): + """Disabled object + + Object for representing disabled positions in chessboard + + :param vector: Position of disabled square + """ + + def __init__(self, vector: ChessVector, *args, **kwargs): + self.vector = vector + + def __str__(self): + return " " + + def move(self, vec: ChessVector): + """Move disabled object + + Move the disabled square + + :param vec: New position + """ + self.vector = vec + + +class Empty(): + """Empty object + + Object for representing empty positions in chessboard + + :param vector: Position of empty square + """ + + def __init__(self, vector: ChessVector, *args, **kwargs): + self.vector = vector + + def __str__(self): + return "__" + + def move(self, vec: ChessVector): + """Move empty object + + Move the empty square + + :param vec: New position + """ + self.vector = vec + + +pieceNotations = { + "P": Pawn, + "N": Knight, + "B": Bishop, + "R": Rook, + "Q": Queen, + "K": King +} + +if __name__ == "__main__": + + # Do some testing + pass diff --git a/pawnshop/Utils.py b/pawnshop/Utils.py new file mode 100644 index 0000000..0eb9da3 --- /dev/null +++ b/pawnshop/Utils.py @@ -0,0 +1,159 @@ +# Utils.py + +from typing import List, Generator, TYPE_CHECKING +from string import ascii_lowercase +import sys, os + +if TYPE_CHECKING: + from .ChessBoard import Board + from .ChessVector import ChessVector + from .Pieces import Piece + +def _catchOutofBounce(func): + """Decorator for catching out of bounce ´´IndexError´´""" + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except IndexError: + return False + return wrapper + + +def _positivePos(func): + """Decorator for ensuring a position is not negative""" + def wrapper(pInstance, vector, bInstance, *args, **kwargs): + if not vector.row < 0 and not vector.col < 0: + return func(pInstance, vector, bInstance, *args, **kwargs) + else: + return False + return wrapper + + +def removeDupes(vectorList: List["ChessVector"]) -> List["ChessVector"]: + """Remove duplicate positions + + :param vectorList: List to remove duplicates from + :returns: List without duplicates + :rtype: ``list`` + """ + for i, superVec in enumerate(vectorList): + if superVec.matches(vectorList[i + 1::]): + vectorList.remove(superVec) + return removeDupes(vectorList) + else: + return vectorList + + +def createNotation(board: "Board", startPiece: "Piece", targetVec: "ChessVector", isPawn=False, capture=False) -> str: + """Create a notation for a move + + Creates notation of move according to standard chess notation. + + :param startPiece: Piece to be moved + :param targetVec: Destination of move + :param **Flags: Flags to create notation + :returns: Notation of move + :rtype: ``str`` + + :**Flags: + :isPawn (True): + :capture (True): + """ + notation = "" + targetNot = targetVec.getStr(board) + + if not isPawn: + notation = startPiece.symbol + for piece in board.iterPieces(startPiece.color): + if piece is not startPiece and isinstance(piece, type(startPiece)): + if targetVec.matches(piece.getMoves(board, ignoreCheck=True)): + if piece.vector.col == startPiece.vector.col: + notation += inverseIdx(startPiece.vector.row, board) + else: + notation += toAlpha(startPiece.vector.col) + break + elif capture: + notation = toAlpha(startPiece.vector.col) + + if capture: + notation += "x" + + notation += targetNot + return notation + + +def countAlpha() -> Generator[str, None, None]: + """Generator to count in alphabetical order + + Counts in alphabetical order. + a->b->c->...->aa->ab->...->ba->... + + :yields: Character + :ytype: ``generator`` + """ + stringList = [0] + num = 0 + while True: + yield (num, "".join([ascii_lowercase[num] for num in stringList])) + i = 1 + num += 1 + + while True: + if i > len(stringList): + stringList.insert(0, 0) + break + else: + changeTo = stringList[-i] + 1 + if changeTo >= len(ascii_lowercase): + stringList[-i::] = [0] * (i) + i += 1 + continue + else: + stringList[-i] = changeTo + break + + +def inverseIdx(idx: int, board: "Board") -> str: + """Inverse index + + Inverses idx given board rows and returns string + + :param idx: Index to reverse + :param board: Board to reverse according to rows + :returns: Reversed index + :rtype: ``str`` + """ + return str(board.getRows() - idx) + + +def toAlpha(num: int) -> str: + """Convert number to alphabetical + + Counts through all alpha until reaching number. + (I tried to make it not have to count through all alphas, + however, since my alpha system doesn't match any regular + base number system I was not able to.) + + :param num: Number to convert + :returns: Alphabetical string from num + :rtype: str + """ + for n, notation in countAlpha(): + if num == n: + return notation + +def getResourcePath(relative_path): + """ + Get pyinstaller resource + """ + + if hasattr(sys, '_MEIPASS'): + return os.path.join(sys._MEIPASS, relative_path) + + return os.path.join(os.path.abspath("."), relative_path) + + +if __name__ == "__main__": + # Do some testing + + pass diff --git a/pawnshop/__init__.py b/pawnshop/__init__.py new file mode 100644 index 0000000..f27784b --- /dev/null +++ b/pawnshop/__init__.py @@ -0,0 +1,6 @@ +# import pawnshop.ChessBoard +# import pawnshop.ChessVector +# import pawnshop.Utils +# import pawnshop.GameNotations +__all__ = ["ChessVector", "ChessBoard", "GameNotations", "Utils", "Moves", "Utils", "Pieces", "Exceptions"] +from . import * diff --git a/pawnshop/__pycache__/ChessBoard.cpython-37.pyc b/pawnshop/__pycache__/ChessBoard.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3fb1774f8874ce44ab3c88daccb3195351b17b70 GIT binary patch literal 18076 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 literal 0 HcmV?d00001 diff --git a/pawnshop/__pycache__/ChessBoard.cpython-39.pyc b/pawnshop/__pycache__/ChessBoard.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5196733ef5329e3e74a0b4adc430d147a0a1ce9 GIT binary patch literal 18510 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 HrI`I+aPf~c literal 0 HcmV?d00001 diff --git a/pawnshop/__pycache__/ChessVector.cpython-37.pyc b/pawnshop/__pycache__/ChessVector.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f8b18f55748738932a113f9c7493fbc7dee5727 GIT binary patch literal 5833 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 S{248mRI8oVCx64{Ed4)-w3h?` literal 0 HcmV?d00001 diff --git a/pawnshop/__pycache__/ChessVector.cpython-39.pyc b/pawnshop/__pycache__/ChessVector.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a5b7445cfae1f510920eed14f34886f7e47da00 GIT binary patch literal 6772 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 literal 0 HcmV?d00001 diff --git a/pawnshop/__pycache__/ClassicConfig.cpython-37.pyc b/pawnshop/__pycache__/ClassicConfig.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa42caaf13c367d8880ee3d608b6150f98c35b7c GIT binary patch literal 2349 zcmbu9&u`l{6vs(flI0(994BegHr?8-`{Axj(;kPQSmLb15UfkrZUY1ts%J8eYFlzi zrCEmbhq<^_Z}Y~xx27XCh+w6wI?ej zLjFOKK05%P!%sf32w{ZL0eOU9Ye0w8r*zt9L0`cy@Iu@2ZQtQ8x5t!IhHcTX1MC@g zflG#qz-7Z;W=shJ;Q6jCBy5$Wy2RUV=e)wb1ws@HaCDP++kPrdfv&YcXR4$PF>5X_j2m}oVuP< zAB<^u)vx}v!K-f;{v>{3pu2Cb-eTv@`hr`jp-}$yYYPOKC?3tBHRwngc3W|D81^jTw(mVmIuYv#<`-KXnGE=T3J?6!7tsN%u@fb*hdQcR!t`{3f$0ctp~LBV z|3@&&d2q~mr0j3wxUby(DC`|2%H0hy@#lrG^&cT%mFRq|0O)*FF&{mslx!xbvJYU3 z&+~HoJTHyZOZf%Vaq6Q-taO)1Gz+@=*xxSSBa-ieggTvsL z0*qN>Ettivbv@M>e161Ef?Bz)Z|>jw>PuWg)EV+1fCDYUD4EtI9_M*=DsOujv*Q83 oi#LvZ3XMmr)U#~(Re`c^jpp8S{%;f-v}(;}6fBy3tA#TC7t#XN`~Uy| literal 0 HcmV?d00001 diff --git a/pawnshop/__pycache__/ClassicConfig.cpython-39.pyc b/pawnshop/__pycache__/ClassicConfig.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70ce3d88028012c6f537a66bc49a241da53c1f0b GIT binary patch literal 2340 zcmbu9&u`pB6vt=0w%7Y3o6YaGls0X{j}0ZHg%gS@(haRpL`i9>khPUrb|xE>U3)v@ zGzto*R03{ATsf3u{vS?r;lODnZd?#2-W!j%woudqYx()}ot=5_`OO=5r>3d|et$k< zU#(UM`2$7%w+Z4SxMjy8gb_vuzQbK^pHNO2+OlB> z*fZ<`R}7bdtA@SYoCM3^e5MnO#%RI{LF3Tn2X<_c;al*=)D z$`-(u4KD(FhL?aVhR*?44WG}=xd5E4eGNGCc@enA9rn6@pGyUGxuBK{>PkUfEvRb+ zwNg-ToX~LDuk-qgsb9$!dEA6OYxvWAnm2;!=k&>M>`nI8Gr~(-75(Xy2D^`P6^SI*&HnDCZ0hM7dTIN#C_q1UE7hEC1K$E6(G zId*cK^D{2PxUyM%7=dSWnnlrUqG-mM*&Hv&aEd15c($@RwH((|#z*E~v+=Ll_}6Uw zYc~Eh8~>V(f6Zu=bufb1IGn}MY+`6OF*KVPnoSJNCWdA-nNN(-Luc6!8B2*Jmn&dG za!ejy{E_@Xz9+jBKOWD#guI~Gb|>^_>xm89ZWVms*5NkIp^pn-`sic!J9c$|HNiQCbZmomQzu+d{E^M%{mkzGI z{2%(X5%f|AJD7@8_C7z7Eh=VVmCh;6ZXP6fo;X3zViC=GF#o9tZc`g>T@hVXU6F(& zG_jIW-%e|(1GH@AJxIC{>k6jI?XFA)d^>~ts`7EP18>-kl2a?qY6in%xl)6v=`3y_ zU{3!x9q0Z5=aI5MkK=viZbxB%FH!F85DPCZO#~Kpwa!ZagUq|YgCOv0BQWunr)&`)LSsPV zO6BT~mC6~$PoY~XBM}edgg=Z`&EOpvnT&5Hs*)UtC>D$hjQZjl>Lt_;d}xPK;6YUGQ~e$G ztAWmU3wqzL1emkNMli`+>t?1g`QnVt1hsP8pWVCj$z6Pfs5|6A0B2W(QIhM!NjoTS wV;Hl80l$UsCL=H&ty9mk;jRPauLdn#uloOYd!7ZiBW31#N+LxC`1f+6LX>UC>?Oz;hS0$9>Sg z(H+n|-Ur<`>vch|@B!$7(H`hkz6N^DXkYaB@X%6!@k_sv(Ru_PH5Y}^eQ0_Jogib% z$=Kp_Y;z0d+GY1~l8F5vM?2ll|9B%$%_QXD~$b7gh}C0@CsV1ko;;LMk%}zWlihiKAhSt2XVHPsx?1%=dh2;4 zW_c_#eP=dT7=w*5Aasv9)cj{DOnicuz6D(qrt^kOso4sDY`-B689NZPTlTAhbZcMQ__@(m1f@`}8p$M!qxm>ydv)(NGM z31EWm09scUGog@ToW(hi>i~^3%z><3n^emJNQ3AQpoq=L1O=Axj$Rwgn6?#W z+Qw@s{5DNYBTEf%yb4_%hRMZ-8Ai5XMvvATXWGQp@!d}nQ|Q52Zr29P+Lz!FT3v9W z3iSmz^|sPkKHpMGs?!a~g&4m8la=sU3VgO1Ar!pu?pAlb0fP#Xg}HdLgvvfcrB-0( z$Tqx6Z&sQ347n>Xzb1G2%)K@`oN|72m*FjJH98z$0NvnWti!!T@CChFAJdaX9>{61 zCyOkCRQqxi)m3o(GXTG>V{0wp1u?*D=QD1=J>aLQh#GG3|+{E(8 zgPAFhnGjrKMcfm?%*+$~Yksm=$Lqwqw(M9N9O6AcID~eyHsDc(dI?-}YU20O948bi zyHyUKX1a(XIK8Gt@HhwmAx0#rrxeb-QI0^^(#0nE8KanE#|u)kY0|R}u>9 zED7gAea%hzp>`jVdA+gU-j5qF>@OCpM+@?k;}U+{22X@e~-k!w$5?pEGnl`(M=` B*>wN_ literal 0 HcmV?d00001 diff --git a/pawnshop/__pycache__/Exceptions.cpython-39.pyc b/pawnshop/__pycache__/Exceptions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..311efe6680c8f63c44947632b199046abe45a302 GIT binary patch literal 2286 zcmb`I!EW0|5QcY2N+M;;cAdaUlQf}{lP^JgDo_+{fYu0dh+!ZFiUAdd z#0wbq0;ZQtGRgmhq1cr>;GiApf_4=L?SS@V3v`RL3%V^kpgYO~e+#rP1JD8KHt4Qg z1HDH3bwIDn9_SuvUj=gG#4)FG^9|eGiXnnq7YFqMhTVcmu>qHC;7C4jr335Ty7MYc z)lrn5Z@kvOt1z))LVADKt9>EzC{sezT_G|ps}${ZA%3f(bmr*@A$2Uon3*=5K-1*M z_`6|cm9fK-Hr5VdMU|<%w8P@8Jl6TJi2lg!u`X_mz0+6~WukNYyf`x$HIQZ&(fZuu z^vfs7p%*Xv7^Zo0-Ul|~1U(qIADG7m9;Di;*Ji0=qLc6-QLzf6GJOB_Wf(_!uFEh( z1f>q&e{th5OOD;1|xdbImzC_cv_J_s4GCvQ?oB@=Nlh z1ZHjEkVPdh^{h%q1K*aBDG#)*12B#hnQmr}#=H)Mh||bgA*Mt-aGdGE6u__!Ow2jI zyO7KC=9pb>aLu@7aFxMe4Mt9P*S*=ku}afsZ+c|gxP(se!NM`c?y$cNsui&ZKOL+1 z*Sqxvx=2B+bLHbzglv2bU&d4t*D3psYoI{c&Tz}`gr z3!Eq+N^Q;#ol|!Fe0v_yBDs5b=UtrUbv)gO_SgZ(Mze>{4eAxSboa8NJliuyo5>E8 zLW0##)d(-4LSH{d0L_JV3kE^K^Jh>TtikKsXv`Y;$1X9c{bIKHQaYG+L+8#-Sc`Zb z!jk299)R~wvkBlqy&|68&xwr=QzfT_9{-z=uF6o4!b4rtzb3k1b7x1+}zXDQU5b9jdUPbxQu1 z*UMng1_^wQ3}(@k%)Q&;ETZY-?Mu;w;QgxE2+*Kj5l!$*WoA@*zZwmlrbweRW&VEx z&ziuYHn!)dE+&sFmM8xdyf~V=KpQ_9S(M~L?C*B#VE$30&mT2o>O6J#RW8*jJ$!oC msHg~^!EL7f|E7=4dtvQ=KmCsX;Nr>HTDMwTZ5XpZ-~9)c6|-Fc literal 0 HcmV?d00001 diff --git a/pawnshop/__pycache__/FourPlayerConfig.cpython-37.pyc b/pawnshop/__pycache__/FourPlayerConfig.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e7e6f5eb0c02b0218348c59791d3f0034e830567 GIT binary patch literal 4012 zcmb`J%X1q=5XN^`D_KuJWIN8oAq4VL02AI2Oh{tKA($l28y1C)q8ZDIC9N{Mvg4F< zpqwhgEfiM{;mE(VH=v3t-#O((cdJLK!o&nq%GroM)Z_E->!}E;8>+$YzK>ptHCF3F6Tq4B(JM5m1C z9V2?zh~6`z_l@WSBl-}cti)a9h>t*X%pU{inLhz8FrNl4GJl%LnFZEuKLgf%o&he& zA@Mnl=d2N(GotfGG-pKfMs&f57L4fPmfbw-4Z~=@fa{L9MEWJ^BI#G8mr1XXUL{>3 zy+(SS^aklo(p#jrN$-$;P5KR~NBS*kmGmy@cceb)J<=NKebPFqAeE%cqz%#)(kAHx z($y`yal{*WG%bf;jQ(oPTX#pRjyL9&Wm%5-<>&6h+jlI{678p!OwC(cwl^+1iD*Iu ziD*)E6VV>=FcD1&C6pYO6LM1SkyCO+gd!3>vDR?K`Y$&_DPV!+=>jt+p-=7s^Z3(4cTf1>v6iFr0m2gB}MGsZ}nuw*@jlu zb_&}%DvJzf=77@)&Lns!!PrQ*%_SJm=hzj&dI#=eafuM@JND3 z`;1+0b3S&h;6DNT$F4PF*P1a^Gj^>RyVi_d6US~#E=SNCxp0N#G65;tg_&HK$%UC* zn8}5iT$r(FJxjzqOT;` RY0kD8cgiI``J7)@fQ*M(ZqN_!Bq2QhmPvj;JI5VHp{ zdk`m9f;JL((H!yqpp{^zIbx z8)mX$CL3mS)59Q6T;F!!CivndokPqy#8dqo{L(qZoI}hx*#9<9J$d1LLjT0Le%fnq zf;or0IEVV=9AeHP<{Z4O>mlZPh`AnGPBaH!V$LDv9NJc#L(DnEoI`!0KtIG>4>8w6 zeR2*l=MZxa^~pKJoI{LKM{|7PH=bE|o`pw1n%1Uuclt-`2W#2JC&8!L$a-Pp3BF}N zvwn6Sx^Q+ngZ8+vc>axJ`~4sdY+rt2Z`u#6XV$8t+)eu@YV{i)w&{A%f)mz>otaZ{ zy4#d>S+Ny1RQo_)UYI*wy%kCoR+j@6hSm9?-fPQF6jqluqLrXi?bg;i;Y!fmfrX%_ zmRhw9spf*ta*03@xGh;oToE6P_|MJXE22?ZVFAsWI!lJ_1 zU6mUl)OJAErt3V2b>??^N*JiaaLoO^JADt`*dj@DJ9d{~ZFh6o z*v$pEH@W1Wzy{VHuy}Nc;U?S7;_;5fHZ{UYRQM0Ba4d@c<8!gAf_2y?u%W{^LpwQ) z)9qjl_D-&=f_4zen?YP)T!1Ts>*u04E1F^LehZ!}a#2rp0wtu1v%cT1H9Nk4rE(z7 z@J38u#y%DPLgOKyem|i%P2I(2r=cns z9|G}m{-A<%Gm`C4O=DVSjh=zxyiY@#hu=$_^Kn>&!~S4s6p!i{$7g1oizv>_UA?k! z=^~D;Q)^4#ho8M_cB1|U#`W0Q%5iqK9f)2_p22e|{2L@|XYHcn+Qn4SHs8ax@pjC+ gMf0vb{2%X^QyKgJc;}rmjAu}J%CXJ&Xew|21CRmv7ytkO literal 0 HcmV?d00001 diff --git a/pawnshop/__pycache__/FourPlayerConfig.cpython-39.pyc b/pawnshop/__pycache__/FourPlayerConfig.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..861831f6481ada5ee3460f4a794d305d7bd26b47 GIT binary patch literal 4003 zcmb`J-*XdH6vy{&HffS3{awm$iy#=lioX>r0xd5;)7FW z)HfVoeL&y%Bm6&{JB}|t_1On!c=DW+vtjQb zH3n8*VfC&Y2Q4sf11>U80QWO*2QD%1NcrppPR8y6PGar`E~~8EL-W~dSy%}kwyYV;nzgJmmNjQtXE#OTq(1<&`2eoF@*L@hr1PX7k)9{L zKzfmMf%FpTWzs98i=INF}KvT_&xQ zu8=lJZ;`HUiuwV6Pz^jB`pKDft`AjRf7l;UBWgGpdEnh%lug;X@2JeIvnl*h*-lww zGE7>x>l-fGFnOF-E4ML#odBY&Q=D; zx;l>xXJwDGDbA(1FU2@08Cys(p3TOS;d&eHVsV=9664+EdVh*bDK4jYAjN|z9_liN z-eP|Yt>OOw<74QAF?7P{nlOe=7(*wFp^1&xRLe0$qbyt@Wf=pPBnvZTVWuq1l!ckH zFjE$0EIR2DF?WfWyF|=25c8~wxl6>{C1Ols+|C-*im8$u#GHeea}aY5V$MO#Ifyw2 zacU)KBS9|e5$_LL31;dMGxdm>dc;gUVx}G;o2+isSCXZmU6rJv?avF&Olg=Y4Kt-- zrZmiyh8eRZ(;!Y=-*Vw59`?Pjr3oEAb&_A7b{w_*>k1%7y)e_{6w=Nv`cF zW*^GMJ`|IEh}nmjeeiZt4>8w6%=OT6Vsgk!%s#~IL)(gdh}nmjeJCah#3AN-h`An$ z$v(vFL(D!DlYNNUhZtoElP51^Jge|biw=M^oO{mo-QPLiI^Q_U0-po#=VRxgz!Q8^ z{NUd9;QVy07`VWA4vu1sogj6LG4hqTCvHKURablW#P>Avk@HBH>{8f-lh!rv)G?D? zYp5lqahTo#^7zcm@#@t`>8QFK>L{wthD)86YR6G^VIy7%+tsz&dOKPP*Pg&!*wG8k z+J@3IVSBkzpIX~6#pz}vikHIH+KDe9?4R&)?v78(#!~m<)RX&A$#AgTi?M(OMaElI z8&O5*-7t_U>(;mzObFhKgTeJA%6^ci`hvZkflq`hLcJZrzQj%}Vz-{@_LIU_GA4f| zuC--NOVc+~i{hr5PvFucw_C_A2-B?wo29WDO1yzE3G!40`0qm%V2h~&TA6MIai;F2 zYw%3=QV0L_yvt#@YI5^!JV8wEbOSrC4?&23%6=EGPl7y^9rp!g$6ZL-OWpa`c`tS6 zE{wMT%lga&U&82rNq}3M5)?6kba<-@O$Qit>sjUBOQLVlaqVebHI0qaSihz}_JZ+r zxDNXQc5h^Iv~44kZH0GWvlP~J*a~B{7@B^@Ik?ihekwM3*@$X4oA5AEh&#F+YN@oz z2SKaWXa~WC$}W@RotHh2F?9w~o4$a4ERo+AbRSFnTtLqyz8AoQiJuMd$wXJM%)=;0 zP+r8L-h887*GJLb2jUlkUIp1ktXh%YgKoJy^x$KP0ZnNZejuh0;Is$_g5K1~9!hMS zpY3rfCObEC@xt7>vpBbQt)+qhe&f2)j#C?_w|7l`x)sV!Q=P!sMvp=AA}>m=CrX); tu-^m1db{@Bl6_YU{MY-xcyl@Nzr26*4i1T;+p9j~3j3Y=D_1ItzX3Qx`Lh53 literal 0 HcmV?d00001 diff --git a/pawnshop/__pycache__/GameNotations.cpython-39.pyc b/pawnshop/__pycache__/GameNotations.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec8e9d8a95287e371e2cc976f6d49c3e5be58670 GIT binary patch literal 4902 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 L)n7eYca8r6-f#=; literal 0 HcmV?d00001 diff --git a/pawnshop/__pycache__/Moves.cpython-37.pyc b/pawnshop/__pycache__/Moves.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..85cb88fe298c0c808859d70f4f84876a7b436d28 GIT binary patch literal 6769 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 YQh|$IF3FOrELBcbPFGg&d%`vT3rv5G(f|Me literal 0 HcmV?d00001 diff --git a/pawnshop/__pycache__/Moves.cpython-39.pyc b/pawnshop/__pycache__/Moves.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd72137c339a0e522921deb827f016a7d16dfd63 GIT binary patch literal 12369 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 ClOBrz literal 0 HcmV?d00001 diff --git a/pawnshop/__pycache__/Pieces.cpython-37.pyc b/pawnshop/__pycache__/Pieces.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43501bd9b847f288b7c41b532c3629f3ab4cf506 GIT binary patch literal 8908 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 literal 0 HcmV?d00001 diff --git a/pawnshop/__pycache__/Pieces.cpython-39.pyc b/pawnshop/__pycache__/Pieces.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c05d30e2c4f321d478af790a39d10fb9aaf6a6d GIT binary patch literal 12332 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 literal 0 HcmV?d00001 diff --git a/pawnshop/__pycache__/Utils.cpython-37.pyc b/pawnshop/__pycache__/Utils.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..23da5f6e24c78da2cc805539024ea74927449d96 GIT binary patch literal 2361 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 literal 0 HcmV?d00001 diff --git a/pawnshop/__pycache__/__init__.cpython-37.pyc b/pawnshop/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a8f4e9ef3ddc50309ff9905361ef6c507403a55f GIT binary patch literal 148 zcmZ?b<>g`kf@vou6F~H15CH>>K!yVl7qb9~6oz01O-8?!3`HPe1o6w(*(xTqIJKxa zCN00HxH!foKe;qFHLs*NCZMt;BR?;uAhA5JI3vFR!HAE~%*!l^kJl@xyv1RYo1ape MlWGStwit*R0G{V1GXMYp literal 0 HcmV?d00001 diff --git a/pawnshop/__pycache__/__init__.cpython-39.pyc b/pawnshop/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27d5691cfd2ada9c3cb88a1774e76a38d3f3b327 GIT binary patch literal 269 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 NT>D-6y;Q5Ze*%}j5N7}Y literal 0 HcmV?d00001 diff --git a/pawnshop/configurations/__pycache__/__init__.cpython-39.pyc b/pawnshop/configurations/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2dfc9881590b8d772bc7a2b8e90883b138ee951 GIT binary patch literal 167 zcmYe~<>g`kf*^&o1Q7igL?8o3AjbiSi&=m~3PUi1CZpdloLW>I zla^mpTpW{lIYq;;_lhPbtkwwFBAm8HgDGDhDhG literal 0 HcmV?d00001 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..462691e --- /dev/null +++ b/setup.py @@ -0,0 +1,30 @@ +import setuptools + +with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + +setuptools.setup( + name="pawnshop", # Replace with your own username + version="1.0.3", + author="Nils Forssén", + author_email="forssennils@gmail.com", + description="A simple chess library as hobby project.", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/NilsForssen/pawnshop", + packages=setuptools.find_packages(), + package_data={ + "pawnshop": ["configurations\\*.JSON"] + }, + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires='>=3.6', + extras_requires={ + "dev": [ + "pytest>=3.7", + ], + }, +) diff --git a/tests/__pycache__/test_1.cpython-39-pytest-6.2.2.pyc b/tests/__pycache__/test_1.cpython-39-pytest-6.2.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d07f0c21d80fbd801b5c18a57cce6b2ae5bed06e GIT binary patch literal 2830 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 AOaK4? literal 0 HcmV?d00001 diff --git a/tests/test_1.py b/tests/test_1.py new file mode 100644 index 0000000..c324f48 --- /dev/null +++ b/tests/test_1.py @@ -0,0 +1,128 @@ +# test_1.py + +from pawnshop.ChessVector import ChessVector +from pawnshop.ChessBoard import initClassic +from pawnshop.Exceptions import IllegalMove, CheckMate +from pawnshop.GameNotations import board2FEN +from pawnshop.Pieces import Queen + + +board = initClassic() + + +class UnsuccessfulTest(Exception): + pass + + +def move(start, target, **kwargs): + board.movePiece(ChessVector(start, board), ChessVector(target, board), printout=False, **kwargs) + + +def test_moves(): + movelist = [ + ("a2", "a3"), + ("e7", "e5"), + ("b1", "c3"), + ("d7", "d5"), + ("c3", "d5"), + ("d8", "h4") + ] + for m in movelist: + move(*m) + + try: + move("f2", "f4") + raise UnsuccessfulTest + except IllegalMove: + pass + + move("d5", "c7") + + try: + move("h4", "f2") + raise UnsuccessfulTest + except IllegalMove: + pass + + try: + move("b7", "b6") + raise UnsuccessfulTest + except IllegalMove: + pass + + movelist = [ + ("e8", "d8"), + ("c7", "a8"), + ("f8", "c5"), + ("a8", "b6") + ] + + for m in movelist: + move(*m) + + try: + move("b7", "b6") + raise UnsuccessfulTest + except IllegalMove: + pass + + move("a7", "b6") + + move("h4", "f2", ignoreOrder=True) + + try: + move("e1", "f2", ignoreOrder=True) + raise UnsuccessfulTest + except CheckMate: + pass + + move("d2", "d4", ignoreCheck=True, ignoreOrder=True, ignoreMate=True) + move("d1", "d3", ignoreCheck=True) + move("c1", "e3", ignoreCheck=True, ignoreOrder=True) + + try: + move("e1", "a1") + raise UnsuccessfulTest + except IllegalMove: + pass + + move("f2", "f5", ignoreOrder=True) + move("f5", "d3", ignoreOrder=True) + + try: + move("e1", "a1") + raise UnsuccessfulTest + except IllegalMove: + pass + + move("d3", "f5", ignoreOrder=True) + move("e1", "c1", ignoreOrder=True) + move("d4", "e5") + move("g8", "h6", ignoreCheck=True) + + try: + move("d8", "h8", ignoreCheck=True, ignoreOrder=True) + raise UnsuccessfulTest + except IllegalMove: + pass + + try: + move("d8", "d7", ignoreOrder=True) + raise UnsuccessfulTest + except IllegalMove: + pass + + move("d8", "e7", ignoreOrder=True) + move("f5", "h5") + move("f7", "f5", ignoreOrder=True) + move("e5", "f6", ignoreOrder=True) + move("e7", "e8", ignoreOrder=True) + move("f6", "f7", ignoreOrder=True) + move("f7", "f8", promote=Queen) + move("e8", "f8") + + print(board) + + +def test_FEN(): + assert board2FEN(board) == "1nb2k1r/1p4pp/1p5n/2b4q/8/P3B3/1PP1P1PP/2KR1BNR" diff --git a/tests/test_2.py b/tests/test_2.py new file mode 100644 index 0000000..cf64b74 --- /dev/null +++ b/tests/test_2.py @@ -0,0 +1,29 @@ +# test_2.py + +from pawnshop.ChessBoard import init4P +from pawnshop.ChessVector import ChessVector + +board = init4P() + +def move(start, target, **kwargs): + board.movePiece(ChessVector(start, board), ChessVector(target, board), **kwargs) + +def test_moves(): + move("f2", "f3") + + move("g2", "g3", ignoreOrder=True) + + move("g1", "g2", ignoreOrder=True) + + move("b6", "c6", ignoreOrder=True) + + move("m7", "l7", ignoreOrder=True) + + move("n7", "m7", ignoreOrder=True) + + move("h2", "h3", ignoreOrder=True) + + move("g2", "g1", ignoreOrder=True) + + move("b5", "c5", ignoreOrder=True) + -- 2.30.2