prototyp: abgewiesene kommandos kosten keinen zug, zaehler beschriftet
This commit is contained in:
@@ -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");
|
||||||
|
Outcome::TurnPassed
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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;
|
self.moves_executed += 1;
|
||||||
}
|
}
|
||||||
}
|
outcome
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user