How tobymao/18xx works
Purpose and Entry Points
18xx.games is a Ruby application for playing many asynchronous 18xx board-game titles online. Rack starts the server through config.ru; HTTP/API wiring is rooted in api.rb, with game and user routes under routes/game.rb and routes/user.rb. The client application is Ruby-rendered through assets/app/app.rb.
Application Boundary and Persistence
The web layer manages authentication, users, sessions, game creation, and game lifecycle separately from deterministic game rules. Database access/models are in db.rb, models/game.rb, and models/action.rb; migrations define the PostgreSQL schema in migrate/0001_initial.rb. Persisted games retain action history, enabling reconstruction, undo/redo, and replay rather than treating the engine as a transient request-only state.
Game Management and Action Flow
assets/app/game_manager.rb is the client-side coordinator for loading games and submitting interactions. Server-side game routes receive actions, while lib/engine/action.rb and concrete action classes such as lib/engine/action/lay_tile.rb represent serializable player commands. The engine validates and applies actions against the current round/step, updates state and logs, then the persisted state/action stream is returned to clients.
Shared Rules Engine
lib/engine.rb is the core domain entry point. lib/engine/game/base.rb supplies common game state and lifecycle behavior; domain objects such as lib/engine/corporation.rb and lib/engine/tile.rb model economic and map state. Generic round orchestration (lib/engine/round/operating.rb) delegates legal decisions and mutations to reusable steps (lib/engine/step/track.rb, lib/engine/step/buy_train.rb). This is the primary boundary: UI and database code should not encode rules.
Title Implementations
Each title is a specialization of the shared engine, normally defining metadata, entities, map, and a game class under lib/engine/game/<title>/. For example, lib/engine/game/g_1846/game.rb, lib/engine/game/g_1846/entities.rb, and lib/engine/game/g_1846/map.rb. Title-specific steps override or extend shared mechanics, e.g. lib/engine/game/g_1817/step/loan.rb. The loader/registry boundary is represented by lib/engine/game.rb and assets/app/game_class_loader.rb.
Client Rendering
The frontend is a Ruby component tree, not a separate JavaScript SPA. assets/app/view/game.rb composes game screens; assets/app/view/game/game_data.rb bridges engine state into renderable data; map visuals are assembled in assets/app/view/game/map.rb and tile-part components such as assets/app/view/game/part/track.rb. User gestures select legal engine actions; rendering reflects the authoritative server game state.
Runtime and Infrastructure
The system is containerized with application, PostgreSQL, Redis, and nginx components: Dockerfile, db/Dockerfile, redis/Dockerfile, and nginx/nginx.conf; Compose definitions include docker-compose.prod.yml. Redis-backed messaging/queue concerns are indicated by lib/bus.rb and queue.rb. Unicorn configuration is at config/unicorn.rb; New Relic configuration exists at config/newrelic.yml. Mail flows for verification, resets, and turn notifications live in assets/app/mail/.
Main entry points: game.rb Game routes · operating.rb Operating round · db.rb Game persistence · docker-compose.prod.yml Production compose
Components 23
Ruby Client
Ruby-rendered interactive game UI.
- Client app
assets/app/app.rb - Game manager · Loads games and submits player interactions.
assets/app/game_manager.rb - Game screen
assets/app/view/game.rb - Game data bridge · Transforms engine state into renderable data.
assets/app/view/game/game_data.rb - Map rendering
assets/app/view/game/map.rb
Web Application
HTTP, authentication, and game lifecycle boundary.
- Rack entry
config.ru - API root
api.rb - Game routes · Receives game lifecycle and action requests.
routes/game.rb - User routes
routes/user.rb - Mail flows · Verification, reset, and turn notifications.
Rules Engine
Deterministic shared rules and title implementations.
- Engine entry
lib/engine.rb - Title registry · Resolves supported game title implementations.
lib/engine/game.rb - Base game · Common game lifecycle and state behavior.
lib/engine/game/base.rb - Action model · Player commands are validated and replayable.
lib/engine/action.rb - Operating round
lib/engine/round/operating.rb - Shared steps · Reusable legal-decision and mutation rules.
lib/engine/step/track.rb - 1846 title · Defines title-specific game, entities, and map.
lib/engine/game/g_1846/game.rb - 1817 loan step
lib/engine/game/g_1817/step/loan.rb
Persistence & Async
Durable game history and background messaging.
- Game persistence · Stores users, games, and action history for replay and undo.
db.rb - Game record
models/game.rb - Action history
models/action.rb - Bus and queue · Supports asynchronous messaging and queued work.
lib/bus.rb
Deployment
Containerized production runtime.
- Production compose · Runs app, PostgreSQL, Redis, and nginx.
docker-compose.prod.yml
Connections 27
- Rack entry → API root: boots
- API root → Game routes: mounts
- API root → User routes: mounts
- Client app → Game manager: coordinates
- Game manager → Game routes: loads and submits actions
- Game manager → Game screen: drives
- Game screen → Game data bridge: renders from
- Game screen → Map rendering: composes
- Game routes → Action model: deserializes
- Game routes → Engine entry: executes rules
- Engine entry → Title registry: loads title
- Title registry → 1846 title: selects
- 1846 title → Base game: specializes
- Engine entry → Operating round: advances rounds
- Operating round → Shared steps: delegates to
- Operating round → 1817 loan step: uses title override
- Action model → Operating round: validated and applied
- Game routes → Game record: persists game state
- Game routes → Action history: appends commands
- Game record → Game persistence: stored in
- Action history → Game persistence: stored in
- User routes → Game persistence: manages users and sessions
- Game routes → Bus and queue: publishes async work
- Bus and queue → Mail flows: turn notifications
- Production compose → Rack entry: runs application
- Production compose → Game persistence: runs PostgreSQL
- Production compose → Bus and queue: runs Redis