LAN-Games

LAN-Games

Docker app from Cdybkr's Repository

Overview

Self-hosted LAN board game platform: eight turn-based classics — Monopoly, Risk, Battleship, Yahtzee, Life, Checkers, Connect Four, and Tic-Tac-Toe — playable in any browser on your network. Real-time multiplayer via Socket.IO, with lobbies, hidden-information support (Battleship/Risk), and spectator mode. Unlike most party game servers it has real user accounts: JWT-based auth with bcrypt-hashed passwords, and game state persists in a SQLite database (mount the data path so it survives updates). Designed for trusted local networks — don't expose it to the internet. Turnkey setup: the login-token secret (JWT) is auto-generated on first boot and persisted in the data directory; optionally override it under advanced settings.

LAN Games

A self-hosted multiplayer game platform for LAN parties. Eight classic games with real-time play, hidden information, and spectator support — all running on a server you control.

LAN Games — self-hosted multiplayer board games for your LAN party

CI License: MIT Node Code style: Prettier PRs welcome

8 games · 660+ tests · single-server setup · Node.js 18+


Why This Exists

LAN parties want games everyone in the room can play together. Most multiplayer games today require internet, accounts, and matchmaking — LAN Games is the opposite. One Node.js server on your network hosts eight turn-based board games in the browser. No accounts beyond a username, no internet after install, no configuration required. The platform is game-agnostic: each game is a server module behind a small interface, so adding a ninth game doesn't touch the framework. Hidden-information games like Battleship and Risk coexist with open-board games through a per-game state-filtering contract, and spectators can watch any game with full visibility.


Quickstart

  1. Clone and install:

    git clone https://github.com/kbennett2000/lan-games.git
    cd lan-games/server
    npm install
    
  2. Start the server:

    # Generate a random secret (once) and start:
    export JWT_SECRET=$(node -e "process.stdout.write(require('crypto').randomBytes(48).toString('hex'))")
    npm start
    
  3. Play. Open http://localhost:3000 in your browser. Create a game, share the URL with friends on your network, and play.

No database setup, no external services, no configuration files. Docker is also supported — see the detailed Quick Start below.

Windows note: The root package.json scripts use bash syntax (. ./.env). Run from Git Bash, WSL, or use the cd server && npm start path directly with JWT_SECRET set as an environment variable. A .env.example is included for reference.


The Games

Monopoly
2–8 players · ~90 min
Monopoly
Risk
2–6 players · ~120 min
Risk
The Game of Life
2–6 players · ~45 min
Life
Battleship
2 players · ~15 min
Battleship
Yahtzee
1–8 players · ~20 min
Yahtzee
Checkers
2 players · ~15 min
Checkers
Connect Four
2 players · ~5 min
Connect Four
Tic-Tac-Toe
2 players · ~2 min
Tic-Tac-Toe

Every game supports save/resume, in-game chat, and spectator mode. Games with hidden information (Battleship, Risk, Life) enforce privacy server-side — each player sees only what they're allowed to.


Table of Contents

Why This Exists · Quickstart · The Games · Features · Quick Start

In this README: Project Structure · Architecture · Security Notes · Development · Documentation · Roadmap

Standalone docs: How to Play · Configuration · Adding a New Game · API Reference · Socket.io Events


Features

Framework

  • Multi-game — add any turn-based game by implementing one interface file; no framework changes required
  • Real-time multiplayer — all clients sync instantly via Socket.io WebSockets
  • Player accounts — register/login with username + password; JWT persisted in localStorage
  • Lobby — create, browse, and join open games; game-type badge shown on every card
  • Save & Resume — pause any in-progress game and continue it later from the lobby
  • Auto-reconnect — disconnected players are marked AFK; their turn is auto-skipped after 30 s
  • In-game chat — room-scoped, real-time, 300-character cap
  • Spectator mode — anyone logged in can watch any game in progress (👁 Spectate button on every in-progress lobby card). Spectators see the full unfiltered state (including hidden information for games like Battleship, Risk, and Life), chat with players, and watch the action log; they cannot take actions. Players are notified when spectators join. See the "Spectator mode" section below.
  • Configurable — every rule, price, and board value lives in JSON; hot-reload without a restart

