1use std::{error::Error, fmt::Display, hint::unreachable_unchecked};
2use rand::{
3 distributions::{Distribution, Standard},
4 Rng,
5};
6use logos::Logos;
7
8#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
10pub enum Prefabs {
11 None,
12 Melee,
13 Projectile,
14 JumpPad,
15 Stairs,
16 HiM,
17}
18
19impl Distribution<Prefabs> for Standard {
20 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Prefabs {
21 match rng.gen_range(0..=5) {
22 0 => Prefabs::None,
23 1 => Prefabs::Melee,
24 2 => Prefabs::Projectile,
25 3 => Prefabs::JumpPad,
26 4 => Prefabs::Stairs,
27 5 => Prefabs::HiM,
28 _ => unsafe { unreachable_unchecked() }
29 }
30 }
31}
32
33impl Default for Prefabs {
34 fn default() -> Self {
35 Self::None
36 }
37}
38
39impl Prefabs {
40 pub fn short_name<'a>(&self) -> &'a str {
42 match self {
43 Prefabs::None => "0",
44 Prefabs::Melee => "n",
45 Prefabs::Projectile => "p",
46 Prefabs::JumpPad => "J",
47 Prefabs::Stairs => "s",
48 Prefabs::HiM => "H",
49 }
50 }
51}
52
53#[derive(Debug, Default, Clone, Copy)]
55pub struct Cell {
56 height: i32,
57 prefab: Prefabs,
58}
59
60impl Cell {
61 pub fn new() -> Self {
62 Self::default()
63 }
64
65 pub fn height(&self) -> i32 {
67 self.height
68 }
69
70 pub fn prefab(&self) -> Prefabs {
72 self.prefab
73 }
74
75 pub fn is_none(&self) -> bool {
77 self.prefab == Prefabs::None
78 }
79
80 pub fn set_height(&mut self, height: i32) {
81 self.height = height
82 }
83
84 pub fn set_prefab(&mut self, prefab: Prefabs) {
85 self.prefab = prefab
86 }
87}
88
89#[derive(Debug, Default)]
91pub struct Pattern(pub [[Cell; 16]; 16]);
92
93impl Pattern {
94 pub fn new() -> Self {
95 Self::default()
96 }
97
98 pub fn to_pattern_string(&self) -> String {
100 let pattern = &self.0;
101
102 let mut height_buf = Vec::new();
103 let mut prefab_buf = Vec::new();
104
105 for i in 0..16 {
106 for j in 0..16 {
107 let cell = &pattern[i][j];
108
109 let height_str = if cell.height >= 10 || cell.height.is_negative() {
110 format!("({})", cell.height)
111 } else {
112 cell.height.to_string()
113 };
114
115 height_buf.push(height_str);
116 prefab_buf.push(cell.prefab.short_name().to_owned());
117 }
118
119 height_buf.push("\n".to_owned());
120 prefab_buf.push("\n".to_owned());
121 }
122
123 height_buf.push("\n".to_owned());
124
125 [height_buf, prefab_buf]
126 .concat()
127 .into_iter()
128 .collect::<String>()
129 .trim()
130 .to_owned()
131 }
132}
133
134pub fn parse(source: impl AsRef<str>) -> Result<Pattern, ParseError> {
136 let source = source.as_ref();
137 let lines = source.lines();
138 let mut token_grid = Vec::new();
139 let mut pattern = Pattern::default();
140
141 for line in lines {
142 let mut linebuf = Vec::new();
143 let lexer = Tokens::lexer(line);
144
145 lexer.for_each(|token| linebuf.push(token));
146 token_grid.push(linebuf);
147 }
148
149 let filtered_tokens = token_grid
150 .into_iter()
151 .filter(|p| !p.is_empty())
152 .collect::<Vec<_>>();
153
154 if filtered_tokens.len() < 32 {
155 return Err(ParseError("Input is not long enough to be a valid pattern.".into()));
156 }
157
158 let (l, r) = filtered_tokens.split_at(16);
159
160 for i in 0..16 {
161 for j in 0..16 {
162 let height = match l[i][j] {
163 Tokens::Number(n) => n,
164 Tokens::Prefab(n) if n == Prefabs::None => 0,
165 _ => {
166 return Err(ParseError("Invalid token when parsing numbers".to_string()))
167 }
168 };
169
170 let prefab = match r[i][j] {
171 Tokens::Prefab(n) => n,
172 Tokens::Number(_) => Prefabs::None,
173 Tokens::Error => {
174 return Err(ParseError("Invalid token when parsing prefabs".to_string()))
175 }
176 };
177
178 pattern.0[i][j] = Cell { height, prefab };
179 }
180 }
181
182 Ok(pattern)
183}
184
185#[derive(Debug, Clone)]
187pub struct ParseError(pub String);
188
189impl Display for ParseError {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 write!(f, "{:?}", self)
192 }
193}
194
195impl Error for ParseError {}
196
197#[derive(Logos, Clone, Copy, Debug, PartialEq, PartialOrd)]
198#[doc(hidden)]
199pub enum Tokens {
200 #[regex(r"\d|\(-?\d+\)", |lexer| {
201 let slice = lexer.slice();
202
203 if slice.starts_with('(') {
204 let mut chars = slice.chars();
205 chars.next();
206 chars.next_back();
207 chars.as_str().parse()
208
209 } else {
210 slice.parse()
211 }
212 })]
213 Number(i32),
214
215 #[regex("[a-zA-Z0]", |lexer| {
216 match lexer.slice().chars().next().unwrap() {
217 '0' => Some(Prefabs::None),
218 'n' => Some(Prefabs::Melee),
219 'p' => Some(Prefabs::Projectile),
220 'J' => Some(Prefabs::JumpPad),
221 's' => Some(Prefabs::Stairs),
222 'H' => Some(Prefabs::HiM),
223 _ => None
224 }
225 },
226 priority = 2)]
227 Prefab(Prefabs),
228
229 #[error]
230 Error,
231}
232
233#[cfg(test)]
234mod test {
235 use std::{fs::File, io::Write};
236
237 use super::*;
238
239 #[test]
240 fn serde() {
241 let src = include_str!("../example.cgp");
242 let out = parse(src).unwrap().to_pattern_string();
243
244 assert_eq!(src, &*out);
245 }
246
247 #[cfg(feature = "draw2d")]
248 #[test]
249 fn parser_draw2d() {
250 use crate::draw2d::draw::Draw2d;
251
252 let src = include_str!("../example.cgp");
253 let data = Draw2d::draw(parse(src).unwrap());
254
255 let bytes = &*data;
256 let mut file = File::create("example.png").unwrap();
257 file.write_all(bytes).unwrap();
258 }
259}
260