Skip to main content

driving/
driving.rs

1//! Drive a car or a truck through a small town and reach the garage.
2//!
3//! Up = gas, Down = brake and reverse, Left / Right = steer, Space = handbrake, H = horn,
4//! E = engine on / off, Tab = switch between car and truck, L = load / unload the truck,
5//! F = speed, gear and fuel, G = where is the garage. Escape quits.
6//!
7//! All sounds are generated, no files needed.
8
9use audio_code::prelude::*;
10
11const MAP: &str = "
12    ####################
13    #P.......#........G#
14    #........#.........#
15    #...######...####..#
16    #..................#
17    #..T...........#...#
18    ####################
19";
20
21struct Town {
22    car: BodyId,
23    truck: BodyId,
24    driving: BodyId,
25    garage: BodyId,
26}
27
28impl Town {
29    fn new(ctx: &mut Context) -> Self {
30        // Engine: a low buzz that loops without clicks (whole cycles, no fades).
31        let engine = Synth::saw(55.0).length(1.0).attack(0.0).release(0.0).volume(0.25)
32            .with(Synth::square(110.0).length(1.0).attack(0.0).release(0.0).volume(0.08));
33        ctx.audio.register("engine", engine);
34        ctx.audio.register("horn", Synth::square(415.0).length(0.5).volume(0.25).with(Synth::square(523.0).length(0.5).volume(0.2)));
35        ctx.audio.register("shift", Synth::noise().length(0.04).decay(90.0).volume(0.3));
36        ctx.audio.register("crash", Synth::noise().length(0.4).decay(10.0).volume(0.8).with(Synth::sine(80.0).sweep_to(30.0).length(0.3)));
37        ctx.audio.register("start", Synth::saw(40.0).sweep_to(55.0).length(0.6).volume(0.3));
38        ctx.audio.register("garage", Synth::sine(880.0).length(0.15).decay(10.0));
39
40        let level = ctx.physics.build_level(MAP, 6.0);
41        let sounds = |v: Vehicle| {
42            v.with_engine_sound("engine")
43                .with_horn_sound("horn")
44                .with_shift_sound("shift")
45                .with_crash_sound("crash")
46                .with_start_sound("start")
47        };
48        let car_start = level.point('P').unwrap_or(Vec3::ZERO);
49        let car = ctx.physics.add(Body::car(car_start).with_facing(90.0).with_vehicle(sounds(Vehicle::car().with_fuel(5.0, 8.0))));
50        let truck_start = level.point('T').unwrap_or(Vec3::ZERO);
51        let truck = ctx.physics.add(
52            Body::truck(truck_start).with_facing(90.0).with_vehicle(sounds(Vehicle::truck()).engine_off()),
53        );
54        let garage_at = level.point('G').unwrap_or(Vec3::ZERO);
55        let garage = ctx.physics.add(Body::sensor(garage_at).with_box(6.0, 6.0, 3.0).with_tag("garage"));
56        ctx.loop_on(garage, "garage");
57        ctx.follow(car);
58        Self { car, truck, driving: car, garage }
59    }
60
61    fn name(&self) -> &'static str {
62        if self.driving == self.car { "car" } else { "truck" }
63    }
64}
65
66impl Scene for Town {
67    fn start(&mut self, ctx: &mut Context) {
68        ctx.speak("You sit in the car. Up drives, down brakes. Drive to the garage in the north east. F1 for help.");
69    }
70
71    fn update(&mut self, ctx: &mut Context) {
72        let id = self.driving;
73        ctx.drive(id);
74
75        if ctx.pressed(Key::H) {
76            ctx.horn(id);
77        }
78        if ctx.pressed(Key::E) {
79            let running = ctx.physics.vehicle(id).is_some_and(|v| v.is_engine_on());
80            if running {
81                ctx.stop_engine(id);
82                ctx.speak("Engine off");
83            } else if !ctx.start_engine(id) {
84                ctx.speak("The tank is empty");
85            }
86        }
87        if ctx.pressed(Key::Tab) {
88            if let Some(v) = ctx.physics.vehicle_mut(id) {
89                v.throttle = 0.0;
90                v.brake = 0.0;
91                v.handbrake = true;
92            }
93            self.driving = if id == self.car { self.truck } else { self.car };
94            ctx.follow(self.driving);
95            let text = format!("You switch to the {}. E starts the engine.", self.name());
96            ctx.speak(text);
97        }
98        if ctx.pressed(Key::L) && self.driving == self.truck {
99            let speed = ctx.physics.vehicle(self.truck).map_or(0.0, |v| v.speed().abs());
100            if speed > 0.5 {
101                ctx.speak("Stop first");
102            } else if let Some(v) = ctx.physics.vehicle_mut(self.truck) {
103                let load = if v.load > 0.0 { 0.0 } else { v.max_load };
104                v.set_load(load);
105                let text = format!("Load {} tons, total weight {} tons", load / 1000.0, ctx.physics.weight(self.truck) / 1000.0);
106                ctx.speak(text);
107            }
108        }
109        if ctx.pressed(Key::F)
110            && let Some(v) = ctx.physics.vehicle(id)
111        {
112            let mut text = format!("{}, gear {}", v.describe_speed(), v.gear_name());
113            if let Some(fuel) = v.fuel() {
114                text.push_str(&format!(", {fuel:.1} liters left"));
115            }
116            ctx.speak(text);
117        }
118        if ctx.pressed(Key::G) {
119            let text = ctx.physics.describe(id, self.garage);
120            ctx.speak(text);
121        }
122        if ctx.pressed(Key::F1) {
123            ctx.speak("Up gas, down brake and reverse, left and right steer, space handbrake, H horn, E engine, Tab switch vehicle, L load the truck, F speed, G garage.");
124        }
125
126        if let Some(gear) = ctx.physics.gear_changed(id)
127            && gear == -1
128        {
129            ctx.speak("Reverse");
130        }
131        if let Some((_, speed)) = ctx.physics.crashed(id)
132            && speed > 5.0
133        {
134            let text = format!("Crash at {:.0} kilometers per hour", speed * 3.6);
135            ctx.speak(text);
136        }
137        if ctx.physics.entered_tag(id, "garage").is_some() {
138            let text = format!("You parked the {} in the garage. Well done!", self.name());
139            ctx.speak(text);
140            ctx.quit();
141        }
142        if ctx.pressed(Key::Escape) {
143            ctx.quit();
144        }
145    }
146}
147
148fn main() -> audio_code::Result<()> {
149    Engine::new("Driving").run_with(Town::new)
150}