this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

wip

+221
+62
CLAUDE.md
··· 1 + # CLAUDE.md 2 + 3 + This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 + 5 + ## Project 6 + 7 + Turn-based management game with a card-based UI (Magic: The Gathering style), playable in a web browser. This is a learning project — the user is discovering game development, Rust, and Svelte simultaneously. Prioritize explanations and trade-offs over raw code output. 8 + 9 + ## Learning mode (important) 10 + 11 + This is a learning project. Do NOT write implementation code on the user's behalf when they could learn by writing it themselves. Instead, leverage the Learning output style to orient them: explain concepts, compare trade-offs, point at the relevant patterns, and use the "Learn by Doing" request format to hand off meaningful code decisions. Scaffolding, configuration, and boilerplate are fair game to write directly — but core logic (game engine, state transitions, business rules, component architecture) should come from the user, with Claude guiding. 12 + 13 + ## Architecture 14 + 15 + - **`backend/`** — Rust (Axum + SQLx + Tokio). Serves the game API and owns the game state machine. Game state is stored as JSONB in PostgreSQL. 16 + - **`frontend/`** — Svelte 5 + TypeScript SPA (Vite, no SvelteKit). Communicates with the backend via REST. 17 + - **`infra/`** — Kubernetes manifests (deployment target). Not used for local dev. 18 + - **`docker-compose.yml`** — Local dev: PostgreSQL 17 only. 19 + 20 + ## Development 21 + 22 + ### Prerequisites 23 + - Rust toolchain, Node.js, Docker 24 + - `cargo install sqlx-cli --no-default-features --features postgres` 25 + 26 + ### Start local environment 27 + ```bash 28 + docker compose up -d # PostgreSQL 29 + cd backend && cargo run # API on :3000 (runs migrations automatically) 30 + cd frontend && npm run dev # Svelte dev server 31 + ``` 32 + 33 + ### Backend commands (from `backend/`) 34 + ```bash 35 + cargo run # build + run (migrations auto-applied) 36 + cargo build # compile only 37 + cargo sqlx prepare # generate offline query metadata for CI builds 38 + RUST_LOG=tower_http=debug cargo run # verbose HTTP request logging 39 + ``` 40 + 41 + ### Frontend commands (from `frontend/`) 42 + ```bash 43 + npm run dev # vite dev server with HMR 44 + npm run build # production build 45 + npm run check # svelte-check + tsc type checking 46 + ``` 47 + 48 + ### Database 49 + ```bash 50 + sqlx database create # create the dyfc database 51 + sqlx migrate run # apply migrations (also done on cargo run) 52 + docker compose down -v # full DB reset (destroys volume) 53 + ``` 54 + 55 + ### SQLx compile-time checking 56 + SQLx macros (`query!`, `query_scalar!`) verify SQL against the live database at compile time. PostgreSQL must be running and migrations applied before `cargo build` will succeed. Set `SQLX_OFFLINE=true` to build against cached metadata instead (run `cargo sqlx prepare` first). 57 + 58 + ## Key conventions 59 + 60 + - Backend environment variables live in `backend/.env` (loaded by dotenvy). Required: `DATABASE_URL`, `RUST_LOG`. 61 + - SQL migrations go in `backend/migrations/` with numeric prefix ordering. 62 + - Game state is serialized as a single JSONB column via serde. The game engine logic (state machine) should remain pure — no HTTP or DB concerns — for testability.
+6
backend/archi.md
··· 1 + src/ 2 + main.rs # HTTP + DB glue 3 + game/ 4 + mod.rs # re-exports 5 + state.rs # GameState, Gauges, GameStatus 6 + action.rs # Action, GaugeDelta, apply_action
+11
backend/src/game/action.rs
··· 1 + pub struct Action { 2 + id: String, 3 + label: String, 4 + effect: GaugeDelta, 5 + } 6 + 7 + pub struct GaugeDelta { 8 + pub money: i32, 9 + pub internal_support: i32, 10 + pub mental_load: i32, 11 + }
+5
backend/src/game/mod.rs
··· 1 + pub mod state; 2 + pub use state::{GameOverReason, GameState, GameStatus, Gauges}; 3 + 4 + pub mod action; 5 + pub use action::{Action, GaugeDelta};
+62
backend/src/game/state.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + 3 + #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] 4 + pub struct Gauges { 5 + pub money: i32, 6 + pub internal_support: i32, 7 + pub mental_load: i32, 8 + } 9 + 10 + impl Gauges { 11 + pub fn starting() -> Self { 12 + Self { 13 + money: 1000, 14 + internal_support: 50, 15 + mental_load: 20, 16 + } 17 + } 18 + } 19 + 20 + #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] 21 + #[serde(tag = "kind", content = "reason")] 22 + pub enum GameStatus { 23 + Running, 24 + Lost(GameOverReason), 25 + } 26 + 27 + #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] 28 + pub enum GameOverReason { 29 + Bankruptcy, 30 + LostSupport, 31 + Burnout, 32 + } 33 + 34 + impl GameStatus { 35 + pub fn from_gauges(gauges: Gauges) -> Self { 36 + // TODO(human): inspect `gauges` and return the correct GameStatus. 37 + // - money <= 0 → Lost(Bankruptcy) 38 + // - internal_support <= 0 → Lost(LostSupport) 39 + // - mental_load >= 100 → Lost(Burnout) 40 + // - otherwise -> Running 41 + let min_internal_support = 1; 42 + let max_mental_load = 99; 43 + let min_money = 1; 44 + if gauges.internal_support < min_internal_support { 45 + return GameStatus::Lost(GameOverReason::LostSupport); 46 + } 47 + if gauges.mental_load > max_mental_load { 48 + return GameStatus::Lost(GameOverReason::Burnout); 49 + } 50 + if gauges.money < min_money { 51 + return GameStatus::Lost(GameOverReason::Bankruptcy); 52 + } 53 + GameStatus::Running 54 + } 55 + } 56 + 57 + #[derive(Debug, Clone, Serialize, Deserialize)] 58 + pub struct GameState { 59 + turn: u32, 60 + gauges: Gauges, 61 + status: GameStatus, 62 + }
+3
backend/src/main.rs
··· 11 11 use tower_http::trace::TraceLayer; 12 12 use tracing_subscriber; 13 13 14 + mod game; 15 + use crate::game::Gauges; 16 + 14 17 // === App State === 15 18 // Ce qui est partage entre tous les handlers (clone-able, passe via State) 16 19
+72
todo.md
··· 1 + # TODO — Moteur de jeu (première itération) 2 + 3 + Suivi de ce qu'il reste à implémenter pour avoir un moteur de jeu minimal: 4 + jauges + action simple, vérifiable par `cargo check` puis testable via l'API. 5 + 6 + --- 7 + 8 + ## 1. Créer la structure du module `game` 9 + 10 + - [x] Créer le dossier `backend/src/game/`. 11 + - [x] Créer `backend/src/game/mod.rs` — déclare les sous-modules (`pub mod state;`, 12 + `pub mod action;`) et re-exporte les types publics (`pub use state::...;`). 13 + - [x] Ajouter `mod game;` au début de `backend/src/main.rs` pour que le compilateur 14 + découvre le module. 15 + 16 + ## 2. Définir les types d'état (`backend/src/game/state.rs`) 17 + 18 + - [x] `Gauges` — struct avec `money: i32`, `internal_support: i32`, `mental_load: i32`. 19 + Dérive `Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq`. 20 + - [ ] `Gauges::starting()` — valeurs de départ (`money: 1000`, `internal_support: 50`, 21 + `mental_load: 20`). 22 + - [ ] `GameOverReason` — enum unitaire: `Bankruptcy`, `LostSupport`, `Burnout`. 23 + - [ ] `GameStatus` — enum: `Running` | `Lost(GameOverReason)`. Utiliser 24 + `#[serde(tag = "kind", content = "reason")]` pour un JSON propre. 25 + - [ ] `GameState` — struct: `turn: u32`, `gauges: Gauges`, `status: GameStatus`. 26 + - [ ] `GameState::new()` — construit l'état initial en utilisant `Gauges::starting()` 27 + et `GameStatus::from_gauges(...)`. 28 + 29 + ## 3. Règle de fin de partie (`backend/src/game/state.rs`) 30 + 31 + - [ ] `GameStatus::from_gauges(Gauges) -> GameStatus`. 32 + - `money <= 0` → `Lost(Bankruptcy)` 33 + - `internal_support <= 0` → `Lost(LostSupport)` 34 + - `mental_load >= 100` → `Lost(Burnout)` 35 + - sinon → `Running` 36 + - Attention: inégalités strictes côté règles (`0` et `100` sont déjà perdants). 37 + - Décision à faire: ordre de priorité si plusieurs conditions sont vraies en même temps. 38 + 39 + ## 4. Actions et transition (`backend/src/game/action.rs`) 40 + 41 + - [ ] `GaugeDelta` — même forme que `Gauges`. Dérive `Default` pour construire avec 42 + `..Default::default()`. 43 + - [ ] `Action` — struct: `id: String`, `label: String`, `effect: GaugeDelta`. 44 + - [ ] `GameError` — enum avec au moins `GameOver` pour l'instant. 45 + - [ ] `apply_action(&GameState, &Action) -> Result<GameState, GameError>`. 46 + - Si `state.status` n'est pas `Running` → `Err(GameOver)`. 47 + - Sinon: additionner `effect` à `gauges`, `turn + 1`, recalculer `status`. 48 + - Cœur de la boucle de jeu — état immuable en entrée, nouvel état en sortie. 49 + 50 + ## 5. Câblage dans `main.rs` 51 + 52 + - [ ] Supprimer les anciens `struct GameState` et `struct Resources` (placeholders 53 + `gold/food/population`) devenus obsolètes. 54 + - [ ] Adapter `create_game` pour utiliser le nouveau `game::GameState::new()`. 55 + - [ ] Vérifier que `cargo build` passe (SQLx a besoin de Postgres lancé). 56 + 57 + ## 6. Tests unitaires (optionnel mais recommandé) 58 + 59 + - [ ] Dans `state.rs`, module `#[cfg(test)]` qui vérifie `from_gauges` sur chaque 60 + condition de fin + cas de coexistence (money ET support à 0 en même temps). 61 + - [ ] Dans `action.rs`, test qui applique une action et vérifie le nouvel état 62 + (gauges, turn, status). 63 + 64 + --- 65 + 66 + ## Plus tard (hors scope de cette itération) 67 + 68 + - Endpoint `POST /games/{id}/actions` qui charge l'état, appelle `apply_action`, 69 + resauvegarde le JSONB. 70 + - Catalogue d'actions côté backend (ou côté frontend?) — à décider. 71 + - Système d'événements (3 choix par tour, tirés d'un pool). 72 + - Résultats de matchs et leur influence sur les jauges.