This commit is contained in:
Steven 2024-01-28 00:56:57 -05:00
commit 08319e6445
4 changed files with 317 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

75
Cargo.lock generated Normal file
View file

@ -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",
]

9
Cargo.toml Normal file
View file

@ -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"

232
src/main.rs Normal file
View file

@ -0,0 +1,232 @@
use std::collections::HashMap;
use rand::{distributions::{Distribution, WeightedIndex}, rngs::ThreadRng, thread_rng};
type Tile = i32;
type PossibleStates = Vec<Tile>;
#[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<CellInfo>,
}
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<CellInfo> {
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<Vec<Cell>>,
}
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::<Vec<_>>();
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();
}