add staging api
This commit is contained in:
+87
-30
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user