Skip to main content

pencil_puzzles_core/
view.rs

1//! A rectangular view for browser clients (I4, P10): cells, outside labels
2//! and links between cells.
3/// A cell or link mark offered by a genre.
4#[derive(Clone, Debug)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize))]
6#[non_exhaustive]
7pub struct MarkOption {
8    /// Stable value used when submitting answers.
9    pub value: String,
10    /// Human-readable accessible name.
11    pub name: String,
12    /// Whether the cell is filled rather than displaying a text symbol.
13    pub shaded: bool,
14    /// Symbol drawn in the cell; empty for an unmarked cell.
15    pub symbol: String,
16    /// For a link mark, how many parallel lines to draw (0 for none).
17    pub lines: u32,
18}
19impl MarkOption {
20    /// A shaded cell mark.
21    pub fn shaded(value: &str, name: &str) -> Self {
22        let mut mark = Self::new(value, name, "");
23        mark.shaded = true;
24        mark
25    }
26
27    /// A mark with its accessible name and displayed symbol.
28    pub fn new(value: &str, name: &str, symbol: &str) -> Self {
29        Self {
30            shaded: false,
31            value: value.into(),
32            name: name.into(),
33            symbol: symbol.into(),
34            lines: 0,
35        }
36    }
37
38    /// A link mark drawn as `lines` parallel lines.
39    pub fn link(value: &str, name: &str, lines: u32) -> Self {
40        let mut mark = Self::new(value, name, "");
41        mark.lines = lines;
42        mark
43    }
44}
45/// One cell in row-major order.
46#[derive(Clone, Debug)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize))]
48#[non_exhaustive]
49pub struct ViewCell {
50    /// Current mark or clue value.
51    pub value: String,
52    /// Optional region index for highlighting rule violations.
53    pub region: Option<u32>,
54    /// Whether a player may change the cell.
55    pub editable: bool,
56    /// Whether the value is drawn in a circle, such as a Hashi island.
57    pub circled: bool,
58    /// Text printed in the cell whatever its mark, such as a Hitori number;
59    /// empty for none. Marks are drawn around it.
60    pub label: String,
61    /// Drawn as a block instead of a mark, as Kakuro clue cells are.
62    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
63    pub block: Option<BlockCell>,
64}
65impl ViewCell {
66    /// An editable cell with the given mark.
67    pub fn editable(value: String) -> Self {
68        Self {
69            value,
70            editable: true,
71            region: None,
72            circled: false,
73            label: String::new(),
74            block: None,
75        }
76    }
77
78    /// A block cell that players cannot change, holding `value` in answers.
79    pub fn block(value: String, block: BlockCell) -> Self {
80        Self {
81            editable: false,
82            block: Some(block),
83            ..Self::editable(value)
84        }
85    }
86}
87/// A shaded cell that holds no mark. When either label is non-empty, a
88/// diagonal from the top-left to the bottom-right corner splits it, with
89/// `upper` in the top-right half and `lower` in the bottom-left half.
90#[derive(Clone, Debug)]
91#[cfg_attr(feature = "serde", derive(serde::Serialize))]
92#[non_exhaustive]
93pub struct BlockCell {
94    /// Label in the top-right half; empty for none.
95    pub upper: String,
96    /// Label in the bottom-left half; empty for none.
97    pub lower: String,
98    /// Accessible name, such as `down 4, across 6`.
99    pub name: String,
100}
101impl BlockCell {
102    /// A block with two labels and an accessible name.
103    pub fn new(upper: String, lower: String, name: String) -> Self {
104        Self { upper, lower, name }
105    }
106}
107/// A straight link between the centres of two cells in the same row or
108/// column, such as a Hashi bridge.
109#[derive(Clone, Debug)]
110#[cfg_attr(feature = "serde", derive(serde::Serialize))]
111#[non_exhaustive]
112pub struct ViewLink {
113    /// The link's two cells; the first comes first in row-major order.
114    pub place: crate::Place,
115    /// Current mark, one of [`View::link_marks`].
116    pub value: String,
117    /// Whether a player may change the link.
118    pub editable: bool,
119}
120impl ViewLink {
121    /// An editable link between two cells, given as (row, column).
122    pub fn editable(from: (u32, u32), to: (u32, u32), value: String) -> Self {
123        Self {
124            place: crate::Place::Link {
125                row: from.0,
126                col: from.1,
127                row2: to.0,
128                col2: to.1,
129            },
130            value,
131            editable: true,
132        }
133    }
134}
135/// A rectangular board with labels outside its four sides and links
136/// between cells (I4).
137///
138/// Clients send marks back as the cell values in row-major order followed
139/// by the link values in order. Further layers, such as cell borders, can be
140/// added when a genre needs them.
141#[derive(Clone, Debug, Default)]
142#[cfg_attr(feature = "serde", derive(serde::Serialize))]
143#[non_exhaustive]
144pub struct View {
145    /// Number of rows.
146    pub rows: u32,
147    /// Number of columns.
148    pub cols: u32,
149    /// Cells in row-major order.
150    pub cells: Vec<ViewCell>,
151    /// Column labels above the board.
152    pub top: Vec<String>,
153    /// Row clues to the right.
154    pub right: Vec<String>,
155    /// Column clues below the board.
156    pub bottom: Vec<String>,
157    /// Row labels to the left.
158    pub left: Vec<String>,
159    /// Available marks in cycling order, unmarked first.
160    pub marks: Vec<MarkOption>,
161    /// Links between cells, in the genre's order.
162    pub links: Vec<ViewLink>,
163    /// Available link marks in cycling order, unmarked first.
164    pub link_marks: Vec<MarkOption>,
165    /// Row indices with a strong boundary before them.
166    pub row_separators: Vec<u32>,
167    /// Column indices with a strong boundary before them.
168    pub col_separators: Vec<u32>,
169    /// What happens to unmarked cells when checking a finished answer.
170    pub finish_note: String,
171    /// Short instructions for the displayed rules, one rule per line.
172    pub instructions: String,
173}
174
175/// A numeric clue input in a visual puzzle editor.
176#[derive(Clone, Debug)]
177#[cfg_attr(feature = "serde", derive(serde::Serialize))]
178#[non_exhaustive]
179pub struct ClueInput {
180    /// The cell, row or column carrying the clue.
181    pub place: crate::Place,
182    /// Current text, empty when no clue has been entered.
183    pub value: String,
184    /// Smallest accepted clue.
185    pub min: u32,
186    /// Largest accepted clue.
187    pub max: u32,
188    /// Whether an empty field is a valid absent clue.
189    pub optional: bool,
190    /// For a text field instead of a number: the pattern a value must match
191    /// (as in HTML), with `min` and `max` unused.
192    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
193    pub pattern: Option<String>,
194}
195impl ClueInput {
196    /// A bounded numeric clue input.
197    pub fn new(place: crate::Place, value: String, min: u32, max: u32, optional: bool) -> Self {
198        Self {
199            place,
200            value,
201            min,
202            max,
203            optional,
204            pattern: None,
205        }
206    }
207
208    /// A text field whose value must match `pattern` (as in HTML).
209    pub fn text(place: crate::Place, value: String, pattern: &str, optional: bool) -> Self {
210        Self {
211            place,
212            value,
213            min: 0,
214            max: 0,
215            optional,
216            pattern: Some(pattern.into()),
217        }
218    }
219}
220/// A visual clue editor supplied by a genre, independent of browser code.
221#[derive(Clone, Debug)]
222#[cfg_attr(feature = "serde", derive(serde::Serialize))]
223#[non_exhaustive]
224pub struct PuzzleEditor {
225    /// Stable shape encoding used to submit the fields.
226    pub shape: String,
227    /// Background grid, labels and region boundaries.
228    pub view: View,
229    /// Inputs in the genre's canonical order.
230    pub fields: Vec<ClueInput>,
231}
232impl PuzzleEditor {
233    /// A visual clue editor for a validated shape.
234    pub fn new(shape: String, view: View, fields: Vec<ClueInput>) -> Self {
235        Self {
236            shape,
237            view,
238            fields,
239        }
240    }
241}