bot.rsannotatedbot.rssource141 lines · 5.8 KB · raw

What the bot playing the game says about itself: the zbanks/alttp C bot (packages/zbanks), read from its own globals once a UI pass.

Plain data in, so this crate needs neither the bot nor its C and still compiles for wasm; the app fills a [Bot] from zbanks::Report.

7use egui::{FontId, RichText};
9use crate::palette::{FAINT, GOAL, GOOD, GREY, PLAIN, RED, SECTION};

One goal on the bot's goal list (ap_plan.c's struct ap_goal).

12pub struct Goal<'a> {

GOAL_* without the prefix: EXPLORE, CHEST, PICKUP, NPC, ITEM, SCRIPT.

14    pub kind: &'a str,

The node the goal is about, in the bot's own naming.

16    pub node: &'a str,

The screen that node is on.

18    pub screen: &'a str,
19    pub attempts: i32,

The score the planner last gave it, as words when it is not a cost ("complete", "unsatisfiable", "over limit" - zbanks::score_words).

22    pub score: String,

Whether that score is one the planner will not pick.

24    pub out_of_reach: bool,
25    pub complete: bool,
26}
28pub struct Bot<'a> {

The frame last passed to ap_tick.

30    pub frame: u32,

ap_info_string: the line upstream's Snes9x drew over the picture.

32    pub info: &'a str,

The task list, current task first, in ap_print_task's words.

34    pub tasks: &'a [String],
35    pub goals: &'a [Goal<'a>],

All goals the bot has, of which goals are the first.

37    pub goal_count: usize,
38    pub link: (u16, u16),

assert_bp failures so far (each a trap the host stepped over, as gdb would), and the frame of the last.

41    pub traps: u64,
42    pub last_trap_frame: u64,

base() requests that mapped to nothing; should stay 0.

44    pub stray_reads: u64,

Goals the bot gave up on, waiting for Link's possessions to change, and how many the host has put back so far.

47    pub given_up: usize,
48    pub retried: u64,

Set by the host's stall detector (packages/zbanks/src/stall.rs): the bot has not chosen a goal, or has not moved, for its threshold's worth of frames, or the planner has given up for good. None means not stalled - never omitted quietly, so the panel cannot look normal while the bot has stopped playing.

54    pub stalled: Option<&'a str>,

The host's own recovery from a stall (packages/zbanks/src/recovery.rs): the last attempt's line, and whether recovery has given up for good (shown in red, like stalled, instead of the ordinary attention colour) - once true it stays true for the life of the process, same as stalled's own ManualMode used to before recovery could clear it.

61    pub recovery: Option<(&'a str, bool)>,

Held on the keyboard: the bot steps aside (upstream alttp.c:57).

63    pub paused_by_x: bool,
64}

A small header used by more than one panel in this crate. pub(crate) so jev.rs's "History" section can use the exact same look.

68pub(crate) fn section(ui: &mut egui::Ui, title: &str) {
69    ui.add_space(4.0);
70    ui.label(RichText::new(title).color(SECTION).strong());
71}

The bot's line, its tasks and its goals.

74pub fn show(ui: &mut egui::Ui, bot: &Bot) {
75    ui.heading("Bot");
76    if let Some(stalled) = bot.stalled {
77        ui.add(egui::Label::new(RichText::new(stalled).color(RED).strong()).wrap());
78    }
79    if let Some((line, gave_up)) = bot.recovery {
80        let colour = if gave_up { RED } else { GOAL };
81        ui.add(egui::Label::new(RichText::new(line).color(colour).strong()).wrap());
82    }
83    ui.label(RichText::new("zbanks/alttp, C").color(GREY));
84    let info = if bot.info.is_empty() { "(nothing yet)" } else { bot.info };
85    ui.add(egui::Label::new(RichText::new(info).font(FontId::monospace(12.0)).color(GOAL)).wrap());
86    if bot.paused_by_x {
87        ui.label(RichText::new("X held: the bot steps aside, the pad is yours").color(RED));
88    }
89    let mut facts = format!(
90        "frame {}   link {:#06x},{:#06x}   goals {}",
91        bot.frame, bot.link.0, bot.link.1, bot.goal_count
92    );
93    if bot.traps > 0 {
94        facts.push_str(&format!("   traps {} (last frame {})", bot.traps, bot.last_trap_frame));
95    }
96    if bot.given_up > 0 || bot.retried > 0 {
97        facts.push_str(&format!("   given up {} (put back {})", bot.given_up, bot.retried));
98    }
99    ui.label(RichText::new(facts).color(GREY));
100    if bot.stray_reads > 0 {
101        ui.label(RichText::new(format!("{} reads of unmapped memory", bot.stray_reads)).color(RED));
102    }
103
104    egui::ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| {
105        section(ui, "Tasks");
106        if bot.tasks.is_empty() {
107            ui.label(RichText::new("none").color(GREY));
108        }
109        for (i, task) in bot.tasks.iter().enumerate() {
110            let colour = if i == 0 { GOOD } else { PLAIN };
111            ui.add(
112                egui::Label::new(RichText::new(task).font(FontId::monospace(11.0)).color(colour))
113                    .wrap(),
114            );
115        }
116        section(ui, "Goals");
117        for goal in bot.goals {
118            let colour = if goal.complete {
119                GOOD
120            } else if goal.out_of_reach {
121                FAINT
122            } else {
123                PLAIN
124            };
125            let line = format!(
126                "{:<7} {:>12}  {}  on {}{}",
127                goal.kind,
128                goal.score,
129                goal.node,
130                goal.screen,
131                if goal.attempts > 0 { format!("  ({} tries)", goal.attempts) } else { String::new() },
132            );
133            ui.add(egui::Label::new(RichText::new(line).font(FontId::monospace(11.0)).color(colour)).wrap());
134        }
135        if bot.goal_count > bot.goals.len() {
136            ui.label(
137                RichText::new(format!("... and {} more", bot.goal_count - bot.goals.len())).color(GREY),
138            );
139        }
140    });
141}