expanded basic game features

This commit is contained in:
2026-04-27 08:45:44 -07:00
parent 0217d6636f
commit 5ff452ba2c
21 changed files with 5001 additions and 172 deletions

13
docs/README.md Normal file
View File

@@ -0,0 +1,13 @@
# VCK Online Docs
This folder contains developer documentation for the VCK Online dev/test server and game engine.
## Start here
- `README_SERVER.md`: how to run the FastAPI server and use the dev HTML client
- `dev-setup.md`: local environment setup (venv + MariaDB connector)
- `database.md`: database expectations, SSH tunnel, and stored procedure setup
- `server.md`: FastAPI server architecture and API surface
- `game.md`: game engine model and the DB-backed game bootstrap flow
- `testing.md`: how to run the included test scripts

79
docs/README_SERVER.md Normal file
View File

@@ -0,0 +1,79 @@
# VCK Online FastAPI Server
Simple REST API server for developing and testing the Valeria Card Kingdoms Online game.
## Setup
1. Install dependencies:
```bash
pip install -r requirements.txt
```
2. Make sure database is accessible (SSH tunnel if needed):
```bash
ssh -L 3306:localhost:3306 lukesau.com
```
## Running the Server
```bash
python3 server.py
```
Or with uvicorn directly:
```bash
uvicorn server:app --host 0.0.0.0 --port 8000 --reload
```
The server will start on `http://localhost:8000`
## API Endpoints
### Lobby
- `POST /api/lobby/join` - Join lobby with name
```json
{"name": "Player Name"}
```
Returns: `{"player_id": "...", "message": "Joined lobby"}`
- `POST /api/lobby/ready` - Mark player as ready
```json
{"player_id": "..."}
```
Returns game info if all players ready
- `POST /api/lobby/unready` - Mark player as not ready
- `POST /api/lobby/leave?player_id=...` - Leave lobby
- `GET /api/lobby/status?player_id=...` - Get lobby status
### Game
- `GET /api/game/{game_id}/state` - Get current game state
- `POST /api/game/{game_id}/action` - Perform game action
```json
{
"player_id": "...",
"action_type": "hire_citizen|buy_domain|slay_monster|act_on_required_action|roll_phase|harvest_phase|play_turn",
"citizen_id": 123, // for hire_citizen
"domain_id": 456, // for buy_domain
"monster_id": 789, // for slay_monster
"gold_cost": 5, // for hire_citizen/buy_domain
"strength_cost": 3, // for slay_monster
"magic_cost": 0, // optional
"action": "choose 1" // for act_on_required_action
}
```
## Web Client
Visit `http://localhost:8000` for a simple HTML client to test the API.
## Development Notes
- Games are stored in-memory (will be lost on server restart)
- Inactive games are cleaned up after 3 minutes of no activity
- Inactive lobby players are removed after 60 seconds
- This is a development/testing server, not production-ready

64
docs/database.md Normal file
View File

@@ -0,0 +1,64 @@
# Database
## Overview
The game bootstrap in `game.py` loads card data from a MariaDB database named `vckonline` using stored procedures to select card sets and randomize stacks.
The code assumes it can connect to:
- host: `127.0.0.1`
- port: `3306`
- user: `vckonline`
- password: `vckonline`
- database: `vckonline`
This is designed to work with an SSH tunnel that forwards the remote DB to local port 3306.
## SSH tunnel
Keep an SSH port forward running while using the DB locally:
```bash
ssh -L 3306:localhost:3306 lukesau.com
```
## Stored procedures
The server/game code expects these procedures to exist:
- `select_base1_monsters()`
- `select_base1_citizens()`
- `select_base2_monsters()`
- `select_base2_citizens()`
- `select_random_domains()`
- `select_random_dukes()`
To install all procedures:
```bash
./sql/run_sql.sh sql/create_all_stored_procedures.sql
```
See `sql/INSTALL_PROCEDURES.md` for additional options (mysql client, interactive MariaDB session, installing individually).
## User / grants setup
If you have authentication or permissions problems, use:
- `sql/USER_SETUP_GUIDE.md`: investigation and fix commands (create users for `localhost`, `127.0.0.1`, `%`, and grant privileges)
- `sql/fix_user_setup.sql`: a convenience SQL script in this repo (if you prefer to run a script vs copy/paste commands)
## Verifying the DB
Quick port-level check (no Python deps):
```bash
python3 check_db_server.py
```
Full end-to-end check (Python + DB + tables + stored procs):
```bash
python3 test_database.py
```

33
docs/dev-setup.md Normal file
View File

