DEV Community

Cover image for I Built a Chess Engine for 6 6 Crazyhouse — Now It's #1 on chess.com
Vladimir Doronin
Vladimir Doronin

Posted on

I Built a Chess Engine for 6 6 Crazyhouse — Now It's #1 on chess.com

Hey folks! I'm Vladimir, a backend developer. Over the past 9 months, my side project — a chess engine for an obscure chess variant — went from a "what if..." idea to #1 on the chess.com leaderboard. No neural networks. Just good old alpha-beta search, written in Rust.

If you're into chess programming, CPU-bound optimization, or bridging Python and Rust via PyO3 — this one's for you.

What is Minihouse?

chess.com has a "Variants" section with alternative chess modes. One of them is Minihouse (aka 6×6 Crazyhouse):

  • 6×6 board instead of 8×8 (no queen, one pawn each)
  • Crazyhouse mechanic: when you capture a piece, it goes into your "hand" — and you can drop it back onto any empty square instead of making a regular move

This completely changes the game. In standard chess, material is lost permanently. Here, every trade gives both players new pieces to drop. Tactics dominate strategy. Games often end in 5–10 moves with a surprise checkmate from a dropped knight.

There are powerful engines for standard chess (Stockfish, Leela Chess Zero) and even for 8×8 Crazyhouse. But for 6×6 Crazyhouse? Nothing existed. A complete void.

So I thought: fine, I'll write one myself.

Timeline: 9 Months From First Commit to #1

Nine months sounds like a lot, but this was strictly a "sixth terminal" project — the terminal you only get to when everything else is done. Not full-time development. Stolen minutes between real work.

Stage 1: Naive Python (May–June 2025)

The classic approach: minimax with alpha-beta pruning, all in Python. I had to write the game logic from scratch — no libraries exist for a 6×6 board with Crazyhouse drop rules. Ended up with ~860 lines in gamestate.py: move generation, validation, check handling, pawn promotion (pawns reach the back rank fast on 6×6).

The engine played. Badly. Search depth 3–4 moves, seconds per move. For Crazyhouse, where each position branches into dozens of variants due to possible drops, that's not enough.

Stage 2: Python Optimizations (Jul–Oct 2025)

Added everything I could without changing the language:

  • Transposition table with Zobrist hashing
  • Null-move pruning (with a twist: disabled when the opponent has pieces in hand — otherwise you miss dangerous drops)
  • Late Move Reduction (LMR)
  • Iterative deepening with aspiration windows
  • Move ordering: MVV-LVA, killer moves, history heuristic

Better. Depth 5–6. But Python just isn't built for evaluating millions of positions.

Stage 3: Rewriting the Core in Rust (Nov 2025)

The key commit: "Rewrite chess engine core in Rust via PyO3 for ~50x speedup".

I rewrote the entire search and evaluation function in Rust, keeping the Python wrapper for GUI and bot compatibility. The bridge is PyO3 — Rust compiles into a native Python module that you call with a regular import.

Result: depth 8–10 moves, ~3.8 seconds per move. For a 6×6 board, that's serious depth — the search tree with drops on a small board is incredibly bushy.

The Rust core — ~3,300 lines:

Module Purpose LOC
search.rs Alpha-beta + PVS + quiescence + null-move ~1,000
gamestate.rs Move generation, make/undo ~750
eval.rs Position evaluation function ~440
types.rs Data types (pieces, moves, board) ~330
cache.rs SQLite position cache ~100
zobrist.rs Position hashing ~75
lib.rs PyO3 bindings ~600

Stage 4: chess.com Bot (Dec 2025)

Next I needed a way to play against real humans. I wrote a Playwright automation (~2,000 lines): login, search for Minihouse games, board recognition, move execution, result handling. The bot opens chess.com in a regular Chrome instance, finds a game, and plays.

Important note: I always openly stated on chess.com that a bot was playing. No deception — opponents knew what they were up against.

