Skip to main content

terrain_forge/algorithms/
prefab.rs

1use crate::{Algorithm, Grid, Rng, Tile};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::path::Path;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7/// Configuration for prefab placement.
8pub struct PrefabConfig {
9    /// Maximum prefabs to place. Default: 3.
10    pub max_prefabs: usize,
11    /// Minimum distance between prefabs. Default: 5.
12    pub min_spacing: usize,
13    /// Allow rotating prefabs. Default: true.
14    pub allow_rotation: bool,
15    /// Allow mirroring prefabs. Default: false.
16    pub allow_mirroring: bool,
17    /// Use weight-based selection. Default: true.
18    pub weighted_selection: bool,
19    /// How prefabs interact with existing tiles. Default: Overwrite.
20    pub placement_mode: PrefabPlacementMode,
21    /// Filter prefabs by tags. Default: None (use all).
22    pub tags: Option<Vec<String>>,
23}
24
25impl Default for PrefabConfig {
26    fn default() -> Self {
27        Self {
28            max_prefabs: 3,
29            min_spacing: 5,
30            allow_rotation: true,
31            allow_mirroring: false,
32            weighted_selection: true,
33            placement_mode: PrefabPlacementMode::Overwrite,
34            tags: None,
35        }
36    }
37}
38
39#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
40/// How a prefab is applied to the grid.
41pub enum PrefabPlacementMode {
42    Overwrite,
43    Merge,
44    PaintFloor,
45    PaintWall,
46}
47
48#[derive(Clone, Debug, Serialize, Deserialize)]
49/// Maps a pattern character to tile, marker, and mask values.
50pub struct PrefabLegendEntry {
51    pub tile: Option<String>,
52    pub marker: Option<String>,
53    pub mask: Option<String>,
54}
55
56#[derive(Clone, Debug, Serialize, Deserialize)]
57/// Serializable prefab definition for JSON/TOML storage.
58pub struct PrefabData {
59    pub name: String,
60    pub width: usize,
61    pub height: usize,
62    pub pattern: Vec<String>,
63    pub weight: f32,
64    pub tags: Vec<String>,
65    #[serde(default)]
66    pub legend: Option<HashMap<String, PrefabLegendEntry>>,
67}
68
69#[derive(Clone, Debug, Default)]
70/// A single cell in a resolved prefab.
71pub struct PrefabCell {
72    pub tile: Option<Tile>,
73    pub marker: Option<String>,
74    pub mask: Option<String>,
75}
76
77#[derive(Debug, Clone)]
78/// A resolved prefab ready for placement.
79pub struct Prefab {
80    pub name: String,
81    pub width: usize,
82    pub height: usize,
83    pub cells: Vec<PrefabCell>,
84    pub symbols: Vec<char>,
85    pub legend: Option<HashMap<char, PrefabLegendEntry>>,
86    pub weight: f32,
87    pub tags: Vec<String>,
88}
89
90impl Prefab {
91    /// Creates a prefab from ASCII pattern strings (`#` = wall, `.` = floor).
92    pub fn new(pattern: &[&str]) -> Self {
93        let height = pattern.len();
94        let width = pattern.first().map(|s| s.len()).unwrap_or(0);
95        let (cells, symbols) = parse_pattern_with_legend(pattern, None);
96        Self {
97            name: "unnamed".to_string(),
98            width,
99            height,
100            cells,
101            symbols,
102            legend: None,
103            weight: 1.0,
104            tags: Vec::new(),
105        }
106    }
107
108    /// Creates a prefab from serializable [`PrefabData`].
109    pub fn from_data(data: PrefabData) -> Self {
110        let width = data.width;
111        let height = data.height;
112        let legend = data.legend.as_ref().map(convert_legend);
113        let (cells, symbols) = parse_pattern_with_legend(&data.pattern, legend.as_ref());
114
115        Self {
116            name: data.name,
117            width,
118            height,
119            cells,
120            symbols,
121            legend,
122            weight: data.weight,
123            tags: data.tags,
124        }
125    }
126
127    /// Creates a rectangular floor prefab.
128    pub fn rect(w: usize, h: usize) -> Self {
129        Self {
130            name: format!("rect_{}x{}", w, h),
131            width: w,
132            height: h,
133            cells: vec![
134                PrefabCell {
135                    tile: Some(Tile::Floor),
136                    marker: None,
137                    mask: None,
138                };
139                w * h
140            ],
141            symbols: vec!['.'; w * h],
142            legend: None,
143            weight: 1.0,
144            tags: vec!["rectangle".to_string()],
145        }
146    }
147
148    /// Rotate prefab 90 degrees clockwise
149    /// Returns a 90° clockwise rotation.
150    pub fn rotated(&self) -> Self {
151        let mut rotated_cells = vec![PrefabCell::default(); self.width * self.height];
152        let mut rotated_symbols = vec!['#'; self.width * self.height];
153        for y in 0..self.height {
154            for x in 0..self.width {
155                let old_idx = y * self.width + x;
156                let new_x = self.height - 1 - y;
157                let new_y = x;
158                let new_idx = new_y * self.height + new_x;
159                rotated_cells[new_idx] = self.cells[old_idx].clone();
160                rotated_symbols[new_idx] = self.symbols[old_idx];
161            }
162        }
163
164        Self {
165            name: format!("{}_rot90", self.name),
166            width: self.height,
167            height: self.width,
168            cells: rotated_cells,
169            symbols: rotated_symbols,
170            legend: self.legend.clone(),
171            weight: self.weight,
172            tags: self.tags.clone(),
173        }
174    }
175
176    /// Mirror prefab horizontally
177    /// Returns a horizontally mirrored copy.
178    pub fn mirrored_horizontal(&self) -> Self {
179        let mut mirrored_cells = vec![PrefabCell::default(); self.width * self.height];
180        let mut mirrored_symbols = vec!['#'; self.width * self.height];
181        for y in 0..self.height {
182            for x in 0..self.width {
183                let old_idx = y * self.width + x;
184                let new_x = self.width - 1 - x;
185                let new_idx = y * self.width + new_x;
186                mirrored_cells[new_idx] = self.cells[old_idx].clone();
187                mirrored_symbols[new_idx] = self.symbols[old_idx];
188            }
189        }
190
191        Self {
192            name: format!("{}_mirror_h", self.name),
193            width: self.width,
194            height: self.height,
195            cells: mirrored_cells,
196            symbols: mirrored_symbols,
197            legend: self.legend.clone(),
198            weight: self.weight,
199            tags: self.tags.clone(),
200        }
201    }
202
203    /// Mirror prefab vertically
204    /// Returns a vertically mirrored copy.
205    pub fn mirrored_vertical(&self) -> Self {
206        let mut mirrored_cells = vec![PrefabCell::default(); self.width * self.height];
207        let mut mirrored_symbols = vec!['#'; self.width * self.height];
208        for y in 0..self.height {
209            for x in 0..self.width {
210                let old_idx = y * self.width + x;
211                let new_y = self.height - 1 - y;
212                let new_idx = new_y * self.width + x;
213                mirrored_cells[new_idx] = self.cells[old_idx].clone();
214                mirrored_symbols[new_idx] = self.symbols[old_idx];
215            }
216        }
217
218        Self {
219            name: format!("{}_mirror_v", self.name),
220            width: self.width,
221            height: self.height,
222            cells: mirrored_cells,
223            symbols: mirrored_symbols,
224            legend: self.legend.clone(),
225            weight: self.weight,
226            tags: self.tags.clone(),
227        }
228    }
229
230    /// Returns `true` if the cell at `(x, y)` is a floor.
231    pub fn get(&self, x: usize, y: usize) -> bool {
232        self.cell_tile(x, y) == Some(Tile::Floor)
233    }
234
235    /// Returns the tile at `(x, y)`, if set.
236    pub fn cell_tile(&self, x: usize, y: usize) -> Option<Tile> {
237        if x < self.width && y < self.height {
238            self.cells[y * self.width + x].tile
239        } else {
240            None
241        }
242    }
243
244    /// Returns the marker string at `(x, y)`, if set.
245    pub fn cell_marker(&self, x: usize, y: usize) -> Option<&str> {
246        if x < self.width && y < self.height {
247            self.cells[y * self.width + x].marker.as_deref()
248        } else {
249            None
250        }
251    }
252
253    /// Returns the mask string at `(x, y)`, if set.
254    pub fn cell_mask(&self, x: usize, y: usize) -> Option<&str> {
255        if x < self.width && y < self.height {
256            self.cells[y * self.width + x].mask.as_deref()
257        } else {
258            None
259        }
260    }
261
262    /// Returns `true` if this prefab has the given tag.
263    pub fn has_tag(&self, tag: &str) -> bool {
264        self.tags.contains(&tag.to_string())
265    }
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize)]
269/// Serializable prefab library for JSON storage.
270pub struct PrefabLibraryData {
271    pub prefabs: Vec<PrefabData>,
272}
273
274#[derive(Debug, Clone)]
275/// Collection of prefabs with weighted selection and tag filtering.
276pub struct PrefabLibrary {
277    prefabs: Vec<Prefab>,
278    by_tag: HashMap<String, Vec<usize>>,
279}
280
281impl PrefabLibrary {
282    /// Creates an empty prefab library.
283    pub fn new() -> Self {
284        Self {
285            prefabs: Vec::new(),
286            by_tag: HashMap::new(),
287        }
288    }
289
290    /// Adds a prefab to the library, indexing its tags.
291    pub fn add_prefab(&mut self, prefab: Prefab) {
292        let index = self.prefabs.len();
293
294        for tag in &prefab.tags {
295            self.by_tag.entry(tag.clone()).or_default().push(index);
296        }
297
298        self.prefabs.push(prefab);
299    }
300
301    /// Loads a library from a JSON file.
302    pub fn load_from_json<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
303        let content = std::fs::read_to_string(path)?;
304        let data: PrefabLibraryData = serde_json::from_str(&content)?;
305
306        let mut library = Self::new();
307        for prefab_data in data.prefabs {
308            library.add_prefab(Prefab::from_data(prefab_data));
309        }
310
311        Ok(library)
312    }
313
314    /// Loads and merges libraries from multiple JSON paths.
315    pub fn load_from_paths<I, P>(paths: I) -> Result<Self, Box<dyn std::error::Error>>
316    where
317        I: IntoIterator<Item = P>,
318        P: AsRef<Path>,
319    {
320        let mut library = Self::new();
321        for path in paths {
322            let loaded = Self::load_from_json(path)?;
323            library.extend_from(loaded);
324        }
325        Ok(library)
326    }
327
328    /// Loads all JSON prefab files from a directory.
329    pub fn load_from_dir<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
330        let mut entries: Vec<std::path::PathBuf> = std::fs::read_dir(path)?
331            .filter_map(|entry| entry.ok().map(|e| e.path()))
332            .filter(|p| p.extension().and_then(|e| e.to_str()) == Some("json"))
333            .collect();
334        entries.sort();
335        if entries.is_empty() {
336            return Ok(Self::new());
337        }
338        Self::load_from_paths(entries)
339    }
340
341    /// Saves the library to a JSON file.
342    pub fn save_to_json<P: AsRef<Path>>(&self, path: P) -> Result<(), Box<dyn std::error::Error>> {
343        let data = PrefabLibraryData {
344            prefabs: self
345                .prefabs
346                .iter()
347                .map(|p| PrefabData {
348                    name: p.name.clone(),
349                    width: p.width,
350                    height: p.height,
351                    pattern: self.prefab_to_pattern(p),
352                    weight: p.weight,
353                    tags: p.tags.clone(),
354                    legend: p.legend.as_ref().map(convert_legend_to_strings),
355                })
356                .collect(),
357        };
358
359        let content = serde_json::to_string_pretty(&data)?;
360        std::fs::write(path, content)?;
361        Ok(())
362    }
363
364    fn prefab_to_pattern(&self, prefab: &Prefab) -> Vec<String> {
365        let mut pattern = Vec::new();
366        for y in 0..prefab.height {
367            let mut row = String::new();
368            for x in 0..prefab.width {
369                let idx = y * prefab.width + x;
370                let symbol = prefab.symbols.get(idx).copied().unwrap_or_else(|| {
371                    if prefab.get(x, y) {
372                        '.'
373                    } else {
374                        '#'
375                    }
376                });
377                row.push(symbol);
378            }
379            pattern.push(row);
380        }
381        pattern
382    }
383
384    /// Returns all prefabs in the library.
385    pub fn get_prefabs(&self) -> &[Prefab] {
386        &self.prefabs
387    }
388
389    /// Merges another library into this one.
390    pub fn extend_from(&mut self, other: PrefabLibrary) {
391        for prefab in other.prefabs {
392            self.add_prefab(prefab);
393        }
394    }
395
396    /// Returns prefabs matching the given tag.
397    pub fn get_by_tag(&self, tag: &str) -> Vec<&Prefab> {
398        self.by_tag
399            .get(tag)
400            .map(|indices| indices.iter().map(|&i| &self.prefabs[i]).collect())
401            .unwrap_or_default()
402    }
403
404    /// Selects a random prefab using weights, optionally filtered by tag.
405    pub fn select_weighted(&self, rng: &mut Rng, tag: Option<&str>) -> Option<&Prefab> {
406        let tags = tag.map(|t| vec![t.to_string()]);
407        self.select_with_tags(rng, tags.as_deref(), true)
408    }
409
410    /// Selects a random prefab filtered by multiple tags.
411    pub fn select_with_tags(
412        &self,
413        rng: &mut Rng,
414        tags: Option<&[String]>,
415        weighted: bool,
416    ) -> Option<&Prefab> {
417        let candidates = if let Some(tags) = tags {
418            self.get_by_any_tag(tags)
419        } else {
420            self.prefabs.iter().collect()
421        };
422
423        if candidates.is_empty() {
424            return None;
425        }
426
427        if !weighted {
428            return rng.pick(&candidates).copied();
429        }
430
431        let total_weight: f32 = candidates.iter().map(|p| p.weight).sum();
432        if total_weight <= 0.0 {
433            return rng.pick(&candidates).copied();
434        }
435
436        let mut target = rng.random() as f32 * total_weight;
437        for prefab in &candidates {
438            target -= prefab.weight;
439            if target <= 0.0 {
440                return Some(prefab);
441            }
442        }
443
444        candidates.last().copied()
445    }
446
447    /// Returns prefabs matching any of the given tags.
448    pub fn get_by_any_tag(&self, tags: &[String]) -> Vec<&Prefab> {
449        if tags.is_empty() {
450            return Vec::new();
451        }
452        let mut indices = Vec::new();
453        for tag in tags {
454            if let Some(found) = self.by_tag.get(tag) {
455                indices.extend_from_slice(found);
456            }
457        }
458        if indices.is_empty() {
459            return Vec::new();
460        }
461        indices.sort_unstable();
462        indices.dedup();
463        indices.iter().map(|&i| &self.prefabs[i]).collect()
464    }
465
466    /// Creates an identity (no-op) transform.
467    pub fn create_default() -> Self {
468        let mut library = Self::new();
469
470        // Add some basic prefabs
471        let mut room = Prefab::rect(5, 5);
472        room.name = "small_room".to_string();
473        room.tags = vec!["room".to_string(), "small".to_string()];
474        library.add_prefab(room);
475
476        let mut corridor = Prefab::new(&[".....", "#####"]);
477        corridor.name = "corridor".to_string();
478        corridor.tags = vec!["corridor".to_string()];
479        library.add_prefab(corridor);
480
481        let mut cross = Prefab::new(&["#.#", "...", "#.#"]);
482        cross.name = "cross".to_string();
483        cross.tags = vec!["junction".to_string()];
484        library.add_prefab(cross);
485
486        library
487    }
488}
489
490impl Default for PrefabLibrary {
491    fn default() -> Self {
492        Self::create_default()
493    }
494}
495
496/// Rotation and mirroring transform for prefabs.
497#[derive(Debug, Clone, Default)]
498pub struct PrefabTransform {
499    pub rotation: u8, // 0, 1, 2, 3 for 0°, 90°, 180°, 270°
500    pub mirror_h: bool,
501    pub mirror_v: bool,
502}
503
504impl PrefabTransform {
505    /// Applies this transform to a prefab, returning a new one.
506    pub fn apply(&self, prefab: &Prefab) -> Prefab {
507        let mut result = prefab.clone();
508
509        // Apply mirroring first
510        if self.mirror_h {
511            result = result.mirrored_horizontal();
512        }
513        if self.mirror_v {
514            result = result.mirrored_vertical();
515        }
516
517        // Apply rotation
518        for _ in 0..self.rotation {
519            result = result.rotated();
520        }
521
522        result
523    }
524
525    /// Creates a random transform.
526    pub fn random(rng: &mut Rng, allow_rotation: bool, allow_mirroring: bool) -> Self {
527        Self {
528            rotation: if allow_rotation {
529                rng.range(0, 4) as u8
530            } else {
531                0
532            },
533            mirror_h: allow_mirroring && rng.chance(0.5),
534            mirror_v: allow_mirroring && rng.chance(0.5),
535        }
536    }
537}
538/// Places prefabs from a [`PrefabLibrary`] onto a grid.
539#[derive(Debug, Clone)]
540pub struct PrefabPlacer {
541    config: PrefabConfig,
542    library: PrefabLibrary,
543}
544
545impl PrefabPlacer {
546    /// Creates a new prefab placer with the given config and library.
547    pub fn new(config: PrefabConfig, library: PrefabLibrary) -> Self {
548        Self { config, library }
549    }
550
551    /// Creates a prefab placer with default config and the given library.
552    pub fn with_library(library: PrefabLibrary) -> Self {
553        Self::new(PrefabConfig::default(), library)
554    }
555}
556
557impl Default for PrefabPlacer {
558    fn default() -> Self {
559        Self::with_library(PrefabLibrary::default())
560    }
561}
562
563impl Algorithm<Tile> for PrefabPlacer {
564    fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
565        self.generate_internal(grid, seed, None);
566    }
567
568    fn name(&self) -> &'static str {
569        "PrefabPlacer"
570    }
571}
572
573impl PrefabPlacer {
574    /// Generates prefab placements and returns semantic layer data.
575    pub fn generate_with_semantic(
576        &self,
577        grid: &mut Grid<Tile>,
578        seed: u64,
579        semantic: &mut crate::semantic::SemanticLayers,
580    ) {
581        self.generate_internal(grid, seed, Some(semantic));
582    }
583
584    fn generate_internal(
585        &self,
586        grid: &mut Grid<Tile>,
587        seed: u64,
588        mut semantic: Option<&mut crate::semantic::SemanticLayers>,
589    ) {
590        let mut rng = Rng::new(seed);
591        let mut placed: Vec<(usize, usize, usize, usize)> = Vec::new();
592
593        for _ in 0..self.config.max_prefabs * 10 {
594            if placed.len() >= self.config.max_prefabs {
595                break;
596            }
597
598            let base_prefab = if let Some(prefab) = self.library.select_with_tags(
599                &mut rng,
600                self.config.tags.as_deref(),
601                self.config.weighted_selection,
602            ) {
603                prefab
604            } else {
605                continue;
606            };
607
608            // Apply random transformations
609            let transform = PrefabTransform::random(
610                &mut rng,
611                self.config.allow_rotation,
612                self.config.allow_mirroring,
613            );
614            let prefab = transform.apply(base_prefab);
615
616            if prefab.width + 2 >= grid.width() || prefab.height + 2 >= grid.height() {
617                continue;
618            }
619
620            let x = rng.range_usize(1, grid.width() - prefab.width - 1);
621            let y = rng.range_usize(1, grid.height() - prefab.height - 1);
622
623            let overlaps = placed.iter().any(|&(px, py, pw, ph)| {
624                let s = self.config.min_spacing;
625                !(x + prefab.width + s < px
626                    || px + pw + s < x
627                    || y + prefab.height + s < py
628                    || py + ph + s < y)
629            });
630
631            if overlaps {
632                continue;
633            }
634
635            for py in 0..prefab.height {
636                for px in 0..prefab.width {
637                    let cell_tile = prefab.cell_tile(px, py);
638                    let cell_marker = prefab.cell_marker(px, py);
639                    let cell_mask = prefab.cell_mask(px, py);
640                    let gx = (x + px) as i32;
641                    let gy = (y + py) as i32;
642
643                    let mut applied = false;
644                    if let Some(tile) = cell_tile {
645                        let current = *grid.get(gx, gy).unwrap_or(&Tile::Wall);
646                        let should_place = match self.config.placement_mode {
647                            PrefabPlacementMode::Overwrite => true,
648                            PrefabPlacementMode::Merge => matches!(current, Tile::Wall),
649                            PrefabPlacementMode::PaintFloor => matches!(current, Tile::Floor),
650                            PrefabPlacementMode::PaintWall => matches!(current, Tile::Wall),
651                        };
652                        if should_place {
653                            grid.set(gx, gy, tile);
654                            applied = true;
655                        }
656                    }
657
658                    if let Some(layers) = semantic.as_deref_mut() {
659                        let marker_allowed = cell_tile.is_none() || applied;
660                        if marker_allowed {
661                            if let Some(tag) = cell_marker {
662                                layers.markers.push(crate::semantic::Marker::with_tag(
663                                    gx as u32,
664                                    gy as u32,
665                                    tag.to_string(),
666                                ));
667                            }
668                            if let Some(mask) = cell_mask {
669                                apply_prefab_mask(&mut layers.masks, gx, gy, mask);
670                            }
671                        }
672                    }
673                }
674            }
675            placed.push((x, y, prefab.width, prefab.height));
676        }
677    }
678}
679
680fn parse_pattern_with_legend(
681    pattern: &[impl AsRef<str>],
682    legend: Option<&HashMap<char, PrefabLegendEntry>>,
683) -> (Vec<PrefabCell>, Vec<char>) {
684    let mut cells = Vec::new();
685    let mut symbols = Vec::new();
686    for row in pattern {
687        for ch in row.as_ref().chars() {
688            let cell = if let Some(legend) = legend {
689                legend
690                    .get(&ch)
691                    .map(parse_legend_entry)
692                    .unwrap_or_else(|| default_cell_from_symbol(ch))
693            } else {
694                default_cell_from_symbol(ch)
695            };
696            cells.push(cell);
697            symbols.push(ch);
698        }
699    }
700    (cells, symbols)
701}
702
703fn default_cell_from_symbol(ch: char) -> PrefabCell {
704    match ch {
705        '.' => PrefabCell {
706            tile: Some(Tile::Floor),
707            marker: None,
708            mask: None,
709        },
710        '#' => PrefabCell::default(),
711        _ => PrefabCell::default(),
712    }
713}
714
715fn parse_legend_entry(entry: &PrefabLegendEntry) -> PrefabCell {
716    PrefabCell {
717        tile: parse_tile_name(entry.tile.as_deref()),
718        marker: entry.marker.clone(),
719        mask: entry.mask.clone(),
720    }
721}
722
723fn parse_tile_name(value: Option<&str>) -> Option<Tile> {
724    let value = value?;
725    match value.trim().to_ascii_lowercase().as_str() {
726        "floor" | "f" => Some(Tile::Floor),
727        "wall" | "w" => Some(Tile::Wall),
728        "empty" | "none" | "skip" => None,
729        _ => None,
730    }
731}
732
733fn convert_legend(legend: &HashMap<String, PrefabLegendEntry>) -> HashMap<char, PrefabLegendEntry> {
734    let mut out = HashMap::new();
735    for (key, value) in legend {
736        let mut chars = key.chars();
737        if let Some(ch) = chars.next() {
738            out.insert(ch, value.clone());
739        }
740    }
741    out
742}
743
744fn convert_legend_to_strings(
745    legend: &HashMap<char, PrefabLegendEntry>,
746) -> HashMap<String, PrefabLegendEntry> {
747    legend
748        .iter()
749        .map(|(ch, entry)| (ch.to_string(), entry.clone()))
750        .collect()
751}
752
753fn apply_prefab_mask(masks: &mut crate::semantic::Masks, x: i32, y: i32, mask: &str) {
754    if x < 0 || y < 0 {
755        return;
756    }
757    let (x, y) = (x as usize, y as usize);
758    if y >= masks.height || x >= masks.width {
759        return;
760    }
761    match mask.trim().to_ascii_lowercase().as_str() {
762        "no_spawn" | "nospawn" | "reserved" => {
763            if let Some(row) = masks.no_spawn.get_mut(y) {
764                if let Some(cell) = row.get_mut(x) {
765                    *cell = true;
766                }
767            }
768        }
769        _ => {}
770    }
771}