feat(web_client): debug message
This commit is contained in:
parent
dd4814e448
commit
dd503b5288
4 changed files with 107 additions and 35 deletions
|
|
@ -12,7 +12,9 @@ use crate::components::{ConnectingScreen, GameScreen, LoginScreen};
|
|||
use crate::i18n::I18nContextProvider;
|
||||
use crate::trictrac::backend::TrictracBackend;
|
||||
use crate::trictrac::bot_local::bot_decide;
|
||||
use crate::trictrac::types::{GameDelta, JanEntry, PlayerAction, ScoredEvent, SerTurnStage, ViewState};
|
||||
use crate::trictrac::types::{
|
||||
GameDelta, JanEntry, PlayerAction, ScoredEvent, SerTurnStage, ViewState,
|
||||
};
|
||||
use trictrac_store::CheckerMove;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
|
@ -194,7 +196,9 @@ pub fn App() -> impl IntoView {
|
|||
if remote_config.is_none() {
|
||||
loop {
|
||||
let restart = run_local_bot_game(screen, &mut cmd_rx, pending).await;
|
||||
if !restart { break; }
|
||||
if !restart {
|
||||
break;
|
||||
}
|
||||
}
|
||||
pending.update(|q| q.clear());
|
||||
screen.set(Screen::Login { error: None });
|
||||
|
|
@ -328,8 +332,12 @@ async fn run_local_bot_game(
|
|||
let mut vs = ViewState::default_with_names("You", "Bot");
|
||||
for cmd in backend.drain_commands() {
|
||||
match cmd {
|
||||
BackendCommand::ResetViewState => { vs = backend.get_view_state().clone(); }
|
||||
BackendCommand::Delta(delta) => { vs.apply_delta(&delta); }
|
||||
BackendCommand::ResetViewState => {
|
||||
vs = backend.get_view_state().clone();
|
||||
}
|
||||
BackendCommand::Delta(delta) => {
|
||||
vs.apply_delta(&delta);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -440,15 +448,21 @@ fn compute_scored_event(prev: &ViewState, next: &ViewState, player_id: u16) -> O
|
|||
&& prev.active_mp_player == Some(player_id)
|
||||
{
|
||||
// My own roll: positive totals are mine.
|
||||
next.dice_jans.iter().filter(|e| e.total > 0).cloned().collect()
|
||||
} else if next.active_mp_player == Some(player_id)
|
||||
&& prev.active_mp_player != Some(player_id)
|
||||
{
|
||||
next.dice_jans
|
||||
.iter()
|
||||
.filter(|e| e.total > 0)
|
||||
.cloned()
|
||||
.collect()
|
||||
} else if next.active_mp_player == Some(player_id) && prev.active_mp_player != Some(player_id) {
|
||||
// Opponent just moved: negative totals (their penalty) are scored for me.
|
||||
next.dice_jans
|
||||
.iter()
|
||||
.filter(|e| e.total < 0)
|
||||
.map(|e| JanEntry { total: -e.total, points_per: -e.points_per, ..e.clone() })
|
||||
.map(|e| JanEntry {
|
||||
total: -e.total,
|
||||
points_per: -e.points_per,
|
||||
..e.clone()
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
return None;
|
||||
|
|
@ -496,7 +510,10 @@ fn push_or_show(
|
|||
});
|
||||
// Animation belongs to the buffered confirmation step; clear it on the
|
||||
// fallback live state so it doesn't fire again after the queue drains.
|
||||
screen.set(Screen::Playing(GameUiState { last_moves: None, ..new_state }));
|
||||
screen.set(Screen::Playing(GameUiState {
|
||||
last_moves: None,
|
||||
..new_state
|
||||
}));
|
||||
} else {
|
||||
// No pause: show scoring directly on the live state.
|
||||
screen.set(Screen::Playing(GameUiState {
|
||||
|
|
@ -519,8 +536,7 @@ fn infer_pause_reason(prev: &ViewState, next: &ViewState, player_id: u16) -> Opt
|
|||
return Some(PauseReason::AfterOpponentRoll);
|
||||
}
|
||||
// Was at HoldOrGoChoice, now Move, opponent still active → opponent went.
|
||||
if prev.turn_stage == SerTurnStage::HoldOrGoChoice
|
||||
&& next.turn_stage == SerTurnStage::Move
|
||||
if prev.turn_stage == SerTurnStage::HoldOrGoChoice && next.turn_stage == SerTurnStage::Move
|
||||
{
|
||||
return Some(PauseReason::AfterOpponentGo);
|
||||
}
|
||||
|
|
@ -534,14 +550,18 @@ fn infer_pause_reason(prev: &ViewState, next: &ViewState, player_id: u16) -> Opt
|
|||
None
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::trictrac::types::{PlayerScore, SerStage, SerTurnStage};
|
||||
|
||||
fn score() -> PlayerScore {
|
||||
PlayerScore { name: String::new(), points: 0, holes: 0, can_bredouille: false }
|
||||
PlayerScore {
|
||||
name: String::new(),
|
||||
points: 0,
|
||||
holes: 0,
|
||||
can_bredouille: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn vs(dice: (u8, u8), turn_stage: SerTurnStage, active: Option<u16>) -> ViewState {
|
||||
|
|
@ -554,6 +574,7 @@ mod tests {
|
|||
dice,
|
||||
dice_jans: Vec::new(),
|
||||
dice_moves: (CheckerMove::default(), CheckerMove::default()),
|
||||
message: "".into(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -561,21 +582,30 @@ mod tests {
|
|||
fn dice_change_is_after_roll() {
|
||||
let prev = vs((0, 0), SerTurnStage::RollDice, Some(1));
|
||||
let next = vs((3, 5), SerTurnStage::Move, Some(1));
|
||||
assert_eq!(infer_pause_reason(&prev, &next, 0), Some(PauseReason::AfterOpponentRoll));
|
||||
assert_eq!(
|
||||
infer_pause_reason(&prev, &next, 0),
|
||||
Some(PauseReason::AfterOpponentRoll)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hold_to_move_is_after_go() {
|
||||
let prev = vs((3, 5), SerTurnStage::HoldOrGoChoice, Some(1));
|
||||
let next = vs((3, 5), SerTurnStage::Move, Some(1));
|
||||
assert_eq!(infer_pause_reason(&prev, &next, 0), Some(PauseReason::AfterOpponentGo));
|
||||
assert_eq!(
|
||||
infer_pause_reason(&prev, &next, 0),
|
||||
Some(PauseReason::AfterOpponentGo)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_switch_is_after_move() {
|
||||
let prev = vs((3, 5), SerTurnStage::Move, Some(1));
|
||||
let next = vs((3, 5), SerTurnStage::RollDice, Some(0));
|
||||
assert_eq!(infer_pause_reason(&prev, &next, 0), Some(PauseReason::AfterOpponentMove));
|
||||
assert_eq!(
|
||||
infer_pause_reason(&prev, &next, 0),
|
||||
Some(PauseReason::AfterOpponentMove)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -167,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();
|
||||
}
|
||||
|
|
@ -330,3 +332,20 @@ mod tests {
|
|||
.any(|c| matches!(c, BackendCommand::TerminateRoom)));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Public API: WASM delegates to `inner`, other targets are no-ops ───────────
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod inner {
|
||||
use web_sys::console;
|
||||
|
||||
pub fn console_log(message: String) {
|
||||
console::log_1(&message.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use inner::console_log;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn console_log(message: String) {}
|
||||
|
|
|
|||
|
|
@ -70,8 +70,18 @@ impl ViewState {
|
|||
turn_stage: SerTurnStage::RollDice,
|
||||
active_mp_player: None,
|
||||
scores: [
|
||||
PlayerScore { name: host_name.to_string(), points: 0, holes: 0, can_bredouille: false },
|
||||
PlayerScore { name: guest_name.to_string(), points: 0, holes: 0, can_bredouille: false },
|
||||
PlayerScore {
|
||||
name: host_name.to_string(),
|
||||
points: 0,
|
||||
holes: 0,
|
||||
can_bredouille: false,
|
||||
},
|
||||
PlayerScore {
|
||||
name: guest_name.to_string(),
|
||||
points: 0,
|
||||
holes: 0,
|
||||
can_bredouille: false,
|
||||
},
|
||||
],
|
||||
dice: (0, 0),
|
||||
dice_jans: Vec::new(),
|
||||
|
|
@ -86,11 +96,7 @@ impl ViewState {
|
|||
/// Convert a store `GameState` to a `ViewState`.
|
||||
/// `host_store_id` and `guest_store_id` are the trictrac `PlayerId`s assigned
|
||||
/// to the host (mp player 0) and guest (mp player 1) respectively.
|
||||
pub fn from_game_state(
|
||||
gs: &GameState,
|
||||
host_store_id: u64,
|
||||
guest_store_id: u64,
|
||||
) -> Self {
|
||||
pub fn from_game_state(gs: &GameState, host_store_id: u64, guest_store_id: u64) -> Self {
|
||||
let board_vec = gs.board.to_vec();
|
||||
let board: [i8; 24] = board_vec.try_into().expect("board is always 24 fields");
|
||||
|
||||
|
|
@ -125,7 +131,12 @@ impl ViewState {
|
|||
holes: p.holes,
|
||||
can_bredouille: p.can_bredouille,
|
||||
})
|
||||
.unwrap_or_else(|| PlayerScore { name: String::new(), points: 0, holes: 0, can_bredouille: false })
|
||||
.unwrap_or_else(|| PlayerScore {
|
||||
name: String::new(),
|
||||
points: 0,
|
||||
holes: 0,
|
||||
can_bredouille: false,
|
||||
})
|
||||
};
|
||||
|
||||
// is_double for scoring: dice show the same value (both dice identical).
|
||||
|
|
@ -134,13 +145,16 @@ impl ViewState {
|
|||
|
||||
// Build JanEntry list from the PossibleJans map.
|
||||
let empty_move = CheckerMove::new(0, 0).unwrap_or_default();
|
||||
let mut dice_jans: Vec<JanEntry> = gs.dice_jans
|
||||
let mut dice_jans: Vec<JanEntry> = gs
|
||||
.dice_jans
|
||||
.iter()
|
||||
.map(|(jan, moves)| {
|
||||
// HelplessMan: is_double = true only when *both* dice are unplayable
|
||||
// (the moves list contains a single (empty, empty) sentinel).
|
||||
let is_double = if *jan == Jan::HelplessMan {
|
||||
moves.first().map(|&(m1, m2)| m1 == empty_move && m2 == empty_move)
|
||||
moves
|
||||
.first()
|
||||
.map(|&(m1, m2)| m1 == empty_move && m2 == empty_move)
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
dice_are_double
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ pub struct GameState {
|
|||
roll_first: bool,
|
||||
// NOTE: add to a Setting struct if other fields needed
|
||||
pub schools_enabled: bool,
|
||||
pub debug_message: String,
|
||||
}
|
||||
|
||||
// implement Display trait
|
||||
|
|
@ -119,6 +120,7 @@ impl Default for GameState {
|
|||
dice_jans: PossibleJans::default(),
|
||||
roll_first: true,
|
||||
schools_enabled: false,
|
||||
debug_message: "".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -147,6 +149,11 @@ impl GameState {
|
|||
game
|
||||
}
|
||||
|
||||
pub fn get_debug_message(&self) -> String {
|
||||
// format!("{:?}", self.history.last())
|
||||
format!("{:?}", self.debug_message)
|
||||
}
|
||||
|
||||
pub fn mirror(&self) -> GameState {
|
||||
let mirrored_active_player = if self.active_player_id == 1 { 2 } else { 1 };
|
||||
let mut mirrored_players = HashMap::new();
|
||||
|
|
@ -171,6 +178,7 @@ impl GameState {
|
|||
dice_jans: self.dice_jans.mirror(),
|
||||
roll_first: self.roll_first,
|
||||
schools_enabled: self.schools_enabled,
|
||||
debug_message: self.debug_message.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -596,6 +604,7 @@ impl GameState {
|
|||
dice_jans: PossibleJans::default(),
|
||||
roll_first: false, // Assume not first roll
|
||||
schools_enabled: false, // Assume disabled
|
||||
debug_message: "".into(), // Assume disabled
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue