I'm making chess in python. It was all working and I had the moves as a list of indexes but I decided to use a class instead as I thought this would be better. However, now the function to get all the legal moves doesn't seem to work and I've found that this is because for some reason it thinks it can move a pawn from a4 to a5 on the starting board and I think this could be because it doesn't change the position back to the normal position after checking a2 to a4 but does move it on the board which is made up of a list of lists however I cannot find why. The error is caused by the code in lines 379-387 which stops you making a move if it puts you in check because when I comment it out there's no error. I've been searching for hours so if someone could help then it would be much appreciated. Also any other feedback would be helpful.
Exceptions.py
# Base class for other exceptions
pass
class NoPieceThereError(Error):
# When there's no piece on the selected tile
pass
class InvalidFENError(Error):
# When the inputed FEN is invalid
pass
class WrongPieceColour(Error):
# Chose the wrong piece colour
pass
class InvalidMove(Error):
# Not a legal move
pass
class InvalidInput(Error):
pass
class KingNotFound(Error):
pass
Chess.py
import random
alphabet = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z".split()
def IsInt(number):
try:
int(number)
return True
except:
return False #returns True if the input is a number
def AddTuples(tuple1, tuple2):
return tuple([x + y for x, y in zip(tuple1, tuple2)])
def MultiplyTuple(tuple1, multiplyer):
return tuple([int(x * multiplyer) for x in tuple1])
def PositionToIndex(position):
try:
row = int(position[1]) - 1
column = alphabet.index(position[0].upper())
return (row, column) # Converts a position "e2" to an index "(1, 4)"
except:
raise Exceptions.InvalidInput
def IndexToPosition(index):
try:
row = str(index[0] + 1)
column = alphabet[index[1]].lower()
return column + row # Converts an index "(1, 4)" to a position "e2"
except:
raise Exceptions.InvalidInput
class FEN():
def __init__(self, passed_FEN):
passed_FEN = passed_FEN.split()
if len(passed_FEN) != 6:
raise Exceptions.InvalidFENError
self.board_FEN = passed_FEN[0]
self.turn = passed_FEN[1]
self.castling = passed_FEN[2]
self.en_passant = passed_FEN[3]
self.half_move_clock = passed_FEN[4]
self.full_move_clock = passed_FEN[5]
def __repr__(self):
return " ".join([
self.board_FEN,
self.turn,
self.castling,
self.en_passant,
self.half_move_clock,
self.full_move_clock
])
def ChangeTurn(self):
self.turn = "b" if self.turn == "w" else "w"
@property
def board_FEN_split(self):
return self.board_FEN.split("/")
class Move():
def __init__(self, start_row, start_column, end_row, end_column, capturing_piece, capturing_row, capturing_column, FEN_before):
self.start_row = start_row
self.start_column = start_column
self.end_row = end_row
self.end_column = end_column
self.capturing = capturing_piece
self.capturing_row = capturing_row
self.capturing_column = capturing_column
self.FEN_before = FEN_before
@property
def start(self):
return (self.start_row, self.start_column)
@property
def end(self):
return (self.end_row, self.end_column)
@property
def capturing_pos(self):
return (self.capturing_row, self.capturing_column)
def __repr__(self):
if self.capturing != None:
string = f" capturing {self.capturing.__repr__()} at {IndexToPosition(self.capturing_pos)}"
else:
string = ""
return f"Piece moved from {IndexToPosition(self.start)} to {IndexToPosition(self.end)}" + string + f", FEN before: {self.FEN_before}"
def __eq__(self, other):
return True if self.__repr__() == other.__repr__() else False
@property
def taking_equals_moved(self):
return True if None == self.capturing_row == self.capturing_column or self.capturing_row == self.end_row and self.capturing_column == self.end_column else False
"""
class Engine():
piece_values = {
"p": 1,
"n": 3,
"b": 3,
"r": 5,
"q": 9
}
@staticmethod
def EvaluatePosition(board):
total = 0
for row in board.board:
for cell in row:
if cell != None:
if cell.__repr__().lower() in Engine.piece_values:
if cell.__repr__().islower():
total -= Engine.piece_values[cell.__repr__()]
else:
total += Engine.piece_values[cell.__repr__().lower()]
return total
@staticmethod
def ChooseComputerMove(board):
move = random.choice(board.GetAllLegalMoves())
board.MovePiece(*move)
@staticmethod
def Search(board, depth=10, alpha=0, beta=0):
if depth == 0:
return Engine.EvaluatePosition(board)
moves = board.GetAllLegalMoves()
if len(moves) == 0:
if board.IsInCheck(board.FEN.turn):
return -10000
return 0
for move in moves:
board.MovePiece(*move)
evaluation = -Engine.Search(board, depth-1, -beta, -alpha)
board.UnmakeMove()
if evaluation >= beta:
# Move was too good so the opponent will avoid this position
return beta
alpha = max(alpha, evaluation)
return alpha
"""
class Board():
def __init__(self,
passed_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
extra_pieces = [],
board_height = 8,
board_width = 8
):
pieces = [
#default pieces
('p', 'Pawn', Pawn),
('r', 'Rook', Rook),
('n', 'Knight', Knight),
('b', 'Bishop', Bishop),
('q', 'Queen', Queen),
('k', 'King', King),
] + extra_pieces #any extra piece types
self.pieces_letters, self.pieces_names, self.pieces_classes = [tuple([piece[x] for piece in pieces]) for x in range(3)]
self.FEN = FEN(passed_FEN)
self.move_list = []
self.board = []
self.board_height = board_height
self.board_width = board_width
for row_index, row in enumerate(self.FEN.board_FEN_split[::-1]):
self.board.append([])
cell_index = 0
for cell in row:
if IsInt(cell):
[self.board[-1].append(None) for x in range(int(cell))]
cell_index += int(cell)
else:
self.board[-1].append(self.CreatePiece(cell, (row_index, cell_index)))
cell_index += 1
def CreatePiece(self, piece_code, position):
piece_name = self.pieces_classes[self.pieces_letters.index(piece_code.lower())]
colour = "w" if piece_code.upper() == piece_code else "b"
return piece_name(colour, position, self)
def DisplayBoard(self, debug = False):
print()
print()
for index, column in enumerate(self.board[::-1]):
if debug:
print(7 - index, end = " ")
else:
print(8 - index, end = " ")
print(" | ".join(" " if x == None else x.__repr__() for x in column))
print("-" * 4 * self.board_width)
print(" ", end = "")
if debug:
print(" | ".join([str(x) for x in range(self.board_width)]))
else:
print(" | ".join(alphabet[:self.board_width]))
def GetBoardValue(self, row, column):
return self.board[row][column]
def FindKing(self, colour):
for rowi, row in enumerate(self.board):
for celli, cell in enumerate(row):
if cell != None:
if cell.type == "King" and cell.colour == colour:
return (rowi, celli)
raise Exceptions.KingNotFound
def IsInCheck(self, colour):
for row in self.board:
for cell in row:
if cell != None:
if cell.colour != colour:
if cell.IsChecking():
return True
return False
def GetAllLegalMoves(self):
LegalMoves = []
for row in self.board:
for cell in row:
if cell != None:
if cell.colour == self.FEN.turn:
LegalMoves += cell.LegalMoves()
return LegalMoves
def FindMove(self, moves = None, start_row = "*", start_column = "*", end_row = "*", end_column = "*"):
if moves == None:
moves = self.GetAllLegalMoves()
for move in moves:
if (
(start_row == "*" or start_row == move.start_row) and
(start_column == "*" or start_column == move.start_column) and
(end_row == "*" or end_row == move.end_row) and
(end_column == "*" or end_column == move.end_column)
):
return move
raise Exceptions.InvalidInput
def MakeMove(self, move):
self.move_list.append(move)
# Changing en passant FEN
if self.board[move.start_row][move.start_column].__repr__().lower() == "p" and abs(move.start_row - move.end_row) == 2:
self.FEN.en_passant = IndexToPosition(((move.start_row + move.end_row) / 2, move.end_column))
else:
self.FEN.en_passant = "-"
# Move piece
print(move)
self.board[move.start_row][move.start_row].position = move.end
self.board[move.end_row][move.end_column] = self.board[move.start_row][move.start_column]
self.board[move.start_row][move.start_column] = None
# If taking a piece not at the location it moved to i.e. en passant
if not move.taking_equals_moved:
self.board[capturing_row][capturing_column] = None
def UnmakeMove(self, number_to_undo=1):
for x in range(number_to_undo):
if self.move_list == []:
print(f"No more moves to undo. Only undid {x} moves")
return
move = self.move_list.pop()
# Move piece back
self.board[move.end_row][move.end_column].position = move.start
self.board[move.start_row][move.start_column] = self.board[move.end_row][move.end_column]
self.board[move.end_row][move.end_column] = None
# Restore captured piece
if move.capturing != None:
self.board[move.capturing_row][move.capturing_column] = move.capturing
# Restore FEN
self.FEN = move.FEN_before
def OnBoard(self, row, column):
return True if 0 <= row < self.board_height and 0 <= column < self.board_width else False
class Piece():
def __init__(self, colour, position, board):
self.colour = colour
self.position = position
self.board = board
def SimilarMoves(self, directions, *args, **kwargs):
legal_moves = []
for direction in directions:
legal_moves += self.CheckForCollision(direction, *args, **kwargs)
return legal_moves
def CheckForCollision(self, direction, limit, must_capture = False, can_capture = True, can_capture_en_passant = False, current_turn = False):
legal_moves = []
checks = 1
current_pos = self.position
while checks <= limit or limit == 0:
current_pos = AddTuples(current_pos, direction)
#if off the board
if not self.board.OnBoard(*current_pos):
break
current_pos_piece = self.GetBoardValue(*current_pos)
#if moving to an empty square
if current_pos_piece == None:
#capturing en passant
#if there is an en passant somewhere and the piece is allowed to capture en passant
if can_capture_en_passant and self.board.FEN.en_passant != "-":
#if it's capturing the en passant square
if PositionToIndex(self.board.FEN.en_passant) == current_pos:
#if it not capturing it's own piece
en_passant_start = self.board.board_height - 4 if self.colour == "w" else 3
if self.position[0] == en_passant_start:
move = Move(
*self.position,
*current_pos,
self.GetBoardValue(en_passant_start, current_pos[1]),
en_passant_start, current_pos[1],
FEN(self.board.FEN.__repr__()))
legal_moves.append(move)
#moving to empty space
elif not must_capture:
legal_moves.append(Move(
*self.position,
*current_pos,
self.GetBoardValue(*current_pos),
None, None,
FEN(self.board.FEN.__repr__())))
#moving to own piece colour
elif self.colour == current_pos_piece.colour:
break
#moving to opponent piece colour
elif self.colour != current_pos_piece.colour:
if can_capture:
legal_moves.append(Move(
*self.position,
*current_pos,
self.board.GetBoardValue(*current_pos),
*current_pos,
FEN(self.board.FEN.__repr__())
))
break
checks += 1
if current_turn == True:
index = 0
while index < len(legal_moves):
self.board.MakeMove(legal_moves[index])
if self.board.IsInCheck(self.colour):
legal_moves.pop(index)
else:
index += 1
self.board.UnmakeMove()
return legal_moves
@property
def row(self):
return self.position[0]
@property
def column(self):
return self.position[1]
def __repr__(self):
letter = self.board.pieces_letters[self.board.pieces_names.index(self.type)]
return letter.upper() if self.colour == "w" else letter
def IsEmpty(self, row, column):
return True if self.GetBoardValue(row, column) == None else False
def GetBoardValue(self, row, column):
return self.board.GetBoardValue(row, column)
@property
def direction(self):
if self.colour == "w":
return 1
else:
return -1
@property
def enemy_colour(self):
if self.colour == "w":
return "b"
else:
return "w"
def CheckCorrectTurn(self):
return True if self.colour == self.board.FEN.turn else False
def IsChecking(self):
enemy_king_pos = self.board.FindKing(self.enemy_colour)
moves = self.LegalMoves(current_turn = False)
for move in moves:
if move == enemy_king_pos:
return True
return False
class Pawn(Piece):
def __init__(self, colour, position, board):
super().__init__(colour, position, board)
self.type = "Pawn"
def LegalMoves(self, current_turn = True):
legal_moves = []
print(self.position)
on_starting_row = self.row == 1 and self.colour == 'w' or self.row == self.board.board_height - 2 and self.colour == "b"
#forwards
limit = 2 if on_starting_row else 1
legal_moves += self.CheckForCollision((self.direction, 0), limit, can_capture = False, current_turn=current_turn)
#capturing
capturing_directions = ((self.direction, 1), (self.direction, -1))
legal_moves += self.SimilarMoves(capturing_directions, 1, must_capture = True, can_capture_en_passant = True, current_turn=current_turn)
return legal_moves
class Rook(Piece):
directions = ((1, 0), (0, 1), (-1, 0), (0, -1))
def __init__(self, colour, position, board):
super().__init__(colour, position, board)
self.type = "Rook"
def LegalMoves(self, current_turn = True):
legal_moves = self.SimilarMoves(self.directions, 0, current_turn=current_turn)
return legal_moves
class Knight(Piece):
directions = ((2, 1), (1, 2), (-1, 2), (-2, 1), (-2, -1), (-1, -2), (1, -2), (2, -1))
def __init__(self, colour, position, board):
super().__init__(colour, position, board)
self.type = "Knight"
def LegalMoves(self, current_turn = True):
legal_moves = self.SimilarMoves(self.directions, 1, current_turn=current_turn)
return legal_moves
class Bishop(Piece):
directions = ((1, 1), (-1, 1), (-1, -1), (1, -1))
def __init__(self, colour, position, board):
super().__init__(colour, position, board)
self.type = "Bishop"
def LegalMoves(self, current_turn = True):
legal_moves = self.SimilarMoves(self.directions, 0, current_turn=current_turn)
return legal_moves
class King(Piece):
directions = Rook.directions + Bishop.directions
def __init__(self, colour, position, board):
super().__init__(colour, position, board)
self.type = "King"
def LegalMoves(self, current_turn = True):
legal_moves = self.SimilarMoves(self.directions, 1, current_turn=current_turn)
return legal_moves
class Queen(Piece):
directions = Rook.directions + Bishop.directions
def __init__(self, colour, position, board):
super().__init__(colour, position, board)
self.type = "Queen"
def LegalMoves(self, current_turn = True):
legal_moves = self.SimilarMoves(self.directions, 0, current_turn=current_turn)
return legal_moves
def main():
entered_FEN_valid = False
while not entered_FEN_valid:
try:
player_FEN = input("Would you like to play with a custom FEN. If so put it here, or press enter for a default board: ")
if not player_FEN:
player_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
b = Board(player_FEN)
entered_FEN_valid = True
except Exceptions.InvalidFENError:
print("Invalid FEN")
#no longer needed as I added Smith --> print("Moves are entered in the form \"{row} {column}\" with row and column being numbers from 0-7. Bottom left is 0 0. Example input: \"1 4\"")
debug = False # True if input("Debug Mode? (Y/N): ").lower() == "y" else False
player_colour = "w"
player_won = False
apawn = b.board[1][0]
while not player_won:
#if len(b.GetAllLegalMoves()) == 0:
# player_won = True
# print(b.IsInCheck("b"))
# if b.IsInCheck(b.FEN.turn):
# player_who_won = "w" if b.FEN.turn == "b" else "b"
# else:
# player_who_won = "no one"
# break
try:
if True: #b.FEN.turn == player_colour:
b.DisplayBoard(debug)
#print(Engine.Search(b, depth = 20))
#print("\n".join([x.__repr__() for x in b.move_list]))
#print([[IndexToPosition(position) for position in move] for move in b.GetAllLegalMoves()])
#print(len(b.GetAllLegalMoves()))
start = input("\nEnter start: ")
if start.lower()[:4] == "undo":
b.UnmakeMove(int(start[-1]))
else:
start = PositionToIndex(start)
if b.GetBoardValue(*start) == None:
raise Exceptions.NoPieceThereError
moves = b.GetBoardValue(*start).LegalMoves()
if moves == []:
raise Exceptions.WrongPieceColour
print("Legal moves: \n" + str([IndexToPosition((x.end_row, x.end_column)) for x in moves]))
end = PositionToIndex(input("\nEnter end: "))
print(start, end)
b.MakeMove(b.FindMove(
moves = moves,
start_row = start[0],
start_column = start[1],
end_row = end[0],
end_column = end[1]))
else:
Engine.ChooseComputerMove(b)
except Exceptions.NoPieceThereError:
print("There is no piece there")
except Exceptions.WrongPieceColour:
print("That is not the correct piece colour")
except Exceptions.InvalidMove:
print("That is not a legal move")
#except Exceptions.InvalidInput:
# print("Invalid input")
#except:
# print("Unknown error")
print(player_who_won, "won!")
if __name__ == "__main__":
#main()
b = Board()
print(b.GetAllLegalMoves())