Congklak
Congklak in the browser where you choose which region's rules to play, each ruleset cited to its sources

- regional rulesets you can switch between
- 3
- cited sources, each with a confidence level
- 5
- seeds counted after every move
- 98
- game servers needed for online play
- 0
On this page
The problem
Congklak is a seed-sowing board game played across the archipelago under many names: dakon, congkak, sungka. The rules differ by region. Does the game end when a player has no legal move, or as soon as they have three empty holes? Does landing in an empty hole on your own side capture the opposite hole, or end your turn? Must a seed go all the way round the board before it can capture? Published sources disagree on all three.
Every congklak app I found picks one reading and presents it as "the rules". Two players who learned the game in different places can't tell whether the app is wrong or they are. I built this version for Indonesian players who want to recognise the game they grew up with, for newcomers who need to be taught, and for anyone curious where the rules come from.
What I built
You choose which region's rules to play
The app ships three rulesets, each tied to its sources. One follows the most widely used reading. One follows Sleman regency's cultural documentation, where the game ends at three empty holes. One follows Malaysia's national culture department, where you capture only after a full lap. The active ruleset is always named on screen. A comparison view replays the same moves under two rulesets side by side and marks the first move where they part ways.
Each source is weighed and recorded
The sources page lists five sources in three kinds: government cultural documentation, a specialist board-game reference, and a news article. Each carries a stated confidence. The Sleman source contradicts itself on when the game ends, so I recorded both readings and implemented the one it states most firmly. The news article reverses the capture rule compared with both other kinds. I logged it as a known disagreement and didn't implement it. I also turned down a fourth ruleset, because no source backed the differences it would have added.
Rules are data, so a new region needs no new code
Each ruleset is a data file with its options, sources and recorded disagreements. The game code never checks for a region by name. The build refuses to ship a ruleset without a citation. When the Malay lap rule needed something the format couldn't express, I extended the format and left the game logic untouched.
The 98 seeds are counted after every move
The board holds exactly 98 seeds. A sowing loop that loses or duplicates one is the classic bug in this family of games, and it's silent: play just continues, wrongly. I wrote a check that counts all 98 after every step of every turn, before I wrote the sowing itself. Because the rules engine always gives the same result from the same moves, undo, replays, the computer opponent and online play all build on it.
I tested the finished site by hand
The test suite covered the engine well, but it couldn't see the interface bugs. The computer opponent never moved at animated speeds. The three typefaces in the design were never loaded. The board shrank to 160 pixels wide on a phone. Also, the first version sowed anticlockwise, while all three sources say clockwise. I found each of these by using the built site in two browsers, and a layout test now pins the direction.
Result
Congklak is live and public, in Indonesian and English, and a ruleset without a citation fails the deploy. You can play a friend on the same device, the computer at three difficulty levels, or someone on another device by code with no game server. Seeds travel hole by hole with speed control, and a preview shows where a move will end before you make it. There is also a replay viewer and a learn mode with three board lessons. Traditional words such as lumbung, biji, menembak and dakon stay in the interface. I built it on 3 and 4 August 2026.
Under the hoodTechnical detail for engineers
applyMove(state, move, ruleset) → { state, events }is pure: no clock, no unseeded randomness, no floats, no browser APIs. A game is its move list plus a ruleset id.- The board is a flat
Int8Array(16): indices 0–6 are player A's holes, 7 is A's lumbung, 8–14 are B's, 15 is B's. lib/engine/conserve.tssums all 16 positions after every event, in every test and in dev builds.- Rulesets are JSON packs in
data/rulesets/(umum,jawa-sleman,congkak-melayu), validated with a Zod schema at build time. There is noif (region === 'jawa')in the engine. - Sources: Sleman, Bantul and JKKN Malaysia (government), gambiter.com (specialist reference), detikEdu (mass media). detikEdu inverts menembak and is recorded, not implemented.
jawa-sleman's final sweep is an inference, flagged as such in its divergence entry. - A turn emits
scoop,sow,relay,bank,capture,extraTurnandendevents; the animation replays them.prefers-reduced-motionresolves instantly to a written summary. - The AI is minimax with alpha-beta and iterative deepening in a Web Worker, at depths 2, 5 and 9 plus seeded noise. It handles extra turns, where nodes don't alternate players.
- P2P runs over WebRTC with manual offer/answer paste or a PeerJS-brokered short code (dynamically imported). Peers confirm the ruleset id at connect and exchange a state hash every turn; a mismatch halts the game.
- Two P2P bugs came from the transport: a channel captured by value so the host's handshake was never sent, and PeerJS returning data in a different shape than sent. The network layer is now a pure reducer with 22 tests that run without networking.
- UI bugs fixed after browser testing: an unstable effect dependency re-firing every frame, a child effect running before the parent wrote, fonts declared as CSS variables but never loaded, and
mx-autoon a flex item cancelling the stretch. - 7 pages × 2 locales. Five runtime dependencies. 120 tests in 13 files plus a simulation of thousands of random games per ruleset. About 8,600 lines of TypeScript, 36 commits.
Screenshots
Hiring, or building something similar?
I'm open to full-time roles and select freelance work.