expanded basic game features
This commit is contained in:
13
docs/README.md
Normal file
13
docs/README.md
Normal 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
79
docs/README_SERVER.md
Normal 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
64
docs/database.md
Normal 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
33
docs/dev-setup.md
Normal 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
121
docs/game.md
Normal 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
88
docs/server.md
Normal 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
31
docs/testing.md
Normal 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 `/`.
|
||||
|
||||
Reference in New Issue
Block a user