use crate::grid_system::GridSystem;
use crate::tile_set::TileSetVirtual;
use crate::wfc_util::*;
use rand::prelude::*;
use rand::rngs::StdRng;
use std::collections::{HashMap, HashSet, VecDeque};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CellState {
Uncollapsed,
Collapsed,
Conflict,
}
#[derive(Debug, Clone)]
pub struct CellWfcData {
pub state: CellState,
pub entropy: f64,
pub rand_seed: u64,
pub rand_num: i32,
pub possibilities: Vec<TileId>,
}
impl CellWfcData {
pub fn new(rand_seed: u64, possibilities: Vec<TileId>) -> Self {
let mut rng = StdRng::seed_from_u64(rand_seed);
let rand_num = rng.random::<i32>().abs();
Self {
state: CellState::Uncollapsed,
entropy: 0.0, rand_seed,
rand_num,
possibilities,
}
}
}
pub type WfcSystemData = HashMap<CellId, CellWfcData>;
#[derive(Debug, Clone)]
pub struct SystemSnapshot {
data: WfcSystemData,
completed_count: usize,
}
#[derive(Debug, Clone)]
pub struct WfcConfig {
pub max_recursion_depth: usize,
pub random_seed: Option<u64>,
}
impl Default for WfcConfig {
fn default() -> Self {
Self {
max_recursion_depth: 3, random_seed: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum WfcError {
Grid(GridError),
NoUncollapsedCells,
CellNotFound(CellId),
TileNotFound,
CellAlreadyCollapsed,
InvalidTileChoice,
UnresolvableConflicts,
InconsistentState,
InitializationFailed(String),
}
impl From<GridError> for WfcError {
fn from(error: GridError) -> Self {
WfcError::Grid(error)
}
}
impl std::fmt::Display for WfcError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WfcError::Grid(e) => write!(f, "Grid error: {}", e),
WfcError::NoUncollapsedCells => write!(f, "No uncollapsed cells available"),
WfcError::CellNotFound(cell_id) => {
write!(f, "Cell not found in WFC data: {:?}", cell_id)
}
WfcError::TileNotFound => write!(f, "Tile not found in tile set"),
WfcError::CellAlreadyCollapsed => write!(f, "Cell is already collapsed"),
WfcError::InvalidTileChoice => write!(f, "Invalid tile choice for cell"),
WfcError::UnresolvableConflicts => write!(f, "Conflicts cannot be resolved"),
WfcError::InconsistentState => write!(f, "WFC system state is inconsistent"),
WfcError::InitializationFailed(msg) => write!(f, "Initialization failed: {}", msg),
}
}
}
impl std::error::Error for WfcError {}
pub trait WfcInitializer<EdgeData>
where
EdgeData: Clone + PartialEq + std::fmt::Debug + Send + Sync,
{
fn initialize(&mut self, manager: &mut WfcManager<EdgeData>) -> Result<(), WfcError>;
}
pub struct DefaultInitializer;
impl<EdgeData> WfcInitializer<EdgeData> for DefaultInitializer
where
EdgeData: Clone + PartialEq + std::fmt::Debug + Send + Sync,
{
fn initialize(&mut self, manager: &mut WfcManager<EdgeData>) -> Result<(), WfcError> {
manager.tile_set.build_tile_set()?;
for cell_id in manager.grid.get_all_cells() {
let rand_seed = manager.rng.random();
let all_tiles = manager.tile_set.get_all_tile_ids();
let cell_data = CellWfcData::new(rand_seed, all_tiles);
manager.wfc_data.insert(cell_id, cell_data);
}
manager.update_all_entropies()?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum StepResult {
Collapsed,
ConflictsResolved,
ConflictResolutionFailed,
Complete,
}
pub struct WfcManager<EdgeData>
where
EdgeData: Clone + PartialEq + std::fmt::Debug + Send + Sync,
{
grid: GridSystem,
tile_set: Box<dyn TileSetVirtual<EdgeData>>,
wfc_data: WfcSystemData,
completed_count: usize,
rng: StdRng,
config: WfcConfig,
#[allow(dead_code)]
entropy_cache: HashMap<Vec<TileId>, f64>,
}
impl<EdgeData> WfcManager<EdgeData>
where
EdgeData: Clone + PartialEq + std::fmt::Debug + Send + Sync,
{
pub fn new(
grid: GridSystem,
tile_set: Box<dyn TileSetVirtual<EdgeData>>,
) -> Result<Self, WfcError> {
let config = WfcConfig::default();
let seed = config
.random_seed
.unwrap_or_else(|| rand::rng().random());
let rng = StdRng::seed_from_u64(seed);
Ok(Self {
grid,
tile_set,
wfc_data: HashMap::new(),
completed_count: 0,
rng,
config,
entropy_cache: HashMap::new(),
})
}
pub fn with_config(
grid: GridSystem,
tile_set: Box<dyn TileSetVirtual<EdgeData>>,
config: WfcConfig,
) -> Result<Self, WfcError> {
let seed = config
.random_seed
.unwrap_or_else(|| rand::rng().random());
let rng = StdRng::seed_from_u64(seed);
Ok(Self {
grid,
tile_set,
wfc_data: HashMap::new(),
completed_count: 0,
rng,
config,
entropy_cache: HashMap::new(),
})
}
pub fn initialize_with<I: WfcInitializer<EdgeData>>(
&mut self,
initializer: &mut I,
) -> Result<(), WfcError> {
initializer.initialize(self)
}
pub fn run(&mut self) -> Result<(), WfcError> {
while !self.is_complete() {
self.collapse()?;
}
if !self.resolve_conflicts()? {
return Err(WfcError::UnresolvableConflicts);
}
Ok(())
}
pub fn run_step(&mut self) -> Result<StepResult, WfcError> {
if self.is_complete() {
if self.has_conflicts() {
if self.resolve_conflicts()? {
Ok(StepResult::ConflictsResolved)
} else {
Ok(StepResult::ConflictResolutionFailed)
}
} else {
Ok(StepResult::Complete)
}
} else {
self.collapse()?;
Ok(StepResult::Collapsed)
}
}
pub fn pre_collapse(&mut self, cell: CellId, tile: TileId) -> Result<(), WfcError> {
let cell_data = self
.wfc_data
.get_mut(&cell)
.ok_or(WfcError::CellNotFound(cell))?;
if cell_data.state != CellState::Uncollapsed {
return Err(WfcError::CellAlreadyCollapsed);
}
if !cell_data.possibilities.contains(&tile) {
return Err(WfcError::InvalidTileChoice);
}
self.set_tile_for_cell(cell, tile)?;
self.propagate_effects(cell)?;
Ok(())
}
pub fn is_complete(&self) -> bool {
self.completed_count == self.grid.get_cells_count()
}
pub fn get_cell_state(&self, cell_id: CellId) -> Result<CellState, WfcError> {
self.wfc_data
.get(&cell_id)
.map(|data| data.state)
.ok_or(WfcError::CellNotFound(cell_id))
}
pub fn get_collapsed_cell_tile(&self, cell_id: CellId) -> Result<TileId, WfcError> {
let cell_data = self
.wfc_data
.get(&cell_id)
.ok_or(WfcError::CellNotFound(cell_id))?;
if cell_data.state == CellState::Collapsed && cell_data.possibilities.len() == 1 {
Ok(cell_data.possibilities[0])
} else {
Err(WfcError::InconsistentState)
}
}
pub fn get_grid(&self) -> &GridSystem {
&self.grid
}
pub fn get_all_tile_ids(&self) -> Vec<TileId> {
(0..self.tile_set.get_tile_count()).collect()
}
pub fn get_tile(&self, tile_id: TileId) -> Option<&Tile<EdgeData>> {
if tile_id < self.tile_set.get_tile_count() {
self.tile_set.get_tile(tile_id)
} else {
None
}
}
fn collapse(&mut self) -> Result<(), WfcError> {
let min_entropy_cell = self.find_min_entropy_cell()?;
let chosen_tile = self.choose_tile_from_probabilities(min_entropy_cell)?;
self.set_tile_for_cell(min_entropy_cell, chosen_tile)?;
self.propagate_effects(min_entropy_cell)?;
Ok(())
}
fn find_min_entropy_cell(&self) -> Result<CellId, WfcError> {
self.wfc_data
.iter()
.filter(|(_, data)| data.state == CellState::Uncollapsed)
.min_by(|(_, a), (_, b)| {
a.entropy
.partial_cmp(&b.entropy)
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(&cell_id, _)| cell_id)
.ok_or(WfcError::NoUncollapsedCells)
}
fn choose_tile_from_probabilities(&mut self, cell_id: CellId) -> Result<TileId, WfcError> {
let cell_data = self
.wfc_data
.get(&cell_id)
.ok_or(WfcError::CellNotFound(cell_id))?;
if cell_data.possibilities.is_empty() {
return Err(WfcError::InvalidTileChoice);
}
let mut total_weight = 0i32;
for &tile_id in &cell_data.possibilities {
if let Some(tile) = self.tile_set.get_tile(tile_id) {
total_weight += tile.weight;
}
}
if total_weight == 0 {
return Ok(cell_data.possibilities[0]); }
let rand_num = cell_data.rand_num % total_weight;
let mut weight_sum = 0i32;
for &tile_id in &cell_data.possibilities {
if let Some(tile) = self.tile_set.get_tile(tile_id) {
weight_sum += tile.weight;
if weight_sum > rand_num { return Ok(tile_id);
}
}
}
Ok(*cell_data.possibilities.last().unwrap())
}
fn set_tile_for_cell(&mut self, cell_id: CellId, tile_id: TileId) -> Result<(), WfcError> {
let cell_data = self
.wfc_data
.get_mut(&cell_id)
.ok_or(WfcError::CellNotFound(cell_id))?;
cell_data.possibilities.clear();
cell_data.possibilities.push(tile_id);
cell_data.entropy = 0.0;
cell_data.state = CellState::Collapsed;
self.completed_count += 1;
Ok(())
}
fn propagate_effects(&mut self, start_cell: CellId) -> Result<(), WfcError> {
if self.is_complete() {
return Ok(());
}
let mut propagation_queue = VecDeque::new();
let mut processed_cells = HashSet::new();
propagation_queue.push_back(start_cell);
processed_cells.insert(start_cell);
while let Some(current_cell) = propagation_queue.pop_front() {
let neighbors = self.grid.get_neighbors(current_cell);
for neighbor in neighbors {
if processed_cells.contains(&neighbor) {
continue;
}
let neighbor_data = self
.wfc_data
.get(&neighbor)
.ok_or(WfcError::CellNotFound(neighbor))?;
if neighbor_data.state != CellState::Uncollapsed {
continue;
}
let constraint_updated = self.update_neighbor_possibilities(neighbor)?;
if constraint_updated {
propagation_queue.push_back(neighbor);
processed_cells.insert(neighbor);
}
}
}
Ok(())
}
fn update_neighbor_possibilities(&mut self, neighbor: CellId) -> Result<bool, WfcError> {
let neighbor_data = self
.wfc_data
.get(&neighbor)
.ok_or(WfcError::CellNotFound(neighbor))?
.clone();
if neighbor_data.state != CellState::Uncollapsed {
return Ok(false); }
let compatible_tiles = self.filter_compatible_tiles(neighbor)?;
let old_count = neighbor_data.possibilities.len();
let new_count = compatible_tiles.len();
if new_count != old_count {
let new_entropy = self.calculate_entropy(&compatible_tiles);
let neighbor_data_mut = self.wfc_data.get_mut(&neighbor).unwrap();
neighbor_data_mut.possibilities = compatible_tiles;
neighbor_data_mut.entropy = new_entropy;
if neighbor_data_mut.possibilities.is_empty() {
neighbor_data_mut.state = CellState::Conflict;
}
Ok(true)
} else {
Ok(false)
}
}
fn filter_compatible_tiles(&self, cell_id: CellId) -> Result<Vec<TileId>, WfcError> {
let cell_data = self
.wfc_data
.get(&cell_id)
.ok_or(WfcError::CellNotFound(cell_id))?;
let mut compatible_tiles = Vec::new();
for &tile_id in &cell_data.possibilities {
if self.tile_is_compatible(tile_id, cell_id)? {
compatible_tiles.push(tile_id);
}
}
Ok(compatible_tiles)
}
fn tile_is_compatible(&self, tile_id: TileId, cell_id: CellId) -> Result<bool, WfcError> {
let neighbors = self.grid.get_neighbors(cell_id);
let mut neighbor_possibilities = Vec::new();
for neighbor in neighbors {
if let Some(neighbor_data) = self.wfc_data.get(&neighbor) {
neighbor_possibilities.push(neighbor_data.possibilities.clone());
} else {
neighbor_possibilities.push(self.tile_set.get_all_tile_ids());
}
}
Ok(self
.tile_set
.judge_possibility(&neighbor_possibilities, tile_id))
}
fn calculate_entropy(&self, possibilities: &[TileId]) -> f64 {
if possibilities.is_empty() {
return 0.0;
}
if possibilities.len() == 1 {
return 0.0;
}
let total_weight: f64 = possibilities
.iter()
.filter_map(|&tile_id| self.tile_set.get_tile(tile_id))
.map(|tile| tile.weight as f64)
.sum();
if total_weight == 0.0 {
return (possibilities.len() as f64).log2();
}
possibilities
.iter()
.filter_map(|&tile_id| self.tile_set.get_tile(tile_id))
.map(|tile| tile.weight as f64 / total_weight)
.filter(|&prob| prob > 0.0)
.map(|prob| -prob * prob.log2())
.sum()
}
fn update_all_entropies(&mut self) -> Result<(), WfcError> {
let cell_ids: Vec<CellId> = self.wfc_data.keys().copied().collect();
for cell_id in cell_ids {
let possibilities = self.wfc_data[&cell_id].possibilities.clone();
let entropy = self.calculate_entropy(&possibilities);
self.wfc_data.get_mut(&cell_id).unwrap().entropy = entropy;
}
Ok(())
}
pub fn resolve_conflicts(&mut self) -> Result<bool, WfcError> {
let conflict_cells = self.collect_conflict_cells();
if conflict_cells.is_empty() {
return Ok(true);
}
self.layered_backtrack_resolution(conflict_cells)
}
fn collect_conflict_cells(&self) -> Vec<CellId> {
self.wfc_data
.iter()
.filter(|(_, data)| data.state == CellState::Conflict)
.map(|(&cell_id, _)| cell_id)
.collect()
}
fn layered_backtrack_resolution(
&mut self,
conflict_cells: Vec<CellId>,
) -> Result<bool, WfcError> {
let mut layers = vec![conflict_cells];
self.resolve_conflicts_recursive(&mut layers, 0)
}
fn resolve_conflicts_recursive(
&mut self,
layers: &mut Vec<Vec<CellId>>,
depth: usize,
) -> Result<bool, WfcError> {
if depth >= self.config.max_recursion_depth {
return Ok(false);
}
for layer_idx in (0..layers.len()).rev() {
for &cell in &layers[layer_idx].clone() {
self.recover_cell_possibilities(cell, layers)?;
}
}
let all_cells: Vec<CellId> = layers.iter().flatten().copied().collect();
if self.backtrack_solution(&all_cells, 0)? {
return Ok(true);
}
if depth < self.config.max_recursion_depth - 1 {
let next_layer = self.build_next_layer(&layers[depth])?;
if !next_layer.is_empty() {
layers.push(next_layer);
return self.resolve_conflicts_recursive(layers, depth + 1);
}
}
Ok(false)
}
fn recover_cell_possibilities(
&mut self,
cell_id: CellId,
layers: &[Vec<CellId>],
) -> Result<(), WfcError> {
let layers_vec: Vec<Vec<CellId>> = layers.to_vec();
let (cx, _) = find_in_2d_vector(&layers_vec, &cell_id).unwrap_or((layers.len(), 0));
let neighbors = self.grid.get_neighbors(cell_id);
let mut neighbor_possibilities = Vec::new();
for neighbor in neighbors {
let (nx, _) = find_in_2d_vector(&layers_vec, &neighbor).unwrap_or((layers.len(), 0));
if nx >= cx {
if let Some(neighbor_data) = self.wfc_data.get(&neighbor) {
neighbor_possibilities.push(neighbor_data.possibilities.clone());
} else {
neighbor_possibilities.push(vec![]);
}
} else {
neighbor_possibilities.push(vec![]);
}
}
let mut new_possibilities = Vec::new();
for tile_id in self.tile_set.get_all_tile_ids() {
if self
.tile_set
.judge_possibility(&neighbor_possibilities, tile_id)
{
new_possibilities.push(tile_id);
}
}
let new_entropy = self.calculate_entropy(&new_possibilities);
let new_state = if new_possibilities.is_empty() {
CellState::Conflict
} else {
CellState::Uncollapsed
};
let cell_data = self
.wfc_data
.get_mut(&cell_id)
.ok_or(WfcError::CellNotFound(cell_id))?;
cell_data.possibilities = new_possibilities;
cell_data.entropy = new_entropy;
cell_data.state = new_state;
Ok(())
}
fn build_next_layer(&self, current_layer: &[CellId]) -> Result<Vec<CellId>, WfcError> {
let mut next_layer = Vec::new();
for &cell in current_layer {
let neighbors = self.grid.get_neighbors(cell);
for neighbor in neighbors {
if let Some(neighbor_data) = self.wfc_data.get(&neighbor) {
if neighbor_data.state == CellState::Collapsed
&& !next_layer.contains(&neighbor)
{
next_layer.push(neighbor);
}
}
}
}
Ok(next_layer)
}
fn backtrack_solution(&mut self, cells: &[CellId], index: usize) -> Result<bool, WfcError> {
if index >= cells.len() {
return Ok(true);
}
let cell_id = cells[index];
let cell_data = self
.wfc_data
.get(&cell_id)
.ok_or(WfcError::CellNotFound(cell_id))?;
if cell_data.possibilities.is_empty() {
return Ok(false);
}
if index == cells.len() - 1 {
let first_possibility = cell_data.possibilities[0];
self.set_tile_for_cell(cell_id, first_possibility)?;
return Ok(true);
}
let snapshot = self.create_snapshot();
for &possibility in &cell_data.possibilities.clone() {
if self.tile_is_compatible(possibility, cell_id)? {
self.set_tile_for_cell(cell_id, possibility)?;
if self.backtrack_solution(cells, index + 1)? {
return Ok(true);
}
self.restore_snapshot(snapshot.clone())?;
}
}
Ok(false)
}
fn create_snapshot(&self) -> SystemSnapshot {
SystemSnapshot {
data: self.wfc_data.clone(),
completed_count: self.completed_count,
}
}
fn restore_snapshot(&mut self, snapshot: SystemSnapshot) -> Result<(), WfcError> {
self.wfc_data = snapshot.data;
self.completed_count = snapshot.completed_count;
Ok(())
}
fn has_conflicts(&self) -> bool {
self.wfc_data
.values()
.any(|data| data.state == CellState::Conflict)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tile_set::TileSet;
struct TestTileSet {
tiles: TileSet<&'static str>,
}
impl TestTileSet {
pub fn new() -> Self {
let mut tiles = TileSet::new();
tiles.add_tile(vec!["A", "B", "C", "D"], 10);
tiles.add_tile(vec!["B", "A", "D", "C"], 15);
Self { tiles }
}
}
impl TileSetVirtual<&'static str> for TestTileSet {
fn build_tile_set(&mut self) -> Result<(), GridError> {
Ok(())
}
fn judge_possibility(
&self,
_neighbor_possibilities: &[Vec<TileId>],
_candidate: TileId,
) -> bool {
true
}
fn get_tile(&self, tile_id: TileId) -> Option<&Tile<&'static str>> {
self.tiles.get_tile(tile_id)
}
fn get_tile_count(&self) -> usize {
self.tiles.get_tile_count()
}
fn get_all_tile_ids(&self) -> Vec<TileId> {
self.tiles.get_all_tile_ids()
}
}
#[test]
fn test_wfc_manager_creation() {
let grid = GridSystem::new();
let tile_set = Box::new(TestTileSet::new()) as Box<dyn TileSetVirtual<&'static str>>;
let manager = WfcManager::new(grid, tile_set).unwrap();
assert_eq!(manager.completed_count, 0);
assert!(manager.is_complete()); }
#[test]
fn test_wfc_states() {
assert_eq!(CellState::Uncollapsed, CellState::Uncollapsed);
assert_ne!(CellState::Uncollapsed, CellState::Collapsed);
let data = CellWfcData::new(12345, vec![0, 1]);
assert_eq!(data.state, CellState::Uncollapsed);
assert_eq!(data.rand_seed, 12345);
assert_eq!(data.possibilities.len(), 2);
}
}