Sunday, June 21, 2026

Producing Z3/Python code with Claude


I’ve written a pair posts these days on getting an LLM to generate code to resolve chess issues. The primary used Claude to generate Prolog and the second used ChatGPT to generate Prolog. This publish will use Claude to generate Z3/Python code.

The puzzle is one I’ve written about earlier than:

Place all of the items—king, queen, two bishops, two knights, and two rooks—on a 6 × 5 chessboard, with the requirement that the 2 bishops be on reverse coloured squares and no piece is attacking one other.

By the way, it’s widespread for “piece” to exclude pawns, as above. However then what do you name all of the issues on a chessboard? You may name them “chess items,” by which case a pawn is a “chess piece” however not a “piece.” One conference is to make use of “chessmen” or just “males” to incorporate items and pawns.

This was the immediate I used.

Write Z3/Python code to seek out all options to the next chess puzzle.

Place all of the items—king, queen, two bishops, two knights, and two rooks—on a 6 × 5 chessboard, with the requirement that the 2 bishops be on reverse coloured squares and no piece is attacking one other.

The code discovered 192 = 8 × 24 options. The issue of 8 comes from 23 methods of swapping the pairs of bishops, knights, and rooks. The script experiences


Complete uncooked options: 192
Distinctive options (deduplicating piece-pair swaps): 24

── Answer 1 ──
  0 1 2 3 4
0 . Ok . . N
1 . . . . B
2 . . R . .
3 Q . . . .
4 . . . R .
5 . B . . N
  King: (0,1)
  Queen: (3,0)
  Bishop1: (5,1) [light]
  Bishop2: (1,4) [dark]
  Knight1: (5,4)
  Knight2: (0,4)
  Rook1: (4,3)
  Rook2: (2,2)

── Answer 2 ──
  0 1 2 3 4
0 N . . Ok .
1 B . . . .
2 . . R . .
3 . . . . Q
4 . R . . .
5 N . . B .
  King: (0,3)
  Queen: (3,4)
  Bishop1: (5,3) [light]
  Bishop2: (1,0) [dark]
  Knight1: (5,0)
  Knight2: (0,0)
  Rook1: (4,1)
  Rook2: (2,2)

...

── Answer 24 ──
  0 1 2 3 4
0 B . . B .
1 N . . . .
2 . . . . Q
3 . . R . .
4 . R . . .
5 N . . Ok .
  King: (5,3)
  Queen: (2,4)
  Bishop1: (0,0) [light]
  Bishop2: (0,3) [dark]
  Knight1: (5,0)
  Knight2: (1,0)
  Rook1: (3,2)
  Rook2: (4,1)

Right here’s the code.


"""
Chess Puzzle Solver utilizing Z3
Place king, queen, 2 bishops, 2 knights, 2 rooks on a 6×5 board
such that:
  - The 2 bishops are on opposite-colored squares
  - No piece assaults some other piece
"""

from z3 import (
    Int, Solver, Or, And, Not, Distinct, If,
    sat, ForAll, Implies
)
import itertools

ROWS = 6
COLS = 5
TOTAL_CELLS = ROWS * COLS  # 30

PIECES = ["king", "queen", "bishop1", "bishop2", "knight1", "knight2", "rook1", "rook2"]
N = len(PIECES)  # 8 items

def cell(r, c):
    return r * COLS + c

def pos_to_rc(pos):
    return pos // COLS, pos % COLS

def shade(r, c):
    """Sq. shade: 0=mild, 1=darkish"""
    return (r + c) % 2

def attacks_king(r1, c1, r2, c2):
    return max(abs(r1 - r2), abs(c1 - c2)) == 1

def attacks_queen(r1, c1, r2, c2):
    return (r1 == r2 or c1 == c2 or
            abs(r1 - r2) == abs(c1 - c2))

def attacks_bishop(r1, c1, r2, c2):
    return abs(r1 - r2) == abs(c1 - c2)

def attacks_rook(r1, c1, r2, c2):
    return r1 == r2 or c1 == c2

def attacks_knight(r1, c1, r2, c2):
    dr, dc = abs(r1 - r2), abs(c1 - c2)
    return (dr == 1 and dc == 2) or (dr == 2 and dc == 1)

# Map piece index to its assault perform
ATTACK_FN = {
    0: attacks_king,
    1: attacks_queen,
    2: attacks_bishop,
    3: attacks_bishop,
    4: attacks_knight,
    5: attacks_knight,
    6: attacks_rook,
    7: attacks_rook,
}

