--- /dev/null
+# Pawnshop
+A simple chess package for python written solely as hobby project. I've also written a GUI with networking socket capabilities (add link here) and might create some AI furter down the line.
+
+The package also includes a 4-player mode, although the ruleset is unchanged from the classic game, Checkmate -> 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.
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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*(?P<castleQ>O-O-O)|(?P<castleK>O-O)|(?P<piece>[A-Z]*)(?P<pcol>[a-h]?)(?P<capture>[x]?)(?P<col>[a-h]+)(?P<rank>\d+)=?(?P<promote>[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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# import pawnshop.ChessBoard
+# import pawnshop.ChessVector
+# import pawnshop.Utils
+# import pawnshop.GameNotations
+__all__ = ["ChessVector", "ChessBoard", "GameNotations", "Utils", "Moves", "Utils", "Pieces", "Exceptions"]
+from . import *
--- /dev/null
+# 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)
--- /dev/null
+{
+ "rows": 8,
+ "cols": 8,
+ "colors": [],
+ "disabled": [],
+ "pieces": {},
+ "moves": {},
+ "promoteTo": {},
+ "promoteFrom": {},
+ "promoteAt": {},
+ "turnorder": []
+}
--- /dev/null
+# 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)
--- /dev/null
+[testpypi]
+username = __token__
+password = pypi-AgENdGVzdC5weXBpLm9yZwIkZGVmMzQ2NmItYTRiZS00NDQyLWI0NGMtMjE1YjA2YTdhYTA2AAIleyJwZXJtaXNzaW9ucyI6ICJ1c2VyIiwgInZlcnNpb24iOiAxfQAABiBeZ1fDv4uC4Jd4PqJ6DyIgOIzHaihlRwrxALZuKY5fvA
+
+[pypi]
+username = __token__
+password = pypi-AgEIcHlwaS5vcmcCJDA4OGFhYzUxLWU0ZGQtNDk5YS1hNzlmLTA0OTJmNDVkOTEzNwACJXsicGVybWlzc2lvbnMiOiAidXNlciIsICJ2ZXJzaW9uIjogMX0AAAYgYA0Y_MBPziPX1LkFMOTOyk1Az8oj6qVAhVFdz2OUW34
--- /dev/null
+Metadata-Version: 2.1
+Name: pawnshop
+Version: 1.0.3
+Summary: A simple chess library as hobby project.
+Home-page: https://github.com/NilsForssen/pawnshop
+Author: Nils Forssén
+Author-email: forssennils@gmail.com
+License: UNKNOWN
+Description: # Pawnshop
+ A simple chess package for python written solely as hobby project. I've also written a GUI with networking socket capabilities (add link here) and might create some AI furter down the line.
+
+ The package also includes a 4-player mode, although the ruleset is unchanged from the classic game, Checkmate -> 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
--- /dev/null
+.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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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*(?P<castleQ>O-O-O)|(?P<castleK>O-O)|(?P<piece>[A-Z]*)(?P<pcol>[a-h]?)(?P<capture>[x]?)(?P<col>[a-h]+)(?P<rank>\d+)=?(?P<promote>[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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# import pawnshop.ChessBoard
+# import pawnshop.ChessVector
+# import pawnshop.Utils
+# import pawnshop.GameNotations
+__all__ = ["ChessVector", "ChessBoard", "GameNotations", "Utils", "Moves", "Utils", "Pieces", "Exceptions"]
+from . import *
--- /dev/null
+# 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)
--- /dev/null
+{
+ "rows": 8,
+ "cols": 8,
+ "colors": [],
+ "disabled": [],
+ "pieces": {},
+ "moves": {},
+ "promoteTo": {},
+ "promoteFrom": {},
+ "promoteAt": {},
+ "turnorder": []
+}
--- /dev/null
+# 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)
--- /dev/null
+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",
+ ],
+ },
+)
--- /dev/null
+# 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"
--- /dev/null
+# 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)
+