commit 08319e64458d9cd99b568585f8dba786c2c1c70e Author: Steven Date: Sun Jan 28 00:56:57 2024 -0500 MVP diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..435bf63 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,75 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wfc_2d_demo" +version = "0.1.0" +dependencies = [ + "rand", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e65cfb7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "wfc_2d_demo" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8.5" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b310201 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,232 @@ +use std::collections::HashMap; +use rand::{distributions::{Distribution, WeightedIndex}, rngs::ThreadRng, thread_rng}; + +type Tile = i32; +type PossibleStates = Vec; + +#[derive(Clone)] +struct Cell { + possible_states: PossibleStates, + entropy: f64, + collapsed: bool, +} + +impl Cell { + fn new(possible_states: PossibleStates) -> Self { + let entropy = calculate_entropy(&possible_states); + Cell { + possible_states, + entropy, + collapsed: false, + } + } +} + +#[derive(Clone)] +struct CellInfo { + entropy: f64, + coordinates: (usize, usize), +} + +struct MinHeap { + data: Vec, +} + +impl MinHeap { + fn new() -> Self { + MinHeap { data: Vec::new() } + } + + fn add(&mut self, element: CellInfo) { + self.data.push(element); + self.heapify_up(self.data.len() - 1); + } + + fn pop(&mut self) -> Option { + if self.data.is_empty() { + return None; + } + let last_index = self.data.len() - 1; + self.data.swap(0, last_index); + let result = self.data.pop(); + self.heapify_down(0); + result + } + + fn heapify_up(&mut self, mut index: usize) { + while index != 0 { + let parent_index = (index - 1) / 2; + if self.data[index].entropy < self.data[parent_index].entropy { + self.data.swap(parent_index, index); + } + index = parent_index; + } + } + + fn heapify_down(&mut self, mut index: usize) { + let length = self.data.len(); + loop { + let left_child = 2 * index + 1; + let right_child = 2 * index + 2; + + let mut smallest = index; + + if left_child < length && self.data[left_child].entropy < self.data[smallest].entropy { + smallest = left_child; + } + + if right_child < length && self.data[right_child].entropy < self.data[smallest].entropy { + smallest = right_child; + } + + if smallest != index { + self.data.swap(index, smallest); + index = smallest; + } else { + break; + } + } + } +} + +fn calculate_entropy(data: &[Tile]) -> f64 { + let mut frequency_map = HashMap::new(); + for &value in data { + *frequency_map.entry(value).or_insert(0) += 1; + } + let len = data.len() as f64; + let mut entropy = 0.0; + for &frequency in frequency_map.values() { + let probability = frequency as f64 / len; + entropy -= probability * probability.log2(); + } + entropy +} + +struct Grid { + cells: Vec>, +} + +impl Grid { + fn new(width: usize, height: usize, possible_states: PossibleStates) -> Self { + let cells = vec![vec![Cell::new(possible_states.clone()); width]; height]; + Grid { cells } + } + + fn observe_and_collapse(&mut self) { + let mut rng = thread_rng(); + + // Collect updates in a separate step to avoid borrowing conflicts + let mut updates = Vec::new(); + for (y, row) in self.cells.iter().enumerate() { + for (x, cell) in row.iter().enumerate() { + if !cell.collapsed { + let updated_states = self.updated_possible_states_based_on_neighbors(x, y, &cell.possible_states); + updates.push(((x, y), updated_states)); + } + } + } + + // Apply updates + for ((x, y), updated_states) in updates { + self.cells[y][x].possible_states = updated_states; + } + + // Now proceed with collapse + if let Some((x, y)) = self.find_min_entropy_cell() { + self.collapse_cell(x, y, &mut rng); + } + } + + + fn find_min_entropy_cell(&self) -> Option<(usize, usize)> { + let mut heap = MinHeap::new(); + + for (y, row) in self.cells.iter().enumerate() { + for (x, cell) in row.iter().enumerate() { + if !cell.collapsed { + heap.add(CellInfo { + entropy: cell.entropy, + coordinates: (x, y), + }); + } + } + } + + heap.pop().map(|cell_info| cell_info.coordinates) + } + + fn collapse_cell(&mut self, x: usize, y: usize, rng: &mut ThreadRng) { + let cell = &mut self.cells[y][x]; + + if cell.possible_states.is_empty() { + return; + } + + let weights = cell.possible_states.iter().map(|&_state| 1).collect::>(); + let dist = WeightedIndex::new(&weights).unwrap(); + let chosen_state = cell.possible_states[dist.sample(rng)]; + + cell.possible_states = vec![chosen_state]; + cell.entropy = 0.0; + cell.collapsed = true; + } + + fn updated_possible_states_based_on_neighbors(&self, x: usize, y: usize, current_states: &PossibleStates) -> PossibleStates { + let mut neighbors = Vec::new(); + + if y > 0 { neighbors.push(&self.cells[y - 1][x]); } + if y < self.cells.len() - 1 { neighbors.push(&self.cells[y + 1][x]); } + if x > 0 { neighbors.push(&self.cells[y][x - 1]); } + if x < self.cells[0].len() - 1 { neighbors.push(&self.cells[y][x + 1]); } + + let filtered_states: PossibleStates = current_states.iter() + .filter(|&&state| { + neighbors.iter().all(|neighbor| { + !neighbor.collapsed || (neighbor.collapsed && neighbor.possible_states != vec![state]) + }) + }) + .cloned() + .collect(); + + // println!("Cell[{}, {}] - Before: {:?}, After: {:?}", x, y, current_states, filtered_states); + filtered_states + } + + + + fn generate(&mut self) { + while self.cells.iter().any(|row| row.iter().any(|cell| !cell.collapsed)) { + self.observe_and_collapse(); + + for row in &mut self.cells { + for cell in row { + if cell.possible_states.is_empty() && !cell.collapsed { + cell.possible_states = vec![0]; + cell.collapsed = true; + } + } + } + } + } + + fn print_grid(&self) { + for row in &self.cells { + for cell in row { + let state = cell.possible_states.get(0).map_or('0', |&s| { + if s == 0 { '0' } else { char::from_digit(s as u32, 10).unwrap_or('?') } + }); + print!("{} ", state); + } + println!(); + } + } +} + +fn main() { + let possible_states = vec![1, 2]; + let mut grid = Grid::new(16, 16, possible_states); + + grid.generate(); + grid.print_grid(); +}