{"slug": "all-pieces-on-a-6-by-5-board", "title": "All pieces on a 6 by 5 board", "summary": "Claude generated Z3/Python code to solve a chess puzzle placing all pieces on a 6x5 board with bishops on opposite colors and no attacks. The solver found 192 raw solutions, deduplicated to 24 unique arrangements after accounting for piece swaps.", "body_md": "I’ve written a couple posts lately on getting an LLM to generate code to solve chess problems. [The first](https://www.johndcook.com/blog/2026/06/11/prolog-claude/) used Claude to generate Prolog and [the second](https://www.johndcook.com/blog/2026/06/15/writing-prolog-with-chatgpt/) used ChatGPT to generate Prolog. This post will use Claude to generate Z3/Python code.\n\nThe puzzle is one I’ve written about [before](https://www.johndcook.com/blog/2025/07/13/all-pieces-on-a-small-chessboard/):\n\nPlace all the pieces—king, queen, two bishops, two knights, and two rooks—on a 6 × 5 chessboard, with the requirement that the two bishops be on opposite colored squares and no piece is attacking another.\n\nIncidentally, it’s common for “piece” to exclude pawns, as above. But then what do you call all the things on a chessboard? You might call them “chess pieces,” in which case a pawn is a “chess piece” but not a “piece.” One convention is to use “chessmen” or simply “men” to include pieces and pawns.\n\nThis was the prompt I used.\n\nWrite Z3/Python code to find all solutions to the following chess puzzle.\n\nPlace all the pieces—king, queen, two bishops, two knights, and two rooks—on a 6 × 5 chessboard, with the requirement that the two bishops be on opposite colored squares and no piece is attacking another.\n\nThe code found 192 = 8 × 24 solutions. The factor of 8 comes from 23 ways of swapping the pairs of bishops, knights, and rooks. The script reports\n\n```\nTotal raw solutions: 192\nUnique solutions (deduplicating piece-pair swaps): 24\n\n── Solution 1 ──\n  0 1 2 3 4\n0 . K . . N\n1 . . . . B\n2 . . R . .\n3 Q . . . .\n4 . . . R .\n5 . B . . N\n  King: (0,1)\n  Queen: (3,0)\n  Bishop1: (5,1) [light]\n  Bishop2: (1,4) [dark]\n  Knight1: (5,4)\n  Knight2: (0,4)\n  Rook1: (4,3)\n  Rook2: (2,2)\n\n── Solution 2 ──\n  0 1 2 3 4\n0 N . . K .\n1 B . . . .\n2 . . R . .\n3 . . . . Q\n4 . R . . .\n5 N . . B .\n  King: (0,3)\n  Queen: (3,4)\n  Bishop1: (5,3) [light]\n  Bishop2: (1,0) [dark]\n  Knight1: (5,0)\n  Knight2: (0,0)\n  Rook1: (4,1)\n  Rook2: (2,2)\n\n...\n\n── Solution 24 ──\n  0 1 2 3 4\n0 B . . B .\n1 N . . . .\n2 . . . . Q\n3 . . R . .\n4 . R . . .\n5 N . . K .\n  King: (5,3)\n  Queen: (2,4)\n  Bishop1: (0,0) [light]\n  Bishop2: (0,3) [dark]\n  Knight1: (5,0)\n  Knight2: (1,0)\n  Rook1: (3,2)\n  Rook2: (4,1)\n```\n\nHere’s the code.\n\n```\n\"\"\"\nChess Puzzle Solver using Z3\nPlace king, queen, 2 bishops, 2 knights, 2 rooks on a 6×5 board\nsuch that:\n  - The two bishops are on opposite-colored squares\n  - No piece attacks any other piece\n\"\"\"\n\nfrom z3 import (\n    Int, Solver, Or, And, Not, Distinct, If,\n    sat, ForAll, Implies\n)\nimport itertools\n\nROWS = 6\nCOLS = 5\nTOTAL_CELLS = ROWS * COLS  # 30\n\nPIECES = [\"king\", \"queen\", \"bishop1\", \"bishop2\", \"knight1\", \"knight2\", \"rook1\", \"rook2\"]\nN = len(PIECES)  # 8 pieces\n\ndef cell(r, c):\n    return r * COLS + c\n\ndef pos_to_rc(pos):\n    return pos // COLS, pos % COLS\n\ndef color(r, c):\n    \"\"\"Square color: 0=light, 1=dark\"\"\"\n    return (r + c) % 2\n\ndef attacks_king(r1, c1, r2, c2):\n    return max(abs(r1 - r2), abs(c1 - c2)) == 1\n\ndef attacks_queen(r1, c1, r2, c2):\n    return (r1 == r2 or c1 == c2 or\n            abs(r1 - r2) == abs(c1 - c2))\n\ndef attacks_bishop(r1, c1, r2, c2):\n    return abs(r1 - r2) == abs(c1 - c2)\n\ndef attacks_rook(r1, c1, r2, c2):\n    return r1 == r2 or c1 == c2\n\ndef attacks_knight(r1, c1, r2, c2):\n    dr, dc = abs(r1 - r2), abs(c1 - c2)\n    return (dr == 1 and dc == 2) or (dr == 2 and dc == 1)\n\n# Map piece index to its attack function\nATTACK_FN = {\n    0: attacks_king,\n    1: attacks_queen,\n    2: attacks_bishop,\n    3: attacks_bishop,\n    4: attacks_knight,\n    5: attacks_knight,\n    6: attacks_rook,\n    7: attacks_rook,\n}\n\ndef solve_all():\n    solutions = []\n\n    # Each piece gets an integer position in [0, TOTAL_CELLS)\n    pos = [Int(f\"pos_{p}\") for p in PIECES]\n\n    s = Solver()\n\n    # All positions in valid range\n    for p in pos:\n        s.add(p >= 0, p < TOTAL_CELLS)\n\n    # All pieces on distinct squares\n    s.add(Distinct(*pos))\n\n    # Bishops on opposite colors\n    # bishop1 = pos[2], bishop2 = pos[3]\n    # color of pos = ((pos // COLS) + (pos % COLS)) % 2\n    b1_color = (pos[2] / COLS + pos[2] % COLS) % 2  # Z3 integer arithmetic\n    b2_color = (pos[3] / COLS + pos[3] % COLS) % 2\n\n    # Z3 doesn't do Python //; use integer division carefully\n    # We'll encode opposite colors: sum of colors == 1\n    # color(pos) = (row + col) % 2 = (pos//COLS + pos%COLS) % 2\n    # For Z3 int vars, use: (pos / COLS + pos % COLS) % 2\n    s.add((pos[2] / COLS + pos[2] % COLS) % 2 != (pos[3] / COLS + pos[3] % COLS) % 2)\n\n    # No piece attacks another\n    # We enumerate all (i,j) pairs and for each possible (pos_i, pos_j) assignment,\n    # assert that those pieces don't attack each other.\n    # Since positions are Z3 vars, we use a constraint table approach:\n    # For each pair (i,j), add constraints over all concrete (r1,c1,r2,c2) combos.\n\n    # Pre-build attack lookup tables for each piece-type pair\n    # This avoids slow Z3 symbolic reasoning over large disjunctions.\n\n    # We'll encode: for all concrete assignments, if pos[i]==cell(r1,c1) and pos[j]==cell(r2,c2),\n    # then piece i must not attack piece j.\n    # Equivalently: NOT (pos[i]==cell(r1,c1) AND pos[j]==cell(r2,c2)) for all attacking pairs.\n\n    # For efficiency, we build a set of \"forbidden (cell_i, cell_j)\" pairs per piece pair.\n\n    for i, j in itertools.combinations(range(N), 2):\n        fn_i = ATTACK_FN[i]\n        fn_j = ATTACK_FN[j]\n        forbidden = []\n        for r1, c1 in itertools.product(range(ROWS), range(COLS)):\n            for r2, c2 in itertools.product(range(ROWS), range(COLS)):\n                if (r1, c1) == (r2, c2):\n                    continue\n                # i attacks j or j attacks i\n                if fn_i(r1, c1, r2, c2) or fn_j(r2, c2, r1, c1):\n                    forbidden.append((cell(r1, c1), cell(r2, c2)))\n\n        if forbidden:\n            # Add: NOT (pos[i] == ci AND pos[j] == cj) for each forbidden pair\n            s.add(And([\n                Not(And(pos[i] == ci, pos[j] == cj))\n                for ci, cj in forbidden\n            ]))\n\n    # Enumerate all solutions\n    solution_count = 0\n    while s.check() == sat:\n        m = s.model()\n        sol = [m[pos[k]].as_long() for k in range(N)]\n        solutions.append(sol)\n        solution_count += 1\n\n        # Block this solution (and symmetric duplicates via blocking exact assignment)\n        s.add(Or([pos[k] != sol[k] for k in range(N)]))\n\n        if solution_count % 10 == 0:\n            print(f\"  Found {solution_count} solutions so far...\")\n\n    return solutions\n\ndef print_board(sol):\n    board = [[\".\" for _ in range(COLS)] for _ in range(ROWS)]\n    symbols = [\"K\", \"Q\", \"B\", \"B\", \"N\", \"N\", \"R\", \"R\"]\n    for k, p in enumerate(sol):\n        r, c = pos_to_rc(p)\n        board[r][c] = symbols[k]\n    print(\"  \" + \" \".join(str(c) for c in range(COLS)))\n    for r in range(ROWS):\n        print(f\"{r} \" + \" \".join(board[r]))\n\ndef canonicalize(sol):\n    \"\"\"\n    Produce a canonical form to deduplicate solutions where\n    bishop1/bishop2, knight1/knight2, rook1/rook2 are interchangeable.\n    Returns a frozenset-based key.\n    \"\"\"\n    king_pos   = sol[0]\n    queen_pos  = sol[1]\n    bishops    = tuple(sorted([sol[2], sol[3]]))\n    knights    = tuple(sorted([sol[4], sol[5]]))\n    rooks      = tuple(sorted([sol[6], sol[7]]))\n    return (king_pos, queen_pos, bishops, knights, rooks)\n\ndef main():\n    print(\"Solving chess puzzle on 6×5 board...\")\n    print(\"Pieces: King, Queen, 2 Bishops (opposite colors), 2 Knights, 2 Rooks\")\n    print(\"Constraint: No piece attacks another\\n\")\n\n    solutions = solve_all()\n\n    # Deduplicate by canonical form\n    seen = {}\n    for sol in solutions:\n        key = canonicalize(sol)\n        if key not in seen:\n            seen[key] = sol\n\n    unique = list(seen.values())\n    print(f\"\\nTotal raw solutions: {len(solutions)}\")\n    print(f\"Unique solutions (deduplicating piece-pair swaps): {len(unique)}\\n\")\n\n    for idx, sol in enumerate(unique, 1):\n        print(f\"── Solution {idx} ──\")\n        print_board(sol)\n        labels = [\"King\", \"Queen\", \"Bishop1\", \"Bishop2\", \"Knight1\", \"Knight2\", \"Rook1\", \"Rook2\"]\n        for k, p in enumerate(sol):\n            r, c = pos_to_rc(p)\n            col_name = \"light\" if color(r, c) == 0 else \"dark\"\n            sq = f\"({r},{c})\"\n            extra = f\" [{col_name}]\" if \"Bishop\" in labels[k] else \"\"\n            print(f\"  {labels[k]}: {sq}{extra}\")\n        print()\n\nif __name__ == \"__main__\":\n    main()\n```\n\n**Related post**: [Lessons Learned With the Z3 SAT/SMT Solver](https://www.johndcook.com/blog/2025/03/17/lessons-learned-with-the-z3-sat-smt-solver/)", "url": "https://wpnews.pro/news/all-pieces-on-a-6-by-5-board", "canonical_source": "https://www.johndcook.com/blog/2026/06/20/z3-python-claude/", "published_at": "2026-06-20 21:41:16+00:00", "updated_at": "2026-06-20 22:06:23.136062+00:00", "lang": "en", "topics": ["large-language-models", "ai-tools", "ai-research"], "entities": ["Claude", "ChatGPT", "Z3", "Python", "Prolog"], "alternates": {"html": "https://wpnews.pro/news/all-pieces-on-a-6-by-5-board", "markdown": "https://wpnews.pro/news/all-pieces-on-a-6-by-5-board.md", "text": "https://wpnews.pro/news/all-pieces-on-a-6-by-5-board.txt", "jsonld": "https://wpnews.pro/news/all-pieces-on-a-6-by-5-board.jsonld"}}