def solve_all():
    options = []

    # Every bit will get an integer place in [0, TOTAL_CELLS)
    pos = [Int(f"pos_{p}") for p in PIECES]

    s = Solver()

    # All positions in legitimate vary
    for p in pos:
        s.add(p >= 0, p < TOTAL_CELLS)

    # All items on distinct squares
    s.add(Distinct(*pos))

    # Bishops on reverse colours
    # bishop1 = pos[2], bishop2 = pos[3]
    # shade of pos = ((pos // COLS) + (pos % COLS)) % 2
    b1_color = (pos[2] / COLS + pos[2] % COLS) % 2  # Z3 integer arithmetic
    b2_color = (pos[3] / COLS + pos[3] % COLS) % 2

    # Z3 would not do Python //; use integer division rigorously
    # We'll encode reverse colours: sum of colours == 1
    # shade(pos) = (row + col) % 2 = (pos//COLS + pospercentCOLS) % 2
    # For Z3 int vars, use: (pos / COLS + pos % COLS) % 2
    s.add((pos[2] / COLS + pos[2] % COLS) % 2 != (pos[3] / COLS + pos[3] % COLS) % 2)

    # No piece assaults one other
    # We enumerate all (i,j) pairs and for every doable (pos_i, pos_j) project,
    # assert that these items do not assault one another.
    # Since positions are Z3 vars, we use a constraint desk method:
    # For every pair (i,j), add constraints over all concrete (r1,c1,r2,c2) combos.

    # Pre-build assault lookup tables for every piece-type pair
    # This avoids sluggish Z3 symbolic reasoning over giant disjunctions.

    # We'll encode: for all concrete assignments, if pos[i]==cell(r1,c1) and pos[j]==cell(r2,c2),
    # then piece i have to not assault piece j.
    # Equivalently: NOT (pos[i]==cell(r1,c1) AND pos[j]==cell(r2,c2)) for all attacking pairs.

    # For effectivity, we construct a set of "forbidden (cell_i, cell_j)" pairs per piece pair.

    for i, j in itertools.combos(vary(N), 2):
        fn_i = ATTACK_FN[i]
        fn_j = ATTACK_FN[j]
        forbidden = []
        for r1, c1 in itertools.product(vary(ROWS), vary(COLS)):
            for r2, c2 in itertools.product(vary(ROWS), vary(COLS)):
                if (r1, c1) == (r2, c2):
                    proceed
                # i assaults j or j assaults i
                if fn_i(r1, c1, r2, c2) or fn_j(r2, c2, r1, c1):
                    forbidden.append((cell(r1, c1), cell(r2, c2)))

        if forbidden:
            # Add: NOT (pos[i] == ci AND pos[j] == cj) for every forbidden pair
            s.add(And([
                Not(And(pos[i] == ci, pos[j] == cj))
                for ci, cj in forbidden
            ]))

    # Enumerate all options
    solution_count = 0
    whereas s.test() == sat:
        m = s.mannequin()
        sol = [m[pos[k]].as_long() for ok in vary(N)]
        options.append(sol)
        solution_count += 1

        # Block this resolution (and symmetric duplicates by way of blocking actual project)
        s.add(Or([pos[k] != sol[k] for ok in vary(N)]))

        if solution_count % 10 == 0:
            print(f"  Discovered {solution_count} options thus far...")

    return options

def print_board(sol):
    board = [["." for _ in range(COLS)] for _ in vary(ROWS)]
    symbols = ["K", "Q", "B", "B", "N", "N", "R", "R"]
    for ok, p in enumerate(sol):
        r, c = pos_to_rc(p)
        board[r][c] = symbols[k]
    print("  " + " ".be part of(str(c) for c in vary(COLS)))
    for r in vary(ROWS):
        print(f"{r} " + " ".be part of(board[r]))

def canonicalize(sol):
    """
    Produce a canonical type to deduplicate options the place
    bishop1/bishop2, knight1/knight2, rook1/rook2 are interchangeable.
    Returns a frozenset-based key.
    """
    king_pos   = sol[0]
    queen_pos  = sol[1]
    bishops    = tuple(sorted([sol[2], sol[3]]))
    knights    = tuple(sorted([sol[4], sol[5]]))
    rooks      = tuple(sorted([sol[6], sol[7]]))
    return (king_pos, queen_pos, bishops, knights, rooks)

def predominant():
    print("Fixing chess puzzle on 6×5 board...")
    print("Items: King, Queen, 2 Bishops (reverse colours), 2 Knights, 2 Rooks")
    print("Constraint: No piece assaults anothern")

    options = solve_all()

    # Deduplicate by canonical type
    seen = {}
    for sol in options:
        key = canonicalize(sol)
        if key not in seen:
            seen[key] = sol

    distinctive = listing(seen.values())
    print(f"nTotal uncooked options: {len(options)}")
    print(f"Distinctive options (deduplicating piece-pair swaps): {len(distinctive)}n")

    for idx, sol in enumerate(distinctive, 1):
        print(f"── Answer {idx} ──")
        print_board(sol)
        labels = ["King", "Queen", "Bishop1", "Bishop2", "Knight1", "Knight2", "Rook1", "Rook2"]
        for ok, p in enumerate(sol):
            r, c = pos_to_rc(p)
            col_name = "mild" if shade(r, c) == 0 else "darkish"
            sq = f"({r},{c})"
            additional = f" [{col_name}]" if "Bishop" in labels[k] else ""
            print(f"  {labels[k]}: {sq}{additional}")
        print()

if __name__ == "__main__":
    predominant()

Associated publish: Classes Realized With the Z3 SAT/SMT Solver

Related Articles

Latest Articles