Monopoly

  • Full rules: dice, doubles (3× → jail), property buying, auctions, rent, color-group monopoly detection, even-building rule, mortgage/unmortgage, jail (fine / doubles / card), Chance & Community Chest (full 16-card decks), trades (money + properties + jail cards), Income Tax (flat or 10% of net worth), bankruptcy with asset transfer
  • Visual CSS Grid board with color bands, player tokens, house/hotel indicators, and ownership dots

Connect Four

  • Standard 7 × 6 board; drop pieces by clicking column buttons
  • Win detection: horizontal, vertical, and both diagonals
  • Draw detection when the board is full

Risk

  • Classic 42-territory world map across 6 continents; 2–6 players
  • Three-phase turns: reinforce → attack → fortify
  • Auto-distributed initial setup; armies and territories dealt evenly to all players
  • Dice combat: attacker rolls up to 3, defender auto-rolls up to 2; ties go to defender
  • Continent bonuses (NA 5, SA 2, EU 5, AF 3, AS 7, AU 2) applied at the start of every reinforce phase
  • 44-card deck (42 territory + 2 wild); valid sets (3 of a kind, 3 different, or any 2 + wild) traded for escalating bonus armies (4, 6, 8, 10, 12, 15, then +5 each)
  • Player elimination transfers all cards to the conqueror; last player standing wins by world domination
  • First game with hidden information — each player's hand is private, enforced server-side by the game's getStateForPlayer filter

Tic-Tac-Toe

  • Classic 3 × 3 board; 2 players take turns marking cells with and
  • Win detection: rows, columns, both diagonals
  • Draw detection when the board fills

Yahtzee

  • Standard 5-dice, 13-category, three-rolls-per-turn rules; 1–8 players (solo play supported)
  • Up to 3 rolls per turn with arbitrary holds between rolls
  • All 13 categories (six upper + three-/four-of-a-kind, full house, two straights, Yahtzee, chance)
  • Upper-section bonus (+35 when subtotal ≥ 63); ties produce a shared-winner array
  • Two-click commit on category selection prevents accidental score-locking
  • First non-board game — UI is a shared score sheet (players as columns, categories as rows) plus a dice tray, all inside the renderer-owned board area

Battleship

  • Classic 2-player, 10×10 grid; five ships per side (Carrier 5, Battleship 4, Cruiser 3, Submarine 3, Destroyer 2)
  • Drag-and-drop ship placement with R-key rotation during drag
  • Two-grid firing layout — your fleet on the left, opponent's waters on the right; fixed positions across turns with active/inactive treatment that flips
  • Both fleets revealed at game over (winner and loser see the layout that beat them)
  • First game with a simultaneous private setup phase — both players place ships in parallel; the phase transitions to firing on a barrier (both Ready) rather than via a turn. Modelled as status='playing' + turnState.phase='setup' with currentPlayerIndex=null
  • First game with fully hidden state per player — opponents' ship positions are stripped by getStateForPlayer on every emit, not masked

Checkers

  • Classic American Checkers on an 8×8 board; 12 pieces per side, 2 players
  • Diagonal movement with mandatory captures and multi-jump chain captures
  • King coronation on reaching the back row (kings move forward and backward, one square at a time — not "flying" kings)
  • Stalemate-as-loss: player with no legal move loses
  • Click-driven move selection consuming server-supplied action descriptors — the renderer highlights only legal pieces and destinations
  • Animated piece movement, capture fading, chain-capture sequencing, and king coronation visual

