//! The whole demo game, played to the win over the bus — the regression net //! for verbs, walk-then-act, parser targeting, death-rewind and scoring. //! (Run `mrpci-headless --sample` first; CI does.) use mrpci_core::{Command, Event, GameState, World}; fn game_dir() -> Option { for p in ["games/neon-precinct", "../games/neon-precinct"] { if std::path::Path::new(p).join("game.json").exists() { return Some(p.to_string()); } } None } fn boot() -> Option { let dir = game_dir()?; let mut gs = GameState::new(World::load_game(dir)); gs.apply(Command::NewGame { room: None }); Some(gs) } /// Apply a command, then settle any walk-then-act it queued. fn act(gs: &mut GameState, cmd: Command) -> Vec { let mut ev = gs.apply(cmd); ev.extend(gs.settle(2000)); ev } fn say(gs: &mut GameState, text: &str) -> Vec { act(gs, Command::Parse { text: text.into() }) } #[test] fn full_case_closes_at_25_points() { let Some(mut gs) = boot() else { return }; // Street → alley → keycard. say(&mut gs, "go east"); assert_eq!(gs.world.current, 2, "east edge exit reaches Rain Alley"); say(&mut gs, "take keycard"); assert!(gs.inventory.contains(&"keycard".to_string()), "keycard picked up (walk-then-act)"); assert_eq!(gs.score, 5); // Back to the street; typed hotspot walking must reach the precinct. say(&mut gs, "go west"); assert_eq!(gs.world.current, 0); say(&mut gs, "walk to precinct door"); assert_eq!(gs.world.current, 1, "'walk to precinct door' pathfinds onto the door hotspot"); // Locker: keycard opens it, script hands over the evidence. say(&mut gs, "use keycard on locker"); assert!(gs.flags.get("locker_open").copied().unwrap_or(false)); assert_eq!(gs.score, 15); say(&mut gs, "look at locker"); assert!(gs.inventory.contains(&"evidence".to_string()), "room script bags the data-slate"); // Terminal closes the case. say(&mut gs, "use evidence on terminal"); assert!(gs.won, "terminal win fires"); assert_eq!(gs.score, 25, "all points on the table"); assert!(!gs.inventory.contains(&"evidence".to_string()), "terminal consumes the slate"); } #[test] fn death_rewinds_to_room_entry_not_the_start() { let Some(mut gs) = boot() else { return }; act(&mut gs, Command::GotoRoom { n: 2 }); say(&mut gs, "take keycard"); assert_eq!(gs.score, 5); let ev = say(&mut gs, "touch fusebox"); assert!(ev.iter().any(|e| matches!(e, Event::Died { .. })), "the fusebox kills"); assert_eq!(gs.world.current, 2, "rewind lands at this room's entry, not a restart"); assert_eq!(gs.score, 0, "points rewind with the checkpoint"); assert!(gs.inventory.is_empty(), "so does the pocket"); } #[test] fn taking_a_solid_prop_clears_its_footprint() { let Some(mut gs) = boot() else { return }; let prop = mrpci_core::PropDef { name: "loot crate".into(), sprite: "crate".into(), x: 160, y: 160, takeable: true, solid: true, ..Default::default() }; act(&mut gs, Command::UpsertProp { room: None, prop }); assert!(!gs.world.screens.walkable(160, 158), "solid prop blocks while present"); say(&mut gs, "take loot crate"); assert!(gs.inventory.contains(&"loot crate".to_string())); assert!(gs.world.screens.walkable(160, 158), "footprint cleared once taken — no invisible wall"); } #[test] fn dialogue_ends_on_bye_and_talk_walks_over() { let Some(mut gs) = boot() else { return }; say(&mut gs, "walk to precinct door"); assert_eq!(gs.world.current, 1); let ev = say(&mut gs, "talk to sergeant"); assert!( ev.iter().any(|e| matches!(e, Event::DialogueOpen { .. })), "a distant talk walks over and still opens the conversation" ); let ev = say(&mut gs, "bye"); assert!(ev.iter().any(|e| matches!(e, Event::DialogueClosed)), "'bye' ends it"); assert!(gs.dlg.is_none()); }