from typing import Iterable, Callable from itertools import product from copy import deepcopy from sys import setrecursionlimit as setRecursionLimit setRecursionLimit(100000) class Piece: def __init__(self, type, color): self.type = type self.color = color def __str__(self): return self.type.upper() if self.color == 'white' else self.type @property def symbol(self): symbols = [{ 'p': '♙', 'r': '♖', 'n': '♘', 'b': '♗', 'q': '♕', 'k': '♔', }, { 'p': '♟︎', 'r': '♜', 'n': '♞', 'b': '♝', 'q': '♛', 'k': '♚', }] return symbols[0 if self.color == 'white' else 1][self.type] @staticmethod def possibleMoves(x, y, board, legalMoves=None) -> Callable[[int, int], Iterable[tuple]]: piece = board.getPieceAt(x, y) moves = [] # TODO: If player is in check, only validate the moves that are leading to places that can be blocked in order to save the king. Make a function that overwrites `moves`. Notice: The king is the only piece that can 'Escape' to a position that may or may not be in the list of positions to be blocked. Make an exception pieceIsEnemyColor = lambda pieceToCheck: pieceToCheck != None and pieceToCheck.color != piece.color pieceIsEmpty = lambda pieceToCheck: pieceToCheck == None pieceIsEmptyOrEnemyColor = lambda pieceToCheck: pieceToCheck == None or pieceToCheck.color != piece.color positionInsideBounds = lambda x, y: x in range(8) and y in range(8) def addMoveIfTrue(xOffset, yOffset, condition: Callable[[Piece], bool]): """Tests a condition against a position away from self. Adds if condition returns true""" if condition(board.getPieceAt(x + xOffset, y + yOffset)): moves.append((x + xOffset, y + yOffset)) return True return False # TODO: fix deepcopy segfault and recursion error def assertNotCheck(newX, newY) -> bool: testBoard = deepcopy(board) testBoard.movePiece((x, y), (newX, newY)) return not testBoard.checkCheck(piece.color) def addWhileInsideBoard(direction: tuple): localX, localY = x, y while positionInsideBounds(localX, localY): localX += direction[0] localY += direction[1] if board.getPieceAt(localX, localY) == None: moves.append((localX, localY)) else: if board.getPieceAt(localX, localY).color != piece.color: moves.append((localX, localY)) return if piece.type == 'p': localY = 1 if piece.color == 'black' else -1 startPosition = 1 if piece.color == 'black' else 6 addMoveIfTrue(1, localY, pieceIsEnemyColor) addMoveIfTrue(-1, localY, pieceIsEnemyColor) if addMoveIfTrue(0, localY, pieceIsEmpty): addMoveIfTrue(0, localY * 2, lambda pieceToCheck: pieceToCheck == None and y == startPosition) elif piece.type == 'n': positions = [ (-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1), ] for position in positions: addMoveIfTrue(*position, pieceIsEmptyOrEnemyColor) elif piece.type == 'k': positions = list(product([-1, 0, 1], repeat=2)) positions.remove((0, 0)) for position in positions: addMoveIfTrue(*position, pieceIsEmptyOrEnemyColor) elif piece.type == 'r': for direction in [(1, 0), (-1, 0), (0, 1), (0, -1)]: addWhileInsideBoard(direction) elif piece.type == 'b': for direction in product([-1, 1], repeat=2): addWhileInsideBoard(direction) elif piece.type == 'q': directions = list(product([-1, 0, 1], repeat=2)) directions.remove((0, 0)) for direction in directions: addWhileInsideBoard(direction) # Remove moves that will lead the piece out of the board moves = [move for move in moves if positionInsideBounds(*move)] # Remove moves that won't block the path for whichever piece has caused the game to check if legalMoves != None and piece.type != 'k': moves = [move for move in moves if move in legalMoves] # Remove moves that will put the king in check # moves = [position for position in moves if assertNotCheck(*position)] return moves