pub struct SavedGame {
pub title: String,
pub solved_by: String,
pub notes: String,
pub current_move_idx: usize,
/* private fields */
}Expand description
Sequence of pusher steps with accompanying metadata (ie. title, author, etc..).
Besides movement direction, SavedGame also represents:
- forward or reverse
SolvingMode - pusher jumps (in reverse solving mode)
- pusher selection steps on boards with multiple pushers (
Multiban)
A saved game is tied to the physical board it was recorded against. Mirroring, rotating,
resizing, or trimming that board can invalidate movement directions and piece positions. The
mismatch is not detected automatically and not enforced anywhere.
GameBoard and SavedGame are separate structures, and the client is
expected to maintain the relationship between them.
§Examples
let saved_game = SavedGame::parsed("llRRddsssSSSnNN", Tessellation::Hexoban)?;
let saved_game = SavedGame::parsed("nlLNrr", Tessellation::Trioban)?;
assert_eq!(saved_game[3].direction(), Direction::NorthEast);
assert_eq!(saved_game[3], Step::PushOrPull(Direction::NorthEast));§Editing
SavedGame is not editable in a sense where Steps can be added, removed, inserted or
modified. This is by design. This type is meant for archival purposes, as an input of Sokoban
data to writers and as output of Sokoban data readers. Game engines and solvers will need to use
their own structures to track movement history. At the end of solving session, they may use
SavedGame to archive progress of that session.
§Step counts, and the metrics that are not here
moves_count() and pushes_count()
are facts about the step sequence, so they live here.
The rest of the standard metric set does not. Box lines and box changes need to know which box
each push moved, and no saved game text format encodes box identity, so they can only be had by
replaying the steps against a GameBoard. Replaying a game is playing it
and that is out of scope for this crate.
§Solutions
Whether a SavedGame solves a board, how far it replays on one, and what its
SolvingMode::Reverse solution looks like played forwards are all questions about playing
rather than about what is recorded, so again - out of scope for this crate. Nothing here plays,
validates or edits a game: game engines and solvers keep their own board state, and are expected
to track these as they go rather than replay a SavedGame to recover them.
§Serde
With the serde feature, SavedGame is a map whose steps are the same text a .sok file would
carry:
{
"tessellation": "Sokoban",
"moves": "[]LuR",
"title": "",
"solved_by": "",
"notes": "",
"current_move_idx": 0
}There is deliberately no solving_mode field. SolvingMode::Reverse is always encoded in the
text itself as a leading jump section ([] when the game does not open with a jump), so the
moves are the single source of truth and no document can declare a mode contradicting its own
steps. A solving_mode key is rejected rather than silently ignored, as are all other unknown
fields.
Everything except tessellation and moves may be omitted. current_move_idx is special: the
parser reads it from a * marker inside the moves text, so omitting the field keeps whatever
the text says, while providing it overrides that. An empty moves string is the empty
SolvingMode::Forward game (an empty reverse game prints as "[]").
use sokoban_elements::*;
let saved_game = SavedGame::parsed("lurd", Tessellation::Sokoban)?;
let json = serde_json::to_string(&saved_game)?;
assert_eq!(serde_json::from_str::<SavedGame>(&json)?, saved_game);Fields§
§title: String§solved_by: String§notes: String§current_move_idx: usizeIndex of currently played move from steps().
This can be found in some sokoban file formats so SavedGame has a place for it. On the
other hand, SavedGame doesn’t use it in any way, so it doesn’t validate it against
steps() either. SavedGame clients are advised to maintain and
validate this value themselves.
Implementations§
Source§impl SavedGame
impl SavedGame
Sourcepub fn parsed(
data: &str,
tessellation: Tessellation,
) -> Result<Self, SavedGameSyntaxError>
pub fn parsed( data: &str, tessellation: Tessellation, ) -> Result<Self, SavedGameSyntaxError>
Constructs SavedGame from parsed string of moves in context of
Tessellation.
§Examples
let saved_game: SavedGame = SavedGame::parsed("lurdLurd", Tessellation::Sokoban)?;§Grammar
Save game strings use following characters:
| char | |
|---|---|
| Move pusher | ‘l’, ‘u’, ‘r’, ‘d’, ‘w’, ‘e’, ‘n’, ‘s’ |
| Push/pull box | ‘L’, ‘U’, ‘R’, ‘D’, ‘W’, ‘E’, ‘N’, ‘S’ |
| Begin jump | ‘[’ |
| End jump | ‘]’ |
| Begin pusher change | ‘{’ |
| End pusher change | ‘{’ |
| Current position | ‘*’ |
Mapping between characters and movement direction depends on
Tessellation context and is not the same in every
Tessellation. Also, not all Tessellations
use all movement characters, so this method may return error if it finds legal movement
character but in wrong game tessellation context. Ie.
Sokoban will return error for character 'W'.
SavedGame strings mostly follow SokobanYASC
specification:
::::::::::::::::::::::::::: Moves ::::::::::::::::::::::::::
:: ::
:: Remarks: ::
:: ::
:: Moves may be run-length encoded, e.g., "3r4U" means ::
:: "rrrUUUU", and "2(3(dr)R)" means "drdrdrRdrdrdrR". ::
:: Each line must, however, have at least one proper ::
:: non-digit character. Spaces between moves are ::
:: allowed. ::
:: ::
:: Jumps and pulls: Only in reverse mode saved games ::
:: and solutions. ::
:: ::
:: Reverse mode saved games and solutions must begin ::
:: with a jump, even if it is empty. An example: ::
:: "[]U[rr]d". ::
:: ::
:: Pusher changes: Only in puzzles with multiple ::
:: pushers, e.g., Multiban. Moves inside the braces ::
:: depict the relative movement to get from the ::
:: currently active pusher to the next active pusher. ::
:: At game start, a "{...}" sequence activates the ::
:: pusher relative to the top-left pusher. An example: ::
:: "{rddd}Urr{uul}uLU". If the top-left pusher is the ::
:: first active pusher, then the empty "{}" can be ::
:: omitted. ::
:: ::
:: The current position is optional and defaults to the ::
:: position after the last move. ::
:: ::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::Sourcepub fn from_steps(
data: &[Step],
tessellation: Tessellation,
solving_mode: Option<SolvingMode>,
) -> Result<Self, SavedGameSyntaxError>
pub fn from_steps( data: &[Step], tessellation: Tessellation, solving_mode: Option<SolvingMode>, ) -> Result<Self, SavedGameSyntaxError>
Constructs new SavedGame from sequence of Step, validating each of them in context
of given game tessellation.
- if
datacontainsStep::Jump, marks createdSavedGameasSolvingMode::Reverse - if
datadoesn’t containStep::Jumpuses value ofsolving_modeargument
§Examples
use sokoban_elements::*;
let moves = vec![
Step::Move(Direction::Down),
Step::PushOrPull(Direction::Down),
Step::PushOrPull(Direction::Down),
Step::Jump(Direction::Down),
Step::Jump(Direction::Down),
Step::Move(Direction::Down),
];
let saved_game: SavedGame =
SavedGame::from_steps(&moves, Tessellation::Sokoban, Some(SolvingMode::Forward))?;
assert_eq!(saved_game.solving_mode(), SolvingMode::Reverse);Sourcepub fn printed_steps(&self) -> String
pub fn printed_steps(&self) -> String
Serializes steps() into saved game string.
Sourcepub fn solving_mode(&self) -> SolvingMode
pub fn solving_mode(&self) -> SolvingMode
SolvingMode this SavedGame was recorded in.
Sourcepub fn tessellation(&self) -> Tessellation
pub fn tessellation(&self) -> Tessellation
Tessellation this SavedGame was recorded against.
Sourcepub fn moves_count(&self) -> usize
pub fn moves_count(&self) -> usize
Total number of Step::Move steps in this SavedGame.
Sourcepub fn pushes_count(&self) -> usize
pub fn pushes_count(&self) -> usize
Total number of Step::PushOrPull steps in this SavedGame.
Trait Implementations§
Source§impl<'de> Deserialize<'de> for SavedGame
impl<'de> Deserialize<'de> for SavedGame
Source§fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>
impl StructuralPartialEq for SavedGame
Auto Trait Implementations§
impl Freeze for SavedGame
impl RefUnwindSafe for SavedGame
impl Send for SavedGame
impl Sync for SavedGame
impl Unpin for SavedGame
impl UnsafeUnpin for SavedGame
impl UnwindSafe for SavedGame
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> FmtForward for T
impl<T> FmtForward for T
Source§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.Source§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.Source§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.Source§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.Source§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.Source§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.Source§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.Source§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Source§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
Source§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
Source§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.Source§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.Source§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
Source§impl<T> Tap for T
impl<T> Tap for T
Source§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read moreSource§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read moreSource§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read moreSource§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read moreSource§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.Source§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.Source§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.Source§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.Source§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.