Skip to content

Repository files navigation

Card Game Engine

A customizable card game engine driven by declarative JSON rulesets, with multi-device gameplay over local WiFi.

Card Game Engine lets you define the rules of any card game — blackjack, poker, and more — in a .cardgame.json file. The engine loads the ruleset at runtime, manages game state through a phase-based finite state machine, and enforces all rules without a single line of game-specific code. An Android TV acts as the shared display and game host, while players connect their phones as controllers by scanning a QR code.

Architecture

   +------------------+        CouchKit (WiFi)        +------------------+
   |   Android TV     | <---------------------------> |   Phone (1..n)   |
   |   (Host)         |                               |   (Client)       |
   |                  |   game state, player views     |                  |
   |  Expo + RN-tvOS  | ----------------------------> |  Vite + React    |
   |  expo-file-system |                               |                  |
   |                  |   player actions               |                  |
   |  Game Engine     | <---------------------------- |  Controller UI   |
   +------------------+                               +------------------+
          |
          v
   .cardgame.json ruleset

The TV runs the authoritative game engine: it loads the ruleset, advances the FSM, evaluates expressions, and filters state per player. Phones receive only their own view of the game and send actions back to the host. All networking is handled by CouchKit over local WiFi with no internet required.

Features

  • Declarative JSON rulesets — define game logic without writing code
  • Safe expression language — conditions and effects use a constrained (non-Turing-complete) evaluator with if() conditional branching and while() loops
  • 41 query builtins + 23 effect builtins — covering common card game mechanics (draw, discard, shuffle, score, card matching, pattern matching, turn order, trick-taking, string variables, etc.)
  • Phase-based FSM — supports automatic, player_action, and simultaneous phase types
  • Turn order mechanics — clockwise/counterclockwise direction, reverse, skip, and set-next-player effects
  • Seeded PRNG — mulberry32 with crypto.getRandomValues seed hardening enables deterministic replay from an action log
  • Hidden information — per-player state filtering via createPlayerView with per-variable public visibility
  • Security hardening — internal actions (advance_phase, reset_round) blocked from client submissions, action log capped at 500 entries
  • Zod schema validation — rulesets are validated against a strict schema at load time
  • 2 deck presets + custom decksstandard52, standard54, plus fully custom card lists

Project Structure

card-game-engine/
├── packages/
│   ├── shared/        @card-engine/shared   — game engine core (types, expression
│   │                                          evaluator, interpreter, PRNG)
│   ├── schema/        @card-engine/schema   — JSON Schema, Zod validation, types
│   │                                          (card, ruleset, state)
│   ├── host/          @card-engine/host     — Android TV app (Expo + CouchKit host
│   │                                          + expo-file-system storage)
│   └── client/        @card-engine/client   — phone controller (Vite + React +
│                                              CouchKit client)
├── rulesets/          .cardgame.json rule files
├── package.json       Bun monorepo root (workspaces)
└── tsconfig.json      composite TS project references
Package Runtime Key Dependencies
schema Pure TypeScript Zod
shared Pure TypeScript, zero framework deps @card-engine/schema, Zod
host Expo + React Native CouchKit host, expo-file-system
client Vite + React 18 CouchKit client

Getting Started

Prerequisites

  • Bun >= 1.2.19
  • Android TV device or emulator (for running the host)
  • Modern browser (for client development)

Install

git clone https://github.com/faluciano/card-game-engine.git
cd card-game-engine
bun install

Development

Start the client dev server (hot-reloading web app):

bun run dev:client

Testing

Tests live in the shared, schema, and host packages and use Vitest:

cd packages/shared
bunx vitest run

883 tests across the shared (770), schema (19), and host (94) packages cover the engine core (expression evaluator, builtins, interpreter, PRNG, schema validation, player views, game phases, integration scenarios), schema meta fields, and the host package (storage, importers). The client package is verified via tsc type-checking and Vite production build.

Build and Deploy

Build the client, bundle it for CouchKit, and run on Android TV:

bun run build:android

This is a shorthand that bundles the client assets and launches the Expo Android build. You can also run the steps individually:

bun run build:client       # TypeScript check + Vite production build
bun run bundle:client      # Bundle client dist into the host's Android assets

