1use audio_code::prelude::*;
12
13const MAP: &str = "
14 ###########
15 #B....#..B#
16 #.###.#.#.#
17 #...#...#.#
18 ###.#####.#
19 #B........#
20 #.#######.#
21 #....P....#
22 ###########
23";
24
25struct Maze {
26 player: BodyId,
27 beacons: Vec<BodyId>,
28 clock: Timer,
29}
30
31impl Maze {
32 fn new(ctx: &mut Context) -> Self {
33 ctx.audio.register("step", Synth::noise().length(0.08).decay(60.0));
34 ctx.audio.register("wall", Synth::sine(90.0).length(0.15).decay(25.0).volume(0.8));
35 ctx.audio.register("beacon", Synth::sine(1320.0).length(0.08).then(Synth::silence(0.04)).then(Synth::sine(1320.0).length(0.08)).then(Synth::silence(0.8)));
36 ctx.audio.register("found", Synth::sine(440.0).sweep_to(1200.0).length(0.6).decay(4.0).volume(0.3));
37
38 let level = ctx.physics.build_level(MAP, 2.0);
40 let player = ctx.physics.add(Body::kinematic(level.point('P').unwrap()).with_sphere(0.4).with_tag("player"));
41 ctx.follow(player); let mut beacons = Vec::new();
44 for spot in level.points('B') {
45 let beacon = ctx.physics.add(Body::sensor(spot).with_sphere(0.8).with_tag("beacon"));
46 ctx.loop_on(beacon, "beacon"); beacons.push(beacon);
48 }
49 Self { player, beacons, clock: Timer::new() }
50 }
51}
52
53impl Scene for Maze {
54 fn start(&mut self, ctx: &mut Context) {
55 ctx.speak("Find all three beacons. Up and down to walk, left and right to turn. C locates the nearest beacon.");
56 }
57
58 fn update(&mut self, ctx: &mut Context) {
59 if ctx.pressed(Key::Escape) {
60 ctx.push_scene(Pause);
61 }
62 if ctx.pressed(Key::Left) || ctx.pressed(Key::Right) {
63 let degrees = if ctx.pressed(Key::Left) { -45.0 } else { 45.0 };
64 let facing = ctx.physics.turn(self.player, degrees);
65 ctx.speak(math::direction_name(facing));
66 }
67
68 let forward = ctx.input.repeating(Key::Up, 0.35, 0.35);
69 let backward = ctx.input.repeating(Key::Down, 0.35, 0.35);
70 if forward || backward {
71 let step = if forward { 1.0 } else { -1.0 };
72 if ctx.physics.move_forward(self.player, step).blocked {
73 ctx.play("wall");
74 } else {
75 ctx.audio.sound("step").random_pitch(0.85, 1.15).play();
76 }
77 }
78
79 if ctx.pressed(Key::C) {
80 let position = ctx.physics.position(self.player);
81 match ctx.physics.nearest_with_tag(position, "beacon") {
82 Some(beacon) => {
83 let text = ctx.physics.describe(self.player, beacon);
84 ctx.speak(text);
85 }
86 None => ctx.speak("No beacons left"),
87 }
88 }
89 if ctx.pressed(Key::F) {
90 let facing = ctx.physics.facing(self.player);
91 ctx.speak(format!("Facing {}", math::direction_name(facing)));
92 }
93
94 if let Some(beacon) = ctx.physics.entered_tag(self.player, "beacon") {
95 ctx.physics.remove(beacon);
96 self.beacons.retain(|b| *b != beacon);
97 ctx.play("found");
98 if self.beacons.is_empty() {
99 self.finish(ctx);
100 } else {
101 ctx.speak(format!("Beacon found! {} left.", self.beacons.len()));
102 }
103 }
104 if ctx.ui.closed("done") {
105 ctx.quit();
106 }
107 }
108
109 fn pause(&mut self, _ctx: &mut Context) {
111 self.clock.pause();
112 }
113
114 fn resume(&mut self, _ctx: &mut Context) {
115 self.clock.resume();
116 }
117}
118
119impl Maze {
120 fn finish(&mut self, ctx: &mut Context) {
121 let seconds = self.clock.elapsed_secs().round() as u32;
122 let best: u32 = ctx.storage.get_or("best_time", u32::MAX);
123 let mut text = format!("You found every beacon in {seconds} seconds.");
124 if seconds < best {
125 ctx.storage.set("best_time", seconds);
126 text.push_str(" That is a new record!");
127 } else {
128 text.push_str(&format!(" Your record is {best} seconds."));
129 }
130 ctx.ui.message("done", text);
131 }
132}
133
134struct Pause;
135
136impl Scene for Pause {
137 fn start(&mut self, ctx: &mut Context) {
138 ctx.time.pause();
139 ctx.ui.open(Menu::new("pause", "Paused").button("resume", "Resume").button("quit", "Quit game"));
140 }
141
142 fn update(&mut self, ctx: &mut Context) {
143 if ctx.ui.clicked("pause", "resume") || ctx.ui.closed("pause") {
144 ctx.ui.close_id("pause");
145 ctx.pop_scene();
146 }
147 if ctx.ui.clicked("pause", "quit") {
148 ctx.quit();
149 }
150 }
151
152 fn end(&mut self, ctx: &mut Context) {
153 ctx.time.resume();
154 }
155}
156
157fn main() -> audio_code::Result<()> {
158 Engine::new("Beacon").echo_speech(true).run_with(Maze::new)
159}