feat: m2 etagengenerierung — prefabs, floors, korridore, abstieg und sieg
Der handgebaute Testraum ist weg; Etagen entstehen aus Daten. - game/rng.rs: xorshift64*, Seed im Game (Ticket 14) - game/generate.rs: freie Platzierung, Pflichtraeume zuerst, Kette i->i-1 fuer strukturelle Erreichbarkeit, acht Orientierungen - data/: [[prefabs]] und [[floors]] als neunte und zehnte Registry, Ladepruefungen fuer rechteckige Layouts und Pflichtraum-Flaeche - Descend plus Siegbedingung: keine [[floors]] mit der naechsten Nummer heisst gewonnen — die Zahl 5 steht nirgends im Code - GameOver-Screen, mitlaufender Bildausschnitt im Grid Befund: Korridore stanzten 2340 Loecher in gezeichnete Waende, weil sie AUF der Tuer starteten. Erst Schritt nach aussen, dann L-Form, sonst Wegsuche ueber den freien Raum — die kann eine Raumwand nicht beruehren. Der Test zaehlt jetzt null. 21 Tests.
This commit is contained in:
@@ -92,6 +92,62 @@ pub struct CharacterOption {
|
||||
// Startausruestung brauchen Kampf bzw. Inventar.
|
||||
}
|
||||
|
||||
/// Ein von Hand gezeichneter Raum als ASCII-Block (Ticket 10).
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Prefab {
|
||||
pub id: String,
|
||||
#[serde(default)]
|
||||
pub tags: Vec<String>,
|
||||
pub layout: String,
|
||||
/// Ersetzt die Etagentabelle fuer die `l`-Plaetze dieses Raums (Ticket 16).
|
||||
#[serde(default)]
|
||||
pub loot: Option<String>,
|
||||
#[serde(default)]
|
||||
pub overrides: Option<String>,
|
||||
}
|
||||
|
||||
impl Prefab {
|
||||
/// Zeilen ohne fuehrende/abschliessende Leerzeile. TOML-Mehrzeiler beginnen
|
||||
/// mit einem Zeilenumbruch, sobald `"""` allein auf der Zeile steht.
|
||||
pub fn rows(&self) -> Vec<&str> {
|
||||
self.layout.lines().filter(|l| !l.trim().is_empty()).collect()
|
||||
}
|
||||
|
||||
pub fn size(&self) -> (u16, u16) {
|
||||
let rows = self.rows();
|
||||
(rows.first().map_or(0, |r| r.chars().count() as u16), rows.len() as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gewichteter Verweis auf eine Prefab-**Tag**-Gruppe. Tags statt IDs, sonst
|
||||
/// muesste jede Etagentabelle jedes neue Modder-Prefab einzeln aufzaehlen.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct WeightedTag {
|
||||
pub tag: String,
|
||||
#[serde(default = "one")]
|
||||
pub weight: u32,
|
||||
}
|
||||
|
||||
/// Eine Ebene des Dungeons und zugleich ihr Bauplan (Ticket 10).
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct FloorDef {
|
||||
pub id: String,
|
||||
pub number: u8,
|
||||
/// Felder, nicht Zellen.
|
||||
pub size: (u16, u16),
|
||||
/// Zielzahl inklusive Pflichtraeume.
|
||||
pub rooms: usize,
|
||||
/// Tags, die garantiert vorkommen — zuerst platziert, das *ist* die Garantie.
|
||||
#[serde(default)]
|
||||
pub required: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub prefabs: Vec<WeightedTag>,
|
||||
#[serde(default)]
|
||||
pub overrides: Option<String>,
|
||||
// TODO(M3): `creatures` — welche Gegner die `e`-Plaetze fuellen.
|
||||
// TODO(M4): `loot` — die Beutetabelle fuer die `l`-Plaetze (Ticket 16).
|
||||
}
|
||||
|
||||
/// Alle geladenen Definitionen. Liegt **neben** `Game`, nicht darin — als Feld
|
||||
/// kollidierte `&mut game.player` mit `&game.registries` bei jedem
|
||||
/// Methodenaufruf (Ticket 07).
|
||||
@@ -101,8 +157,10 @@ pub struct Registries {
|
||||
pub slots: HashMap<String, Slot>,
|
||||
pub categories: HashMap<String, Category>,
|
||||
pub options: HashMap<String, CharacterOption>,
|
||||
// TODO(M2+): `affixes`, `abilities`, `quests`, `creatures`, `prefabs`,
|
||||
// `floors`, `loot_tables`, `scripts` — zwoelf sind es am Ende.
|
||||
pub prefabs: HashMap<String, Prefab>,
|
||||
pub floors: HashMap<String, FloorDef>,
|
||||
// TODO(M3+): `affixes`, `abilities`, `quests`, `creatures`, `loot_tables`,
|
||||
// `scripts` — zwoelf sind es am Ende.
|
||||
}
|
||||
|
||||
impl Registries {
|
||||
@@ -113,4 +171,21 @@ impl Registries {
|
||||
v.sort_by_key(|s| (s.order, s.id.clone()));
|
||||
v
|
||||
}
|
||||
|
||||
/// Prefabs mit einem bestimmten Tag, in stabiler Reihenfolge — sonst
|
||||
/// haengt die Etage von der HashMap-Iterationsreihenfolge ab und derselbe
|
||||
/// Seed erzeugt zwei verschiedene Dungeons.
|
||||
pub fn prefabs_tagged(&self, tag: &str) -> Vec<&Prefab> {
|
||||
let mut v: Vec<&Prefab> =
|
||||
self.prefabs.values().filter(|p| p.tags.iter().any(|t| t == tag)).collect();
|
||||
v.sort_by(|a, b| a.id.cmp(&b.id));
|
||||
v
|
||||
}
|
||||
|
||||
pub fn floor_number(&self, number: u8) -> Option<&FloorDef> {
|
||||
// Stabil, falls zwei Mods dieselbe Nummer vergeben: die kleinere ID.
|
||||
// Ohne das entscheidet die HashMap-Reihenfolge, und derselbe Seed
|
||||
// erzeugte zwei verschiedene Dungeons.
|
||||
self.floors.values().filter(|f| f.number == number).min_by_key(|f| &f.id)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user