-2

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())
  • Welcome to [Stack Overflow.](https://stackoverflow.com/ "Stack Overflow"). While it is beneficial to show your work effort, it is counter productive to dump all code. Please edit your question to show a minimal reproducible set consisting of sample input, expected output, actual output, and only the relevant code necessary to reproduce the problem. See [Minimal Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example "Minimal Reproducible Example") for details on how to best help us help you. – itprorh66 Jun 24 '21 at 23:53

1 Answers1

0

So I found that the positions of the pieces kept changing randomly so I just made it a property of the Piece class that finds it each time

class Piece():

    @property
    def position(self):
        for rowi, row in enumerate(self.board.board):
            for celli, cell in enumerate(row):
                if cell == self:
                    return (rowi, celli)