Skip to content
AFM Studio
  • Pages

    AFM Studio
  • Work
  • Lab
  • About
  • CV
  • Contact
  • Projects

    AksaraA searchable library for Otorita IKN's knowledge, where the server checks every reader's access across three levels
  • Daily TaqwaTamper-resistant prayer attendance system for ~2,000 OIKN government employees at Masjid Negara IKN
  • Lantara v2A permit engine for Otorita IKN where administrators add new permit types from a screen, without a developer
  • eKiosk IKNTwo control rooms for Nusantara: kiosk content management and live bus tracking with arrival times that grade themselves
  • E-Monev IKNA monitoring platform for Nusantara's 23-year build, tracking every unit's quarterly progress against 24 national KPIs
  • JDIH OIKNOIKN's public legal document portal, with a controlled publishing workflow and AI summaries that never leave the server
  • Jejak PenanamanA check-in app for IKN tree-planting events that proves each participant was on site with a geofenced selfie
  • Dashboard SAKTIA nightly pipeline and dashboard that replaces OIKN's manual budget downloads for all six of its budget units
  • DoseRxA bedside dose calculator for 127 drugs that rounds each dose to tablets and syrups Indonesian pharmacies sell
  • CubiqA speedcubing trainer with competition-grade timing and a solver I wrote for each of the 8 major puzzles
  • ScimotionAn interactive science library of 86 articles, each explained next to animations the reader can control
  • WA Daily Scrum BotA WhatsApp bot that chases missing standup reports for a 14-person team and flags tasks stuck for days

↑ ↓ to move · Enter to open · Esc to close

Congklak

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

Screenshots
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.ts sums 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 no if (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, extraTurn and end events; the animation replays them. prefers-reduced-motion resolves 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-auto on 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

Papan Permainan
Belajar
Aturan
Bandingkan Aturan
Tanding
Putar Ulang

Hiring, or building something similar?

I'm open to full-time roles and select freelance work.