Stage 5: Overnight Training on a Server (Jan–Feb 2026)

I have a dedicated server with 12 CPU cores. During the day, they're busy with production workloads. At night — free.

I wrote a systemd service + timer. Every day at 00:00 UTC, a process starts. It waits until 02:00 UTC (when production load is guaranteed to drop), then plays against itself for 8 hours: AI as white vs. AI as black. Search depth 6, with 20% "exploration" moves (taking the second-best move instead of the best). At 10:00 UTC — graceful shutdown, resources return to production.

# minichesstrain.timer
[Timer]
OnCalendar=*-*-* 00:00:00
Persistent=false
Enter fullscreen mode Exit fullscreen mode

Each night produces dozens of games, with every position and its best move saved to SQLite. By February 2026 — 27,519 cached positions. When the engine encounters a known position, it skips the calculation and immediately uses the cached answer.

Evaluation Function: How Crazyhouse Differs From Standard Chess

This is the most interesting part from an engineering perspective. You can't just take a standard chess eval function — Crazyhouse has fundamentally different priorities.

1. Pieces in Hand Are Worth More Than on the Board

In standard chess, a knight is worth ~320 points. In Crazyhouse, a knight in hand is worth more — it can be instantly dropped on any empty square. The engine values hand pieces at 60–100% above their base value.

2. Progressive Drop Threat

The more pieces in hand, the more dangerous each additional one becomes. One piece in hand is a threat. Three pieces in hand is checkmate in 2 moves. The formula is non-linear:

let drop_threat = (hand_total) * DROP_THREAT_BONUS 
    + hand_total.max(1) * (hand_total - 1).max(0) * 10;
Enter fullscreen mode Exit fullscreen mode

3. King Safety Is Everything

On a 6×6 board, the king has nowhere to hide. A pawn shield (pawns in front of the king) is worth +55 points per pawn. An exposed king with opponent pieces in hand? That's a penalty up to –390 points. The engine basically panics if the king is exposed.

if pawn_shield == 0 && opponent_hand_pieces > 0 {
    score -= EXPOSED_KING_PENALTY * opponent_hand_pieces.min(3);
    // Up to -130 * 3 = -390 penalty
}
Enter fullscreen mode Exit fullscreen mode

4. Knight Drop With Check

A knight in hand that can be dropped near the enemy king gets a +40 bonus. A rook that can land on the king's file — +25. The engine evaluates not just current threats, but latent ones — pieces in hand ready to attack.

5. Passed Pawns Are Devalued

In standard chess, a passed pawn is powerful. In Crazyhouse, the opponent can just drop a piece in its path. So the passed pawn bonus is significantly reduced compared to classical chess.

Neural Network? Tried It. Didn't Work.

The project has an nn/ folder with an AlphaZero-style approach: CNN for policy + value (PyTorch), MCTS, RL wrapper. I'm comfortable writing small neural networks, and there were many experiments — different architectures, different training approaches. Nothing useful came out of it for this task. Maybe I didn't try hard enough, but more likely it's objective limitations:

  • Tiny dataset. There's no game database for 6×6 Crazyhouse — everything has to be self-generated
  • Limited resources. I have 12 CPU cores at night, not a GPU cluster
  • Alpha-beta turned out to be good enough. On a small board, classical search at depth 8–10 with a good eval function covers most tactical shots

In the end, a hand-tuned eval in 440 lines of Rust outperformed all my neural network experiments. For a niche with zero competition — well-tuned classical search just works.

Results: 228 Games on chess.com

The bot played 228 games on chess.com in Minihouse mode:

Metric Value
Wins 212 (93.0%)
Losses 12
Draws 4
Checkmates 139 games
Resignation / timeout 65 games
Average winning game length 8.1 moves
Peak rating ~2300

Winning against the former #1
Beating the former #1 in Minihouse

The average winning game lasted 8 moves. Most people don't even understand what happened — the engine finds drop tactics that humans simply don't see.

