fix(client_web): pre-game : allow guest to roll die without waiting for host
This commit is contained in:
parent
6995f9c888
commit
87677a09b0
3 changed files with 27 additions and 34 deletions
|
|
@ -5,10 +5,10 @@ use futures::channel::mpsc::UnboundedSender;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use trictrac_store::{Board as StoreBoard, CheckerMove, Color, Dice as StoreDice, Jan, MoveRules};
|
use trictrac_store::{Board as StoreBoard, CheckerMove, Color, Dice as StoreDice, Jan, MoveRules};
|
||||||
|
|
||||||
|
use super::die::Die;
|
||||||
use crate::app::{GameUiState, NetCommand, PauseReason};
|
use crate::app::{GameUiState, NetCommand, PauseReason};
|
||||||
use crate::i18n::*;
|
use crate::i18n::*;
|
||||||
use crate::trictrac::types::{PlayerAction, PreGameRollState, SerStage, SerTurnStage};
|
use crate::trictrac::types::{PlayerAction, PreGameRollState, SerStage, SerTurnStage};
|
||||||
use super::die::Die;
|
|
||||||
|
|
||||||
use super::board::Board;
|
use super::board::Board;
|
||||||
use super::score_panel::PlayerScorePanel;
|
use super::score_panel::PlayerScorePanel;
|
||||||
|
|
@ -81,9 +81,8 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
|
||||||
// wait until the buffer is drained and the live screen state is shown.
|
// 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
|
// Guard: never auto-roll during the pre-game ceremony (the ceremony overlay
|
||||||
// has its own Roll button for PlayerAction::PreGameRoll).
|
// has its own Roll button for PlayerAction::PreGameRoll).
|
||||||
let show_roll = is_my_turn
|
let show_roll =
|
||||||
&& vs.turn_stage == SerTurnStage::RollDice
|
is_my_turn && vs.turn_stage == SerTurnStage::RollDice && vs.stage != SerStage::PreGameRoll;
|
||||||
&& vs.stage != SerStage::PreGameRoll;
|
|
||||||
if show_roll && !waiting_for_confirm {
|
if show_roll && !waiting_for_confirm {
|
||||||
let cmd_tx_auto = cmd_tx.clone();
|
let cmd_tx_auto = cmd_tx.clone();
|
||||||
Effect::new(move |_| {
|
Effect::new(move |_| {
|
||||||
|
|
@ -374,7 +373,7 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
|
||||||
});
|
});
|
||||||
let my_die = if player_id == 0 { pgr.host_die } else { pgr.guest_die };
|
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 opp_die = if player_id == 0 { pgr.guest_die } else { pgr.host_die };
|
||||||
let can_roll = is_my_turn && !waiting_for_confirm;
|
let can_roll = my_die.is_none() && !waiting_for_confirm;
|
||||||
let show_tie = pgr.tie_count > 0;
|
let show_tie = pgr.tie_count > 0;
|
||||||
view! {
|
view! {
|
||||||
<div class="ceremony-overlay">
|
<div class="ceremony-overlay">
|
||||||
|
|
@ -385,7 +384,7 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
|
||||||
})}
|
})}
|
||||||
<div class="ceremony-dice">
|
<div class="ceremony-dice">
|
||||||
<div class="ceremony-die-slot">
|
<div class="ceremony-die-slot">
|
||||||
<span class="ceremony-die-label">{my_name_ceremony}</span>
|
<span class="ceremony-die-label">{my_name_ceremony}{t!(i18n, you_suffix)}</span>
|
||||||
<Die value=my_die.unwrap_or(0) used=false />
|
<Die value=my_die.unwrap_or(0) used=false />
|
||||||
</div>
|
</div>
|
||||||
<div class="ceremony-die-slot">
|
<div class="ceremony-die-slot">
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,8 @@ impl TrictracBackend {
|
||||||
guest_die: self.pre_game_dice[1],
|
guest_die: self.pre_game_dice[1],
|
||||||
tie_count: self.tie_count,
|
tie_count: self.tie_count,
|
||||||
});
|
});
|
||||||
// The active mp player is whoever hasn't rolled yet (host rolls first).
|
// Both players roll independently; no single "active" player.
|
||||||
vs.active_mp_player = match self.pre_game_dice {
|
vs.active_mp_player = None;
|
||||||
[None, _] => Some(0),
|
|
||||||
[Some(_), None] => Some(1),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
self.view_state = vs;
|
self.view_state = vs;
|
||||||
}
|
}
|
||||||
|
|
@ -52,16 +48,11 @@ impl TrictracBackend {
|
||||||
|
|
||||||
/// Process one ceremony die-roll for `mp_player` (0 = host, 1 = guest).
|
/// Process one ceremony die-roll for `mp_player` (0 = host, 1 = guest).
|
||||||
fn handle_pre_game_roll(&mut self, mp_player: u16) {
|
fn handle_pre_game_roll(&mut self, mp_player: u16) {
|
||||||
// Enforce turn order: host rolls first, then guest.
|
let idx = mp_player as usize;
|
||||||
let expected: u16 = match self.pre_game_dice {
|
// Ignore if this player already rolled.
|
||||||
[None, _] => 0,
|
if self.pre_game_dice[idx].is_some() {
|
||||||
[Some(_), None] => 1,
|
|
||||||
_ => return, // both already rolled (shouldn't happen)
|
|
||||||
};
|
|
||||||
if mp_player != expected {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let idx = mp_player as usize;
|
|
||||||
let single = self.dice_roller.roll().values.0;
|
let single = self.dice_roller.roll().values.0;
|
||||||
self.pre_game_dice[idx] = Some(single);
|
self.pre_game_dice[idx] = Some(single);
|
||||||
|
|
||||||
|
|
@ -132,8 +123,8 @@ impl TrictracBackend {
|
||||||
impl BackEndArchitecture<PlayerAction, GameDelta, ViewState> for TrictracBackend {
|
impl BackEndArchitecture<PlayerAction, GameDelta, ViewState> for TrictracBackend {
|
||||||
fn new(_rule_variation: u16) -> Self {
|
fn new(_rule_variation: u16) -> Self {
|
||||||
let mut game = GameState::new(false);
|
let mut game = GameState::new(false);
|
||||||
game.init_player("Host");
|
game.init_player("Blancs");
|
||||||
game.init_player("Guest");
|
game.init_player("Noirs");
|
||||||
|
|
||||||
let view_state = ViewState::from_game_state(&game, HOST_PLAYER_ID, GUEST_PLAYER_ID);
|
let view_state = ViewState::from_game_state(&game, HOST_PLAYER_ID, GUEST_PLAYER_ID);
|
||||||
|
|
||||||
|
|
@ -309,11 +300,14 @@ mod tests {
|
||||||
if b.get_view_state().stage != SerStage::PreGameRoll {
|
if b.get_view_state().stage != SerStage::PreGameRoll {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
match b.get_view_state().active_mp_player {
|
let pgr = b.get_view_state().pre_game_roll.clone().unwrap_or_default();
|
||||||
Some(0) => b.inform_rpc(0, PlayerAction::PreGameRoll),
|
let host_needs = pgr.host_die.is_none();
|
||||||
Some(1) => b.inform_rpc(1, PlayerAction::PreGameRoll),
|
let guest_needs = pgr.guest_die.is_none();
|
||||||
_ => break,
|
if !host_needs && !guest_needs {
|
||||||
|
break; // both rolled but stage not yet resolved — shouldn't happen
|
||||||
}
|
}
|
||||||
|
if host_needs { b.inform_rpc(0, PlayerAction::PreGameRoll); }
|
||||||
|
if guest_needs { b.inform_rpc(1, PlayerAction::PreGameRoll); }
|
||||||
b.drain_commands();
|
b.drain_commands();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -349,19 +343,19 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ceremony_wrong_order_ignored() {
|
fn ceremony_any_order_allowed() {
|
||||||
let mut b = make_backend();
|
let mut b = make_backend();
|
||||||
b.player_arrival(0);
|
b.player_arrival(0);
|
||||||
b.player_arrival(1);
|
b.player_arrival(1);
|
||||||
b.drain_commands();
|
b.drain_commands();
|
||||||
|
|
||||||
// Guest tries to roll before host (host goes first in ceremony).
|
// Guest may roll before host.
|
||||||
b.inform_rpc(1, PlayerAction::PreGameRoll);
|
b.inform_rpc(1, PlayerAction::PreGameRoll);
|
||||||
let cmds = b.drain_commands();
|
let states = drain_deltas(&mut b);
|
||||||
assert!(
|
assert!(!states.is_empty(), "guest PreGameRoll should broadcast a state");
|
||||||
cmds.is_empty(),
|
let pgr = states.last().unwrap().pre_game_roll.as_ref().unwrap();
|
||||||
"guest PreGameRoll should be ignored when it is host's turn"
|
assert!(pgr.guest_die.is_some(), "guest die should be set after guest rolls");
|
||||||
);
|
assert!(pgr.host_die.is_none(), "host die should still be blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ pub struct GameDelta {
|
||||||
|
|
||||||
/// State of the pre-game ceremony where each player rolls one die to decide
|
/// State of the pre-game ceremony where each player rolls one die to decide
|
||||||
/// who goes first. Present only when `stage == SerStage::PreGameRoll`.
|
/// who goes first. Present only when `stage == SerStage::PreGameRoll`.
|
||||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct PreGameRollState {
|
pub struct PreGameRollState {
|
||||||
/// Die value (1–6) rolled by the host; `None` = not yet rolled this round.
|
/// Die value (1–6) rolled by the host; `None` = not yet rolled this round.
|
||||||
pub host_die: Option<u8>,
|
pub host_die: Option<u8>,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue