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