1pub mod game;
3mod raster;
4use ferravex_core::FixedClock;
5use ferravex_engine::input::{ActionMap, Binding, InputCode};
6use game::{Game, HEIGHT, Input, WIDTH};
7use raster::Canvas;
8use std::sync::Mutex;
9
10pub struct Orbit {
11 pub game: Game,
12 pub canvas: Canvas,
13 clock: FixedClock,
14 actions: ActionMap,
15 axis: usize,
16 launch: usize,
17 restart: usize,
18}
19impl Default for Orbit {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24impl Orbit {
25 pub fn new() -> Self {
26 let mut actions = ActionMap::default();
27 let axis = actions.action(vec![Binding {
28 code: InputCode::PadAxis(0),
29 scale: 1.0,
30 deadzone: 0.02,
31 }]);
32 let launch = actions.action(vec![Binding {
33 code: InputCode::Key(32),
34 scale: 1.0,
35 deadzone: 0.0,
36 }]);
37 let restart = actions.action(vec![Binding {
38 code: InputCode::Key(82),
39 scale: 1.0,
40 deadzone: 0.0,
41 }]);
42 let mut app = Self {
43 game: Game::new(),
44 canvas: Canvas::new(),
45 clock: FixedClock::new(120.0, 12),
46 actions,
47 axis,
48 launch,
49 restart,
50 };
51 app.render();
52 app
53 }
54 pub fn advance(&mut self, dt: f64, axis: f32, buttons: u32) {
55 self.actions.feed(InputCode::PadAxis(0), axis);
56 self.actions
57 .feed(InputCode::Key(32), f32::from(buttons & 1 != 0));
58 self.actions
59 .feed(InputCode::Key(82), f32::from(buttons & 2 != 0));
60 let step = self.clock.advance(dt);
61 for _ in 0..step.steps {
62 self.game.tick(
63 Input {
64 axis: self.actions.consume_tick(self.axis).value,
65 launch: self.actions.consume_tick(self.launch).pressed,
66 restart: self.actions.consume_tick(self.restart).pressed,
67 },
68 step.dt,
69 );
70 }
71 }
72 pub fn render(&mut self) {
73 self.canvas.render(&self.game);
74 }
75}
76static APP: Mutex<Option<Orbit>> = Mutex::new(None);
77#[unsafe(no_mangle)]
78pub extern "C" fn init() {
79 *APP.lock().expect("app lock") = Some(Orbit::new());
80}
81#[unsafe(no_mangle)]
83pub extern "C" fn step(dt: f64, axis: f32, buttons: u32) {
84 if let Some(app) = APP.lock().expect("app lock").as_mut() {
85 app.advance(dt, axis, buttons);
86 app.render();
87 }
88}
89#[unsafe(no_mangle)]
90pub extern "C" fn pixels() -> *const u8 {
91 APP.lock()
92 .expect("app lock")
93 .as_ref()
94 .map_or(std::ptr::null(), |app| {
95 app.canvas.frame.pixels().as_ptr().cast()
96 })
97}
98#[unsafe(no_mangle)]
99pub extern "C" fn width() -> u32 {
100 WIDTH
101}
102#[unsafe(no_mangle)]
103pub extern "C" fn height() -> u32 {
104 HEIGHT
105}
106#[unsafe(no_mangle)]
107pub extern "C" fn status() -> u32 {
108 APP.lock()
109 .expect("app lock")
110 .as_ref()
111 .map_or(0, |app| app.game.phase as u32)
112}
113#[unsafe(no_mangle)]
114pub extern "C" fn score() -> u32 {
115 APP.lock()
116 .expect("app lock")
117 .as_ref()
118 .map_or(0, |app| app.game.score)
119}
120#[unsafe(no_mangle)]
121pub extern "C" fn lives() -> u32 {
122 APP.lock()
123 .expect("app lock")
124 .as_ref()
125 .map_or(3, |app| app.game.lives)
126}
127#[unsafe(no_mangle)]
128pub extern "C" fn sector() -> u32 {
129 APP.lock()
130 .expect("app lock")
131 .as_ref()
132 .map_or(1, |app| app.game.sector)
133}
134#[unsafe(no_mangle)]
135pub extern "C" fn paddle() -> f32 {
136 APP.lock()
137 .expect("app lock")
138 .as_ref()
139 .map_or(320.0, |app| app.game.paddle_x)
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 #[test]
146 fn quick_input_tap_survives_zero_tick_and_frame_is_rgba() {
147 let mut app = Orbit::new();
148 app.advance(0.0, 0.0, 1);
149 app.advance(0.0, 0.0, 0);
150 app.advance(1.0 / 120.0, 0.0, 0);
151 assert_eq!(app.game.phase, game::Phase::Playing);
152 app.render();
153 assert_eq!(app.canvas.frame.pixels().len(), (WIDTH * HEIGHT) as usize);
154 assert!(app.canvas.frame.pixels().iter().all(|p| p[3] == 255));
155 assert!(
156 app.canvas
157 .frame
158 .pixels()
159 .iter()
160 .filter(|p| p[0] > 100)
161 .count()
162 > 5000
163 );
164 }
165}