Skip to content
appthrustPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Reversi — AppThrust Play Lab

A phone-friendly Reversi/Othello game for the Tokyo Game Show booth. Two visitors join a queue, receive a private match actor, and play on a live 8 × 8 board. A TV wall shows matches created by this app process. The UI is English-led with concise Japanese subtitles; PostgreSQL is optional for local development and required on AppThrust. When DATABASE_URL is absent, the mock development store remains in memory while the actor still owns each match's game state.

Run locally

Use Node.js 24 and npm:

npm ci
ACTOR_BACKEND=mock npm run dev

Open http://localhost:3000. Use two separate browser profiles/private sessions to play both seats; two tabs in the same profile share a player cookie. /wall is the big-screen view. For another port:

ACTOR_BACKEND=mock npm run dev -- --port 3097

The mock is a development-only, in-memory implementation of the actor methods. It deliberately delays its first call by approximately 1.2 seconds so the waiting screen can be exercised. The UI labels mock timings as a local simulation. NODE_ENV=production rejects ACTOR_BACKEND=mock.

Environment

Copy .env.example to .env.local for local configuration. Supply production values as runtime environment variables/secrets, never build arguments or NEXT_PUBLIC_* values. No credentials are needed to build the app.

Variable Meaning
ACTOR_BACKEND platform-api (default), or mock in development only.
DATABASE_URL PostgreSQL connection URL. Optional locally (in-memory fallback); required on AppThrust.
PLATFORM_API_URL Platform API origin, for example https://dashboard.appthrust.dev. Do not append /api/v1.
APPTHRUST_PROJECT_ID Existing project containing the ActorType. Required for Platform mode.
APPTHRUST_ACTOR_TYPE_ID Existing ActorType serving the Reversi fixture; defaults to reversi.
KEYCLOAK_ISSUER Realm issuer, for example https://iam.appthrust.dev/realms/appthrust.
KEYCLOAK_CLIENT_ID Confidential service-account client with project actor write access.
KEYCLOAK_CLIENT_SECRET Client secret, supplied through the deployment's secret facility.
PORT HTTP listen port; defaults to 3000.

The same client-credentials JWT must be accepted by both Platform API and the actor gateway. This app mints and uses it only in server-only modules; credentials and upstream response bodies are never sent to the browser or logs.

How matches map to actors

Phone → Next.js route handler → Platform API → actor gateway → Reversi actor
  1. proxy.ts issues an httpOnly reversipid cookie on the first visit. It is SameSite=Lax and Secure on HTTPS, including TLS terminated by a trusted proxy. The ingress must preserve the public Host and set/overwrite X-Forwarded-Proto.
  2. The queue reserves two different players in PostgreSQL when DATABASE_URL is set (with an in-memory fallback otherwise). It allocates match-<n> from the database sequence.
  3. lib/appthrust.ts mints a Keycloak client_credentials token server-side, reusing it until 60 seconds before expiry. Concurrent token requests share one in-flight mint.
  4. Player one is seated by POST /api/v1/projects/{project}/actor-types/{type}/actors with {actorId, method:"reversi-join", body:{player,name}}. Player two joins via POST .../actors/{id}/invoke with {method:"reversi-join", body:{player,name}}. Both routes get an idempotency key.
  5. Platform API owns activation retry/backoff. The app waits for its bounded response and does not retry game mutations. The queue shows real elapsed time, then activation attempts and timings.
  6. Every placement and state read runs through the actor. Row 0 is the top row in JSON and UI. The initial four discs are [3][3]=2, [3][4]=1, [4][3]=1, [4][4]=2, and player 1 starts. The actor validates bracketing in all eight directions, flips every bracketed disc, passes automatically when the next player has no legal move, and ends when neither player can move or the board is full. The frontend does not optimistically decide a move or winner.

The actor card keeps creation/activation timings separate from the last call and last turn. The wall uses last-known snapshots and does not wake every actor just to render the TV.

Game HTTP routes

All routes are same-origin, JSON, and Cache-Control: no-store.

Route Request / response
POST /api/queue {name} (trimmed, 1–24 characters) → queue status.
GET /api/queue idle, waiting, spawning, matched, or error; includes elapsedMs and matchId/seat when reserved.
GET /api/match/{id} Invokes reversi-state; returns actor reply plus matchId, seat (0 for spectator), activation, creation, lastTurnMs, backend.
POST /api/match/{id}/place {row,column} → invokes reversi-place with the cookie's player ID, not an ID supplied by the browser.
POST /api/match/{id}/reset Invokes reversi-reset; keeps both players. Only seated players may mutate a match.
GET /api/wall {total, backend, matches}; each match contains state, activation, creation, lastTurnMs, and createdAt.

Actor ok:false maps to HTTP 409, preserving its error code and state. Game codes are bad_request, match_full, not_playing, unknown_player, not_your_turn, bad_cell, and illegal_move. Malformed/oversized app JSON is 400 (bad_request); an invalid name is 400 (bad_name). A different browser origin is 403 (cross_origin_request), and a match outside this process's directory is 404 (match_not_found). Backend failures use sanitized 502/503 codes rather than exposing credentials or raw upstream messages.

Booth deployment boundary

Build this repository as one AppThrust Application component using Dockerfile. It follows the sample deployment pattern: Node 24 slim dependency/build/runtime stages, npm ci, Next.js standalone output, copied public/static assets, and a non-root runtime user. The container starts node server.js on 0.0.0.0:3000.

Run one or more instances. Matchmaking and the TV index use PostgreSQL when configured; the database survives restarts and coordinates concurrent replicas. The actor owns game state and is not replaced by the database. Without DATABASE_URL, local mock development intentionally uses a process-local fallback.

Verification notes

The project keeps npm and package-lock.json canonical. The deployment workflow builds the Docker image for both amd64 and arm64. Use two cookie jars or separate browser profiles for a local mock smoke flow; remove any local cookie jars after use.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages