sokoban_elements/
puzzle.rs1use crate::{GameBoard, SavedGame, SokobanPlus};
2
3#[derive(Debug, Clone, PartialEq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct Puzzle {
22 pub board: GameBoard,
23 #[cfg_attr(feature = "serde", serde(default))]
24 pub title: String,
25 #[cfg_attr(feature = "serde", serde(default))]
26 pub author: String,
27 #[cfg_attr(feature = "serde", serde(default))]
28 pub notes: String,
29 #[cfg_attr(feature = "serde", serde(default))]
30 pub sokoban_plus: Option<SokobanPlus>,
31 #[cfg_attr(feature = "serde", serde(default))]
40 pub saved_games: Vec<SavedGame>,
41}
42
43#[derive(Debug, Default, Clone, PartialEq)]
45#[cfg_attr(
46 feature = "serde",
47 derive(serde::Serialize, serde::Deserialize),
48 serde(default)
49)]
50pub struct Collection {
51 pub title: String,
52 pub author: String,
53 pub created_at: String,
54 pub updated_at: String,
55 pub email: String,
56 pub url: String,
57 pub notes: String,
58 pub puzzles: Vec<Puzzle>,
59}
60
61impl Collection {
62 pub fn new() -> Self {
63 Self {
64 title: String::new(),
65 author: String::new(),
66 created_at: String::new(),
67 updated_at: String::new(),
68 email: String::new(),
69 url: String::new(),
70 notes: String::new(),
71 puzzles: Vec::new(),
72 }
73 }
74}
75
76#[cfg(all(test, feature = "serde"))]
77mod tests {
78 use std::error::Error;
79
80 use super::*;
81 use crate::{BoxID, PlusID, Tessellation};
82
83 fn puzzle() -> Result<Puzzle, Box<dyn Error>> {
84 let mut sokoban_plus = SokobanPlus::new();
85 sokoban_plus.set_box_plus_id(BoxID::from(0_u8), Some(PlusID::new(1)?));
86
87 Ok(Puzzle {
88 board: GameBoard::parsed("#####|#@$.#|#####", Tessellation::Sokoban)?,
89 title: "Puzzle 1".to_string(),
90 author: "Tomislav".to_string(),
91 notes: "First one".to_string(),
92 saved_games: vec![SavedGame::parsed("[]LuR", Tessellation::Sokoban)?],
93 sokoban_plus: Some(sokoban_plus),
94 })
95 }
96
97 #[test]
98 fn it_round_trips_collections_through_json() -> Result<(), Box<dyn Error>> {
99 let mut collection = Collection::new();
100 collection.title = "Test collection".to_string();
101 collection.author = "Tomislav".to_string();
102 collection.puzzles = vec![
103 puzzle()?,
104 Puzzle {
105 board: GameBoard::parsed("---#-#-#-", Tessellation::Hexoban)?,
106 title: String::new(),
107 author: String::new(),
108 notes: String::new(),
109 saved_games: Vec::new(),
110 sokoban_plus: None,
111 },
112 ];
113
114 let json = serde_json::to_string(&collection)?;
115
116 assert_eq!(serde_json::from_str::<Collection>(&json)?, collection);
117
118 Ok(())
119 }
120
121 #[test]
122 fn when_puzzle_has_only_a_board_the_rest_defaults() -> Result<(), Box<dyn Error>> {
123 let puzzle: Puzzle =
124 serde_json::from_str(r##"{"board": {"tessellation": "Sokoban", "board": "#@$.#"}}"##)?;
125
126 assert_eq!(puzzle.board.width(), 5);
127 assert!(puzzle.title.is_empty());
128 assert!(puzzle.saved_games.is_empty());
129 assert_eq!(puzzle.sokoban_plus, None);
130
131 Ok(())
132 }
133
134 #[test]
135 fn when_collection_is_empty_it_round_trips() -> Result<(), Box<dyn Error>> {
136 let collection: Collection = serde_json::from_str("{}")?;
137
138 assert_eq!(collection, Collection::new());
139
140 Ok(())
141 }
142}