The Game of Life

  • 64-square branching board with a start fork (Career vs College), main track, and two-path retirement choice (Countryside Acres vs Millionaire Estates)
  • 1–10 spinner (no dice) — CSS-animated wheel that decelerates and settles on the result
  • College path costs $40,000 in loans but unlocks degree-required careers; career path skips the loan but is locked out of degree careers
  • Marriage adds a spouse peg and collects $5,000 from each other player as wedding gifts
  • Children mechanic — single births (+$5k/player) and twins (+$10k/player); each child counts toward final scoring at $50k each
  • Insurance — auto and life, optional out-of-band purchases that nullify the matching accident squares
  • Stocks — pick a number 1–10; collect $10,000 whenever any player's spinner matches it (cross-turn payouts)
  • House purchase — pick from 2–3 offered house cards (cost vs scoring value); contributes to final score
  • Retirement is the strategic crux: Countryside Acres draws life tiles from a shrinking deck; Millionaire Estates is a cash gamble that resolves at game over
  • First game with a branching, non-grid board — squares are graph nodes with explicit next[] adjacency; the renderer places squares at hand-tuned grid coordinates with arrows showing direction
  • First game with deferred-resolution game over — ME retirees' win/loss is undetermined until the last player retires; documented in docs/renderer-contract.md
  • First game with a "retired-but-still-in-game" pattern — retired players stay in state.players and turn rotation skips past them; the game ends when every player is retired
  • Life tiles are hidden information per player (count visible to opponents, values masked until game over); revealed with a per-tile flip animation during the final score reveal

Quick Start

Prerequisites

  • Node.js 18+ (LTS recommended) — or Docker (see below)
  • C++ build toolchainbetter-sqlite3 and bcrypt are native modules compiled during npm install. If the build fails, install the toolchain for your OS:
    • Windows: npm install -g windows-build-tools or install the "Desktop development with C++" workload from Visual Studio Build Tools
    • macOS: xcode-select --install
    • Linux (Debian/Ubuntu): sudo apt install build-essential python3
    • Docker sidesteps this entirely — the node:20 (Debian) build stage ships the toolchain
  • No external database — SQLite is embedded via better-sqlite3

Install & run

cd server
npm install
npm start          # production
npm run dev        # development (nodemon auto-restart)

The server binds to 0.0.0.0:3000 by default — reachable on your entire LAN.

Docker

1. Create a .env file at the repo root containing your JWT secret (the server refuses to start without one):

# Generates a random secret and writes it to .env in one step
node -e "console.log('JWT_SECRET=' + require('crypto').randomBytes(48).toString('hex'))" > .env

2. Build and start:

docker compose up --build

The Dockerfile runs both the unit and integration test suites during the build — the image is only produced if all tests pass.

Open http://localhost:3000. The SQLite database is stored in a Docker named volume (db-data) and survives container restarts and image rebuilds.

To reset the database inside the container:

docker compose run --rm server node scripts/reset-db.js

Connect

Who URL
Host machine http://localhost:3000
LAN players http://<host-ip>:3000

Find <host-ip> with ip addr (Linux/macOS) or ipconfig (Windows).

Play

  1. Every player registers a username and password on their own browser.
  2. One player creates a game, picks a game type, and optionally customises rules.
  3. Other players join from the lobby.
  4. The host clicks Start Game.

Spectator mode

