prototyp: abgewiesene kommandos kosten keinen zug, zaehler beschriftet

This commit is contained in:
2026-08-12 11:17:31 +02:00
parent 2ecf71af37
commit 604b8987ba

View File

@@ -35,6 +35,16 @@ mod game {
Wait, Wait,
} }
/// BEFUND aus dem Durchstich: Ticket 11 sagte "alle Commands kosten einen
/// Zug". Richtig ist "alle *ausgefuehrten*". Gegen eine Wand zu laufen darf
/// keine Zeit verbrauchen — sonst tickt ein Gift-Debuff, weil jemand
/// danebengetippt hat. Der Grund muss mit zurueck, sonst steht der Spieler
/// vor einem Spiel, das schweigend nichts tut.
pub enum Outcome {
TurnPassed,
Rejected(&'static str),
}
pub struct Game { pub struct Game {
pub map: Vec<&'static str>, pub map: Vec<&'static str>,
pub player: (u16, u16), pub player: (u16, u16),
@@ -78,8 +88,8 @@ mod game {
} }
/// Ticket 11: der **eine** Eingang, an dem ein Zug endet. /// Ticket 11: der **eine** Eingang, an dem ein Zug endet.
pub fn execute(&mut self, cmd: Command) { pub fn execute(&mut self, cmd: Command) -> Outcome {
match cmd { let outcome = match cmd {
Command::Move(dir) => { Command::Move(dir) => {
let (x, y) = self.player; let (x, y) = self.player;
let (nx, ny) = match dir { let (nx, ny) = match dir {
@@ -89,23 +99,28 @@ mod game {
Direction::East => (x + 1, y), Direction::East => (x + 1, y),
}; };
if self.tile(nx, ny) == '#' { if self.tile(nx, ny) == '#' {
self.last = String::from("Wand"); Outcome::Rejected("Da ist eine Wand")
} else { } else {
self.player = (nx, ny); self.player = (nx, ny);
self.last = format!("nach {},{}", nx, ny); self.last = format!("nach {},{}", nx, ny);
Outcome::TurnPassed
} }
self.moves_executed += 1;
} }
Command::Wait => { Command::Wait => {
self.last = String::from("gewartet"); self.last = String::from("gewartet");
self.moves_executed += 1; Outcome::TurnPassed
} }
} };
// Ticket 01: eine Uhr, der Zug. Hier endet er — genau einmal, // Ticket 01: eine Uhr, der Zug. Hier endet er — aber nur, wenn
// egal welches Kommando. TODO: befristete Modifikatoren ticken // wirklich etwas geschehen ist. TODO: befristete Modifikatoren
// lassen und die Event-Warteschlange aus Ticket 08 leerarbeiten. // ticken lassen und die Event-Warteschlange aus Ticket 08
self.turn += 1; // leerarbeiten — beides gehoert genau hierhin.
if let Outcome::TurnPassed = outcome {
self.turn += 1;
self.moves_executed += 1;
}
outcome
} }
} }
} }
@@ -113,7 +128,7 @@ mod game {
// ============================================================================ // ============================================================================
// mod ui — ab hier darf ratatui vorkommen // mod ui — ab hier darf ratatui vorkommen
// ============================================================================ // ============================================================================
use game::{Command, Direction, Game}; use game::{Command, Direction, Game, Outcome};
use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind}; use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind};
use ratatui::layout::Rect; use ratatui::layout::Rect;
use ratatui::style::{Color, Style, Stylize}; use ratatui::style::{Color, Style, Stylize};
@@ -211,10 +226,13 @@ impl Widget for GridWidget<'_> {
struct App { struct App {
game: Game, game: Game,
screens: Vec<Screen>, screens: Vec<Screen>,
/// Instrumentierung: Ohne den Press-Filter muesste dieser Zaehler unter /// Instrumentierung: `total` zaehlt jedes `Event::Key`, `press` nur die
/// Windows doppelt so schnell steigen wie `game.moves_executed`. /// gefilterten. Unter Windows laufen die beiden auseinander — genau das
/// belegt, dass der Filter aus Ticket 11 kein Aberglaube ist.
key_events_total: u64, key_events_total: u64,
key_events_press: u64, key_events_press: u64,
/// Grund des letzten abgewiesenen Kommandos, sonst `None`.
rejected: Option<&'static str>,
} }
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
@@ -223,6 +241,7 @@ fn main() -> std::io::Result<()> {
screens: vec![Screen::MainMenu], screens: vec![Screen::MainMenu],
key_events_total: 0, key_events_total: 0,
key_events_press: 0, key_events_press: 0,
rejected: None,
}; };
ratatui::run(|terminal| { ratatui::run(|terminal| {
@@ -256,9 +275,14 @@ fn main() -> std::io::Result<()> {
} }
Response::Push(s) => app.screens.push(s), Response::Push(s) => app.screens.push(s),
Response::Command(cmd) => { Response::Command(cmd) => {
app.game.execute(cmd); app.rejected = match app.game.execute(cmd) {
Outcome::TurnPassed => None,
Outcome::Rejected(reason) => Some(reason),
};
// Zusage 3: Ein Command schliesst alle Overlays — eine Zeile, // Zusage 3: Ein Command schliesst alle Overlays — eine Zeile,
// keine Sonderbehandlung je Screen. // keine Sonderbehandlung je Screen. Auch bei Rejected: die
// Rueckfrage *wurde* beantwortet, sie darf nicht kleben
// bleiben, nur weil das Kommando ins Leere lief.
while app.screens.last().is_some_and(Screen::is_overlay) { while app.screens.last().is_some_and(Screen::is_overlay) {
app.screens.pop(); app.screens.pop();
} }
@@ -317,19 +341,23 @@ fn draw(frame: &mut Frame, app: &App) {
/// Regel 5 des Prototyp-Skills: Zustand nach jeder Aktion sichtbar machen. /// Regel 5 des Prototyp-Skills: Zustand nach jeder Aktion sichtbar machen.
fn status(app: &App) -> Paragraph<'static> { fn status(app: &App) -> Paragraph<'static> {
let (x, y) = app.game.player; let (x, y) = app.game.player;
let (note, color) = match app.rejected {
Some(reason) => (reason.to_string(), Color::Red),
None => (app.game.last.clone(), Color::Cyan),
};
Paragraph::new(format!( Paragraph::new(format!(
"Zug {} │ Züge {} │ Events {} (Press {}) │ Pos {},{} │ Stack {} │ {}", "Zug {} │ Pos {},{} │ Stack {} │ Tasten {} → Press {} → Züge {} │ {}",
app.game.turn, app.game.turn,
app.game.moves_executed,
app.key_events_total,
app.key_events_press,
x, x,
y, y,
app.screens.len(), app.screens.len(),
app.game.last, app.key_events_total,
app.key_events_press,
app.game.moves_executed,
note,
)) ))
.right_aligned() .right_aligned()
.style(Style::new().fg(Color::Cyan)) .style(Style::new().fg(color))
} }
fn centered(area: Rect, w: u16, h: u16) -> Rect { fn centered(area: Rect, w: u16, h: u16) -> Rect {