diff --git a/Cargo.lock b/Cargo.lock index 5cc44cf..f658daa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,7 +303,7 @@ dependencies = [ [[package]] name = "gfcore" -version = "0.0.1" +version = "0.0.2" dependencies = [ "cardpack", "console_error_panic_hook", diff --git a/Cargo.toml b/Cargo.toml index 06a31c8..a59d9f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "gfcore" description = "Go Fish card game engine" -version = "0.0.1" +version = "0.0.2" edition = "2024" rust-version = "1.85" license = "MIT OR Apache-2.0" diff --git a/src/game/state.rs b/src/game/state.rs index 245020a..74a71d5 100644 --- a/src/game/state.rs +++ b/src/game/state.rs @@ -401,6 +401,27 @@ impl Game { }) } + /// Like [`Self::state`] but always reveals `observer`'s hand regardless of whose + /// turn it is. Used by the WASM layer so the human's hand stays visible + /// throughout bot turns. + /// + /// # Errors + /// + /// Returns [`GfError::InvalidTarget`] if `observer` is out of range for the current player list. + pub fn state_as_observer(&self, observer: usize) -> Result { + let players = PlayerView::from_perspective(&self.players, observer)?; + + Ok(GameState { + phase: self.phase.clone(), + current_player: self.current_player, + players, + draw_pile_size: self.draw_pile.len(), + last_event: self.last_event.clone(), + winner: self.winner, + ask_log: self.ask_log.clone(), + }) + } + /// Returns the index of the player whose turn it is. /// /// # Examples diff --git a/src/wasm_api.rs b/src/wasm_api.rs index b11904f..ad6f40e 100644 --- a/src/wasm_api.rs +++ b/src/wasm_api.rs @@ -301,6 +301,26 @@ pub fn get_state() -> String { }) } +/// Like [`get_state`] but always returns state from player 0's perspective so +/// the human hand remains visible during bot turns. +#[must_use] +#[wasm_bindgen] +pub fn get_human_state() -> String { + GAME.with(|cell| { + let borrow = cell.borrow(); + match borrow.as_ref() { + None => error_json("no game in progress"), + Some(game) => match game.state_as_observer(0) { + Ok(s) => match serde_json::to_string(&s) { + Ok(j) => j, + Err(e) => error_json(&e.to_string()), + }, + Err(e) => error_json(&e.to_string()), + }, + } + }) +} + /// If the current player is a bot, computes and applies their action. /// /// Returns: @@ -359,6 +379,12 @@ pub fn step_bot() -> String { profile.decide(&hand, &state.players, &state.ask_log) }; + // Serialize the action before it is consumed by game.act(). + let action_json = match serde_json::to_string(&action) { + Ok(j) => j, + Err(e) => return error_json(&e.to_string()), + }; + // Apply the action. let event_json = GAME.with(|cell| { let mut borrow = cell.borrow_mut(); @@ -383,7 +409,9 @@ pub fn step_bot() -> String { return event_json; } - format!("{{\"done\":false,\"event\":{event_json}}}") + format!( + "{{\"done\":false,\"player\":{current_player},\"action\":{action_json},\"event\":{event_json}}}" + ) } /// Returns the full game history as YAML.