DEV Community

Cover image for Building a Chess Engine in Python - Part 1: The Immortal King & Bitboard Grid Synchronization
Nicholas
Nicholas

Posted on

Building a Chess Engine in Python - Part 1: The Immortal King & Bitboard Grid Synchronization

"I spent days writing logic masks, only to realize my Chess King became an immortal god who could capture the opposing King."

Connecting a classic 8x8 Pygame matrix UI to a new, high-performance bitboard engine felt like a great idea until the synchronization bugs started crawling out. Over the last few days, I've been hunting down some of the most hilarious and stubborn logical errors I've encountered so far.

Let's dive into what went wrong and how I fixed it.


1. The Mirror Dimension ([row] vs [7 - row])

After building the adapter bridge, the standard pieces moved fine, but two visual elements broke entirely: move history highlights and available move hint dots. They were rendering perfectly on the wrong side of the board.

  • The Cause: A classic coordinate mapping issue. While matrices count rows from top to bottom (0 to 7), bitboards index squares from bottom to top.
  • The Fix: The engine was fetching the raw matrix row. Adjusting the coordinate transformation to [7 - row] instantly brought both worlds into perfect harmony.

2. The Absolute Monarch Bug (King vs. King)

The funniest logical flaw happened during checkmate testing. The King under attack refused to lose. He would block the enemy slider's ray of attack with his own body, consider the squares behind him "safe," and happily wander around until he was literally pinned to the edge of the board. Even worse: kings could stand on adjacent squares and capture each other.

  • The Cause: The King was accidentally included in the blocking occupancy masks while generating specific check masks. He was effectively shading himself from the attack rays.
  • The Fix: Excluded the King's square from the ray-blocking lookup so that attacks correctly pass through his position during validation.

3. Missing Check Restrictions (Code Refactor)

Another major flaw allowed other pieces (like knights, queens, and bishops) to make random moves even when the King was actively under check. The check_mask generation logic was breaking down when dealing with sliders.

Here is how the calculation evolved:

How it looked before:

The mask didn't differentiate piece types, leading to broken lookup tables

elif count == 1:
    self.in_check_flag = True
    checker_square = check_ers.bit_length() - 1
    checker_bit = 1 << checker_square
    check_mask = ray_from_king & ray_from_checker | checker_bit
Enter fullscreen mode Exit fullscreen mode

How it looks now (Working Version):

elif count == 1:
    self.in_check_flag = True
    checker_square = check_ers.bit_length() - 1
    checker_bit = 1 << checker_square
    if checker_bit & (enemy_knight | enemy_pawns):
        check_mask = checker_bit
    else:
        # Properly determining slider type and intersection masks
        is_rook_type = ((checker_bit & enemy_rooks) or (checker_bit & enemy_queen)) and ((king_square // 8 == checker_square // 8) or (king_square % 8 == checker_square % 8))
        if is_rook_type:
            ray_from_king = self.attack_tables.get_rook_attacks(king_square, all_occ)
            ray_from_checker = self.attack_tables.get_rook_attacks(checker_square, all_occ)
        else:
            ray_from_king = self.attack_tables.get_bishop_attacks(king_square, all_occ)
            ray_from_checker = self.attack_tables.get_bishop_attacks(checker_square, all_occ)
        check_mask = ray_from_king & ray_from_checker | checker_bit
Enter fullscreen mode Exit fullscreen mode

By explicitly checking if the attacking piece is a knight/pawn versus a sliding piece (rook/bishop/queen) and calculating the intersecting ray paths properly, all non-evasion moves are now correctly blocked during a check state.

Minor Fixes & Quality of Life

  • Pawn Promotion Crash: The matrix engine identified objects as strings ("w_pawn"), while the bitboard engine uses character notation ("P", "Q", "b"). Added a quick mapper to prevent token crashes during promotion.
  • Draw States: Implemented proper engine detection for Threefold Repetition and Insufficient Material (e.g., King vs King + Knight) to ensure matches can actually end legally.

What's Next? (Proof of Correctness)

Now that the visual bugs are squashed, I need to make sure the math is 100% accurate. My next milestones in Jira are setting up a Perft (Performance Test) function. It will recursively walk the move tree to a specific depth, count the total leaf nodes, and cross-reference them with official chess move datasets to catch any hidden logical edge cases.

Tasks in Jira

Have you ever had a bug where your game pieces completely ignored the laws of physics or rules? Let’s talk about your favorite debugging horror stories in the comments!

python #chess #debugging #showdev

Top comments (0)