def addPiece(self, location, colour, pieceType): # Create the piece piece = ChessPiece(colour, pieceType) # Put the piece in it's initial location assert(self.squares.has_key(location) is False) assert(type(location) == str) self.squares[location] = piece # Update the bitboards field = bitboard.LOCATIONS[bitboard.getIndex(location)] if colour is WHITE: self.whiteBitBoard |= field else: self.blackBitBoard |= field self.allBitBoard |= field return piece
def movePiece(self, colour, start, end, promotionType = QUEEN, testCheck = True, allowSuicide = False, applyMove = True): """Move a piece. 'colour' is the colour of the player moving. 'start' is a the location to move from in algebraic format (string). 'end' is a the location to move to in algebraic format (string). 'promotionType' is the type of piece to promote to if required. 'testCheck' is a flag to control if the opponent will be in check after this move. 'allowSuicide' if True means a move is considered valid even if it would put the moving player in check. 'applyMove' is a flag to control if the move is applied to the board (True) or just tested (False). Returns the pieces moved in the form (result, moves). The moves are a list containing tuples of the form (piece, start, end). If a piece was removed 'end' is None. If the result is successful the pieces on the board are modified. If the move is illegal None is returned. """ assert(promotionType is not KING) assert(type(start) is str and len(start) == 2) assert(type(end) is str and len(end) == 2) # Get the piece to move try: piece = self.squares[start] except KeyError: return None if piece.getColour() is not colour: return None # BitBoard indexes startIndex = bitboard.getIndex(start) endIndex = bitboard.getIndex(end) # Check if this move is possible field = self.allowedMoves[colour][piece.getType()] if field[startIndex] & bitboard.LOCATIONS[endIndex] == 0: return None # Check if there are any pieces between the two moves # Note this only checks horizontal, vertical and diagonal moves so # has no effect on the knights if self.allBitBoard & bitboard.INBETWEEN_SQUARES[startIndex][endIndex]: return None # Get the players if colour is WHITE: enemyColour = BLACK playerState = self.whiteState elif colour is BLACK: enemyColour = WHITE playerState = self.blackState else: assert(False) # Copy the player state before it is changed originalPlayerState = ChessPlayerState(playerState) whiteBitBoard = self.whiteBitBoard blackBitBoard = self.blackBitBoard allBitBoard = self.allBitBoard # Check if moving onto another piece (must be enemy) try: target = self.squares[end] if target.getColour() == colour: return None except KeyError: target = None victim = target # Get the rank relative to this colour's start rank if colour == BLACK: baseFile = '8' else: baseFile = '1' # The new en-passant square enPassantSquare = None # A list of pieces that have been moved moves = [] # Check move is valid: # King can move one square or castle if piece.getType() is KING: # Castling: shortCastle = ('e' + baseFile, 'g' + baseFile) longCastle = ('e' + baseFile, 'c' + baseFile) if (start, end) == shortCastle or (start, end) == longCastle: # Cannot castle out of check if self.inCheck(colour): return None # Cannot castle if required pieces have moved if end[0] == 'c': if not playerState.canLongCastle: return None rookLocation = 'a' + baseFile rookEndLocation = 'd' + baseFile else: if not playerState.canShortCastle: return None rookLocation = 'h' + baseFile rookEndLocation = 'f' + baseFile # Check rook is still there try: rook = self.squares[rookLocation] except KeyError: return None if rook is None or rook.getType() is not ROOK or rook.getColour() != piece.getColour(): return None # Check no pieces between the rook and king a = bitboard.getIndex(rookLocation) b = bitboard.getIndex(rookEndLocation) if self.allBitBoard & bitboard.INBETWEEN_SQUARES[a][b]: return None # The square the king moves over cannot be attackable if self.movePiece(colour, start, rookEndLocation, applyMove = False) is None: return None # Rook moves with the king moves.append((rook, rookLocation, rookEndLocation, False)) # Can no longer castle once the king is moved playerState.canShortCastle = False playerState.canLongCastle = False moves.append((piece, start, end, False)) # Rooks move orthogonal elif piece.getType() is ROOK: # Can no longer castle once have move the required rook if start == 'a' + baseFile: playerState.canLongCastle = False elif start == 'h' + baseFile: playerState.canShortCastle = False moves.append((piece, start, end, False)) # On base rank pawns move on or two squares forwards. # Pawns take other pieces diagonally (1 square). # Pawns can take other pawns moving two ranks using 'en passant'. # Pawns are promoted on reaching the other side of the board. elif piece.getType() is PAWN: # Calculate the files that pawns start on and move over on marches if baseFile == '1': pawnFile = '2' marchFile = '3' farFile = '8' else: pawnFile = '7' marchFile = '6' farFile = '1' # When marching the square that is moved over can be taken by en-passant if (start[1] == '2' and end[1] == '4') or (start[1] == '7' and end[1] == '5'): enPassantSquare = start[0] + marchFile # Can only take when moving diagonally if start[0] != end[0]: # FIXME: Set victim # We either need a victim or be attacking the en-passant square if victim is None: if end != self.enPassantSquare: return None # Kill the pawn that moved moves.append((self.lastMove[0], self.lastMove[2], self.lastMove[2], True)) elif victim is not None: return None # Promote pawns when they hit the far rank if end[1] == farFile: # Delete the current piece and create a new piece moves.append((piece, start, end, True)) moves.append((ChessPiece(colour, promotionType), None, end, False)) else: moves.append((piece, start, end, False)) # Other pieces are well behaved else: moves.append((piece, start, end, False)) # Store this move oldLastMove = self.lastMove self.lastMove = (piece, start, end) oldEnPassantSquare = self.enPassantSquare self.enPassantSquare = enPassantSquare # Delete a victim if victim is not None: moves.append((victim, end, end, True)) # Move the pieces: # Remove the moving pieces from the board for (p, s, e, d) in moves: if s is None: continue self.squares.pop(s) field = bitboard.LOCATIONS[bitboard.getIndex(s)] self.whiteBitBoard &= ~field self.blackBitBoard &= ~field self.allBitBoard &= ~field # Put pieces in their new locations for (p, s, e, d) in moves: if d: continue self.squares[e] = p field = bitboard.LOCATIONS[bitboard.getIndex(e)] if p.getColour() is WHITE: self.whiteBitBoard |= field else: self.blackBitBoard |= field self.allBitBoard |= field # Test for check and checkmate result = moves if testCheck: # Cannot move into check, if would be then undo move if self.inCheck(colour): applyMove = False result = None # Undo the moves if only a test if applyMove is False: # Empty any squares moved into for (p, s, e, d) in moves: if not d: self.squares.pop(e) # Put pieces back into their original locatons for (p, s, e, d) in moves: if s is not None: self.squares[s] = p # Undo player state if colour == WHITE: self.whiteState = originalPlayerState else: self.blackState = originalPlayerState # Undo stored move and en-passant location self.lastMove = oldLastMove self.enPassantSquare = oldEnPassantSquare # Revert bitboards self.whiteBitBoard = whiteBitBoard self.blackBitBoard = blackBitBoard self.allBitBoard = allBitBoard else: self.moves = result # Remember the casualties if victim is not None: self.casualties.append(victim) # If a piece taken or a pawn moved 50 move count is reset if victim is not None or piece.getType() is PAWN: self.fiftyMoveCount = 0 else: self.fiftyMoveCount += 1 return result
def __init__(self, lastState = None): """Constuctor for storing the state of a chess board. 'lastState' is the previous board state or a dictionary containing the initial state of the board or None to start an empty board. Example: pawn = ChessPiece(WHITE, PAWN) ChessBoardState({'a2': pawn, ...}) Note if a dictionary is provided the casualties will only record the pieces killed from this point onwards. """ # Start empty if lastState is None: self.whiteBitBoard = 0 self.blackBitBoard = 0 self.allBitBoard = 0 self.moveNumber = 0 self.squares = {} self.casualties = [] self.moves = [] self.whiteState = ChessPlayerState() self.blackState = ChessPlayerState() # Use provided initial pieces elif type(lastState) is dict: self.moveNumber = 0 self.squares = {} self.casualties = [] self.moves = [] self.whiteBitBoard = 0 self.blackBitBoard = 0 self.allBitBoard = 0 for coord, piece in lastState.iteritems(): self.squares[coord] = piece field = bitboard.LOCATIONS[bitboard.getIndex(coord)] if piece.getColour() is WHITE: self.whiteBitBoard |= field else: self.blackBitBoard |= field self.allBitBoard |= field self.whiteState = ChessPlayerState() self.blackState = ChessPlayerState() # Copy exisiting state elif isinstance(lastState, ChessBoardState): self.whiteBitBoard = lastState.whiteBitBoard self.blackBitBoard = lastState.blackBitBoard self.allBitBoard = lastState.allBitBoard self.moveNumber = lastState.moveNumber + 1 self.squares = lastState.squares.copy() self.casualties = lastState.casualties[:] self.lastMove = lastState.lastMove self.moves = lastState.moves[:] self.enPassantSquare = lastState.enPassantSquare self.whiteState = ChessPlayerState(lastState.whiteState) self.blackState = ChessPlayerState(lastState.blackState) self.fiftyMoveCount = lastState.fiftyMoveCount else: raise TypeError('ChessBoardState(oldState) or ChessBoardState({(0,0):pawn, ...})')