add staging api

This commit is contained in:
2026-07-07 18:39:22 -07:00
parent 6f42e7d2bb
commit a511679a47
+87 -30
View File
@@ -5,25 +5,67 @@ struct ReadmeDoctests;
use core::ops::RangeBounds; use core::ops::RangeBounds;
// TODO: pub struct ValidInstruction<I>(I);
pub trait Game: Clone { pub trait Game: Clone {
type Score: Clone + core::fmt::Debug; type Score: Clone + core::fmt::Debug;
type Stats: Clone + core::fmt::Debug; type Stats: Clone + core::fmt::Debug;
type Config: Clone + core::fmt::Debug; type Config: Clone + core::fmt::Debug;
type Instruction: Clone + core::fmt::Debug; type Instruction: Clone + core::fmt::Debug;
fn score(&self, stats: &Self::Stats, config: &Self::Config) -> Self::Score; fn score(&self, stats: &Self::Stats, config: &Self::Config) -> Self::Score;
fn is_instruction_valid(&self, config: &Self::Config, instruction: &Self::Instruction) -> bool;
fn possible_instructions( fn possible_instructions(
&self, &self,
config: &Self::Config, config: &Self::Config,
) -> impl Iterator<Item = Self::Instruction> + use<Self>; ) -> impl Iterator<Item = Self::Instruction> + use<Self>;
fn is_instruction_valid(&self, config: &Self::Config, instruction: Self::Instruction) -> bool;
fn process_instruction(
&mut self,
stats: &mut Self::Stats,
config: &Self::Config,
instruction: Self::Instruction,
);
fn is_win(&self) -> bool; fn is_win(&self) -> bool;
fn stage_instruction<'a>(
&'a mut self,
stats: &'a mut Self::Stats,
config: &'a Self::Config,
instruction: Self::Instruction,
) -> Option<StagedInstruction<'a, Self>> {
if self.is_instruction_valid(config, &instruction) {
Some(StagedInstruction {
game: self,
stats,
config,
instruction,
})
} else {
None
}
}
}
/// Call .commit() to run the staged instruction.
pub struct StagedInstruction<'a, G: Game> {
game: &'a mut G,
stats: &'a mut G::Stats,
config: &'a G::Config,
instruction: G::Instruction,
}
impl<'a, G: Game> StagedInstruction<'a, G> {
pub fn consume(self) -> (&'a mut G, &'a mut G::Stats, &'a G::Config, G::Instruction) {
let StagedInstruction {
game,
stats,
config,
instruction,
} = self;
(game, stats, config, instruction)
}
}
impl<G: Game> StagedInstruction<'_, G>
where
G: InstructionConsumer,
{
pub fn commit(self) {
G::process_instruction(self);
}
}
/// Apply an atomic state update
pub trait InstructionConsumer: Game {
fn process_instruction(valid_instruction: StagedInstruction<'_, Self>);
} }
/// card_game supports up to 4 identifiably separate decks. /// card_game supports up to 4 identifiably separate decks.
@@ -515,6 +557,7 @@ where
G: Eq + core::hash::Hash, G: Eq + core::hash::Hash,
G::Stats: Default, G::Stats: Default,
G::Instruction: Eq + core::hash::Hash, G::Instruction: Eq + core::hash::Hash,
G: InstructionConsumer,
{ {
pub fn new(state: G, config: SessionConfig<G::Config>) -> Self { pub fn new(state: G, config: SessionConfig<G::Config>) -> Self {
Self { Self {
@@ -542,14 +585,21 @@ where
&self.state.history &self.state.history
} }
pub fn undo(&mut self) { pub fn undo(&mut self) {
self.state if let Some(staged) =
.process_instruction(&mut self.stats, &self.config, SessionInstruction::Undo) self.state
.stage_instruction(&mut self.stats, &self.config, SessionInstruction::Undo)
{
staged.commit();
}
} }
pub fn possible_instructions(&self) -> impl Iterator<Item = G::Instruction> + use<G> { pub fn possible_instructions(&self) -> impl Iterator<Item = G::Instruction> + use<G> {
self.state.state.possible_instructions(&self.config.inner) self.state.state.possible_instructions(&self.config.inner)
} }
pub fn process_instruction(&mut self, instruction: G::Instruction) { pub fn stage_instruction(
self.state.process_instruction( &mut self,
instruction: G::Instruction,
) -> Option<StagedInstruction<'_, SessionState<G>>> {
self.state.stage_instruction(
&mut self.stats, &mut self.stats,
&self.config, &self.config,
SessionInstruction::InnerInstruction(instruction), SessionInstruction::InnerInstruction(instruction),
@@ -583,7 +633,8 @@ where
// Run one possible move // Run one possible move
if let Some(instruction) = it.next() { if let Some(instruction) = it.next() {
session.process_instruction(instruction); // TODO: no unwrap
session.stage_instruction(instruction).unwrap().commit();
continue; continue;
} }
@@ -618,7 +669,7 @@ where
.possible_instructions(&config.inner) .possible_instructions(&config.inner)
.map(SessionInstruction::InnerInstruction) .map(SessionInstruction::InnerInstruction)
} }
fn is_instruction_valid(&self, config: &Self::Config, instruction: Self::Instruction) -> bool { fn is_instruction_valid(&self, config: &Self::Config, instruction: &Self::Instruction) -> bool {
match instruction { match instruction {
SessionInstruction::Undo => !self.history.is_empty(), SessionInstruction::Undo => !self.history.is_empty(),
SessionInstruction::InnerInstruction(instruction) => { SessionInstruction::InnerInstruction(instruction) => {
@@ -626,32 +677,36 @@ where
} }
} }
} }
fn process_instruction( fn is_win(&self) -> bool {
&mut self, self.state.is_win()
stats: &mut Self::Stats, }
config: &Self::Config, }
instruction: Self::Instruction, impl<G> InstructionConsumer for SessionState<G>
) { where
G: Game<Score = i32, Stats: Default>,
G: InstructionConsumer,
{
fn process_instruction(valid_instruction: StagedInstruction<'_, Self>) {
let (game, stats, config, instruction) = valid_instruction.consume();
match instruction { match instruction {
SessionInstruction::Undo => { SessionInstruction::Undo => {
if let Some(snapshot) = self.history.pop() { if let Some(snapshot) = game.history.pop() {
self.state = snapshot.state; game.state = snapshot.state;
stats.increment_undos(); stats.increment_undos();
} }
} }
SessionInstruction::InnerInstruction(instruction) => { SessionInstruction::InnerInstruction(instruction) => {
self.history.push(StateSnapshot { game.history.push(StateSnapshot {
state: self.state.clone(), state: game.state.clone(),
instruction: instruction.clone(), instruction: instruction.clone(),
}); });
self.state game.state
.process_instruction(&mut stats.inner, &config.inner, instruction); .stage_instruction(&mut stats.inner, &config.inner, instruction)
.unwrap()
.commit();
} }
} }
} }
fn is_win(&self) -> bool {
self.state.is_win()
}
} }
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
@@ -660,6 +715,7 @@ where
G: serde::Deserialize<'de> + Clone + Eq + core::hash::Hash, G: serde::Deserialize<'de> + Clone + Eq + core::hash::Hash,
G::Stats: Default, G::Stats: Default,
G::Instruction: serde::Deserialize<'de> + Eq + core::hash::Hash, G::Instruction: serde::Deserialize<'de> + Eq + core::hash::Hash,
G: InstructionConsumer,
SessionConfig<G::Config>: serde::Deserialize<'de>, SessionConfig<G::Config>: serde::Deserialize<'de>,
Vec<G::Instruction>: serde::Deserialize<'de>, Vec<G::Instruction>: serde::Deserialize<'de>,
{ {
@@ -681,7 +737,8 @@ where
let serialized = SerializedSession::deserialize(deserializer)?; let serialized = SerializedSession::deserialize(deserializer)?;
let mut session = Session::new(serialized.initial_state, serialized.config); let mut session = Session::new(serialized.initial_state, serialized.config);
for instruction in serialized.instructions { for instruction in serialized.instructions {
session.process_instruction(instruction); // TODO: no unwrap
session.stage_instruction(instruction).unwrap().commit();
} }
Ok(session) Ok(session)
} }