1use crate::{Grid, Tile};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
11pub struct SemanticConfig {
12 pub size_thresholds: Vec<(usize, String)>,
14 pub marker_types: Vec<(String, f32)>,
16 pub max_markers_per_region: usize,
18 pub marker_scaling_factor: f32,
20 pub connectivity_type: ConnectivityType,
22 pub region_analysis: RegionAnalysisConfig,
24 pub marker_placement: MarkerPlacementConfig,
26}
27
28#[derive(Debug, Clone)]
30pub enum ConnectivityType {
31 FourConnected,
33 EightConnected,
35}
36
37#[derive(Debug, Clone)]
39pub struct RegionAnalysisConfig {
40 pub analyze_shape: bool,
42 pub analyze_connectivity_patterns: bool,
44 pub min_analysis_size: usize,
46}
47
48#[derive(Debug, Clone)]
50pub struct MarkerPlacementConfig {
51 pub strategy: PlacementStrategy,
53 pub min_marker_distance: usize,
55 pub avoid_walls: bool,
57}
58
59#[derive(Debug, Clone)]
61pub enum PlacementStrategy {
62 Random,
64 Center,
66 Edges,
68 Corners,
70}
71
72impl SemanticConfig {
73 pub fn cave_system() -> Self {
75 Self {
76 size_thresholds: vec![
77 (80, "Chamber".to_string()),
78 (25, "Tunnel".to_string()),
79 (5, "Alcove".to_string()),
80 (0, "Crevice".to_string()),
81 ],
82 marker_types: vec![
83 ("PlayerStart".to_string(), 1.0),
84 ("Exit".to_string(), 0.8),
85 ("Treasure".to_string(), 0.4),
86 ("Enemy".to_string(), 0.6),
87 ("Crystal".to_string(), 0.2),
88 ],
89 max_markers_per_region: 2,
90 marker_scaling_factor: 80.0, connectivity_type: ConnectivityType::EightConnected, region_analysis: RegionAnalysisConfig {
93 analyze_shape: true, analyze_connectivity_patterns: true,
95 min_analysis_size: 15,
96 },
97 marker_placement: MarkerPlacementConfig {
98 strategy: PlacementStrategy::Random,
99 min_marker_distance: 5,
100 avoid_walls: true,
101 },
102 }
103 }
104
105 pub fn room_system() -> Self {
107 Self {
108 size_thresholds: vec![
109 (150, "Hall".to_string()),
110 (50, "Room".to_string()),
111 (15, "Chamber".to_string()),
112 (0, "Closet".to_string()),
113 ],
114 marker_types: vec![
115 ("PlayerStart".to_string(), 1.0),
116 ("Exit".to_string(), 1.0),
117 ("Treasure".to_string(), 0.3),
118 ("Enemy".to_string(), 0.4),
119 ("Furniture".to_string(), 0.7),
120 ],
121 max_markers_per_region: 4,
122 marker_scaling_factor: 60.0, connectivity_type: ConnectivityType::FourConnected, region_analysis: RegionAnalysisConfig {
125 analyze_shape: true, analyze_connectivity_patterns: false,
127 min_analysis_size: 8,
128 },
129 marker_placement: MarkerPlacementConfig {
130 strategy: PlacementStrategy::Center, min_marker_distance: 4,
132 avoid_walls: true,
133 },
134 }
135 }
136
137 pub fn maze_system() -> Self {
139 Self {
140 size_thresholds: vec![
141 (50, "Junction".to_string()),
142 (10, "Corridor".to_string()),
143 (0, "DeadEnd".to_string()),
144 ],
145 marker_types: vec![
146 ("PlayerStart".to_string(), 1.0),
147 ("Exit".to_string(), 1.0),
148 ("Treasure".to_string(), 0.1),
149 ("Trap".to_string(), 0.3),
150 ],
151 max_markers_per_region: 1,
152 marker_scaling_factor: 30.0, connectivity_type: ConnectivityType::FourConnected, region_analysis: RegionAnalysisConfig {
155 analyze_shape: false,
156 analyze_connectivity_patterns: true, min_analysis_size: 5,
158 },
159 marker_placement: MarkerPlacementConfig {
160 strategy: PlacementStrategy::Corners, min_marker_distance: 8,
162 avoid_walls: false, },
164 }
165 }
166}
167
168impl Default for SemanticConfig {
169 fn default() -> Self {
170 Self {
171 size_thresholds: vec![
172 (100, "Large".to_string()),
173 (25, "Medium".to_string()),
174 (5, "Small".to_string()),
175 (0, "Tiny".to_string()),
176 ],
177 marker_types: vec![
178 ("PlayerStart".to_string(), 1.0),
179 ("Exit".to_string(), 1.0),
180 ("Treasure".to_string(), 0.3),
181 ("Enemy".to_string(), 0.5),
182 ],
183 max_markers_per_region: 3,
184 marker_scaling_factor: 100.0,
185 connectivity_type: ConnectivityType::FourConnected,
186 region_analysis: RegionAnalysisConfig {
187 analyze_shape: false,
188 analyze_connectivity_patterns: false,
189 min_analysis_size: 10,
190 },
191 marker_placement: MarkerPlacementConfig {
192 strategy: PlacementStrategy::Random,
193 min_marker_distance: 3,
194 avoid_walls: true,
195 },
196 }
197 }
198}
199
200#[derive(Debug, Clone)]
202pub struct Region {
203 pub id: u32,
204 pub kind: String,
205 pub cells: Vec<(u32, u32)>,
206 pub tags: Vec<String>,
207}
208
209#[derive(Debug, Clone, PartialEq, Eq, Hash)]
211pub enum MarkerType {
212 Spawn,
214 Exit,
215
216 QuestObjective {
218 priority: u8,
219 },
220 QuestStart,
221 QuestEnd,
222
223 LootTier {
225 tier: u8,
226 },
227 Treasure,
228
229 EncounterZone {
231 difficulty: u8,
232 },
233 BossRoom,
234 SafeZone,
235
236 Custom(String),
238}
239
240impl MarkerType {
241 pub fn category(&self) -> &'static str {
243 match self {
244 MarkerType::Spawn | MarkerType::Exit => "spawn",
245 MarkerType::QuestObjective { .. } | MarkerType::QuestStart | MarkerType::QuestEnd => {
246 "quest"
247 }
248 MarkerType::LootTier { .. } | MarkerType::Treasure => "loot",
249 MarkerType::EncounterZone { .. } | MarkerType::BossRoom | MarkerType::SafeZone => {
250 "encounter"
251 }
252 MarkerType::Custom(_) => "custom",
253 }
254 }
255}
256
257#[derive(Debug, Clone)]
259pub struct Marker {
260 pub x: u32,
261 pub y: u32,
262 pub marker_type: MarkerType,
263 pub weight: f32,
264 pub region_id: Option<u32>,
265 pub metadata: HashMap<String, String>,
266}
267
268impl Marker {
269 pub fn new(x: u32, y: u32, marker_type: MarkerType) -> Self {
271 Self {
272 x,
273 y,
274 marker_type,
275 weight: 1.0,
276 region_id: None,
277 metadata: HashMap::new(),
278 }
279 }
280
281 pub fn with_tag(x: u32, y: u32, tag: String) -> Self {
283 Self::new(x, y, MarkerType::Custom(tag))
284 }
285
286 pub fn tag(&self) -> String {
288 match &self.marker_type {
289 MarkerType::Spawn => "spawn".to_string(),
290 MarkerType::Exit => "exit".to_string(),
291 MarkerType::QuestObjective { priority } => format!("quest_objective_{}", priority),
292 MarkerType::QuestStart => "quest_start".to_string(),
293 MarkerType::QuestEnd => "quest_end".to_string(),
294 MarkerType::LootTier { tier } => format!("loot_tier_{}", tier),
295 MarkerType::Treasure => "treasure".to_string(),
296 MarkerType::EncounterZone { difficulty } => format!("encounter_{}", difficulty),
297 MarkerType::BossRoom => "boss_room".to_string(),
298 MarkerType::SafeZone => "safe_zone".to_string(),
299 MarkerType::Custom(tag) => tag.clone(),
300 }
301 }
302}
303
304#[derive(Debug, Clone)]
306pub struct MarkerConstraints {
307 pub min_distance_same: Option<f32>,
309 pub min_distance_any: Option<f32>,
311 pub max_distance_from: Vec<(MarkerType, f32)>,
313 pub exclude_types: Vec<MarkerType>,
315 pub require_nearby: Vec<(MarkerType, f32)>,
317}
318
319impl MarkerConstraints {
320 pub fn none() -> Self {
322 Self {
323 min_distance_same: None,
324 min_distance_any: None,
325 max_distance_from: Vec::new(),
326 exclude_types: Vec::new(),
327 require_nearby: Vec::new(),
328 }
329 }
330
331 pub fn quest_objective() -> Self {
333 Self {
334 min_distance_same: Some(10.0),
335 min_distance_any: Some(3.0),
336 max_distance_from: Vec::new(),
337 exclude_types: vec![MarkerType::SafeZone],
338 require_nearby: Vec::new(),
339 }
340 }
341
342 pub fn loot() -> Self {
344 Self {
345 min_distance_same: Some(5.0),
346 min_distance_any: Some(2.0),
347 max_distance_from: Vec::new(),
348 exclude_types: vec![MarkerType::SafeZone],
349 require_nearby: Vec::new(),
350 }
351 }
352}
353
354#[derive(Debug, Clone)]
356pub struct Masks {
357 pub walkable: Vec<Vec<bool>>,
358 pub no_spawn: Vec<Vec<bool>>,
359 pub width: usize,
360 pub height: usize,
361}
362
363#[derive(Debug, Clone)]
365pub struct ConnectivityGraph {
366 pub regions: Vec<u32>,
367 pub edges: Vec<(u32, u32)>,
368}
369
370#[derive(Debug, Clone)]
372pub struct SemanticLayers {
373 pub regions: Vec<Region>,
374 pub markers: Vec<Marker>,
375 pub masks: Masks,
376 pub connectivity: ConnectivityGraph,
377}
378
379pub fn marker_positions(layers: &SemanticLayers, marker_type: &MarkerType) -> Vec<(usize, usize)> {
381 layers
382 .markers
383 .iter()
384 .filter(|marker| &marker.marker_type == marker_type)
385 .map(|marker| (marker.x as usize, marker.y as usize))
386 .collect()
387}
388
389impl Region {
390 pub fn new(id: u32, kind: impl Into<String>) -> Self {
391 Self {
392 id,
393 kind: kind.into(),
394 cells: Vec::new(),
395 tags: Vec::new(),
396 }
397 }
398
399 pub fn add_cell(&mut self, x: u32, y: u32) {
400 self.cells.push((x, y));
401 }
402
403 pub fn add_tag(&mut self, tag: impl Into<String>) {
404 self.tags.push(tag.into());
405 }
406
407 pub fn area(&self) -> usize {
408 self.cells.len()
409 }
410}
411
412impl Marker {
413 pub fn with_weight(mut self, weight: f32) -> Self {
414 self.weight = weight;
415 self
416 }
417
418 pub fn with_region(mut self, region_id: u32) -> Self {
419 self.region_id = Some(region_id);
420 self
421 }
422
423 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
424 self.metadata.insert(key.into(), value.into());
425 self
426 }
427}
428
429#[derive(Debug, Clone)]
431pub struct SemanticRequirements {
432 pub min_regions: HashMap<String, usize>,
434 pub max_regions: HashMap<String, usize>,
436 pub required_connections: Vec<(String, String)>,
438 pub min_walkable_area: Option<usize>,
440 pub required_markers: HashMap<MarkerType, usize>,
442}
443
444impl SemanticRequirements {
445 pub fn none() -> Self {
447 Self {
448 min_regions: HashMap::new(),
449 max_regions: HashMap::new(),
450 required_connections: Vec::new(),
451 min_walkable_area: None,
452 required_markers: HashMap::new(),
453 }
454 }
455
456 pub fn basic_dungeon() -> Self {
458 let mut req = Self::none();
459 req.min_regions.insert("room".to_string(), 3);
460 req.required_connections
461 .push(("room".to_string(), "corridor".to_string()));
462 req.required_markers.insert(MarkerType::Spawn, 1);
463 req.required_markers.insert(MarkerType::Exit, 1);
464 req
465 }
466
467 pub fn validate(&self, layers: &SemanticLayers) -> bool {
469 let mut region_counts: HashMap<String, usize> = HashMap::new();
471 for region in &layers.regions {
472 *region_counts.entry(region.kind.clone()).or_insert(0) += 1;
473 }
474
475 for (kind, min_count) in &self.min_regions {
476 if region_counts.get(kind).unwrap_or(&0) < min_count {
477 return false;
478 }
479 }
480
481 let mut marker_counts: HashMap<MarkerType, usize> = HashMap::new();
483 for marker in &layers.markers {
484 *marker_counts.entry(marker.marker_type.clone()).or_insert(0) += 1;
485 }
486
487 for (marker_type, min_count) in &self.required_markers {
488 if marker_counts.get(marker_type).unwrap_or(&0) < min_count {
489 return false;
490 }
491 }
492
493 true
494 }
495}
496
497impl Masks {
498 pub fn new(width: usize, height: usize) -> Self {
499 Self {
500 walkable: vec![vec![false; width]; height],
501 no_spawn: vec![vec![false; width]; height],
502 width,
503 height,
504 }
505 }
506
507 pub fn from_tiles(tiles: &Grid<Tile>) -> Self {
508 let mut masks = Self::new(tiles.width(), tiles.height());
509
510 for y in 0..tiles.height() {
511 for x in 0..tiles.width() {
512 let walkable = tiles.get(x as i32, y as i32).is_some_and(|t| t.is_floor());
513 masks.walkable[y][x] = walkable;
514 }
515 }
516
517 masks
518 }
519}
520
521impl ConnectivityGraph {
522 pub fn new() -> Self {
523 Self {
524 regions: Vec::new(),
525 edges: Vec::new(),
526 }
527 }
528
529 pub fn add_region(&mut self, id: u32) {
530 if !self.regions.contains(&id) {
531 self.regions.push(id);
532 }
533 }
534
535 pub fn add_edge(&mut self, from: u32, to: u32) {
536 self.add_region(from);
537 self.add_region(to);
538
539 if !self.edges.contains(&(from, to)) && !self.edges.contains(&(to, from)) {
540 self.edges.push((from, to));
541 }
542 }
543}
544
545#[derive(Debug, Clone)]
547pub struct VerticalConnectivity {
548 pub stair_candidates: Vec<(u32, u32, u32, u32)>,
550 pub stairs: Vec<(u32, u32, u32, u32)>,
552 pub floor_accessibility: HashMap<u32, Vec<u32>>,
554}
555
556impl VerticalConnectivity {
557 pub fn new() -> Self {
559 Self {
560 stair_candidates: Vec::new(),
561 stairs: Vec::new(),
562 floor_accessibility: HashMap::new(),
563 }
564 }
565
566 pub fn analyze_stair_candidates(&mut self, floor_grids: &[Grid<Tile>], min_clearance: usize) {
570 self.stair_candidates.clear();
571
572 for floor_idx in 0..floor_grids.len().saturating_sub(1) {
573 let current_floor = &floor_grids[floor_idx];
574 let next_floor = &floor_grids[floor_idx + 1];
575
576 for y in min_clearance..current_floor.height().saturating_sub(min_clearance) {
578 for x in min_clearance..current_floor.width().saturating_sub(min_clearance) {
579 if self.is_valid_stair_location(
580 current_floor,
581 next_floor,
582 x as i32,
583 y as i32,
584 min_clearance as i32,
585 ) {
586 self.stair_candidates.push((
587 x as u32,
588 y as u32,
589 floor_idx as u32,
590 (floor_idx + 1) as u32,
591 ));
592 }
593 }
594 }
595 }
596 }
597
598 fn is_valid_stair_location(
600 &self,
601 floor1: &Grid<Tile>,
602 floor2: &Grid<Tile>,
603 x: i32,
604 y: i32,
605 clearance: i32,
606 ) -> bool {
607 let tile1 = floor1.get(x, y);
609 let tile2 = floor2.get(x, y);
610
611 if !tile1.is_some_and(|t| t.is_floor()) || !tile2.is_some_and(|t| t.is_floor()) {
612 return false;
613 }
614
615 for dy in -clearance..=clearance {
617 for dx in -clearance..=clearance {
618 let check_x = x + dx;
619 let check_y = y + dy;
620
621 let clear1 = floor1.get(check_x, check_y).is_some_and(|t| t.is_floor());
622 let clear2 = floor2.get(check_x, check_y).is_some_and(|t| t.is_floor());
623
624 if !clear1 || !clear2 {
625 return false;
626 }
627 }
628 }
629
630 true
631 }
632
633 pub fn place_stairs(&mut self, max_stairs_per_floor: usize) {
635 self.stairs.clear();
636
637 let mut floor_candidates: HashMap<(u32, u32), Vec<(u32, u32)>> = HashMap::new();
639 for &(x, y, from_floor, to_floor) in &self.stair_candidates {
640 floor_candidates
641 .entry((from_floor, to_floor))
642 .or_default()
643 .push((x, y));
644 }
645
646 for ((from_floor, to_floor), candidates) in floor_candidates {
648 let stairs_to_place = candidates.len().min(max_stairs_per_floor);
649 for &(x, y) in candidates.iter().take(stairs_to_place) {
650 self.stairs.push((x, y, from_floor, to_floor));
651 }
652 }
653 }
654}
655
656impl Default for VerticalConnectivity {
657 fn default() -> Self {
658 Self::new()
659 }
660}
661
662impl Default for ConnectivityGraph {
663 fn default() -> Self {
664 Self::new()
665 }
666}