fix(bogen): vorgemerkte Punkte beim Abschicken verbrauchen
Nach Enter stand unspent_points auf 0, waehrend der Screen den Punkt noch vorgemerkt hielt — die Anzeige rechnete 0 - 1 auf u32 und stuerzte ab. Der Screen leert sein Feld, waehrend er das Kommando uebergibt, dazu saturating_sub an den zwei Lesestellen: unspent_points kann sich auch unter dem offenen Screen aendern. Erster UI-Test des Projekts. handle_key ist ohne Terminal aufrufbar, und sobald ein Screen Zustand haelt, ist die Naht zum Spielzustand eine Fehlerquelle wie jede andere.
This commit is contained in:
@@ -219,7 +219,9 @@ impl Screen {
|
||||
}
|
||||
|
||||
Screen::CharacterSheet { pick, pending } => {
|
||||
let offen = game.unspent_points - pending.iter().sum::<u32>();
|
||||
// `saturating_sub`, weil `unspent_points` sich unter dem
|
||||
// offenen Screen aendern kann — ein Aufstieg im selben Zug.
|
||||
let offen = game.unspent_points.saturating_sub(pending.iter().sum::<u32>());
|
||||
match key {
|
||||
KeyCode::Up | KeyCode::Char('k') => {
|
||||
*pick = pick.saturating_sub(1);
|
||||
@@ -239,14 +241,19 @@ impl Screen {
|
||||
}
|
||||
// **Alle** Punkte in einem Kommando und damit in einem Zug
|
||||
// (Ticket 15).
|
||||
KeyCode::Enter => Response::Command(Command::AssignPoints(
|
||||
Attribute::ASSIGNABLE
|
||||
KeyCode::Enter => {
|
||||
let punkte = Attribute::ASSIGNABLE
|
||||
.iter()
|
||||
.zip(pending.iter())
|
||||
.filter(|(_, n)| **n > 0)
|
||||
.map(|(a, n)| (*a, *n))
|
||||
.collect(),
|
||||
)),
|
||||
.collect();
|
||||
// **Vorgemerktes wird beim Abschicken verbraucht.** Ohne
|
||||
// das behaelt der Screen Punkte, die das Spiel schon
|
||||
// abgezogen hat — und rechnet `0 - 1` auf `u32`.
|
||||
*pending = [0; 5];
|
||||
Response::Command(Command::AssignPoints(punkte))
|
||||
}
|
||||
KeyCode::Esc | KeyCode::Char('c' | 'q') => Response::Pop,
|
||||
_ => Response::None,
|
||||
}
|
||||
@@ -341,3 +348,44 @@ impl Screen {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::data::load_mods;
|
||||
use crate::game::{Creature, Rng};
|
||||
|
||||
fn spiel() -> (Game, Registries) {
|
||||
let (reg, _) = load_mods().expect("core muss laden");
|
||||
let game = Game::new(Creature::new("Test"), ®, Rng::new(1)).expect("Etage 1");
|
||||
(game, reg)
|
||||
}
|
||||
|
||||
/// Gefunden im Spiel: Nach dem Abschicken stand `unspent_points` auf 0,
|
||||
/// waehrend der Screen den Punkt noch vorgemerkt hielt — `0 - 1` auf `u32`.
|
||||
/// Der erste Test, der **Screen-Zustand gegen Spielzustand** prueft; die
|
||||
/// Trennung aus Ticket 11 macht genau diese Naht zur Fehlerquelle.
|
||||
#[test]
|
||||
fn vorgemerkte_punkte_werden_beim_abschicken_verbraucht() {
|
||||
let (mut game, reg) = spiel();
|
||||
game.unspent_points = 1;
|
||||
let mut screen = Screen::sheet();
|
||||
|
||||
screen.handle_key(KeyCode::Char('+'), &game, ®);
|
||||
let Screen::CharacterSheet { pending, .. } = &screen else { panic!("falscher Screen") };
|
||||
assert_eq!(pending[0], 1, "der Punkt ist vorgemerkt");
|
||||
|
||||
let antwort = screen.handle_key(KeyCode::Enter, &game, ®);
|
||||
let Response::Command(cmd) = antwort else { panic!("Enter muss abschicken") };
|
||||
assert!(matches!(game.execute(cmd, ®), crate::game::Outcome::TurnPassed));
|
||||
assert_eq!(game.unspent_points, 0);
|
||||
|
||||
let Screen::CharacterSheet { pending, .. } = &screen else { panic!("falscher Screen") };
|
||||
assert_eq!(pending[0], 0, "und im Screen nicht mehr vorgemerkt");
|
||||
|
||||
// Der Zustand, der abgestuerzt ist: kein offener Punkt, keine Vormerkung.
|
||||
screen.handle_key(KeyCode::Char('+'), &game, ®);
|
||||
let Screen::CharacterSheet { pending, .. } = &screen else { panic!("falscher Screen") };
|
||||
assert_eq!(pending[0], 0, "ohne offene Punkte laesst sich nichts vormerken");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user