diff --git a/.gitignore b/.gitignore index e1768cf..3f7e810 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.pyc *.pycache *.bak - +build/ +dist/ diff --git a/src/Board.py b/src/Board.py index 2e86262..fa93740 100644 --- a/src/Board.py +++ b/src/Board.py @@ -16,7 +16,7 @@ class Board: def __init__(self, mateInOne=False, castleBoard=False, - pessant=False, promotion=False): + passant=False, promotion=False): self.pieces = [] self.history = [] self.points = 0 @@ -24,7 +24,7 @@ def __init__(self, mateInOne=False, castleBoard=False, self.movesMade = 0 self.checkmate = False - if not mateInOne and not castleBoard and not pessant and not promotion: + if not mateInOne and not castleBoard and not passant and not promotion: self.pieces.extend([Rook(self, BLACK, C(0, 7)), Knight(self, BLACK, C(1, 7)), Bishop(self, BLACK, C(2, 7)), @@ -53,7 +53,7 @@ def __init__(self, mateInOne=False, castleBoard=False, kingBlack = King(self, BLACK, C(3, 2)) self.pieces.extend([pawnToPromote, kingWhite, kingBlack]) - elif pessant: + elif passant: pawn = Pawn(self, WHITE, C(1, 4)) pawn2 = Pawn(self, BLACK, C(2, 6)) kingWhite = King(self, WHITE, C(4, 0)) @@ -85,7 +85,7 @@ def undoLastMove(self): king.movesMade -= 1 rook.movesMade -= 1 - elif lastMove.pessant: + elif lastMove.passant: pawnMoved = lastMove.piece pawnTaken = pieceTaken self.pieces.append(pawnTaken) @@ -100,6 +100,12 @@ def undoLastMove(self): pawnPromoted = lastMove.piece promotedPiece = self.pieceAtPosition(lastMove.newPos) self.pieces.remove(promotedPiece) + if pieceTaken: + if pieceTaken.side == WHITE: + self.points += pieceTaken.value + if pieceTaken.side == BLACK: + self.points -= pieceTaken.value + self.pieces.append(pieceTaken) self.pieces.append(pawnPromoted) if pawnPromoted.side == WHITE: self.points -= promotedPiece.value - 1 @@ -148,7 +154,7 @@ def getLastPieceMoved(self): def addMoveToHistory(self, move): pieceTaken = None - if move.pessant: + if move.passant: pieceTaken = move.specialMovePiece self.history.append([move, pieceTaken]) return @@ -177,18 +183,16 @@ def makeStringRep(self, pieces): color = 'blue' if side == WHITE else 'red' pieceRep = colored(piece.stringRep, color) else: - pieceRep = 'x' + pieceRep = ' ' stringRep += pieceRep + ' ' stringRep += '\n' - stringRep = stringRep.strip() - return stringRep + return stringRep.rstrip() def wrapStringRep(self, stringRep): sRep = '\n'.join( - [' a b c d e f g h ', ' '*21] + - ['%d %s %d' % (8-r, s.strip(), 8-r) + ['%d %s' % (8-r, s.rstrip()) for r, s in enumerate(stringRep.split('\n'))] + - [' '*21, ' a b c d e f g h '] + [' '*21, ' a b c d e f g h'] ).rstrip() return sRep @@ -199,22 +203,52 @@ def fileOfPiece(self, piece): transTable = str.maketrans('01234567', 'abcdefgh') return str(piece.position[0]).translate(transTable) - def getShortNotationOfMove(self, move): + def getCoordinateNotationOfMove(self, move): + notation = "" + notation += self.positionToHumanCoord(move.oldPos) + notation += self.positionToHumanCoord(move.newPos) + + if move.promotion: + notation += str(move.specialMovePiece.stringRep) + + return notation + + def getCaptureNotation(self, move, short=False): + notation = "" + pieceToMove = move.piece + pieceToTake = move.pieceToCapture + + if type(pieceToMove) is Pawn: + notation += self.fileOfPiece(pieceToMove) + else: + notation += pieceToMove.stringRep + notation += 'x' + if short: + notation += pieceToTake.stringRep + else: + notation += self.positionToHumanCoord(move.newPos) + + if move.promotion: + notation += str(move.specialMovePiece.stringRep) + + return notation + + def getAlgebraicNotationOfMove(self, move, short=True): notation = "" pieceToMove = move.piece pieceToTake = move.pieceToCapture if move.queensideCastle: - return "0-0-0" + return "O-O-O" if move.kingsideCastle: - return "0-0" + return "O-O" - if pieceToMove.stringRep != 'p': + if not short or type(pieceToMove) is not Pawn: notation += pieceToMove.stringRep if pieceToTake is not None: - if pieceToMove.stringRep == 'p': + if short and type(pieceToMove) is Pawn: notation += self.fileOfPiece(pieceToMove) notation += 'x' @@ -225,15 +259,15 @@ def getShortNotationOfMove(self, move): return notation - def getShortNotationOfMoveWithFile(self, move): - # TODO: Use self.getShortNotationOfMove instead of repeating code + def getAlgebraicNotationOfMoveWithFile(self, move, short=True): + # TODO: Use self.getAlgebraicNotationOfMove instead of repeating code notation = "" pieceToMove = self.pieceAtPosition(move.oldPos) pieceToTake = self.pieceAtPosition(move.newPos) - if pieceToMove.stringRep != 'p': + if not short or type(pieceToMove) is not Pawn: notation += pieceToMove.stringRep - notation += self.fileOfPiece(pieceToMove) + notation += self.fileOfPiece(pieceToMove) if pieceToTake is not None: notation += 'x' @@ -241,39 +275,42 @@ def getShortNotationOfMoveWithFile(self, move): notation += self.positionToHumanCoord(move.newPos) return notation - def getShortNotationOfMoveWithRank(self, move): - # TODO: Use self.getShortNotationOfMove instead of repeating code + def getAlgebraicNotationOfMoveWithRank(self, move, short=True): + # TODO: Use self.getAlgebraicNotationOfMove instead of repeating code notation = "" pieceToMove = self.pieceAtPosition(move.oldPos) pieceToTake = self.pieceAtPosition(move.newPos) - if pieceToMove.stringRep != 'p': + if not short or type(pieceToMove) is not Pawn: notation += pieceToMove.stringRep - notation += self.rankOfPiece(pieceToMove) + + notation += self.rankOfPiece(pieceToMove) if pieceToTake is not None: + if short and type(pieceToMove) is Pawn: + notation += self.fileOfPiece(pieceToMove) notation += 'x' notation += self.positionToHumanCoord(move.newPos) return notation - def getShortNotationOfMoveWithFileAndRank(self, move): - # TODO: Use self.getShortNotationOfMove instead of repeating code + def getAlgebraicNotationOfMoveWithFileAndRank(self, move, short=True): + # TODO: Use self.getAlgebraicNotationOfMove instead of repeating code notation = "" pieceToMove = self.pieceAtPosition(move.oldPos) pieceToTake = self.pieceAtPosition(move.newPos) - if pieceToMove.stringRep != 'p': + if not short or type(pieceToMove) is not Pawn: notation += pieceToMove.stringRep - notation += self.fileOfPiece(pieceToMove) - notation += self.rankOfPiece(pieceToMove) + + notation += self.fileOfPiece(pieceToMove) + notation += self.rankOfPiece(pieceToMove) if pieceToTake is not None: notation += 'x' notation += self.positionToHumanCoord(move.newPos) return notation - return def humanCoordToPosition(self, coord): transTable = str.maketrans('abcdefgh', '12345678') @@ -329,11 +366,11 @@ def makeMove(self, move): kingToMove = move.piece rookToMove = move.specialMovePiece self.movePieceToPosition(kingToMove, move.newPos) - self.movePieceToPosition(rookToMove, move.rookMovePos) + self.movePieceToPosition(rookToMove, move.rookMove.newPos) kingToMove.movesMade += 1 rookToMove.movesMade += 1 - elif move.pessant: + elif move.passant: pawnToMove = move.piece pawnToTake = move.specialMovePiece pawnToMove.position = move.newPos @@ -341,12 +378,21 @@ def makeMove(self, move): pawnToMove.movesMade += 1 elif move.promotion: + pieceToTake = move.pieceToCapture self.pieces.remove(move.piece) + if pieceToTake: + if pieceToTake.side == WHITE: + self.points -= pieceToTake.value + if pieceToTake.side == BLACK: + self.points += pieceToTake.value + self.pieces.remove(pieceToTake) + self.pieces.append(move.specialMovePiece) if move.piece.side == WHITE: self.points += move.specialMovePiece.value - 1 if move.piece.side == BLACK: self.points -= move.specialMovePiece.value - 1 + move.piece.movesMade += 1 else: pieceToMove = move.piece diff --git a/src/InputParser.py b/src/InputParser.py index 71208a7..d59e6ca 100644 --- a/src/InputParser.py +++ b/src/InputParser.py @@ -1,4 +1,5 @@ import re +from Pawn import Pawn class InputParser: @@ -8,44 +9,83 @@ def __init__(self, board, side): self.side = side def parse(self, humanInput): - humanInput = humanInput.lower() - regexShortNotation = re.compile('[rnbkqp][a-z][1-8]') - if regexShortNotation.match(humanInput): - return self.moveForShortNotation(humanInput) + regexCoordinateNotation = re.compile('(?i)[a-h][1-8][a-h][1-8][QRBN]?') + if regexCoordinateNotation.match(humanInput): + return self.moveForCoordinateNotation(humanInput) + regexAlgebraicNotation = re.compile('(?i)O-O|O-O-O|(?:[KQRBNP]?[a-h]?[1-8]?x?[a-h][1-8]|[Pa-h]x?[a-h])(?:=?[QRBN])?') + if regexAlgebraicNotation.match(humanInput): + return self.moveForShortAlgebraicNotation(humanInput) + raise ValueError("Invalid move: %s" % humanInput) - def moveForShortNotation(self, notation): - moves = self.getLegalMovesWithShortNotation(self.side) + def moveForCoordinateNotation(self, notation): + for move in self.board.getAllMovesLegal(self.side): + if self.board.getCoordinateNotationOfMove(move).lower() == notation.lower(): + move.notation = self.notationForMove(move) + return move + raise ValueError("Illegal move: %s" % notation) + + # Only handles SAN, not long-algebraic or descriptive + def moveForShortAlgebraicNotation(self, notation): + shortNotation = notation.replace("x","") + moves = self.getLegalMovesWithNotation(self.side, False) + for move in moves: + if move.notation.replace("x","") == shortNotation: # Bxc3 versus bxc3 + return move + for move in moves: + if move.notation.replace("x","").lower() == shortNotation.lower(): + return move + moves = self.getLegalMovesWithNotation(self.side, True) + for move in moves: + if move.notation.replace("x","") == shortNotation: # Bxc3 versus bxc3 + return move for move in moves: - if move.notation.lower() == notation.lower(): + if move.notation.replace("x","").lower() == shortNotation.lower(): return move + shortNotation = notation.lower().replace("p","").replace("=","") + if re.compile('[a-h][1-8]?[qrbn]?').match(shortNotation): + for move in moves: + if type(move.piece) is Pawn and not move.pieceToCapture and self.board.getCoordinateNotationOfMove(move).replace("=","").lower().endswith(shortNotation): + return move + for move in moves: + if type(move.piece) is Pawn and not move.pieceToCapture and re.sub("[1-8]", "", self.board.getCoordinateNotationOfMove(move)).replace("=","").lower().endswith(shortNotation): + return move # ASSUME lazy pawn move (P)c is unambiguous + shortNotation = shortNotation.lower().replace("x","") + if re.compile('[a-h]?[a-h][1-8]?[qrbn]?').match(shortNotation): + for move in moves: + if type(move.piece) is Pawn and move.pieceToCapture and self.board.getCaptureNotation(move).replace("x","").lower().endswith(shortNotation): + return move # ASSUME lazier pawn capture (P)b(x)c3 is unambiguous + for move in moves: + if type(move.piece) is Pawn and move.pieceToCapture and re.sub("[1-8]", "", self.board.getCaptureNotation(move).replace("x","")).lower().endswith(shortNotation): + return move # ASSUME laziest pawn capture (P)b(x)c is unambiguous + raise ValueError("Illegal move: %s" % notation) def notationForMove(self, move): side = self.board.getSideOfMove(move) - moves = self.getLegalMovesWithShortNotation(side) + moves = self.getLegalMovesWithNotation(side) for m in moves: if m == move: return m.notation - def getLegalMovesWithShortNotation(self, side): + def getLegalMovesWithNotation(self, side, short=True): moves = [] for legalMove in self.board.getAllMovesLegal(side): moves.append(legalMove) - legalMove.notation = self.board.getShortNotationOfMove(legalMove) + legalMove.notation = self.board.getAlgebraicNotationOfMove(legalMove, short) duplicateNotationMoves = self.duplicateMovesFromMoves(moves) for duplicateMove in duplicateNotationMoves: duplicateMove.notation = \ - self.board.getShortNotationOfMoveWithFile(duplicateMove) + self.board.getAlgebraicNotationOfMoveWithFile(duplicateMove, short) duplicateNotationMoves = self.duplicateMovesFromMoves(moves) for duplicateMove in duplicateNotationMoves: duplicateMove.notation = \ - self.board.getShortNotationOfMoveWithRank(duplicateMove) + self.board.getAlgebraicNotationOfMoveWithRank(duplicateMove, short) duplicateNotationMoves = self.duplicateMovesFromMoves(moves) for duplicateMove in duplicateNotationMoves: duplicateMove.notation = \ - self.board.getShortNotationOfMoveWithFileAndRank(duplicateMove) + self.board.getAlgebraicNotationOfMoveWithFileAndRank(duplicateMove, short) return moves diff --git a/src/King.py b/src/King.py index 25afcb2..74b8926 100644 --- a/src/King.py +++ b/src/King.py @@ -38,18 +38,20 @@ def getPossibleMoves(self): kingsideRookMoved = True queensideRookMoved = True - kingsideCastlePositions = [self.position - C(1, 0), - self.position - C(2, 0)] + kingsideCastlePositions = [self.position + C(1, 0), + self.position + C(2, 0)] for pos in kingsideCastlePositions: if self.board.pieceAtPosition(pos): kingsideCastleBlocked = True + break - queensideCastlePositions = [self.position + C(1, 0), - self.position + C(2, 0), - self.position + C(3, 0)] + queensideCastlePositions = [self.position - C(1, 0), + self.position - C(2, 0), + self.position - C(3, 0)] for pos in queensideCastlePositions: if self.board.pieceAtPosition(pos): queensideCastleBlocked = True + break if kingsideCastleBlocked and queensideCastleBlocked: return @@ -61,14 +63,14 @@ def getPossibleMoves(self): if move.newPos == self.position: inCheck = True break - if move.newPos == self.position - C(1, 0) or \ - move.newPos == self.position - C(2, 0): - kingsideCastleCheck = True if move.newPos == self.position + C(1, 0) or \ move.newPos == self.position + C(2, 0): + kingsideCastleCheck = True + if move.newPos == self.position - C(1, 0) or \ + move.newPos == self.position - C(2, 0): queensideCastleCheck = True - kingsideRookPos = self.position - C(3, 0) + kingsideRookPos = self.position + C(3, 0) kingsideRook = self.board.pieceAtPosition(kingsideRookPos) \ if self.board.isValidPos(kingsideRookPos) \ else None @@ -77,7 +79,7 @@ def getPossibleMoves(self): kingsideRook.movesMade == 0: kingsideRookMoved = False - queensideRookPos = self.position + C(4, 0) + queensideRookPos = self.position - C(4, 0) queensideRook = self.board.pieceAtPosition(queensideRookPos) \ if self.board.isValidPos(queensideRookPos) \ else None @@ -90,8 +92,8 @@ def getPossibleMoves(self): if not kingsideCastleBlocked and \ not kingsideCastleCheck and \ not kingsideRookMoved: - move = Move(self, self.position - C(2, 0)) - rookMove = Move(self.position, self.position - C(1, 0)) + move = Move(self, self.position + C(2, 0)) + rookMove = Move(kingsideRook, self.position + C(1, 0)) move.specialMovePiece = \ self.board.pieceAtPosition(kingsideRookPos) move.kingsideCastle = True @@ -100,8 +102,8 @@ def getPossibleMoves(self): if not queensideCastleBlocked and \ not queensideCastleCheck and \ not queensideRookMoved: - move = Move(self, self.position + C(2, 0)) - rookMove = Move(self.position, self.position + C(1, 0)) + move = Move(self, self.position - C(2, 0)) + rookMove = Move(queensideRook, self.position - C(1, 0)) move.specialMovePiece = \ self.board.pieceAtPosition(queensideRookPos) move.queensideCastle = True diff --git a/src/Move.py b/src/Move.py index 4fd1afd..47f087f 100644 --- a/src/Move.py +++ b/src/Move.py @@ -7,14 +7,14 @@ def __init__(self, piece, newPos, pieceToCapture=None): self.kingsideCastle = False self.queensideCastle = False self.promotion = False - self.pessant = False + self.passant = False self.stalemate = False self.piece = piece self.oldPos = piece.position self.newPos = newPos self.pieceToCapture = pieceToCapture - # For en pessant and castling + # For en passant and castling self.specialMovePiece = None # For castling self.rookMove = None @@ -24,11 +24,11 @@ def __str__(self): ' -- New pos : ' + str(self.newPos) if self.notation: displayString += ' Notation : ' + self.notation - if self.pessant: + if self.passant: displayString = 'Old pos : ' + str(self.oldPos) + \ ' -- New pos : ' + str(self.newPos) + \ ' -- Pawn taken : ' + str(self.specialMovePiece) - displayString += ' PESSANT' + displayString += ' PASSANT' return displayString def __eq__(self, other): diff --git a/src/Pawn.py b/src/Pawn.py index 16132d4..148e537 100644 --- a/src/Pawn.py +++ b/src/Pawn.py @@ -13,7 +13,7 @@ class Pawn(Piece): - stringRep = 'p' + stringRep = 'P' value = 1 def __init__(self, board, side, position, movesMade=0): @@ -72,7 +72,7 @@ def getPossibleMoves(self): Bishop(self.board, self.side, newPosition), Queen(self.board, self.side, newPosition)] for piece in piecesForPromotion: - move = Move(self, advanceOnePosition) + move = Move(self, newPosition, pieceToCapture=pieceToTake) move.promotion = True move.specialMovePiece = piece yield move @@ -80,7 +80,7 @@ def getPossibleMoves(self): yield Move(self, newPosition, pieceToCapture=pieceToTake) - # En pessant + # En passant movements = [C(1, 1), C(-1, 1)] \ if self.side == WHITE else [C(1, -1), C(-1, -1)] for movement in movements: @@ -103,6 +103,6 @@ def getPossibleMoves(self): lastMoveWasAdvanceTwo: move = Move(self, self.position + movement, pieceToCapture=pieceBesidePawn) - move.pessant = True + move.passant = True move.specialMovePiece = pieceBesidePawn yield move diff --git a/src/main.py b/src/main.py index 0642492..62d75aa 100644 --- a/src/main.py +++ b/src/main.py @@ -42,7 +42,7 @@ def printCommandOptions(): def printAllLegalMoves(board, parser): - for move in parser.getLegalMovesWithShortNotation(board.currentSide): + for move in parser.getLegalMovesWithNotation(board.currentSide, short=True): print(move.notation) @@ -54,7 +54,6 @@ def getRandomMove(board, parser): def makeMove(move, board): - print() print("Making move : " + move.notation) board.makeMove(move) @@ -73,6 +72,7 @@ def undoLastTwoMoves(board): def startGame(board, playerSide, ai): parser = InputParser(board, playerSide) while True: + print() print(board) print() if board.isCheckmate(): @@ -93,26 +93,26 @@ def startGame(board, playerSide, ai): # printPointAdvantage(board) move = None command = input("It's your move." - " Type '?' for options. ? ").lower() - if command == 'u': + " Type '?' for options. ? ") + if command.lower() == 'u': undoLastTwoMoves(board) continue - elif command == '?': + elif command.lower() == '?': printCommandOptions() continue - elif command == 'l': + elif command.lower() == 'l': printAllLegalMoves(board, parser) continue - elif command == 'r': + elif command.lower() == 'r': move = getRandomMove(board, parser) - elif command == 'quit': + elif command.lower() == 'exit' or command.lower() == 'quit': return - else: - move = parser.moveForShortNotation(command) - if move: - makeMove(move, board) - else: - print("Couldn't parse input, enter a valid command or move.") + try: + move = parser.parse(command) + except ValueError as error: + print("%s" % error) + continue + makeMove(move, board) else: print("AI thinking...")