Anyone logged in can watch any in-progress game without playing.

  • In the lobby, every game in playing status has a 👁 Spectate button next to the Join/Rejoin button.
  • A spectator sees the full unfiltered state — including hidden information like Battleship ship positions, Risk card hands, and Life tiles. (The point is to enjoy watching strategy unfold; spectators are watching, not playing.)
  • Spectators can chat in the same room as players; their messages are prefixed with 👁 so players know which lines come from the gallery.
  • Spectators see the action log, the player roster, and any in-progress modals.
  • The action panel is hidden; the "You're spectating" banner persists at the top of the play area.
  • Players are notified when a spectator joins (log entry). Leaves are silent.
  • A spectator can leave any time via the ✕ Leave button (replacing the host's Save/Quit). Leaving has no effect on the game.

Spectators cannot take actions. The server enforces this (any game:action from a spectator is rejected with a game:error); the client gates click handlers as a UX layer so spectator clicks don't appear to do anything.

A user who is already a player in a game cannot also spectate it — the Spectate button is suppressed in that case, and the server rejects an explicit attempt with "You are already a player in this game".


Project Structure

The repository has three top-level directories: server/ (Node.js backend with game-logic modules under server/games/ and framework under server/src/), client/ (single-page application with per-game renderers under client/js/games/), and docs/ (design notes and reference documentation).

See docs/project-structure.md for the full annotated directory tree.


Architecture

The platform is a single Node.js server (Express + Socket.io) backed by embedded SQLite. Game logic lives in pure-function modules behind a small interface; the framework handles persistence, real-time sync, authentication, and the lobby. Each client renders its game type through a registry-dispatched renderer.

See docs/architecture.md for the component diagram, data-flow walkthrough, database schema, and common GameState shape.


Security Notes

LAN Games is designed for trusted local networks. Authentication uses JWT tokens (the server refuses to start without a JWT_SECRET) and bcrypt-hashed passwords. Every game action is validated server-side, and every state-bearing emission routes through getStateForPlayer to protect hidden information. A full state-emission audit documents the boundary.

The platform is offline by design — once installed, the server and client need zero internet connectivity. All assets are served locally, there are no CDN dependencies, no telemetry, and no outbound HTTP calls.

See docs/security.md for the complete security notes including JWT configuration, LAN binding warnings, and the offline-by-design inventory.


Development

Scripts

npm start              # run the server
npm run dev            # run with nodemon (auto-restart)
npm test               # unit tests (625 across 11 suites)
npm run test:integration  # integration tests (40 across 6 suites)
npm run lint           # ESLint
npm run format:check   # Prettier (verify)
npm run format:write   # Prettier (auto-fix)
npm run reset-db       # drop all tables and recreate schema
npm run reset-db:hard  # delete the .db file entirely
npm run screenshots    # regenerate README screenshots (requires Playwright)
npm run banner         # regenerate the README banner image (requires Playwright)

See docs/development.md for environment variables, port configuration, test details, hot-reloading, and the new-game checklist.


Documentation

Guides

  • How to Play — complete rules and controls for all eight games
  • Adding a New Game — interface contract, state versioning, renderer registration
  • Development — environment variables, scripts, testing, hot-reload, new-game checklist

Reference

Design Documents


Roadmap

  • More games — Chess, Scrabble, Catan, Coup, Liar's Dice, …
  • Action descriptor contract — implemented across five of the eight games (Battleship, Checkers, Risk, Yahtzee, and The Game of Life); the renderer-side rule mirrors are gone in all five. Connect Four and Tic-Tac-Toe deliberately skip the contract — their action surfaces (dropPiece, markCell) are trivial enough that getValidActions covers them. See docs/action-descriptors.md for the contract and the patterns the five migrations established.
  • Turn timer UI — server emits absolute-deadline warnings via game:turn_warning; the client-side countdown bar is implemented in client/js/turn-warning.js.
  • Spectator mode — shipped; see the Spectator mode section above
  • AI players — pluggable bot interface implementing the same applyAction contract
  • Custom board themes — CSS variable overrides per game type
  • Mobile optimisation — touch-friendly controls for handheld players
  • HTTPS / mDNS — easier LAN discovery and secure transport without manual IP lookup

Install LAN-Games on Unraid in a few clicks.

Find LAN-Games in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.

Open the Apps tab on your Unraid server Search Community Apps for LAN-Games Review the template variables and paths Click Install

Categories

Related apps

Explore more like this

Explore all

Details

Repository
ghcr.io/codyburker/lan-games:latest
Last Updated2026-08-30
First Seen2026-08-30

Runtime arguments

Web UI
http://[IP]:[PORT:3000]/
Network
bridge
Shell
sh
Privileged
false

Template configuration

WebUI PortPorttcp

Host port for the LAN Games web UI.

Target
3000
Default
8014
Value
8014
Game DataPathrw

SQLite database directory — user accounts and game state live here. The container runs as a non-root user; if startup logs show a permission error on this path, run: chmod 777 /mnt/user/appdata/lan-games

Target
/app/server/data
Default
/mnt/user/appdata/lan-games
Value
/mnt/user/appdata/lan-games
JWT Secret (optional)Variable

Secret used to sign login tokens. Leave EMPTY for turnkey setup: the container generates one on first boot and persists it in the Game Data directory (.jwt_secret), so logins survive restarts and updates. Set a value only to override with your own secret.

Target
JWT_SECRET
TZVariable

Container timezone.

Default
America/Indiana/Indianapolis
Value
America/Indiana/Indianapolis