Refactor
This commit is contained in:
+10
-136
@@ -1,144 +1,18 @@
|
||||
//! wds — Weirdcore Dating Simulator.
|
||||
//!
|
||||
//! CLI-Phase: Die Kern-Systeme (Signal/Action-Dispatcher, KV-Store,
|
||||
//! Ink-Stories) laufen headless; diese REPL ist ihr erstes Frontend.
|
||||
//! Der Renderer kommt später dazu (siehe notes/renderer-plan.md) und
|
||||
//! konsumiert dieselben Schnittstellen — insbesondere `StoryState` und
|
||||
//! die `Action`-Queue.
|
||||
//! Aufbau: `engine/` ist der headless Kern (Signal/Action-Dispatcher,
|
||||
//! KV-Store, Ink-Stories); Frontends konsumieren ihn über `StoryState`
|
||||
//! und die `Action`-Queue. Aktuell einziges Frontend: die CLI-REPL in
|
||||
//! `cli`. Der Renderer kommt als zweites dazu (notes/renderer-plan.md).
|
||||
|
||||
use std::io::{self, Write};
|
||||
mod cli;
|
||||
mod engine;
|
||||
|
||||
mod assets;
|
||||
mod game;
|
||||
mod ink;
|
||||
mod kv;
|
||||
mod signals;
|
||||
mod story_ctrl;
|
||||
|
||||
use game::{Action, Game};
|
||||
use ink::StoryState;
|
||||
use engine::game::Game;
|
||||
use engine::{assets, signals};
|
||||
|
||||
fn main() {
|
||||
let signals_path = assets::path("assets/signals.toml");
|
||||
let signal_table = signals::load_signals(&signals_path);
|
||||
println!("wds-Konsole — {} Signale aus {}. `help` für Befehle.",
|
||||
signal_table.len(), signals_path);
|
||||
let mut game = Game::new(signal_table);
|
||||
|
||||
// Reserviertes `[init]`-Signal: KV-Defaults setzen, bevor Stories laufen.
|
||||
fire(&mut game, "init", None);
|
||||
|
||||
while let Some(line) = read_line("> ") {
|
||||
let line = line.trim();
|
||||
match line {
|
||||
"" => {}
|
||||
"quit" | "exit" => break,
|
||||
"help" => print_help(),
|
||||
"kv" => dump_kv(&game),
|
||||
"reload" => {
|
||||
game.signals = signals::load_signals(&signals_path);
|
||||
println!("{} Signale geladen.", game.signals.len());
|
||||
}
|
||||
_ => {
|
||||
if let Some(sig) = line.strip_prefix("signal ") {
|
||||
fire(&mut game, sig.trim(), None);
|
||||
} else if let Some(name) = line.strip_prefix("use ") {
|
||||
// Objekt-Interaktion simulieren: Signal ist der gestrippte
|
||||
// Name, $self der volle — wie der LMB-Klick-Pfad in irl3d.
|
||||
let name = name.trim();
|
||||
let key = signals::signal_key(name).to_string();
|
||||
fire(&mut game, &key, Some(name.to_string()));
|
||||
} else {
|
||||
println!("unbekannter Befehl: {line:?} — `help` für Befehle");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_help() {
|
||||
println!(" signal <s> Signal feuern oder Action direkt ausführen");
|
||||
println!(" (z.B. `signal Cube`, `signal set has_key true`)");
|
||||
println!(" use <instance> Objekt-Interaktion simulieren — `use Mushroom.005`");
|
||||
println!(" feuert Signal `Mushroom` mit $self = Mushroom.005");
|
||||
println!(" kv KV-Store anzeigen");
|
||||
println!(" reload signals.toml neu laden");
|
||||
println!(" quit beenden");
|
||||
}
|
||||
|
||||
/// Signal dispatchen, deferred Actions ausgeben und — falls eine Action
|
||||
/// eine Story gestartet hat — den Dialog-Loop fahren.
|
||||
fn fire(game: &mut Game, signal: &str, instance: Option<String>) {
|
||||
{
|
||||
let mut ctx = game.action_ctx(instance);
|
||||
signals::dispatch(signal, &mut ctx);
|
||||
}
|
||||
drain_actions(game);
|
||||
if game.story.is_some() {
|
||||
run_story(game);
|
||||
}
|
||||
}
|
||||
|
||||
/// Dialog-Loop: Story schrittweise treiben, Tags zurück in den Dispatcher,
|
||||
/// Choices über nummerierte Eingabe. Läuft bis `End` (oder EOF).
|
||||
fn run_story(game: &mut Game) {
|
||||
let mut sel: Option<usize> = None;
|
||||
loop {
|
||||
let state = {
|
||||
let mut ctx = game.action_ctx(None);
|
||||
let (state, tags) = story_ctrl::advance(sel, &mut ctx);
|
||||
for t in &tags { signals::dispatch(t, &mut ctx); }
|
||||
state
|
||||
};
|
||||
drain_actions(game);
|
||||
match state {
|
||||
None | Some(StoryState::End) => break,
|
||||
Some(StoryState::Text(text)) => {
|
||||
println!("\n{text}");
|
||||
if read_line(" [Enter] ").is_none() { break; }
|
||||
sel = None;
|
||||
}
|
||||
Some(StoryState::Choice { prompt, options }) => {
|
||||
if !prompt.is_empty() { println!("\n{prompt}"); }
|
||||
for (i, o) in options.iter().enumerate() {
|
||||
println!(" {}) {o}", i + 1);
|
||||
}
|
||||
sel = loop {
|
||||
let Some(line) = read_line(" wahl> ") else { return; };
|
||||
if let Ok(n) = line.trim().parse::<usize>() {
|
||||
if (1..=options.len()).contains(&n) { break Some(n - 1); }
|
||||
}
|
||||
println!(" (1..{})", options.len());
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn drain_actions(game: &mut Game) {
|
||||
for a in game.actions.drain(..) {
|
||||
match a {
|
||||
Action::HideObject(n) => println!("[action] hide_object {n}"),
|
||||
Action::PlaySound(n) => println!("[action] play_sound {n}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dump_kv(game: &Game) {
|
||||
let mut keys: Vec<&String> = game.kv.keys().collect();
|
||||
keys.sort();
|
||||
for k in keys {
|
||||
println!(" {k} = {}", kv::format_value(&game.kv[k]));
|
||||
}
|
||||
}
|
||||
|
||||
/// Zeile von stdin lesen; `None` bei EOF (Ctrl-D) oder Lesefehler.
|
||||
fn read_line(prompt: &str) -> Option<String> {
|
||||
print!("{prompt}");
|
||||
io::stdout().flush().ok();
|
||||
let mut buf = String::new();
|
||||
match io::stdin().read_line(&mut buf) {
|
||||
Ok(0) | Err(_) => None,
|
||||
Ok(_) => Some(buf.trim_end().to_string()),
|
||||
}
|
||||
let game = Game::new(signals::load_signals(&signals_path));
|
||||
cli::run(game, &signals_path);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user