Compare commits

..

No commits in common. "24f5dba0656a97977908c421080642465c36c9f3" and "43196bcef8156cb44dcf25c02f2cff0df35331ec" have entirely different histories.

12 changed files with 50 additions and 366 deletions

View file

@ -34,5 +34,4 @@ web-sys = { version = "0.3", features = [
"OscillatorNode",
"OscillatorType",
"BaseAudioContext",
"HtmlAudioElement",
] }

Binary file not shown.

View file

@ -1131,63 +1131,3 @@ body {
flex-wrap: wrap;
min-height: 2rem; /* reserve height so layout doesn't shift when buttons appear */
}
/* ── Pre-game ceremony overlay ──────────────────────────────────────────── */
.ceremony-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.65);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
}
.ceremony-box {
background: var(--ui-parchment);
border-radius: 8px;
padding: 2.5rem 3rem;
text-align: center;
box-shadow: 0 12px 40px rgba(0,0,0,0.5), 0 0 0 2px var(--ui-gold-dark);
display: flex;
flex-direction: column;
align-items: center;
gap: 1.4rem;
min-width: 300px;
animation: game-over-appear 0.4s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.ceremony-box h2 {
font-family: var(--font-display);
font-size: 1.8rem;
font-weight: 600;
color: var(--ui-ink);
letter-spacing: 0.06em;
}
.ceremony-dice {
display: flex;
gap: 3rem;
align-items: flex-end;
}
.ceremony-die-slot {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.ceremony-die-label {
font-family: var(--font-ui);
font-size: 0.85rem;
color: var(--ui-ink);
font-weight: 500;
}
.ceremony-tie {
font-family: var(--font-display);
font-size: 1rem;
color: var(--ui-red-accent);
font-style: italic;
}

View file

@ -6,7 +6,6 @@
<title>Trictrac</title>
<link data-trunk rel="rust" />
<link data-trunk rel="css" href="assets/style.css" />
<link data-trunk rel="copy-file" href="assets/diceroll.mp3" />
</head>
<body></body>
</html>

View file

@ -42,12 +42,6 @@
"after_opponent_roll": "Opponent rolled",
"after_opponent_go": "Opponent chose to continue",
"after_opponent_move": "Opponent moved — your turn",
"after_opponent_pre_game_roll": "Opponent rolled — your turn",
"pre_game_roll_title": "Who goes first?",
"pre_game_roll_btn": "Roll",
"pre_game_roll_tie": "Tie! Roll again",
"pre_game_roll_your_die": "Your die",
"pre_game_roll_opp_die": "Opponent's die",
"continue_btn": "Continue",
"scored_pts": "+{{ n }} pts",
"hole_made": "Hole! {{ holes }}/12",

View file

@ -42,12 +42,6 @@
"after_opponent_roll": "L'adversaire a lancé les dés",
"after_opponent_go": "L'adversaire s'en va",
"after_opponent_move": "L'adversaire a joué — à vous",
"after_opponent_pre_game_roll": "L'adversaire a lancé — à vous",
"pre_game_roll_title": "Qui joue en premier ?",
"pre_game_roll_btn": "Lancer",
"pre_game_roll_tie": "Égalité ! Relancez",
"pre_game_roll_your_die": "Votre dé",
"pre_game_roll_opp_die": "Dé adverse",
"continue_btn": "Continuer",
"scored_pts": "+{{ n }} pts",
"hole_made": "Trou ! {{ holes }}/12",

View file

@ -13,7 +13,7 @@ use crate::i18n::I18nContextProvider;
use crate::trictrac::backend::TrictracBackend;
use crate::trictrac::bot_local::bot_decide;
use crate::trictrac::types::{
GameDelta, JanEntry, PlayerAction, ScoredEvent, SerStage, SerTurnStage, ViewState,
GameDelta, JanEntry, PlayerAction, ScoredEvent, SerTurnStage, ViewState,
};
use trictrac_store::CheckerMove;
@ -48,8 +48,6 @@ pub enum PauseReason {
AfterOpponentRoll,
AfterOpponentGo,
AfterOpponentMove,
/// Opponent rolled their die in the pre-game ceremony.
AfterOpponentPreGameRoll,
}
/// Which screen is currently shown.
@ -384,19 +382,18 @@ async fn run_local_bot_game(
}
loop {
let pgr = backend.get_view_state().pre_game_roll.clone();
match bot_decide(backend.get_game(), pgr.as_ref()) {
match bot_decide(backend.get_game()) {
None => break,
Some(action) => {
let prev_vs = vs.clone();
backend.inform_rpc(1, action);
// Process each delta individually so intermediate ceremony
// states (both dice shown) can trigger a pause via push_or_show.
for cmd in backend.drain_commands() {
if let BackendCommand::Delta(delta) = cmd {
let delta_prev_vs = vs.clone();
vs.apply_delta(&delta);
}
}
push_or_show(
&delta_prev_vs,
&prev_vs,
GameUiState {
view_state: vs.clone(),
player_id: 0,
@ -406,7 +403,7 @@ async fn run_local_bot_game(
pause_reason: None,
my_scored_event: None,
opp_scored_event: None,
last_moves: compute_last_moves(&delta_prev_vs, &vs),
last_moves: compute_last_moves(&prev_vs, &vs),
},
pending,
screen,
@ -415,8 +412,6 @@ async fn run_local_bot_game(
}
}
}
}
}
}
/// Returns the checker moves to animate when the board changed between two ViewStates.
@ -535,24 +530,6 @@ fn push_or_show(
fn infer_pause_reason(prev: &ViewState, next: &ViewState, player_id: u16) -> Option<PauseReason> {
let opponent_id = 1 - player_id;
// Pre-game ceremony: pause when both dice are revealed simultaneously
// (i.e. the second die was just rolled). Both players see this pause.
if next.stage == SerStage::PreGameRoll {
if let (Some(prev_pgr), Some(next_pgr)) = (&prev.pre_game_roll, &next.pre_game_roll) {
let both_now = next_pgr.host_die.is_some() && next_pgr.guest_die.is_some();
let both_before = prev_pgr.host_die.is_some() && prev_pgr.guest_die.is_some();
if both_now && !both_before {
return Some(PauseReason::AfterOpponentPreGameRoll);
}
}
return None;
}
// Don't fire normal pause rules on the PreGameRoll → InGame transition.
if prev.stage == SerStage::PreGameRoll {
return None;
}
if next.active_mp_player == Some(opponent_id) {
// Dice changed → opponent just rolled.
if next.dice != prev.dice {
@ -597,7 +574,6 @@ mod tests {
dice,
dice_jans: Vec::new(),
dice_moves: (CheckerMove::default(), CheckerMove::default()),
pre_game_roll: None,
}
}

View file

@ -7,8 +7,7 @@ use trictrac_store::{Board as StoreBoard, CheckerMove, Color, Dice as StoreDice,
use crate::app::{GameUiState, NetCommand, PauseReason};
use crate::i18n::*;
use crate::trictrac::types::{PlayerAction, PreGameRollState, SerStage, SerTurnStage};
use super::die::Die;
use crate::trictrac::types::{PlayerAction, SerStage, SerTurnStage};
use super::board::Board;
use super::score_panel::PlayerScorePanel;
@ -79,11 +78,7 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
// Guard: suppressed while waiting_for_confirm — the AfterOpponentMove
// buffered state shows the human's RollDice turn but the auto-roll must
// wait until the buffer is drained and the live screen state is shown.
// Guard: never auto-roll during the pre-game ceremony (the ceremony overlay
// has its own Roll button for PlayerAction::PreGameRoll).
let show_roll = is_my_turn
&& vs.turn_stage == SerTurnStage::RollDice
&& vs.stage != SerStage::PreGameRoll;
let show_roll = is_my_turn && vs.turn_stage == SerTurnStage::RollDice;
if show_roll && !waiting_for_confirm {
let cmd_tx_auto = cmd_tx.clone();
Effect::new(move |_| {
@ -137,13 +132,6 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
let my_score = vs.scores[player_id as usize].clone();
let opp_score = vs.scores[1 - player_id as usize].clone();
// ── Ceremony state (extracted before vs is moved into Board) ────────────────
let is_ceremony = vs.stage == SerStage::PreGameRoll;
let pre_game_roll_data: Option<PreGameRollState> = vs.pre_game_roll.clone();
let my_name_ceremony = my_score.name.clone();
let opp_name_ceremony = opp_score.name.clone();
let cmd_tx_ceremony = cmd_tx.clone();
// ── Scoring notifications ──────────────────────────────────────────────────
let my_scored_event = state.my_scored_event.clone();
let opp_scored_event = state.opp_scored_event.clone();
@ -191,7 +179,7 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
// ── Sound effects (fire once on mount = once per state snapshot) ──────────
// Dice roll: dice just appeared (no preceding moves in this snapshot).
if show_dice && last_moves.is_none() {
crate::sound::play_dice_roll();
crate::sound::play_dice_roll_cinematic();
}
// Checker move: moves were committed in the preceding action.
if last_moves.is_some() {
@ -258,7 +246,6 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
PauseReason::AfterOpponentRoll => t_string!(i18n, after_opponent_roll),
PauseReason::AfterOpponentGo => t_string!(i18n, after_opponent_go),
PauseReason::AfterOpponentMove => t_string!(i18n, after_opponent_move),
PauseReason::AfterOpponentPreGameRoll => t_string!(i18n, after_opponent_pre_game_roll),
});
}
let n = staged_moves.get().len();
@ -267,7 +254,7 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
} else {
String::from(match (&stage, is_my_turn, &turn_stage) {
(SerStage::Ended, _, _) => t_string!(i18n, game_over),
(SerStage::PreGame, _, _) | (SerStage::PreGameRoll, _, _) => t_string!(i18n, waiting_for_opponent),
(SerStage::PreGame, _, _) => t_string!(i18n, waiting_for_opponent),
(SerStage::InGame, true, SerTurnStage::RollDice) => t_string!(i18n, your_turn_roll),
(SerStage::InGame, true, SerTurnStage::HoldOrGoChoice) => t_string!(i18n, hold_or_go),
(SerStage::InGame, true, _) => t_string!(i18n, your_turn),
@ -365,55 +352,6 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
// ── Player score (below board) ────────────────────────────────────
<PlayerScorePanel score=my_score is_you=true />
// ── Pre-game ceremony overlay ─────────────────────────────────────
{is_ceremony.then(|| {
let pgr = pre_game_roll_data.unwrap_or(PreGameRollState {
host_die: None,
guest_die: None,
tie_count: 0,
});
let my_die = if player_id == 0 { pgr.host_die } else { pgr.guest_die };
let opp_die = if player_id == 0 { pgr.guest_die } else { pgr.host_die };
let can_roll = is_my_turn && !waiting_for_confirm;
let show_tie = pgr.tie_count > 0;
view! {
<div class="ceremony-overlay">
<div class="ceremony-box">
<h2>{t!(i18n, pre_game_roll_title)}</h2>
{show_tie.then(|| view! {
<p class="ceremony-tie">{t!(i18n, pre_game_roll_tie)}</p>
})}
<div class="ceremony-dice">
<div class="ceremony-die-slot">
<span class="ceremony-die-label">{my_name_ceremony}</span>
<Die value=my_die.unwrap_or(0) used=false />
</div>
<div class="ceremony-die-slot">
<span class="ceremony-die-label">{opp_name_ceremony}</span>
<Die value=opp_die.unwrap_or(0) used=false />
</div>
</div>
{waiting_for_confirm.then(|| {
let pending_c = pending;
view! {
<button class="btn btn-primary" on:click=move |_| {
pending_c.update(|q| { q.pop_front(); });
}>{t!(i18n, continue_btn)}</button>
}
})}
{can_roll.then(|| {
let cmd_tx_c = cmd_tx_ceremony.clone();
view! {
<button class="btn btn-primary" on:click=move |_| {
cmd_tx_c.unbounded_send(NetCommand::Action(PlayerAction::PreGameRoll)).ok();
}>{t!(i18n, pre_game_roll_btn)}</button>
}
})}
</div>
</div>
}
})}
// ── Game-over overlay ─────────────────────────────────────────────
{stage_is_ended.then(|| {
let opp_name_end_clone = opp_name_end.clone();

View file

@ -128,13 +128,6 @@ mod inner {
});
}
/// Play the pre-recorded dice-roll MP3 asset.
pub fn play_dice_roll() {
if let Ok(audio) = web_sys::HtmlAudioElement::new_with_src("/diceroll.mp3") {
let _ = audio.play();
}
}
/// Ascending three-note chime (C5 E5 G5).
pub fn play_points_scored() {
with_ctx(|ctx| {
@ -165,15 +158,12 @@ mod inner {
#[cfg(target_arch = "wasm32")]
pub use inner::{
play_checker_move, play_dice_roll, play_dice_roll_cinematic, play_hole_scored,
play_points_scored,
play_checker_move, play_dice_roll_cinematic, play_hole_scored, play_points_scored,
};
#[cfg(not(target_arch = "wasm32"))]
pub fn play_checker_move() {}
#[cfg(not(target_arch = "wasm32"))]
pub fn play_dice_roll() {}
#[cfg(not(target_arch = "wasm32"))]
pub fn play_dice_roll_cinematic() {}
#[cfg(not(target_arch = "wasm32"))]
pub fn play_points_scored() {}

View file

@ -1,7 +1,7 @@
use backbone_lib::traits::{BackEndArchitecture, BackendCommand};
use trictrac_store::{DiceRoller, GameEvent, GameState, TurnStage};
use crate::trictrac::types::{GameDelta, PlayerAction, PreGameRollState, SerStage, ViewState};
use crate::trictrac::types::{GameDelta, PlayerAction, ViewState};
// Store PlayerId (u64) values used for the two players.
const HOST_PLAYER_ID: u64 = 1;
@ -14,32 +14,11 @@ pub struct TrictracBackend {
view_state: ViewState,
/// Arrival flags: have host (index 0) and guest (index 1) joined?
arrived: [bool; 2],
/// Die rolled by each player during the ceremony ([host, guest]).
pre_game_dice: [Option<u8>; 2],
/// Number of tied rounds so far.
tie_count: u8,
/// True while the first-player ceremony is running.
ceremony_started: bool,
}
impl TrictracBackend {
fn sync_view_state(&mut self) {
let mut vs = ViewState::from_game_state(&self.game, HOST_PLAYER_ID, GUEST_PLAYER_ID);
if self.ceremony_started {
vs.stage = SerStage::PreGameRoll;
vs.pre_game_roll = Some(PreGameRollState {
host_die: self.pre_game_dice[0],
guest_die: self.pre_game_dice[1],
tie_count: self.tie_count,
});
// The active mp player is whoever hasn't rolled yet (host rolls first).
vs.active_mp_player = match self.pre_game_dice {
[None, _] => Some(0),
[Some(_), None] => Some(1),
_ => None,
};
}
self.view_state = vs;
self.view_state = ViewState::from_game_state(&self.game, HOST_PLAYER_ID, GUEST_PLAYER_ID);
}
fn broadcast_state(&mut self) {
@ -50,42 +29,6 @@ impl TrictracBackend {
self.commands.push(BackendCommand::Delta(delta));
}
/// Process one ceremony die-roll for `mp_player` (0 = host, 1 = guest).
fn handle_pre_game_roll(&mut self, mp_player: u16) {
// Enforce turn order: host rolls first, then guest.
let expected: u16 = match self.pre_game_dice {
[None, _] => 0,
[Some(_), None] => 1,
_ => return, // both already rolled (shouldn't happen)
};
if mp_player != expected {
return;
}
let idx = mp_player as usize;
let single = self.dice_roller.roll().values.0;
self.pre_game_dice[idx] = Some(single);
if let [Some(h), Some(g)] = self.pre_game_dice {
// Both have rolled — broadcast both dice before resolving.
self.broadcast_state();
if h == g {
// Tie: reset for another round.
self.tie_count += 1;
self.pre_game_dice = [None; 2];
self.broadcast_state();
} else {
// Highest die goes first.
let goes_first = if h > g { HOST_PLAYER_ID } else { GUEST_PLAYER_ID };
self.ceremony_started = false;
let _ = self.game.consume(&GameEvent::BeginGame { goes_first });
self.broadcast_state();
}
} else {
// Only one die rolled so far — broadcast the partial result.
self.broadcast_state();
}
}
/// Roll dice using the store's DiceRoller and fire Roll + RollResult events.
fn do_roll(&mut self) {
let dice = self.dice_roller.roll();
@ -143,9 +86,6 @@ impl BackEndArchitecture<PlayerAction, GameDelta, ViewState> for TrictracBackend
commands: Vec::new(),
view_state,
arrived: [false; 2],
pre_game_dice: [None; 2],
tie_count: 0,
ceremony_started: false,
}
}
@ -170,13 +110,11 @@ impl BackEndArchitecture<PlayerAction, GameDelta, ViewState> for TrictracBackend
timer_id: mp_player,
});
// Start the ceremony once both players have arrived.
if self.arrived[0] && self.arrived[1] && self.game.stage == trictrac_store::Stage::PreGame
&& !self.ceremony_started
{
self.ceremony_started = true;
self.pre_game_dice = [None; 2];
self.tie_count = 0;
// Start the game once both players have arrived.
if self.arrived[0] && self.arrived[1] && self.game.stage == trictrac_store::Stage::PreGame {
let _ = self.game.consume(&GameEvent::BeginGame {
goes_first: HOST_PLAYER_ID,
});
self.sync_view_state();
self.commands.push(BackendCommand::ResetViewState);
} else {
@ -197,14 +135,6 @@ impl BackEndArchitecture<PlayerAction, GameDelta, ViewState> for TrictracBackend
}
fn inform_rpc(&mut self, mp_player: u16, action: PlayerAction) {
// During the first-player ceremony only PreGameRoll actions are accepted.
if self.ceremony_started {
if matches!(action, PlayerAction::PreGameRoll) {
self.handle_pre_game_roll(mp_player);
}
return;
}
if self.game.stage == trictrac_store::Stage::Ended {
return;
}
@ -237,6 +167,8 @@ impl BackEndArchitecture<PlayerAction, GameDelta, ViewState> for TrictracBackend
moves: (m1, m2),
};
if self.game.validate(&event) {
let message = format!("Event {:?} validated on {:?}", event, self.game);
console_log(message);
let _ = self.game.consume(&event);
self.drive_automatic_stages();
}
@ -256,7 +188,6 @@ impl BackEndArchitecture<PlayerAction, GameDelta, ViewState> for TrictracBackend
self.drive_automatic_stages();
}
}
PlayerAction::PreGameRoll => {} // ignored outside ceremony
}
self.broadcast_state();
@ -285,7 +216,6 @@ impl BackEndArchitecture<PlayerAction, GameDelta, ViewState> for TrictracBackend
mod tests {
use super::*;
use backbone_lib::traits::BackEndArchitecture;
use crate::trictrac::types::{SerStage, SerTurnStage};
fn make_backend() -> TrictracBackend {
TrictracBackend::new(0)
@ -303,65 +233,26 @@ mod tests {
.collect()
}
/// Drive the ceremony to completion (both players roll until one wins).
fn complete_ceremony(b: &mut TrictracBackend) {
loop {
if b.get_view_state().stage != SerStage::PreGameRoll {
break;
}
match b.get_view_state().active_mp_player {
Some(0) => b.inform_rpc(0, PlayerAction::PreGameRoll),
Some(1) => b.inform_rpc(1, PlayerAction::PreGameRoll),
_ => break,
}
b.drain_commands();
}
}
#[test]
fn both_players_arrive_starts_ceremony() {
fn both_players_arrive_starts_game() {
let mut b = make_backend();
b.player_arrival(0); // host
b.drain_commands();
b.player_arrival(1); // guest
let cmds = b.drain_commands();
// ResetViewState should have been issued to start the ceremony.
// ResetViewState should have been issued after BeginGame.
let has_reset = cmds
.iter()
.any(|c| matches!(c, BackendCommand::ResetViewState));
assert!(has_reset, "expected ResetViewState after both players arrive");
// Stage should now be PreGameRoll, not InGame.
assert_eq!(b.get_view_state().stage, SerStage::PreGameRoll);
}
#[test]
fn ceremony_resolves_to_in_game() {
let mut b = make_backend();
b.player_arrival(0);
b.player_arrival(1);
b.drain_commands();
complete_ceremony(&mut b);
assert_eq!(b.get_view_state().stage, SerStage::InGame);
}
#[test]
fn ceremony_wrong_order_ignored() {
let mut b = make_backend();
b.player_arrival(0);
b.player_arrival(1);
b.drain_commands();
// Guest tries to roll before host (host goes first in ceremony).
b.inform_rpc(1, PlayerAction::PreGameRoll);
let cmds = b.drain_commands();
assert!(
cmds.is_empty(),
"guest PreGameRoll should be ignored when it is host's turn"
has_reset,
"expected ResetViewState after both players arrive"
);
// Game should now be InGame.
use crate::trictrac::types::SerStage;
assert_eq!(b.get_view_state().stage, SerStage::InGame);
}
#[test]
@ -381,15 +272,12 @@ mod tests {
b.player_arrival(1);
b.drain_commands();
// Complete ceremony before rolling.
complete_ceremony(&mut b);
// Roll for whoever won the ceremony (either player could go first).
let first_player = b.get_view_state().active_mp_player.expect("someone should be active");
b.inform_rpc(first_player, PlayerAction::Roll);
// Host rolls (player_id 0, whose store id == HOST_PLAYER_ID == active after BeginGame).
b.inform_rpc(0, PlayerAction::Roll);
let states = drain_deltas(&mut b);
assert!(!states.is_empty(), "expected a state broadcast after roll");
use crate::trictrac::types::SerTurnStage;
let last = states.last().unwrap();
assert!(
matches!(
@ -410,16 +298,13 @@ mod tests {
b.player_arrival(0);
b.player_arrival(1);
b.drain_commands();
complete_ceremony(&mut b);
// Identify who goes first and have the OTHER player try to roll.
let active = b.get_view_state().active_mp_player;
let wrong_player = if active == Some(0) { 1u16 } else { 0u16 };
b.inform_rpc(wrong_player, PlayerAction::Roll);
// Guest tries to roll when it's the host's turn.
b.inform_rpc(1, PlayerAction::Roll);
let cmds = b.drain_commands();
assert!(
cmds.is_empty(),
"wrong player roll should be ignored"
"guest roll should be ignored when it's host's turn"
);
}

View file

@ -1,22 +1,12 @@
use rand::prelude::IndexedRandom;
use trictrac_store::{CheckerMove, Color, GameState, MoveRules, Stage, TurnStage};
use crate::trictrac::types::{PlayerAction, PreGameRollState};
use crate::trictrac::types::PlayerAction;
const GUEST_PLAYER_ID: u64 = 2;
/// Returns the next action for the bot (mp_player 1 / guest), or None if it is not the bot's turn.
/// `pgr` is the current pre-game ceremony state if the ceremony is in progress.
pub fn bot_decide(game: &GameState, pgr: Option<&PreGameRollState>) -> Option<PlayerAction> {
// During the ceremony, the bot (guest) rolls when its die is missing.
if game.stage == Stage::PreGame {
if let Some(pgr) = pgr {
if pgr.guest_die.is_none() {
return Some(PlayerAction::PreGameRoll);
}
}
return None;
}
pub fn bot_decide(game: &GameState) -> Option<PlayerAction> {
if game.stage == Stage::Ended {
return None;
}
@ -25,7 +15,7 @@ pub fn bot_decide(game: &GameState, pgr: Option<&PreGameRollState>) -> Option<Pl
}
match game.turn_stage {
TurnStage::RollDice => Some(PlayerAction::Roll),
TurnStage::HoldOrGoChoice => Some(PlayerAction::Go),
TurnStage::HoldOrGoChoice => Some(PlayerAction::Mark),
TurnStage::Move => {
let rules = MoveRules::new(&Color::Black, &game.board, game.dice);
let sequences = rules.get_possible_moves_sequences(true, vec![]);

View file

@ -14,8 +14,6 @@ pub enum PlayerAction {
Go,
/// Acknowledge point marking (hold / advance points).
Mark,
/// Roll a single die during the pre-game ceremony to decide who goes first.
PreGameRoll,
}
// ── Incremental state update broadcast to all clients ────────────────────────
@ -29,18 +27,6 @@ pub struct GameDelta {
// ── Full game snapshot ────────────────────────────────────────────────────────
/// State of the pre-game ceremony where each player rolls one die to decide
/// who goes first. Present only when `stage == SerStage::PreGameRoll`.
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct PreGameRollState {
/// Die value (16) rolled by the host; `None` = not yet rolled this round.
pub host_die: Option<u8>,
/// Die value (16) rolled by the guest; `None` = not yet rolled this round.
pub guest_die: Option<u8>,
/// Number of tied rounds so far (0 on the first round).
pub tie_count: u8,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct ViewState {
/// Board positions: index i = field i+1. Positive = white, negative = black.
@ -57,9 +43,6 @@ pub struct ViewState {
pub dice_jans: Vec<JanEntry>,
/// Last two checker moves played; default when no move has occurred yet.
pub dice_moves: (CheckerMove, CheckerMove),
/// Present while the pre-game ceremony is in progress.
#[serde(default)]
pub pre_game_roll: Option<PreGameRollState>,
}
/// One scoring event from a dice roll.
@ -103,7 +86,6 @@ impl ViewState {
dice: (0, 0),
dice_jans: Vec::new(),
dice_moves: (CheckerMove::default(), CheckerMove::default()),
pre_game_roll: None,
}
}
@ -202,7 +184,6 @@ impl ViewState {
dice: (gs.dice.values.0, gs.dice.values.1),
dice_jans,
dice_moves: gs.dice_moves,
pre_game_roll: None,
}
}
}
@ -239,8 +220,6 @@ pub struct PlayerScore {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SerStage {
PreGame,
/// Both players have arrived; ceremony in progress to decide who goes first.
PreGameRoll,
InGame,
Ended,
}