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.next().expect("usage: battle_accept <project-dir> [shot-dir]");
49 let shot_dir = args.next().map(PathBuf::from);
50
51 let project = LoadedProject::load(Path::new(&dir)).expect("load project");
52 let mut game = RunnerGame::new(
53 project,
54 RunnerOptions {
55 headless: true,
56 fresh: true,
57 rng_script: Some(vec![50, 100, 1]),
59 ..RunnerOptions::default()
60 },
61 )
62 .expect("boot game");
63
64 let agendas: &[&[Pick]] = &[&[Pick::Item, Pick::Party, Pick::Fight], &[Pick::Fight]];
66 let mut battles_done = 0usize;
67 let mut agenda: Vec<Pick> = Vec::new();
68 let mut keys: Vec<GbButton> = Vec::new();
71 let mut last_log: Vec<String> = Vec::new();
72 let mut snapped: Vec<String> = Vec::new();
73 let mut was_in_battle_prev = false;
74
75 let mut input = InputState::new();
76 for frame in 0..6000u32 {
77 let was_in_battle = was_in_battle_prev;
78
79 let mut mask = 0;
81 if frame % 2 == 0 {
82 if !keys.is_empty() {
83 mask = keys.remove(0).bit_mask();
84 } else if let Some(battle) = game.battle() {
85 if battle.in_menu() {
86 let items = battle.menu_items();
87 let label = if items.first().is_some_and(|i| i == "Fight") {
88 "root"
89 } else if items.iter().any(|i| i.contains('/')) {
90 "party"
91 } else {
92 "sub"
93 };
94 if let Some(dir) = &shot_dir {
95 let tag = format!("battle{}-{label}", battles_done + 1);
96 if !snapped.contains(&tag) {
97 snapped.push(tag.clone());
98 snap(&mut game, &dir.join(format!("{tag}.png")));
99 }
100 }
101 mask = match label {
102 "root" => {
103 let pick = agenda.first().copied().unwrap_or(Pick::Fight);
104 if !agenda.is_empty() {
105 agenda.remove(0);
106 }
107 match pick {
108 Pick::Fight => GbButton::A.bit_mask(),
109 Pick::Party => {
110 keys.push(GbButton::A);
111 GbButton::Down.bit_mask()
112 }
113 Pick::Item => {
114 keys.push(GbButton::Down);
115 keys.push(GbButton::A);
116 GbButton::Down.bit_mask()
117 }
118 }
119 }
120 _ => GbButton::A.bit_mask(),
123 };
124 } else if frame % 4 == 0 {
125 mask = GbButton::A.bit_mask(); }
127 } else if frame % 4 == 0 {
128 mask = GbButton::A.bit_mask(); }
130 }
131 input.set_from_bitmask(mask);
132 game.update(&input);
133 input.begin_frame();
134
135 let in_battle = game.battle().is_some();
136 if in_battle && !was_in_battle {
139 let n = battles_done + 1;
140 let battle = game.battle().unwrap();
141 println!("battle {n} starts:");
142 for c in battle.party() {
143 let status = c.status.as_deref().unwrap_or("-");
144 println!(
145 " {} {}/{} MP {}/{} status {}",
146 c.name, c.hp, c.max_hp, c.mp, c.max_mp, status
147 );
148 }
149 println!(" inventory: {:?}", battle.inventory());
150 agenda = agendas[battles_done].to_vec();
151 }
152 if in_battle {
153 last_log = game.battle().unwrap().log().to_vec();
154 }
155 if was_in_battle && !in_battle {
157 battles_done += 1;
158 println!("\nbattle {battles_done} log:");
159 for line in &last_log {
160 println!(" {line}");
161 }
162 if let Some(party) = game.party_state() {
163 println!("party state after battle {battles_done}:");
164 for m in party {
165 println!(" {} hp {} mp {} status {:?}", m.id, m.hp, m.mp, m.status);
166 }
167 }
168 if let Some(inv) = game.inventory() {
169 println!("inventory: {inv:?}");
170 }
171 println!();
172 last_log.clear();
173 if battles_done == 2 {
174 println!("both battles done — stopping");
175 break;
176 }
177 }
178 was_in_battle_prev = in_battle;
179 }
180
181 if battles_done < 2 {
182 eprintln!("harness: only {battles_done} battle(s) completed within the frame cap");
183 std::process::exit(1);
184 }
185}