battle_accept/
battle_accept.rs1use std::path::{Path, PathBuf};
20
21use dotzuki_engine::render::{FrameBuffer, Rgba};
22use dotzuki_engine::render_config::RenderConfig;
23use dotzuki_renderer::input::{GbButton, InputState};
24use dotzuki_runner::headless::save_png;
25use dotzuki_runner::{LoadedProject, RunnerGame, RunnerOptions, SCREEN_H, SCREEN_W};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29enum Pick {
30 Fight,
31 Party,
32 Item,
33}
34
35fn snap(game: &mut RunnerGame, path: &Path) {
37 let mut fb = FrameBuffer::new(
38 RenderConfig::new(SCREEN_W as u32, SCREEN_H as u32),
39 Rgba::BLACK,
40 );
41 game.draw(&mut fb);
42 save_png(&fb, path).expect("screenshot");
43 println!(" [shot] {}", path.display());
44}
45
46fn main() {
47 let mut args = std::env::args().skip(1);
48 let dir = args
49 .next()
50 .expect("usage: battle_accept <project-dir> [shot-dir]");
51 let shot_dir = args.next().map(PathBuf::from);
52
53 let project = LoadedProject::load(Path::new(&dir)).expect("load project");
54 let mut game = RunnerGame::new(
55 project,
56 RunnerOptions {
57 headless: true,
58 fresh: true,
59 rng_script: Some(vec![50, 100, 1]),
61 ..RunnerOptions::default()
62 },
63 )
64 .expect("boot game");
65
66 let agendas: &[&[Pick]] = &[&[Pick::Item, Pick::Party, Pick::Fight], &[Pick::Fight]];
68 let mut battles_done = 0usize;
69 let mut agenda: Vec<Pick> = Vec::new();
70 let mut keys: Vec<GbButton> = Vec::new();
73 let mut last_log: Vec<String> = Vec::new();
74 let mut snapped: Vec<String> = Vec::new();
75 let mut was_in_battle_prev = false;
76
77 let mut input = InputState::new();
78 for frame in 0..6000u32 {
79 let was_in_battle = was_in_battle_prev;
80
81 let mut mask = 0;
83 if frame % 2 == 0 {
84 if !keys.is_empty() {
85 mask = keys.remove(0).bit_mask();
86 } else if let Some(battle) = game.battle() {
87 if battle.in_menu() {
88 let items = battle.menu_items();
89 let label = if items.first().is_some_and(|i| i == "Fight") {
90 "root"
91 } else if items.iter().any(|i| i.contains('/')) {
92 "party"
93 } else {
94 "sub"
95 };
96 if let Some(dir) = &shot_dir {
97 let tag = format!("battle{}-{label}", battles_done + 1);
98 if !snapped.contains(&tag) {
99 snapped.push(tag.clone());
100 snap(&mut game, &dir.join(format!("{tag}.png")));
101 }
102 }
103 mask = match label {
104 "root" => {
105 let pick = agenda.first().copied().unwrap_or(Pick::Fight);
106 if !agenda.is_empty() {
107 agenda.remove(0);
108 }
109 match pick {
110 Pick::Fight => GbButton::A.bit_mask(),
111 Pick::Party => {
112 keys.push(GbButton::A);
113 GbButton::Down.bit_mask()
114 }
115 Pick::Item => {
116 keys.push(GbButton::Down);
117 keys.push(GbButton::A);
118 GbButton::Down.bit_mask()
119 }
120 }
121 }
122 _ => GbButton::A.bit_mask(),
125 };
126 } else if frame % 4 == 0 {
127 mask = GbButton::A.bit_mask(); }
129 } else if frame % 4 == 0 {
130 mask = GbButton::A.bit_mask(); }
132 }
133 input.set_from_bitmask(mask);
134 game.update(&input);
135 input.begin_frame();
136
137 let in_battle = game.battle().is_some();
138 if in_battle && !was_in_battle {
141 let n = battles_done + 1;
142 let battle = game.battle().unwrap();
143 println!("battle {n} starts:");
144 for c in battle.party() {
145 let status = c.status.as_deref().unwrap_or("-");
146 println!(
147 " {} {}/{} MP {}/{} status {}",
148 c.name, c.hp, c.max_hp, c.mp, c.max_mp, status
149 );
150 }
151 println!(" inventory: {:?}", battle.inventory());
152 agenda = agendas[battles_done].to_vec();
153 }
154 if in_battle {
155 last_log = game.battle().unwrap().log().to_vec();
156 }
157 if was_in_battle && !in_battle {
159 battles_done += 1;
160 println!("\nbattle {battles_done} log:");
161 for line in &last_log {
162 println!(" {line}");
163 }
164 if let Some(party) = game.party_state() {
165 println!("party state after battle {battles_done}:");
166 for m in party {
167 println!(" {} hp {} mp {} status {:?}", m.id, m.hp, m.mp, m.status);
168 }
169 }
170 if let Some(inv) = game.inventory() {
171 println!("inventory: {inv:?}");
172 }
173 println!();
174 last_log.clear();
175 if battles_done == 2 {
176 println!("both battles done — stopping");
177 break;
178 }
179 }
180 was_in_battle_prev = in_battle;
181 }
182
183 if battles_done < 2 {
184 eprintln!("harness: only {battles_done} battle(s) completed within the frame cap");
185 std::process::exit(1);
186 }
187}