Skip to main content

sokoban_elements/game_board/
errors.rs

1use std::error::Error;
2use std::fmt::{Display, Formatter};
3
4use crate::{BoxID, GoalID, PusherID};
5
6/// Variants of [`BoardSyntaxError`].
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum BoardSyntaxErrorKind {
9    /// Failed to expand run length encoded game board string
10    RleDecodingFailed,
11
12    /// Couldn't convert `input` found on row `y` and column `x` into [`TileKind`](crate::TileKind).
13    IllegalCharacter {
14        input: char,
15        x:     usize,
16        y:     usize,
17    },
18
19    /// Layout of parsed [`Hexoban`](crate::Tessellation::Hexoban) game board doesn't match any of
20    /// supported layout schemes.
21    InvalidHexobanLayout { x: usize, y: usize },
22
23    /// When creating game board from [`TileKind`](crate::TileKind) slice, length of provided slice
24    /// doesn't match declared size of final game board.
25    InputSizeNotMatchedByDeclaredSize {
26        input:           usize,
27        declared_width:  usize,
28        declared_height: usize,
29    },
30
31    /// Game board contains more boxes than [`BoxID`](crate::BoxID) can represent
32    /// (`BoxID::MAX + 1`).
33    TooManyBoxes,
34
35    /// Game board contains more goals than [`GoalID`](crate::GoalID) can represent
36    /// (`GoalID::MAX + 1`).
37    TooManyGoals,
38
39    /// Game board contains more pushers than [`PusherID`](crate::PusherID) can represent
40    /// (`PusherID::MAX + 1`).
41    TooManyPushers,
42}
43
44#[derive(Debug)]
45struct ErrorPayload {
46    kind:   BoardSyntaxErrorKind,
47    source: Option<Box<dyn Error>>,
48}
49
50/// [`std::Error`](std::error::Error) when parsing [`GameBoard`](crate::GameBoard).
51#[derive(Debug)]
52pub struct BoardSyntaxError(Box<ErrorPayload>);
53
54impl BoardSyntaxError {
55    pub fn kind(&self) -> &BoardSyntaxErrorKind { &self.0.kind }
56
57    pub fn new(kind: BoardSyntaxErrorKind) -> Self {
58        Self(Box::new(ErrorPayload { kind, source: None }))
59    }
60
61    pub fn new_with_source<E>(kind: BoardSyntaxErrorKind, source: Option<E>) -> Self
62    where
63        E: Into<Box<dyn Error + Send + Sync>>,
64    {
65        Self(Box::new(ErrorPayload {
66            kind,
67            source: match source {
68                Some(e) => Some(e.into()),
69                None => None,
70            },
71        }))
72    }
73}
74
75impl Display for BoardSyntaxError {
76    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
77        match &self.0.kind {
78            BoardSyntaxErrorKind::RleDecodingFailed => {
79                write!(f, "failed to expand run length encoded game board string")
80            }
81
82            BoardSyntaxErrorKind::IllegalCharacter { input: value, x, y } => {
83                write!(
84                    f,
85                    "parsing game board encountered invalid character '{value}' in \
86                    row {y} at col {x}"
87                )
88            }
89
90            BoardSyntaxErrorKind::InvalidHexobanLayout { x, y } => {
91                write!(
92                    f,
93                    "invalid Hexoban textual board layout. Character at ({x}, {y}) is not \
94                    consistent with the rest of the board. Hexoban boards represent each tile \
95                    with two characters, either space+tile or tile+space. This encoding scheme \
96                    must be used for the whole board consistently."
97                )
98            }
99
100            BoardSyntaxErrorKind::InputSizeNotMatchedByDeclaredSize {
101                input,
102                declared_width,
103                declared_height,
104            } => {
105                write!(
106                    f,
107                    "length of input 1D sequence of tiles ({input}) doesn't match declared 2D \
108                    board W x H ({declared_width} x {declared_height})"
109                )
110            }
111
112            BoardSyntaxErrorKind::TooManyBoxes => {
113                write!(
114                    f,
115                    "game board contains more boxes than supported maximum of {}",
116                    u32::from(BoxID::MAX) + 1
117                )
118            }
119
120            BoardSyntaxErrorKind::TooManyGoals => {
121                write!(
122                    f,
123                    "game board contains more goals than supported maximum of {}",
124                    u32::from(GoalID::MAX) + 1
125                )
126            }
127
128            BoardSyntaxErrorKind::TooManyPushers => {
129                write!(
130                    f,
131                    "game board contains more pushers than supported maximum of {}",
132                    u32::from(PusherID::MAX) + 1
133                )
134            }
135        }
136    }
137}
138
139impl Error for BoardSyntaxError {
140    fn source(&self) -> Option<&(dyn Error + 'static)> {
141        match &self.0.source {
142            Some(ref_box_dyn) => Some(ref_box_dyn.as_ref()),
143            None => None,
144        }
145    }
146}