Type-check the shared and client packages:

bun run typecheck

Scripts

Command Description
bun run dev:client Start the client Vite dev server with HMR
bun run build:client TypeScript check + Vite production build
bun run bundle:client Bundle client dist into host's Android assets
bun run build:android Bundle client + Expo Android build
bun run typecheck Type-check shared and client packages
bun run validate Validate all rulesets against the JSON Schema
bun run catalog Generate catalog.json from all rulesets' metadata

Rulesets

A .cardgame.json file declaratively defines everything the engine needs to run a card game: metadata, deck composition, zones, roles, phases (FSM), scoring, visibility rules, and UI hints.

The rulesets/ directory contains example rulesets:

  • blackjack.cardgame.json — the reference implementation demonstrating dealer AI, hand value scoring, and partial visibility
  • crazy-eights.cardgame.json — a matching/shedding game demonstrating wild 8s (suit choosing), per-card play validation, string variables, draw pile reshuffle, and if() conditional branching

Rulesets support optional catalog fields (description, tags, license) in their meta block. Run bun run catalog to generate a catalog.json index of all rulesets for browsing and discovery. Run bun run validate to validate all rulesets against the schema.

See the Ruleset Authoring Guide for the full format specification, expression language reference, and annotated examples. The Engine API Reference documents all public functions and builtins.

Project Status

All four implementation phases are complete with 883 passing tests across shared (770), schema (19), and host (94) packages.

Phase Status Tests
Phase 1 — Engine Core ✅ Complete 770
Phase 1.5 — Documentation ✅ Complete
Phase 2 — Storage & Import ✅ Complete 94
Phase 3 — Host Screens & CouchKit Integration ✅ Complete
Phase 3.4 — Schema Package & Catalog ✅ Complete 19
Phase 4 — Client Controller App ✅ Complete

The app builds and deploys to Android TV via bun run build:android. The host runs an HTTP+WebSocket server via CouchKit; phones connect by scanning a QR code displayed on the TV.

Cross-Network Play (Browser Display)

Besides the Android TV host, the whole game runs in a browser, so players on different networks can join without the native app. A display page (packages/display) owns the authoritative game — the same hostReducer the TV runs — and reaches phones through a small, game-agnostic relay.

graph LR
  subgraph PHONES["📱 Phones"]
    P1["Player 1"]
    P2["Player 2"]
  end

  RELAY["🔀 Relay<br/>(one room each)"]
  DISPLAY["🖥️ Display<br/>(owns the game)"]

  P1 & P2 -- "actions ➡" --> RELAY
  RELAY -- "➡ by room" --> DISPLAY
  DISPLAY -- "⬅ state updates" --> RELAY
  RELAY -- "⬅ to the room" --> P1 & P2
Loading

Live:

Display (put this on the TV) https://card-game-display.pages.dev
Controller (phones) https://card-game-controller.pages.dev

Both deploy from main via Cloudflare Pages.

Joining

The display shows a room code and a QR linking to <controller-url>?room=CODE. Opening the controller without a code shows a join screen where it can be typed; codes are case-insensitive, and a wrong or expired one says so instead of hanging. The display keeps every feature of the TV version — ruleset picker, store, lobby, and table.

Run it locally

export VITE_RELAY_URL="wss://couch-kit-relay.faluciano.workers.dev"
export VITE_CONTROLLER_URL="http://localhost:5173"   # display's join link

bun run dev:display   # room code + QR
bun run dev:client    # the controller

The relay URL is required config with no default in the SDK; to run your own, see services/relay-worker (Cloudflare) or services/relay (Bun) in the @couch-kit repo.

Known Issues

  • all_players_done sentinel always returns true — after any declare action the engine immediately advances through all automatic phases. Affects games where multiple players must each complete an action before the round advances.
  • JDK version after prebuildexpo prebuild --clean regenerates gradle.properties, removing the org.gradle.java.home override. Must re-add JDK 17 path and local.properties with sdk.dir after each prebuild.

License

MIT

About

A customizable card game engine driven by declarative JSON rulesets, with multi-device gameplay over local WiFi

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages