menu_accept/
menu_accept.rs1use std::path::{Path, PathBuf};
23
24use dotzuki_engine::overworld::types::Direction;
25use dotzuki_engine::render::{FrameBuffer, Rgba};
26use dotzuki_engine::render_config::RenderConfig;
27use dotzuki_renderer::input::{GbButton, InputState};
28use dotzuki_runner::headless::save_png;
29use dotzuki_runner::{LoadedProject, RunnerGame, RunnerOptions, SCREEN_H, SCREEN_W};
30
31fn snap(game: &mut RunnerGame, dir: &Option<PathBuf>, name: &str) {
33 let Some(dir) = dir else { return };
34 let mut fb = FrameBuffer::new(
35 RenderConfig::new(SCREEN_W as u32, SCREEN_H as u32),
36 Rgba::BLACK,
37 );
38 game.draw(&mut fb);
39 let path = dir.join(format!("{name}.png"));
40 save_png(&fb, &path).expect("screenshot");
41 println!(" [shot] {}", path.display());
42}
43
44fn frame(game: &mut RunnerGame, mask: u8) {
45 let mut input = InputState::new();
46 input.set_from_bitmask(mask);
47 game.update(&input);
48}
49
50fn idle(game: &mut RunnerGame, n: u32) {
51 for _ in 0..n {
52 frame(game, 0);
53 }
54}
55
56fn press(game: &mut RunnerGame, button: GbButton) {
57 frame(game, button.bit_mask());
58 idle(game, 1);
59}
60
61fn press_a(game: &mut RunnerGame) {
62 press(game, GbButton::A);
63}
64
65fn dismiss(game: &mut RunnerGame) {
67 for _ in 0..40 {
68 if game.dialogue_text().is_none() && game.choice_options().is_none() {
69 return;
70 }
71 press_a(game);
72 }
73 panic!("dialogue did not close");
74}
75
76fn main() {
77 let mut args = std::env::args().skip(1);
78 let dir = args.next().expect("usage: menu_accept <project-dir> [shot-dir]");
79 let shot_dir = args.next().map(PathBuf::from);
80
81 let project = LoadedProject::load(Path::new(&dir)).expect("load project");
82 let mut game = RunnerGame::new(
83 project,
84 RunnerOptions {
85 headless: true,
86 fresh: true,
87 rng_script: Some(vec![50, 100, 1]),
89 ..RunnerOptions::default()
90 },
91 )
92 .expect("boot game");
93
94 dismiss(&mut game); println!("== 1. the shop ==");
96 println!("money at boot: {} (manifest shop.startMoney)", game.money());
97
98 game.debug_place(12, 10, Direction::Right);
100 press_a(&mut game);
101 let rows = game.shop_lines().expect("shop open");
102 println!("shop shelf: {rows:?}");
103 snap(&mut game, &shot_dir, "shop");
104
105 let before = game.money();
107 press_a(&mut game);
108 let rows = game.shop_lines().expect("shop still open");
109 println!(
110 "bought a Potion: money {before} → {} G; inventory {:?}",
111 game.money(),
112 game.inventory().expect("inventory materialized")
113 );
114 println!("shop note: {:?}", rows.last());
115 snap(&mut game, &shot_dir, "shop-bought");
116 assert_eq!(game.money(), 80, "20 G charged");
117 assert_eq!(game.inventory().unwrap().get("potion"), Some(&4));
118 press_a(&mut game); press(&mut game, GbButton::B);
122 let page = game.dialogue_text().expect("scene resumed after the shop");
123 println!("scene resumed with: {page:?}");
124 assert!(page.contains("Come again!"), "page: {page:?}");
125 dismiss(&mut game);
126
127 println!("\n== 2. the whiteout ==");
128 game.debug_place(12, 10, Direction::Left);
130 press_a(&mut game); press_a(&mut game); let mut last_log: Vec<String> = Vec::new();
133 for _ in 0..120 {
134 let Some(battle) = game.battle() else { break };
135 last_log = battle.log().to_vec();
136 press_a(&mut game);
137 }
138 println!("lost battle log:");
139 for line in &last_log {
140 println!(" {line}");
141 }
142 assert_eq!(last_log.last().map(String::as_str), Some("You lost the battle…"));
143
144 let page = game.dialogue_text().expect("post-lose text");
146 println!("post-lose text: {page:?}");
147 assert!(page.contains("no match"), "page: {page:?}");
148 press_a(&mut game); assert!(game.whiteout_active(), "whiteout armed");
152 idle(&mut game, 10);
153 snap(&mut game, &shot_dir, "whiteout-blackout");
154 idle(&mut game, 25);
155 let page = game.dialogue_text().expect("whiteout message");
156 println!("whiteout message: {page:?}");
157 assert!(page.contains("collapsed"), "page: {page:?}");
158 snap(&mut game, &shot_dir, "whiteout-message");
159
160 press_a(&mut game);
162 assert!(!game.whiteout_active());
163 println!("after the whiteout:");
164 println!(" map: {:?} tile: {:?}", game.current_map_id(), game.player_tile());
165 for m in game.party_state().expect("party state") {
166 println!(" {} hp {} mp {} status {:?}", m.id, m.hp, m.mp, m.status);
167 }
168 println!(" __played_main_StartTown flag kept: {}", game.flag("__played_main_StartTown"));
169 assert_eq!(game.current_map_id(), Some("StartTown"));
170 assert!(game
171 .party_state()
172 .unwrap()
173 .iter()
174 .all(|m| m.hp > 0 && m.status.is_none()));
175 snap(&mut game, &shot_dir, "whiteout-after");
176
177 println!("\nacceptance OK");
178}