prototyp: abgewiesene kommandos kosten keinen zug, zaehler beschriftet
This commit is contained in:
@@ -35,6 +35,16 @@ mod game {
|
||||
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 map: Vec<&'static str>,
|
||||
pub player: (u16, u16),
|
||||
@@ -78,8 +88,8 @@ mod game {
|
||||
}
|
||||
|
||||
/// Ticket 11: der **eine** Eingang, an dem ein Zug endet.
|
||||
pub fn execute(&mut self, cmd: Command) {
|
||||
match cmd {
|
||||
pub fn execute(&mut self, cmd: Command) -> Outcome {
|
||||
let outcome = match cmd {
|
||||
Command::Move(dir) => {
|
||||
let (x, y) = self.player;
|
||||
let (nx, ny) = match dir {
|
||||
@@ -89,23 +99,28 @@ mod game {
|
||||
Direction::East => (x + 1, y),
|
||||
};
|
||||
if self.tile(nx, ny) == '#' {
|
||||
self.last = String::from("Wand");
|
||||
Outcome::Rejected("Da ist eine Wand")
|
||||
} else {
|
||||
self.player = (nx, ny);
|
||||
self.last = format!("nach {},{}", nx, ny);
|
||||
Outcome::TurnPassed
|
||||
}
|
||||
self.moves_executed += 1;
|
||||
}
|
||||
Command::Wait => {
|
||||
self.last = String::from("gewartet");
|
||||
self.moves_executed += 1;
|
||||
Outcome::TurnPassed
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Ticket 01: eine Uhr, der Zug. Hier endet er — genau einmal,
|
||||
// egal welches Kommando. TODO: befristete Modifikatoren ticken
|
||||
// lassen und die Event-Warteschlange aus Ticket 08 leerarbeiten.
|
||||
self.turn += 1;
|
||||
// Ticket 01: eine Uhr, der Zug. Hier endet er — aber nur, wenn
|
||||
// wirklich etwas geschehen ist. TODO: befristete Modifikatoren
|
||||
// ticken lassen und die Event-Warteschlange aus Ticket 08
|
||||
// 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
|
||||
// ============================================================================
|
||||
use game::{Command, Direction, Game};
|
||||
use game::{Command, Direction, Game, Outcome};
|
||||
use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind};
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::{Color, Style, Stylize};
|
||||
@@ -211,10 +226,13 @@ impl Widget for GridWidget<'_> {
|
||||
struct App {
|
||||
game: Game,
|
||||
screens: Vec<Screen>,
|
||||
/// Instrumentierung: Ohne den Press-Filter muesste dieser Zaehler unter
|
||||
/// Windows doppelt so schnell steigen wie `game.moves_executed`.
|
||||
/// Instrumentierung: `total` zaehlt jedes `Event::Key`, `press` nur die
|
||||
/// 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_press: u64,
|
||||
/// Grund des letzten abgewiesenen Kommandos, sonst `None`.
|
||||
rejected: Option<&'static str>,
|
||||
}
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
@@ -223,6 +241,7 @@ fn main() -> std::io::Result<()> {
|
||||
screens: vec![Screen::MainMenu],
|
||||
key_events_total: 0,
|
||||
key_events_press: 0,
|
||||
rejected: None,
|
||||
};
|
||||
|
||||
ratatui::run(|terminal| {
|
||||
@@ -256,9 +275,14 @@ fn main() -> std::io::Result<()> {
|
||||
}
|
||||
Response::Push(s) => app.screens.push(s),
|
||||
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,
|
||||
// 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) {
|
||||
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.
|
||||
fn status(app: &App) -> Paragraph<'static> {
|
||||
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!(
|
||||
"Zug {} │ Züge {} │ Events {} (Press {}) │ Pos {},{} │ Stack {} │ {}",
|
||||
"Zug {} │ Pos {},{} │ Stack {} │ Tasten {} → Press {} → Züge {} │ {}",
|
||||
app.game.turn,
|
||||
app.game.moves_executed,
|
||||
app.key_events_total,
|
||||
app.key_events_press,
|
||||
x,
|
||||
y,
|
||||
app.screens.len(),
|
||||
app.game.last,
|
||||
app.key_events_total,
|
||||
app.key_events_press,
|
||||
app.game.moves_executed,
|
||||
note,
|
||||
))
|
||||
.right_aligned()
|
||||
.style(Style::new().fg(Color::Cyan))
|
||||
.style(Style::new().fg(color))
|
||||
}
|
||||
|
||||
fn centered(area: Rect, w: u16, h: u16) -> Rect {
|
||||
|
||||
Reference in New Issue
Block a user