170 lines
4.2 KiB
Rust
170 lines
4.2 KiB
Rust
use std::collections::HashMap;
|
|
use std::sync::atomic::AtomicBool;
|
|
use std::sync::Arc;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum SymmetryType {
|
|
Rotational180,
|
|
Rotational90,
|
|
Diagonal,
|
|
Mirror,
|
|
None,
|
|
}
|
|
|
|
impl Default for SymmetryType {
|
|
fn default() -> Self {
|
|
Self::Rotational180
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum DifficultyPreset {
|
|
Glyph,
|
|
Rune,
|
|
Scroll,
|
|
Codex,
|
|
Grimoire,
|
|
}
|
|
|
|
impl DifficultyPreset {
|
|
pub fn grid_size(&self) -> usize {
|
|
match self {
|
|
Self::Glyph => 5,
|
|
Self::Rune => 5,
|
|
Self::Scroll => 9,
|
|
Self::Codex => 15,
|
|
Self::Grimoire => 21,
|
|
}
|
|
}
|
|
|
|
pub fn black_cell_density(&self) -> f64 {
|
|
match self {
|
|
Self::Glyph => 0.40,
|
|
Self::Rune => 0.35,
|
|
Self::Scroll => 0.28,
|
|
Self::Codex => 0.20,
|
|
Self::Grimoire => 0.18,
|
|
}
|
|
}
|
|
|
|
pub fn frequency_threshold(&self) -> f64 {
|
|
match self {
|
|
Self::Glyph => 0.5,
|
|
Self::Rune => 0.1,
|
|
Self::Scroll => 0.0,
|
|
Self::Codex => 0.0,
|
|
Self::Grimoire => 0.0,
|
|
}
|
|
}
|
|
|
|
pub fn difficulty_level(&self) -> u8 {
|
|
match self {
|
|
Self::Glyph => 0,
|
|
Self::Rune => 1,
|
|
Self::Scroll => 2,
|
|
Self::Codex => 3,
|
|
Self::Grimoire => 3,
|
|
}
|
|
}
|
|
|
|
pub fn obscurity_budget(&self) -> f64 {
|
|
match self {
|
|
Self::Glyph => 0.0,
|
|
Self::Rune => 0.0,
|
|
Self::Scroll => 0.05,
|
|
Self::Codex => 0.20,
|
|
Self::Grimoire => 0.20,
|
|
}
|
|
}
|
|
|
|
pub fn rand_top_base(&self) -> usize {
|
|
match self {
|
|
Self::Glyph => 20,
|
|
Self::Rune => 20,
|
|
Self::Scroll => 15,
|
|
Self::Codex => 10,
|
|
Self::Grimoire => 8,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct GenerationConfig {
|
|
pub width: usize,
|
|
pub height: usize,
|
|
pub symmetry: SymmetryType,
|
|
pub black_cell_density: f64,
|
|
pub min_word_length: usize,
|
|
pub frequency_threshold: f64,
|
|
pub obscurity_budget: f64,
|
|
pub max_backtrack: usize,
|
|
pub max_restarts: usize,
|
|
pub timeout_seconds: u64,
|
|
pub seed: Option<u64>,
|
|
pub cryptic_grid: bool,
|
|
pub rand_top_base: usize,
|
|
#[serde(skip)]
|
|
pub cancelled: Option<Arc<AtomicBool>>,
|
|
/// Per-word staleness penalties (0.0 = max penalty, 1.0 = no penalty).
|
|
/// Applied during solver scoring to deprioritize recently used words.
|
|
#[serde(skip)]
|
|
pub staleness: Option<Arc<HashMap<String, f64>>>,
|
|
}
|
|
|
|
impl Default for GenerationConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
width: 15,
|
|
height: 15,
|
|
symmetry: SymmetryType::default(),
|
|
black_cell_density: 0.22,
|
|
min_word_length: 3,
|
|
frequency_threshold: 0.0,
|
|
obscurity_budget: 0.20,
|
|
max_backtrack: 5000,
|
|
max_restarts: 10,
|
|
timeout_seconds: 90,
|
|
seed: None,
|
|
cryptic_grid: false,
|
|
rand_top_base: 10,
|
|
cancelled: None,
|
|
staleness: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl GenerationConfig {
|
|
pub fn rand_top_for_size(size: usize) -> usize {
|
|
match size {
|
|
0..=5 => 20,
|
|
6..=9 => 15,
|
|
10..=14 => 10,
|
|
_ => 8,
|
|
}
|
|
}
|
|
|
|
pub fn from_preset(preset: DifficultyPreset) -> Self {
|
|
let size = preset.grid_size();
|
|
let (bt, restarts) = match preset {
|
|
DifficultyPreset::Codex => (200000, 40),
|
|
DifficultyPreset::Grimoire => (500000, 60),
|
|
_ => (5000, 10),
|
|
};
|
|
Self {
|
|
width: size,
|
|
height: size,
|
|
black_cell_density: preset.black_cell_density(),
|
|
frequency_threshold: preset.frequency_threshold(),
|
|
obscurity_budget: preset.obscurity_budget(),
|
|
max_backtrack: bt,
|
|
max_restarts: restarts,
|
|
rand_top_base: preset.rand_top_base(),
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|