A brutally fast Connect Four Agent that thinks in bits, not grids.
This is a high-performance Connect Four agent built for the Kaggle ConnectX competition. Instead of treating the board as a boring 2D array, it encodes the entire game state into a single 64-bit integer — a technique called a bitboard. Every move, every win check, every threat evaluation happens through lightning-fast bitwise operations.
The result? An agent that can search millions of positions per second, often looking 15+ moves ahead within Kaggle's strict 2-second time limit.
The complete decision-making pipeline — from raw board to optimal move.
A standard Connect Four board is 7 columns × 6 rows = 42 cells. Most implementations store this as a list or 2D array and loop through cells to check for wins. That's slow.
A bitboard packs the entire board into a single integer. Each bit represents one cell. Two integers — one for Player 1, one for Player 2 — capture the full state:
How 42 board cells map to bit positions inside a 64-bit integer.
Column layout (7 columns × 7 bits each, including sentinel row):
Col 0 Col 1 Col 2 Col 3 Col 4 Col 5 Col 6
bit 0 bit 7 bit 14 bit 21 bit 28 bit 35 bit 42
bit 1 bit 8 bit 15 bit 22 bit 29 bit 36 bit 43
bit 2 bit 9 bit 16 bit 23 bit 30 bit 37 bit 44
bit 3 bit 10 bit 17 bit 24 bit 31 bit 38 bit 45
bit 4 bit 11 bit 18 bit 25 bit 32 bit 39 bit 46
bit 5 bit 12 bit 19 bit 26 bit 33 bit 40 bit 47
------ ------ ------ ------ ------ ------ ------
bit 6 bit 13 bit 20 bit 27 bit 34 bit 41 bit 48 ← sentinel (unused)
Win detection in 4 operations:
# Check horizontal 4-in-a-row:
m = pos & (pos >> 7) # pairs of adjacent pieces
if m & (m >> 14): WIN! # pairs of pairs = four in a rowThat's it. No loops, no boundary checks. Just bit shifts and AND operations. The same trick works for vertical, and both diagonal directions — 12 bitwise operations total to check all four directions.
The bitboard shown below corresponds to a realistic mid-game ConnectX position:
A mid-game position and its corresponding bitboard representation.
Alpha-beta pruning in action — grey branches are never explored.
At its core, the agent uses Negamax with Alpha-Beta pruning — the gold standard for two-player zero-sum games. But raw alpha-beta alone isn't enough to beat strong opponents under a 2-second clock. Here's the full stack of techniques layered on top:
Instead of guessing how deep to search, the agent starts at depth 1, then depth 2, then depth 3, and so on. Each completed depth gives a valid "best move" — so if time runs out mid-search, we still have the best answer from the previous depth. This also feeds information into the next iteration (see: move ordering).
Many different move sequences lead to the same board position. A hash table with 2²⁴ = 16 million entries caches evaluated positions. Each entry stores:
- The position's unique key (64-bit hash)
- The evaluated score
- The search depth
- A flag (EXACT, LOWER bound, or UPPER bound)
- The best move found
The key insight: the position hash is simply pos + (pos | opp) — no Zobrist randomness needed because bitboards are already unique fingerprints.
Mirror symmetry bonus: Every position is also stored for its horizontal mirror. A board with pieces on the left is strategically identical to one mirrored to the right. This effectively doubles the transposition table hit rate for free.
Alpha-beta pruning is only as good as its move ordering. Search the best move first, and you prune almost everything else. The agent uses a layered ordering strategy:
| Priority | Technique | What It Does |
|---|---|---|
| 1st | TT Best Move | If this position was seen before, try that move first |
| 2nd | Must-Block | If the opponent wins next turn, block immediately |
| 3rd | Killer Moves | 2 moves per ply that recently caused beta cutoffs |
| 4th | History Heuristic | Moves that have historically been good get priority |
| 5th | Center Bias | Try center columns before edges (statistically stronger) |
After depth 4, the agent doesn't search the full score range [-∞, +∞]. Instead, it opens a narrow window of ±150 around the previous depth's score. If the true score falls inside, the search is much faster. If it falls outside (a "fail"), the agent re-searches with the full window. This gamble pays off the vast majority of the time.
The first move (expected to be the best thanks to move ordering) is searched with a full window. Every subsequent move is searched with a null window [α, α+1] — just to prove it's worse. If it surprisingly isn't, a full re-search kicks in. This reduces the search tree dramatically when move ordering is good.
All optimization techniques work together to maximize search depth within Kaggle's strict 2-second move limit.
When the search hits its depth limit, the agent needs to estimate who's winning. The evaluation function combines:
- Fork detection: If you have 2+ winning moves, you win regardless (score: +950)
- Single threat: One winning move available (score: +500)
- Opponent fork: Emergency — they have a double threat (score: -950)
- Open line analysis: Scans every possible 4-cell window in all 4 directions. Windows with only your pieces score quadratically (3 pieces = 9 points, 2 = 4 points). This makes near-complete lines exponentially more valuable.
- Center control: Pieces in the center column get a 4× bonus, adjacent columns get 2×. Center control is king in Connect Four.
The entire search core is compiled to native machine code using Numba's @njit decorator. This eliminates Python's interpreter overhead and delivers near-C performance. The agent gracefully falls back to a pure Python implementation if Numba isn't available.
A warm-up call runs at the start to trigger JIT compilation before the game clock starts ticking.
| Metric | Value |
|---|---|
| Competition | Kaggle ConnectX |
| Search Algorithm | Negamax + Alpha-Beta |
| Time Budget | 2 seconds |
| TT Size | 16M entries |
| Language | Python + Numba |
connectx-bitboard-agent/
├── src/
│ └── agent.py # The brain — all search and evaluation logic
├── tests/
│ └── test_agent.py # Basic tests
├── assets/ # Screenshots, diagrams, performance plots
├── .github/
│ └── workflows/
│ └── python-app.yml # CI pipeline (lint + test on push)
├── main.py # Local test runner (agent vs random/negamax)
├── requirements.txt # Python dependencies
├── LICENSE # MIT License
├── .gitignore
└── README.md # You are here
- Python 3.8+
numpynumba(optional but highly recommended)kaggle-environments(for local testing)
# Clone the repo
git clone https://github.com/Tarun995/connectX-bitboard-agent.git
cd connectX-bitboard-agent
# Install dependencies
pip install -r requirements.txt
# Run a local game (agent vs random opponent)
python main.pyUpload src/agent.py directly as your submission file on the ConnectX competition page.
This project is licensed under the MIT License — see the LICENSE file for details.
Built with ♟️ bits and ⚡ bitwise magic





