120 lines
3.5 KiB
Rust
120 lines
3.5 KiB
Rust
|
|
//! games-ground — the GROUND rules aggregate (specs/GroundRules.md,
|
|||
|
|
//! GameKernel K15–K16). T07 scaffolds the state shell; validate/fold per
|
|||
|
|
//! GR-rule land in T08 with rule IDs cross-referenced in doc comments.
|
|||
|
|
|
|||
|
|
use cb_kernel::PlayerId;
|
|||
|
|
use serde::{Deserialize, Serialize};
|
|||
|
|
use std::collections::BTreeMap;
|
|||
|
|
|
|||
|
|
/// GR-O02: per-player state.
|
|||
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|||
|
|
pub struct PlayerState {
|
|||
|
|
/// GR-F01: clamped 0–5.
|
|||
|
|
pub stress: u8,
|
|||
|
|
/// GR-F03.
|
|||
|
|
pub freedom_ready: bool,
|
|||
|
|
/// GR-D01/D02: OFF or the pending/active stage.
|
|||
|
|
pub darvo: DarvoStage,
|
|||
|
|
pub hand: Vec<SolutionCard>,
|
|||
|
|
pub protection: u8,
|
|||
|
|
/// Blame tokens in front of this player (GR-T02), keyed by owner.
|
|||
|
|
pub blame_from: Vec<PlayerId>,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|||
|
|
pub enum DarvoStage {
|
|||
|
|
Off,
|
|||
|
|
Deny,
|
|||
|
|
Attack,
|
|||
|
|
Reverse,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// GR-O04: one Problem card's live state.
|
|||
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|||
|
|
pub struct ProblemState {
|
|||
|
|
pub suit: Suit,
|
|||
|
|
pub value: u8,
|
|||
|
|
pub face_up: bool,
|
|||
|
|
pub denied: bool,
|
|||
|
|
pub claimed_by: Option<PlayerId>,
|
|||
|
|
/// GR-A11: protected from Deny this round by GROUND—OU.
|
|||
|
|
pub protected_this_round: bool,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
|||
|
|
pub enum Suit {
|
|||
|
|
Clarify,
|
|||
|
|
Repair,
|
|||
|
|
Boundary,
|
|||
|
|
Change,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|||
|
|
pub struct SolutionCard {
|
|||
|
|
pub suit: Suit,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// GR-O05: at most one relation per pair; endpoints ordered low→high.
|
|||
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|||
|
|
pub enum Relation {
|
|||
|
|
Bond,
|
|||
|
|
Rivalry,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// GR-O01..O05: the authoritative GROUND aggregate. Fields use ordered
|
|||
|
|
/// collections only (GameKernel K6).
|
|||
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|||
|
|
pub struct GroundState {
|
|||
|
|
pub round: u8,
|
|||
|
|
pub lead: PlayerId,
|
|||
|
|
pub players: BTreeMap<PlayerId, PlayerState>,
|
|||
|
|
/// Keyed by (low, high) player pair.
|
|||
|
|
pub relations: BTreeMap<(PlayerId, PlayerId), Relation>,
|
|||
|
|
pub problems: BTreeMap<u32, ProblemState>,
|
|||
|
|
pub solution_deck: Vec<SolutionCard>,
|
|||
|
|
pub solution_discard: Vec<SolutionCard>,
|
|||
|
|
/// Focus placements: sequence owner → target (GR-T03).
|
|||
|
|
pub focus: BTreeMap<PlayerId, PlayerId>,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[cfg(test)]
|
|||
|
|
mod tests {
|
|||
|
|
use super::*;
|
|||
|
|
use cb_events::state_hash_hex;
|
|||
|
|
|
|||
|
|
fn tiny_state() -> GroundState {
|
|||
|
|
GroundState {
|
|||
|
|
round: 1,
|
|||
|
|
lead: PlayerId(0),
|
|||
|
|
players: BTreeMap::from([(
|
|||
|
|
PlayerId(0),
|
|||
|
|
PlayerState {
|
|||
|
|
stress: 2,
|
|||
|
|
freedom_ready: true,
|
|||
|
|
darvo: DarvoStage::Off,
|
|||
|
|
hand: vec![SolutionCard { suit: Suit::Repair }],
|
|||
|
|
protection: 0,
|
|||
|
|
blame_from: vec![],
|
|||
|
|
},
|
|||
|
|
)]),
|
|||
|
|
relations: BTreeMap::new(),
|
|||
|
|
problems: BTreeMap::new(),
|
|||
|
|
solution_deck: vec![],
|
|||
|
|
solution_discard: vec![],
|
|||
|
|
focus: BTreeMap::new(),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// K7 on the real aggregate: hash stable across clones, sensitive to
|
|||
|
|
/// semantic change.
|
|||
|
|
#[test]
|
|||
|
|
fn ground_state_hashes_canonically() {
|
|||
|
|
let a = tiny_state();
|
|||
|
|
let b = a.clone();
|
|||
|
|
assert_eq!(state_hash_hex(&a), state_hash_hex(&b));
|
|||
|
|
let mut c = a.clone();
|
|||
|
|
c.players.get_mut(&PlayerId(0)).unwrap().stress = 5;
|
|||
|
|
assert_ne!(state_hash_hex(&a), state_hash_hex(&c));
|
|||
|
|
}
|
|||
|
|
}
|