@@ -0,0 +1,33 @@
# Dev setup
## Python environment
This repo expects a Python venv in `.venv/`.
The easiest path on macOS is to use the helper script:
```bash
./setup_venv.sh
```
That script:
- Creates (or reuses) `.venv/`
- Tries to locate `mariadb_config` under `/opt/homebrew`
- Installs `mariadb-connector-c` via Homebrew if needed
- Exports `MARIADB_CONFIG` and runs `pip install -r requirements.txt`
If you already have `.venv/` created and just want to activate with the MariaDB environment variable:
```bash
source ./activate_with_env.sh
```
## Requirements
Dependencies are listed in `requirements.txt` and include:
- `fastapi` + `uvicorn` (API server)
- `mariadb` (DB access; requires MariaDB Connector/C to build)
- `shortuuid` (player id generation)

121
docs/game.md Normal file
View File

@@ -0,0 +1,121 @@
# Game engine (`game.py`)
## Key types
`game.py` defines the core runtime objects used by the server:
- `Game`: contains the current game board state and implements actions/phases
- `Player`: per-player scores and owned cards
- `LobbyMember` / `GameMember`: lightweight records used by the server for lobby/game membership
It also provides:
- `load_game_data(...)`: builds an initial `game_state` dict by pulling data from MariaDB and dealing stacks
- `SummaryEncoder` and `GameObjectEncoder`: JSON encoders used by the server to serialize game state
## Game lifecycle
At runtime (via the server):
- `server.py` calls `load_game_data(game_id, preset, game_gamers)` to create a starting `game_state`
- The server wraps that dict in a `Game` object: `Game(game_state)`
- The server exposes the game via `/api/game/{game_id}/state` and `/api/game/{game_id}/action`
The `Game` object tracks:
- `player_list`
- `monster_grid`, `citizen_grid`, `domain_grid`
- dice: `die_one`, `die_two`, `die_sum`
- `effects`
- `action_required` (used to block on per-player, sequential “choose …” actions)
- `concurrent_action` (used to block on non-ordered, multi-player prompts; see “Concurrent actions” below)
- `last_active_time` (used by the server for cleanup)
## DB-backed bootstrap (`load_game_data`)
`load_game_data` is responsible for:
- Fetching cards using stored procedures:
- citizens/monsters depend on the `preset` (e.g. `"base1"` / `"base2"`)
- domains and dukes are randomized via procedures
- starters are selected via a direct `SELECT * FROM starters`
- Creating `Player` instances from the lobby/game membership list
- Randomizing player order and dealing initial cards
- Dealing stacks onto the board:
- monsters grouped by area, then 5 areas selected
- citizens grouped by roll match, placed into 10 stacks (special-cased for roll 11)
- domains dealt into 5 stacks of 3, with the top visible/accessible
This function currently assumes local DB connectivity via `127.0.0.1:3306` (typically via SSH port forward).
See `docs/database.md` for DB setup and stored procedure installation.
## Actions & phases
The server routes map `action_type` strings to methods on `Game`.
Common paths:
- `roll_phase()` rolls two dice and computes a sum
- `harvest_phase()` pays out from owned starters/citizens for all players based on the roll
- `hire_citizen(...)`, `buy_domain(...)`, `slay_monster(...)` mutate board stacks and player resources
### “choose …” actions
Some special payouts set `action_required` and start a background thread that waits until `act_on_required_action` updates `action_required` with a choice.
This is a dev-oriented approach; it allows the REST API to supply a follow-up choice via `act_on_required_action` while the game engine waits.
## Concurrent actions (non-ordered prompts)
Some prompts are not turn-based: every participating player should be able to
respond at the same time, in any order, and the game must wait until **all**
of them have submitted before progressing. The starting duke selection is the
first example — every player simultaneously discards down to one of the dukes
they were dealt.
These are modeled with `Game.concurrent_action`, which is independent from
`action_required`:
```
concurrent_action = {
"kind": "choose_duke", # routes to a handler in CONCURRENT_HANDLERS
"pending": ["pid1", "pid3"],
"completed": ["pid2"],
"responses": { "pid2": <opaque payload> },
"data": { ... } # handler-specific extras (often empty)
}
```
Engine semantics:
- While `concurrent_action.pending` is non-empty, `advance_tick()` returns
`False`. No phase transitions happen, no harvest progresses, and no
per-player turn actions are accepted. `is_blocked_on_concurrent_action()`
exposes the same predicate.
- Players submit via `Game.submit_concurrent_action(player_id, response, kind=...)`.
The handler's `apply()` validates and applies that player's response
immediately (so per-player effects don't have to wait for the others).
- When the last pending player submits, the handler's `finalize()` runs
(for any cross-player resolution), `concurrent_action` is cleared, and
if the engine was sitting in setup it advances forward.
### Adding a new concurrent action kind
1. Implement a handler class with:
- `apply(self, game, player_id, response)` — validate + apply per-player
side effects. Raise `ValueError` to reject a submission (the player
stays in `pending`).
- `finalize(self, game)` — optional; runs once after every participant
has submitted.
2. Register it in `CONCURRENT_HANDLERS` keyed by `kind`.
3. Build the prompt with `_new_concurrent_action(kind, participant_ids, data=...)`
and assign it to `game.concurrent_action` at the point in the engine
where the gate should appear.
4. On the client, register a renderer in the `CONCURRENT_RENDERERS` map in
the dev client (server.py HTML) keyed on the same `kind`.
Because the engine itself only knows "block while pending is non-empty",
the concurrent gate is fully reusable — no engine changes are required to
add new kinds (mulligan, simultaneous discard, voting, etc.).

88
docs/server.md Normal file
View File

@@ -0,0 +1,88 @@
# Server (`server.py`)
## What it is
`server.py` is a FastAPI development server that:
- Maintains an in-memory lobby (`lobby`)
- Starts games when all lobby players are ready
- Stores active games in-memory (`games`)
- Exposes REST endpoints for lobby operations and game actions
- Serves a simple HTML test client at `/`
This server is intended for development/testing, not production.
## In-memory state
The server keeps three top-level collections:
- `lobby`: list of `LobbyMember` (players waiting to start a game)
- `games`: dict of `game_id -> Game`
- `gamers`: list of `GameMember` (player_id/name/game_id records for in-game players)
There is no persistence; restarting the server resets everything.
## Lobby flow
High-level flow:
- `POST /api/lobby/join`: creates a `LobbyMember` with a `shortuuid` `player_id`
- `POST /api/lobby/ready`: marks the player ready; when all lobby players are ready (and there are at least 2), it starts a game:
- generates a new `game_id` (uuid4)
- moves ready lobby members into `gamers` for that `game_id`
- calls `load_game_data(game_id, "base1", game_gamers)` and constructs `Game(game_state)`
- `GET /api/lobby/status`: returns lobby members + whether the requesting player is already in a game
Lobby cleanup:
- `GET /api/lobby/status` prunes lobby members inactive for > 60 seconds
## Game API
- `GET /api/game/{game_id}/state`: returns the current game state encoded using `GameObjectEncoder` (from `game.py`)
- `POST /api/game/{game_id}/action`: performs a game action and returns the updated game state
Supported `action_type` values currently include:
- `hire_citizen`
- `buy_domain`
- `slay_monster`
- `take_resource`
- `harvest_card`
- `act_on_required_action` (sequential, single-player follow-ups)
- `submit_concurrent_action` (non-ordered, multi-player prompts; see below)
- `roll_phase`
- `harvest_phase`
- `play_turn`
### `submit_concurrent_action`
Used to respond to a `concurrent_action` gate (see `docs/game.md`). The
serialized game state exposes a `concurrent_action` object with `kind`,
`pending`, and `completed` lists; while `pending` is non-empty no other
turn-based action will succeed. Request body:
```
{
"player_id": "<pid>",
"action_type": "submit_concurrent_action",
"kind": "choose_duke", // optional sanity check
"response": "<opaque string>" // handler-specific payload
}
```
The server validates that the player is in `pending` and that `kind`
(if provided) matches the active gate. Players may submit in any order;
when the last pending player submits, the engine auto-advances out of
the setup gate.
## Game cleanup
On startup, a background task deletes games inactive for > 180 seconds and prunes the corresponding `gamers` entries.
## Dev HTML client
The root route `/` serves a simple HTML page that calls the lobby endpoints and can fetch a game state.
For run instructions, see `docs/README_SERVER.md`.

31
docs/testing.md Normal file
View File

@@ -0,0 +1,31 @@
# Testing & diagnostics
## Database connectivity
### Port-level check
`check_db_server.py` checks whether `127.0.0.1:3306` is reachable (useful to verify your SSH tunnel).
```bash
python3 check_db_server.py
```
### End-to-end DB validation
`test_database.py` does a more complete validation:
- imports `mariadb`
- connects to the DB
- checks required tables exist
- prints row counts and card contents (citizens/monsters/domains/dukes/starters)
- checks required stored procedures exist
- calls stored procedures and prints returned rows
```bash
python3 test_database.py
```
## API server smoke test
See `docs/README_SERVER.md` to run the FastAPI server and use the built-in HTML client at `/`.