MVP
This commit is contained in:
commit
08319e6445
4 changed files with 317 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
75
Cargo.lock
generated
Normal file
75
Cargo.lock
generated
Normal 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
9
Cargo.toml
Normal 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
232
src/main.rs
Normal 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();
|
||||||
|
}
|
||||||
Reference in a new issue