1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! Error types and utilities for sokoban puzzles.
use crate::{Direction, State, Tile};
use std::io::Error;
use thiserror::Error;
/// Primary error type for Sokoban, which enumerates all potential error conditions for a puzzle
/// and puzzle solution.
#[derive(Error, Debug)]
#[allow(clippy::module_name_repetitions)]
pub enum SokobanError {
/// The puzzle generated or parsed had an invalid map backing for the provided dimensions. The
/// map backing should always be `(number of rows)*(number of columns)` in length.
#[error("Invalid bounds; got a raw Vec of length {}, but should have been {} ({} * {}).", .found, .r *.c, .r, .c)]
InvalidBounds {
/// The observed dimension of the provided map backing.
found: usize,
/// The number of rows specified.
r: usize,
/// The number of columns specified.
c: usize,
},
/// The puzzle generated or parsed had a player at an invalid position, such as inside a wall,
/// intersecting with a crate, or out of the bounds of the puzzle.
#[error("Invalid starting player position; found a {:?} where I needed a _ at ({}, {}).", .found, .r, .c)]
InvalidStartingPosition {
/// The tile observed at the position, or `None` if the position specified was out of
/// bounds.
found: Option<Tile>,
/// The row index specified in the starting position.
r: usize,
/// The column index specified in the starting position.
c: usize,
},
/// The puzzle generated or parsed had a target at an invalid position, such as inside a wall or
/// out of the bounds of the puzzle.
#[error("Invalid target position; found a {:?} where I needed a _ at ({}, {}).", .found, .r, .c)]
InvalidTargetPosition {
/// The tile observed at the position, or `None` if the position specified was out of
/// bounds.
found: Option<Tile>,
/// The row index specified for the target's position.
r: usize,
/// The column index specified for the target's position.
c: usize,
},
/// The move, if applied to the current state, would have led to the player ending up in a wall.
#[error("Invalid player move; couldn't move into the wall at ({}, {}).", .r, .c)]
InvalidMoveWall {
/// The state before the move, such that the user can recover and attempt another move.
last_state: State,
/// The row index the player attempted to move into.
r: usize,
/// The column index the player attempted to move into.
c: usize,
/// The direction in which the player attempted to move.
dir: Direction,
},
/// The move, if applied to the current state, would have pushed a crate into an invalid
/// position, such as into a wall, intersecting with another crate, or pushing the crate out of
/// bounds.
#[error("Invalid player move; couldn't move into the crate at ({}, {}) because the position behind it was blocked or out-of-bounds.", .r, .c)]
InvalidMoveCrate {
/// The state before the move, such that the user can recover and attempt another move.
last_state: State,
/// The row index of the crate that the player attempted to move.
r: usize,
/// The column index of the crate that the player attempted to move.
c: usize,
/// The direction in which the player attempted to move the crate.
dir: Direction,
},
/// The move, if applied to the current state, would have led to the player ending up in a wall.
/// Note that, unlike other move error variants, this returns the starting position of the
/// player as some moves would result in unrepresentable positions.
#[error("Invalid player move; couldn't move {:?} out of bounds at ({}, {}).", .dir, .r, .c)]
InvalidMoveOOB {
/// The state before the move, such that the user can recover and attempt another move.
last_state: State,
/// The row index the player started from.
r: usize,
/// The column index the player started from.
c: usize,
/// The direction in which the player attempted to move.
dir: Direction,
},
/// During parsing, we encountered an invalid character.
#[error("Encountered invalid character while parsing: {} ({}, {})", .found, .r, .c)]
InvalidChar {
/// The character that was discovered.
found: char,
/// The row index on the map associated with the invalid character.
r: usize,
/// The column index on the map associated with the invalid character.
c: usize,
},
/// During parsing, we encountered a line which had a length that was greater or less than the
/// number of columns expected for the currently parsed map.
#[error("Encountered inconsistent dimensions while parsing. Expected {}, found {}.", .expected, .found)]
InconsistentDimensions {
/// The line number associated with the error.
line: usize,
/// The expected number of columns for the map, as derived from the first non-empty line.
expected: usize,
/// The number of columns found in this line.
found: usize,
},
/// After parsing, we found that there was no player for the provided map.
#[error("Finished parsing, but found no player!")]
MissingPlayer,
/// During parsing, multiple players were found.
#[error("Multiple players found in the map ({:?}, {:?})", .first, .second)]
MultiplePlayers {
/// The position of the first player in (r, c) coordinates.
first: (usize, usize),
/// The position of the second player in (r, c) coordinates.
second: (usize, usize),
},
/// During parsing, an IO error occurred.
#[error("Encountered IO error while parsing: {}", .inner)]
IOError {
/// The wrapped IO error.
inner: std::io::Error,
},
}
impl From<std::io::Error> for SokobanError {
fn from(inner: Error) -> Self {
Self::IOError { inner }
}
}
/// Result type for sokoban puzzles and solutions.
pub type SokobanResult<T> = Result<T, SokobanError>;