0

I'm writing a chess engine, and am trying to make it as fast as possible, so I'm using bitboards to represent each type of piece. I was printing out the number of different board states at each plie (Plie = 1 person moving) and on plie 4 it gets the wrong number.

Is there any way to figure out what is going wrong without sifting through all 200k 4 plie positions?

Also, note I haven't implemented castling, check, en passent, pawn promotion or any draw rules yet, as none of them should have any affect until after the 4th plie.

(Wikipedia link that gives the number of board states per plie, vs mine: 20, 400, 8902, 197742)

import json

with open('White_Pawn.json', 'r') as f:
    white_pawn_table = json.load(f)
    white_pawn_table = {int(q):w for q, w in white_pawn_table.items()}

with open('White_Pawn_Captures.json', 'r') as f:
    white_pawn_captures_table = json.load(f)
    white_pawn_captures_table = {int(q):w for q, w in white_pawn_captures_table.items()}

with open('Black_Pawn.json', 'r') as f:
    black_pawn_table = json.load(f)
    black_pawn_table = {int(q):w for q, w in black_pawn_table.items()}

with open('Black_Pawn_Captures.json', 'r') as f:
    black_pawn_captures_table = json.load(f)
    black_pawn_captures_table = {int(q):w for q, w in black_pawn_captures_table.items()}

with open('Knight.json', 'r') as f:
    knight_table = json.load(f)
    knight_table = {int(q):w for q, w in knight_table.items()}

with open('Bishop.json', 'r') as f:
    bishop_table = json.load(f)
    bishop_table = {int(q):w for q, w in bishop_table.items()}

with open('Rook.json', 'r') as f:
    rook_table = json.load(f)
    rook_table = {int(q):w for q, w in rook_table.items()}

string_to_int = lambda x:int(x, 2)
int_to_string = lambda x:'0' * (66 - len(bin(x))) + bin(x)[2:]
x_in_y = lambda x, y:(x & y) != 0

def show(x):
    for q in range(8):
        print(' '.join([w for w in x[(q * 8):((q + 1) * 8)]]))
    print()

class Board:
    def __init__(self, args = False):
        if args:
            self.maps, self.turn = args
        else:
            self.maps, self.turn = {'White Pawns':65280, 'White Knights':66, 'White Bishops':36, 'White Rooks':129, 'White Queens':16, 'White Kings':8, 'Black Pawns':71776119061217280, 'Black Knights':4755801206503243776, 'Black Bishops':2594073385365405696, 'Black Rooks':9295429630892703744, 'Black Queens':1152921504606846976, 'Black Kings':576460752303423488}, 1

    def move(self, colour, not_colour, piece, move):
        board = Board(args = (self.maps.copy(), not self.turn))

        board.maps[f'{colour} {piece}s'] ^= move[0]
        board.maps[f'{colour} {piece}s'] |= move[1]

        for q in board.maps:
            if not_colour in q:
                board.maps[q] &= ~move[1]

        return board

    def moves(self):
        # Get Pieces and Colours
        colour, not_colour = ['White', 'Black'][::self.turn if self.turn else -1]
        pieces, not_pieces = 0, 0
        for q, w in self.maps.items():
            if colour in q:
                pieces |= w
            else:
                not_pieces |= w

        # Pawn
        if colour == 'White':
            move_table, capture_table = white_pawn_table, white_pawn_captures_table
        else:
            move_table, capture_table = black_pawn_table, black_pawn_captures_table

        temp_pawns = self.maps[f'{colour} Pawns']
        while temp_pawns:
            pawn = temp_pawns & -temp_pawns
            temp_pawns ^= pawn

            for next_pawn in move_table[pawn]:
                if x_in_y(next_pawn, pieces | not_pieces):
                    break
                else:
                    yield self.move(colour, not_colour, 'Pawn', (pawn, next_pawn))
            
            for next_pawn in capture_table[pawn]:
                if x_in_y(next_pawn, not_pieces):
                    yield self.move(colour, not_colour, 'Pawn', (pawn, next_pawn))

        # Knight
        temp_knights = self.maps[f'{colour} Knights']
        while temp_knights:
            knight = temp_knights & -temp_knights
            temp_knights ^= knight

            for next_knight in knight_table[knight]:
                if not x_in_y(next_knight, pieces):
                    yield self.move(colour, not_colour, 'Knight', (knight, next_knight))
        
        # Bishop
        temp_bishops = self.maps[f'{colour} Bishops']
        while temp_bishops:
            bishop = temp_bishops & -temp_bishops
            temp_bishops ^= bishop

            for direction in bishop_table[bishop]:
                for next_bishop in direction:
                    if x_in_y(next_bishop, pieces): break

                    yield self.move(colour, not_colour, 'Bishop', (bishop, next_bishop))

                    if x_in_y(next_bishop, not_pieces): break

        # Rook
        temp_rooks = self.maps[f'{colour} Rooks']
        while temp_rooks:
            rook = temp_rooks & -temp_rooks
            temp_rooks ^= rook

            for direction in rook_table[rook]:
                for next_rook in direction:
                    if x_in_y(next_rook, pieces): break

                    yield self.move(colour, not_colour, 'Rook', (rook, next_rook))

                    if x_in_y(next_rook, not_pieces): break
        
        # Queen
        temp_queens = self.maps[f'{colour} Queens']
        while temp_queens:
            queen = temp_queens & -temp_queens
            temp_queens ^= queen

            for direction in rook_table[queen]:
                for next_queen in direction:
                    if x_in_y(next_queen, pieces): break

                    yield self.move(colour, not_colour, 'Queen', (queen, next_queen))

                    if x_in_y(next_queen, not_pieces): break

            for direction in bishop_table[queen]:
                for next_queen in direction:
                    if x_in_y(next_queen, pieces): break

                    yield self.move(colour, not_colour, 'Queen', (queen, next_queen))

                    if x_in_y(next_queen, not_pieces): break

        # King
        king = self.maps[f'{colour} Kings']
        for direction in rook_table[king]:
            next_king = direction[0]
            if not x_in_y(next_king, pieces):
                yield self.move(colour, not_colour, 'King', (king , next_king))

        for direction in bishop_table[king]:
            next_king = direction[0]
            if not x_in_y(next_king, pieces):
                yield self.move(colour, not_colour, 'King', (king, next_king))

    def next_states(self):
        for q in self.moves():
            yield q

    def __str__(self):
        find = {'Black Kings':'♔ ', 'Black Queens':'♕ ', 'Black Rooks':'♖ ', 'Black Bishops':'♗ ', 'Black Knights':'♘ ', 'Black Pawns':'♙ ', 'White Pawns':'♟ ', 'White Knights':'♞ ', 'White Bishops':'♝ ', 'White Rooks':'♜ ', 'White Queens':'♛ ', 'White Kings':'♚ '}
        map = lambda y:([q for q in self.maps if self.maps[q] & (2**y)] + [False])[0]
        output = '    A    B    C    D    E    F    G    H\n  ╔════╤════╤════╤════╤════╤════╤════╤════╗\n'
        for q in range(8):
            if q % 2 == 0:
                output += '%i ║ %s │░%s░│ %s │░%s░│ %s │░%s░│ %s │░%s░║ %i\n  ╟────┼────┼────┼────┼────┼────┼────┼────╢\n' % tuple([8 - q] + [find[map(q * 8 + w)] if map(q * 8 + w) else '  ' if w % 2 == 0 else '░░' for w in range(8)] + [8 - q])
            else:
                output += '%i ║░%s░│ %s │░%s░│ %s │░%s░│ %s │░%s░│ %s ║ %i\n  ╟────┼────┼────┼────┼────┼────┼────┼────╢\n' % tuple([8 - q] + [find[map(q * 8 + w)] if map(q * 8 + w) else '░░' if w % 2 == 0 else '  ' for w in range(8)] + [8 - q])

        output += '\x1b[1A\x1b[2K' + '  ╚════╧════╧════╧════╧════╧════╧════╧════╝\n    A    B    C    D    E    F    G    H'
        return output

chess = Board()

next_states = [chess]
while True:
    current_states, next_states = next_states, []

    for state in current_states:
        for next_state in state.next_states():
            next_states.append(next_state)

    print(len(next_states))

The json files are too large to put here but you can assume they're correct. Each file has a dictionary with keys 2^0-2^63 each representing a possible position for the piece the file is named after. The values are the different positions they can go from there.

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
  • Does this answer your question? [How to step through Python code to help debug issues?](https://stackoverflow.com/questions/4929251/how-to-step-through-python-code-to-help-debug-issues) – mkrieger1 Feb 06 '22 at 19:00
  • Implement all move types first and then do a perft test, the approach you do now seems too complicated. – eligolf Feb 07 '22 at 04:46

2 Answers2

3
1.  e2-e3
2.  d7-d6
3. Bf1-b5 +

Black's king is now in check, so the number of moves in the 4th ply is more limited. You will need to implement check rules to get the correct number.

Thomas
  • 174,939
  • 50
  • 355
  • 478
0
1. e2-e4 f7-f5
2. Bf1-b5

Pawn at d7 is pinned and can't move.

Michael Butscher
  • 10,028
  • 4
  • 24
  • 25