diff --git a/src/map.rs b/src/map.rs index 71f9c56..964d37f 100644 --- a/src/map.rs +++ b/src/map.rs @@ -7,7 +7,7 @@ pub const MAP_WIDTH: usize = 80; pub const MAP_HEIGHT: usize = 43; pub const MAP_COUNT: usize = MAP_HEIGHT * MAP_WIDTH; -#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)] pub enum TileType { Wall, Floor, diff --git a/src/map_builders/waveform_collapse/common.rs b/src/map_builders/waveform_collapse/common.rs new file mode 100644 index 0000000..49e9fa8 --- /dev/null +++ b/src/map_builders/waveform_collapse/common.rs @@ -0,0 +1,13 @@ +use crate::TileType; + +#[derive(PartialEq, Eq, Hash, Clone)] +pub struct MapChunk { + pub pattern: Vec, + pub exits: [Vec; 4], + pub has_exits: bool, + pub compatible_with: [Vec; 4], +} + +pub fn tile_idx_in_chunk(chunk_size: i32, x: i32, y: i32) -> usize { + ((y * chunk_size) + x) as usize +} diff --git a/src/map_builders/waveform_collapse/constraints.rs b/src/map_builders/waveform_collapse/constraints.rs new file mode 100644 index 0000000..136e9ec --- /dev/null +++ b/src/map_builders/waveform_collapse/constraints.rs @@ -0,0 +1,226 @@ +use super::MapChunk; +use crate::map_builders::waveform_collapse::common::tile_idx_in_chunk; +use crate::{Map, TileType}; +use std::collections::HashSet; + +pub fn build_patterns( + map: &Map, + chunk_size: i32, + include_flipping: bool, + dedupe: bool, +) -> Vec> { + let chunks_x = map.width / chunk_size; + let chunks_y = map.height / chunk_size; + let mut patterns = Vec::new(); + + for cy in 0..chunks_y { + for cx in 0..chunks_x { + // Normal orientation + let mut pattern: Vec = Vec::new(); + let start_x = cx * chunk_size; + let end_x = (cx + 1) * chunk_size; + let start_y = cy * chunk_size; + let end_y = (cy + 1) * chunk_size; + + for y in start_y..end_y { + for x in start_x..end_x { + let idx = map.xy_idx(x, y); + pattern.push(map.tiles[idx]); + } + } + patterns.push(pattern); + + if include_flipping { + // Flip horizontal + pattern = Vec::new(); + for y in start_y..end_y { + for x in start_x..end_x { + let idx = map.xy_idx(end_x - (x + 1), y); + pattern.push(map.tiles[idx]); + } + } + patterns.push(pattern); + + // Flip vertical + pattern = Vec::new(); + for y in start_y..end_y { + for x in start_x..end_x { + let idx = map.xy_idx(x, end_y - (y + 1)); + pattern.push(map.tiles[idx]); + } + } + patterns.push(pattern); + + // Flip both + pattern = Vec::new(); + for y in start_y..end_y { + for x in start_x..end_x { + let idx = map.xy_idx(end_x - (x + 1), end_y - (y + 1)); + pattern.push(map.tiles[idx]); + } + } + patterns.push(pattern); + } + } + } + + // Dedupe + if dedupe { + rltk::console::log(format!( + "Pre de-duplication, there are {} patterns", + patterns.len() + )); + + // Use a set to de-duplicate + let set: HashSet> = patterns.drain(..).collect(); + patterns.extend(set.into_iter()); + + rltk::console::log(format!("There are {} patterns", patterns.len())); + } + + patterns +} + +pub fn render_pattern_to_map( + map: &mut Map, + chunk: &MapChunk, + chunk_size: i32, + start_x: i32, + start_y: i32, +) { + let mut i = 0_usize; + for tile_y in 0..chunk_size { + for tile_x in 0..chunk_size { + let map_idx = map.xy_idx(start_x + tile_x, start_y + tile_y); + map.tiles[map_idx] = chunk.pattern[i]; + map.visible_tiles[map_idx] = true; + + i += 1; + } + } + + for (x, northbound) in chunk.exits[0].iter().enumerate() { + if *northbound { + let map_idx = map.xy_idx(start_x + x as i32, start_y); + map.tiles[map_idx] = TileType::DownStairs; + } + } + for (x, southbound) in chunk.exits[1].iter().enumerate() { + if *southbound { + let map_idx = map.xy_idx(start_x + x as i32, start_y + chunk_size - 1); + map.tiles[map_idx] = TileType::DownStairs; + } + } + for (x, westbound) in chunk.exits[2].iter().enumerate() { + if *westbound { + let map_idx = map.xy_idx(start_x, start_y + x as i32); + map.tiles[map_idx] = TileType::DownStairs; + } + } + for (x, eastbound) in chunk.exits[3].iter().enumerate() { + if *eastbound { + let map_idx = map.xy_idx(start_x + chunk_size - 1, start_y + x as i32); + map.tiles[map_idx] = TileType::DownStairs; + } + } +} + +pub fn patterns_to_constraints(patterns: Vec>, chunk_size: i32) -> Vec { + // Move into the new constraints object + let mut constraints: Vec = Vec::new(); + for p in patterns { + let mut new_chunk = MapChunk { + pattern: p, + exits: [Vec::new(), Vec::new(), Vec::new(), Vec::new()], + has_exits: true, + compatible_with: [Vec::new(), Vec::new(), Vec::new(), Vec::new()], + }; + for exit in new_chunk.exits.iter_mut() { + for _i in 0..chunk_size { + exit.push(false); + } + } + + let mut n_exits = 0; + for x in 0..chunk_size { + // Check for north-bound exits + let north_idx = tile_idx_in_chunk(chunk_size, x, 0); + if new_chunk.pattern[north_idx] == TileType::Floor { + new_chunk.exits[0][x as usize] = true; + n_exits += 1; + } + + // Check for south-bound exits + let south_idx = tile_idx_in_chunk(chunk_size, x, chunk_size - 1); + if new_chunk.pattern[south_idx] == TileType::Floor { + new_chunk.exits[1][x as usize] = true; + n_exits += 1; + } + + // Check for west-bound exits + let west_idx = tile_idx_in_chunk(chunk_size, 0, x); + if new_chunk.pattern[west_idx] == TileType::Floor { + new_chunk.exits[2][x as usize] = true; + n_exits += 1; + } + + // Check for east-bound exits + let east_idx = tile_idx_in_chunk(chunk_size, chunk_size - 1, x); + if new_chunk.pattern[east_idx] == TileType::Floor { + new_chunk.exits[3][x as usize] = true; + n_exits += 1; + } + } + + if n_exits == 0 { + new_chunk.has_exits = false; + } + + constraints.push(new_chunk); + } + + // Build compatibility matrix + let ch = constraints.clone(); + for c in constraints.iter_mut() { + for (j, potential) in ch.iter().enumerate() { + // If there are no exits at all, it's compatible + if !c.has_exits || !potential.has_exits { + for compat in c.compatible_with.iter_mut() { + compat.push(j); + } + } else { + // Evaluate compatibility by direction + for (direction, exit_list) in c.exits.iter_mut().enumerate() { + let opposite = match direction { + 0 => 1, // Our North, Their South + 1 => 0, // Our South, Their North + 2 => 3, // Our West, Their East + _ => 2, // Our East, Their West + }; + + let mut it_fits = false; + let mut has_any = false; + for (slot, can_enter) in exit_list.iter().enumerate() { + if *can_enter { + has_any = true; + if potential.exits[opposite][slot] { + it_fits = true; + } + } + } + if it_fits { + c.compatible_with[direction].push(j); + } + if !has_any { + // There's no exits on this side, we don't care what goes there + for compat in c.compatible_with.iter_mut() { + compat.push(j); + } + } + } + } + } + } + + constraints +} diff --git a/src/map_builders/waveform_collapse/image_loader.rs b/src/map_builders/waveform_collapse/image_loader.rs index 7a8522c..75f125f 100644 --- a/src/map_builders/waveform_collapse/image_loader.rs +++ b/src/map_builders/waveform_collapse/image_loader.rs @@ -1,5 +1,5 @@ -use rltk::rex::XpFile; use crate::{Map, TileType}; +use rltk::rex::XpFile; /// Loads a RexPaint file, and converts it into our map format pub fn load_rex_map(new_depth: i32, xp_file: &XpFile) -> Map { @@ -13,7 +13,7 @@ pub fn load_rex_map(new_depth: i32, xp_file: &XpFile) -> Map { let idx = map.xy_idx(x as i32, y as i32); match cell.ch { 32 => map.tiles[idx] = TileType::Floor, // # - 35 => map.tiles[idx] = TileType::Wall, // # + 35 => map.tiles[idx] = TileType::Wall, // # _ => {} } } @@ -22,4 +22,4 @@ pub fn load_rex_map(new_depth: i32, xp_file: &XpFile) -> Map { } map -} \ No newline at end of file +} diff --git a/src/map_builders/waveform_collapse/solver.rs b/src/map_builders/waveform_collapse/solver.rs new file mode 100644 index 0000000..e69de29