Welcome to the Tarati Boardgame! It's been designed and copyrighted by George Spencer Brown, the author of the incredible "Laws of Form" which introduces the fundamental Calculus of Distinction. A mathematically complete corpus for notation and calculation with distinctions. If you like to learn more about Laws of Form, check out the following:
- video by Louis Kauffman
- Playlist of the 2019 LoF Conference
- LoF Mini Course by Leon Conrad
The Tarati game has a little bit of checkers and chess to its feel: two players with four pawns each start at opposite ends of a board. These are the domestic positions labeled D.
Paws can only move forward, not sideways. When they land on a position with opposite colors next to them, they get hit, and their colors invert.
Landing or hitting a pawn on the opponent's domestic location, upgrades our pawn, allowing it to move in any direction, once. An upgraded pawn is marked. Once a pawn is upgraded, it remains upgraded, even when hit.
The game is over when a player can't move a pawn, or has no more pawns to move. The other player becomes the winner in this case.
The game has an interesting structure, with overlaps to the ideas of concept structures in terms of their importance. Below a brief description of the game board with alchemical correspondences between brackets:
- We have 4 Pawns for each player (four elements), and maximally eight (trigrams) at the same time on a board.
- We have 12 Circumference positions labeled as "C", these correspond to the 12 zodiac or 12 months of the year
- We have 6 Boundary positions around the center, these correspond with the 6 hermetic planetary concepts.
- We have 1 Absolute Middle position, labeled as "A", which corresponds to the Sun, or Tipareth.
Tarati uses a bounded minimax + alpha-beta pruning engine in both:
src/AI.js(React runtime AI)strategy/engine/ai.py(Python simulation AI)
Main ideas:
- Depth-limited search controls strategic horizon per difficulty.
- Move ordering pushes promising lines earlier so alpha-beta can prune more.
- Transposition table (TT) avoids re-solving repeated board states in one search.
- Static evaluation scores:
- piece count
- upgraded pieces
- terminal states with a large winning score (
WINNING_SCORE).
Hard and Champion can branch aggressively, so the engine now supports budgeted search:
maxMs/max_ms: time budget per AI move.maxNodes/max_nodes: node expansion budget.rootProbeNodes/root_probe_nodes: root round-robin probing budget.stochasticTopK/stochastic_top_k: weighted random pick among top lines for variety.
Root round-robin probing means the AI does not over-invest in a single first move early.
It samples each root candidate in slices, then deepens while budget remains. This gives an "anytime" behavior: return the best discovered move even under tight limits.
For the full technical breakdown, see: strategy/AI_ENGINE.md
The strategy/ folder is the analysis and simulation workspace for AI-vs-AI experiments.
strategy/01_simulate_games.ipynb- Generates AI-vs-AI games in parallel.
- Writes full game + move history into SQLite (
strategy/data/games.sqlite). - Supports per-side AI config:
AI_SEARCH_AAI_SEARCH_B
- These map directly to the engine search options:
max_ms,max_nodes,root_probe_nodes,stochastic_top_k.
strategy/02_analyse_games.ipynb- Reads simulation outputs and computes summary statistics.
strategy/03_opener_statistics.ipynb- Focuses on opening move behavior and conversion rates.
strategy/engine/runner.py: importable game runner for multiprocessing-safe execution.strategy/engine/board.py: board topology + move application.strategy/engine/ai.py: search + evaluation.strategy/engine/test_engine.py: validation tests for rules and engine behavior.
- Install Python dependencies:
pip install -r strategy/requirements.txt- Open and run:
strategy/01_simulate_games.ipynb
- Tune before large runs:
N_GAMES,DEPTH_A,DEPTH_B,MAX_MOVES,NUM_WORKERSAI_SEARCH_A/AI_SEARCH_Bbudgets (max_msis the most important for responsiveness)
- Analyse:
- run
strategy/02_analyse_games.ipynb - run
strategy/03_opener_statistics.ipynb
The app follows a component-based architecture using React hooks. Main components:
- App (root)
- Board
- Vertex (board positions)
- DraggableChecker (game pieces)
- Sidebar (game controls)
- TurnIndicator
- Board
Key features:
- Drag-and-drop functionality using
@dnd-kit/core(mobile and desktop) - SVG-based board rendering
- Responsive design with
react-responsive - Custom hooks for board size and turn indication
Technical aspects:
- React hooks (useState, useEffect, useRef)
- Context API for game state management
- CSS-in-JS for styling (react-spring for animations)
- Custom SVG rendering for game board and pieces
The app structure separates game logic (AI.js, GameBoard.js) from UI components, allowing for easy maintenance and potential future enhancements.
First npm install
npm install
Then run the app:
npm start
The last command will make the app available on localhost:3000.
Tarati is deployed for free on GitHub pages. Publish by configuring gh-pages correctly.
Install gh-pages:
npm install gh-pages --save-dev
Add the following to package.json
"scripts": { // <-- in scripts in package.json
"predeploy": "npm run build", // <== add this
"deploy": "gh-pages -d build", // <== add this
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},Also add the final github url of your repo to the package.json file:
{
"name": "tarati-react",
"version": "0.1.0",
"author": "adamblvck",
"homepage": "https://adamblvck.github.io/tarati-react",
"private": false,
"dependencies": {
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/utilities": "^3.2.2",
...
}
,
...
}
Then run the deploy command:npm run deploy
## Board motion
Pieces slide between points and a captured piece turns over. Both are CSS, and
both are driven from one derived fact: **what move just happened**.
Nothing tells the board that. Locally that is an inconvenience; online it is the
whole problem, because the opponent's move arrives on a 2.5s poll as a fresh
`boardState` with no record of what was played. It does not need telling —
`applyMoveToBoard` moves exactly one piece and every rule after that mutates
`color` or `isUpgraded` in place, so exactly one point loses its occupant and
one gains it. `src/helpers/moveDiff.js` recovers the move from that, and its
test replays 300 random games to prove it never guesses wrong.
`diffMove` returns `null` for anything that is not a single ply — an undo, a
replay scrubbed to the end, a poll that caught up several moves — and then the
board snaps, which is the honest thing to do when there is no one path to show.
Three details are load-bearing:
- **The slide is a pure translate**, which is the only reason it can be a plain
CSS transform: a translate has no transform origin. The turn *does* scale, so
`.piece-turn` declares `transform-box: fill-box` for itself — without it,
`transform-origin: center` resolves against the viewBox and flings the disc
off the board. `transform-box` is set nowhere else in this codebase.
- **The entry offset is released from a `useEffect`, never from
`requestAnimationFrame`.** rAF stops dead in a hidden tab, and a move arriving
in a background tab is exactly what happens while you wait for an opponent —
the piece would sit a full pathway from its point until you came back.
- **`delay()` before the engine thinks is 780ms, not 100ms.**
`AI.getNextBestMove` runs synchronously on the main thread for up to the
tier's `maxMs` (two seconds on Champion), so anything still animating when it
starts is frozen until it returns. Timings live in `src/config/motionConfig.js`
so the board, the engine's pause and the result reveal cannot drift apart.
The game-over modal waits `RESULT_REVEAL_MS` for the final move to play out, in
both the local and the online game. A win is very often a total conversion — the
most worth watching move in the game — and it used to be covered by a blurred
overlay in the frame it landed.
`src/components/__tests__/BoardMotion.test.js` covers the state the board hands
the browser, including that nothing is ever left stranded between points.
## Board geometry
The patent says "all lines are of equal length — stopping points are equally
spaced from all adjacent stopping points". The layout used to miss that by
12.9%: the circumference radius carried `- PI/12 + PI/2`, a copy-paste of the
*angle* expression into a *radius*, and the domestic points sat at exactly
`3 * vWidth`, leaving the four D–C pathways 13% long.
Both are fixed in `src/helpers/position.js` — the single source for `Board`,
`MiniBoard` and `SpectatePage` — and mirrored in `strategy/guide/figures.py`,
which reproduces the same formula for the print booklet.
`src/helpers/__tests__/position.test.js` asserts all 42 pathways are now equal.
The web board and the native apps in `../TaratiApple` now render identically.