The Turning Point: Playing Top-20 Players

The biggest leap in engine quality didn't come from code optimization — it came from the right opponents. Self-play and random matches didn't expose weaknesses in the strategy; the engine kept winning but wasn't improving. Everything changed when I arranged games against top-20 Minihouse players in the world.

One top player agreed to regularly play against the bot — despite it thinking agonizingly long per move. These games exposed holes in the eval function: a drop valued incorrectly here, a weak pawn shield there, a positional threat the engine couldn't see. After each loss, I analyzed, adjusted the evaluation, and progress accelerated dramatically.

The Night That Changed Everything

Until February 22nd, the #1 spot in chess.com's Minihouse rating belonged to someone else. My bot was #2.

That evening, I launched the bot in automatic game-seeking mode. It played all night — and by the morning of February 23rd, it had played 138 games, winning 134. Most importantly, it confidently beat top-20 players, including the former #1.

The losses were mostly early on, when the engine hadn't encountered a particular opponent's style. After that, positions from lost games entered the cache — and the bot stopped making the same mistakes.

I opened chess.com in the morning and saw my account was #1 in Minihouse. The feeling: you wake up, and your code did overnight what you spent 9 months working toward.

Side Effect: I Got Better at Chess Too

Here's a funny thing: while building and testing the engine, I was also playing Minihouse myself, no assistance. Before the project, I was consistently top-100, occasionally reaching top-50. But after months of working on the eval function and analyzing thousands of bot games, all those patterns seeped into my brain: knight drops with check, pawn shields, center control on a small board.

Result — I broke into the top-20 in Minihouse. Legitimately, with my own hands. Turns out writing a chess engine is the best way to learn chess.

Full Stack

Python 3.11 — glue, GUI, bot
├── Rust (PyO3) — search engine, eval, hashing
├── Pygame — local GUI for testing
├── Playwright + Chrome — chess.com automation
├── SQLite — position cache (27.5K entries)
├── systemd timer — overnight self-play on server
└── PyTorch — neural network attempt (unused)
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

1. Rust via PyO3 is magic. Rewriting the bottleneck from Python to Rust gave a 50x speedup while keeping the same Python API. If you have a CPU-bound task in Python — seriously look into PyO3.

2. Domain-specific eval > generic neural net (with limited resources). Neural networks win when you have data and GPUs. When you don't — hand-tuning an eval function for a specific chess variant works better.

3. Niche is a superpower. In mainstream chess, my engine wouldn't crack the top 1,000. But in 6×6 Crazyhouse, there's almost no competition — and a small, well-tuned engine can become the best.

4. Overnight self-play on dedicated servers works. 8 hours × 12 cores every night — in a couple of months, the engine accumulated 27,000 positions that work as an opening book.

5. Null-move pruning must be disabled in Crazyhouse when the opponent has pieces in hand. I learned this after several mysterious losses — the engine "skipped" its turn and failed to see the opponent could drop a knight with check.

What I'd Do Differently

Looking back:

  • Write the core in Rust from day one. I spent months optimizing Python, but the 50x speedup only came with Rust. Prototyping in Python is fine, but I shouldn't have stayed there so long.
  • Play strong opponents sooner. Self-play and random matches didn't expose eval weaknesses. Real growth only started after games against the top-20.
  • Log more aggressively. For the first few months, I didn't record why the engine chose a specific move. Once I started logging the search tree, debugging eval became much easier.

The project is fully open source: GitHub. Fork it, tweak the eval function to your style, train your own position cache, and run your own bot. If you play Minihouse on chess.com — you might have already faced my bot. Don't take it personally 😄

And if you haven't tried Minihouse — give it a shot. It's chess on steroids: fast, tactically wild, and every game feels different. Small board, 2–3 minute games. Perfect for killing a lunch break.

Happy to answer questions in the comments — especially from anyone who's worked on chess programming, PyO3, or search optimization!

Top comments (0)