falling_sand/
main.rs

1use std::io::stdout;
2use teng::components::Component;
3use teng::rendering::color::Color;
4use teng::rendering::render::{HalfBlockDisplayRender, Render};
5use teng::rendering::renderer::Renderer;
6use teng::util::planarvec::{Bounds, PlanarVec};
7use teng::{
8    install_panic_handler, terminal_cleanup, terminal_setup, DisplayInfo, Game, SetupInfo,
9    SharedState, UpdateInfo,
10};
11use teng::util::fixedupdate::FixedUpdateRunner;
12
13fn main() -> std::io::Result<()> {
14    terminal_setup()?;
15    install_panic_handler();
16
17    let mut game = Game::new(stdout());
18    game.install_recommended_components();
19    game.add_component(Box::new(FallingSimulationComponent::new()));
20    game.run()?;
21
22    terminal_cleanup()?;
23
24    Ok(())
25}
26
27#[derive(Debug, Clone, Copy, PartialEq)]
28enum PieceKind {
29    Air,
30    Sand,
31    Water,
32}
33
34impl PieceKind {
35    fn density(&self) -> f64 {
36        match self {
37            PieceKind::Air => 0.0,
38            PieceKind::Sand => 2.0,
39            PieceKind::Water => 1.0,
40        }
41    }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq)]
45struct Piece {
46    kind: PieceKind,
47}
48
49#[derive(Default)]
50struct FallingSimulationData {
51    secs_passed: f64,
52    total_pieces: usize,
53    world: PlanarVec<Piece>,
54    has_moved: PlanarVec<bool>,
55}
56
57impl FallingSimulationData {
58    fn new() -> Self {
59        let bounds = Bounds {
60            min_x: -100,
61            max_x: 100,
62            min_y: -100,
63            max_y: 100,
64        };
65
66        Self {
67            secs_passed: 0.0,
68            total_pieces: 0,
69            world: PlanarVec::new(
70                bounds,
71                Piece {
72                    kind: PieceKind::Air,
73                },
74            ),
75            has_moved: PlanarVec::new(bounds, false),
76        }
77    }
78
79    fn swap(&mut self, (x1, y1): (i64, i64), (x2, y2): (i64, i64)) {
80        let temp = self.world[(x1, y1)];
81        self.world[(x1, y1)] = self.world[(x2, y2)];
82        self.world[(x2, y2)] = temp;
83    }
84
85    fn sim_sand(&mut self, (x, y): (i64, i64)) {
86        let piece = self.world[(x, y)];
87
88        // check below
89        if let Some(&below) = self.world.get(x, y - 1) {
90            if below.kind.density() < piece.kind.density() {
91                self.swap((x, y), (x, y - 1));
92                self.has_moved[(x, y)] = true;
93                self.has_moved[(x, y - 1)] = true;
94                // moved, no more sim
95                return;
96            }
97        }
98        // check below and right
99        if let Some(&below_right) = self.world.get(x + 1, y - 1) {
100            if below_right.kind.density() < piece.kind.density() {
101                self.swap((x, y), (x + 1, y - 1));
102                self.has_moved[(x, y)] = true;
103                self.has_moved[(x + 1, y - 1)] = true;
104                // moved, no more sim
105                return;
106            }
107        }
108        // check below and left
109        if let Some(&below_left) = self.world.get(x - 1, y - 1) {
110            if below_left.kind.density() < piece.kind.density() {
111                self.swap((x, y), (x - 1, y - 1));
112                self.has_moved[(x, y)] = true;
113                self.has_moved[(x - 1, y - 1)] = true;
114                // moved, no more sim
115                return;
116            }
117        }
118    }
119
120    fn sim_water(&mut self, (x, y): (i64, i64)) {
121        let piece = self.world[(x, y)];
122
123        // check below
124        if let Some(&below) = self.world.get(x, y - 1) {
125            if below.kind.density() < piece.kind.density() {
126                self.swap((x, y), (x, y - 1));
127                self.has_moved[(x, y)] = true;
128                self.has_moved[(x, y - 1)] = true;
129                // moved, no more sim
130                return;
131            }
132        }
133        // check below and right
134        if let Some(&below_right) = self.world.get(x + 1, y - 1) {
135            if below_right.kind.density() < piece.kind.density() {
136                self.swap((x, y), (x + 1, y - 1));
137                self.has_moved[(x, y)] = true;
138                self.has_moved[(x + 1, y - 1)] = true;
139                // moved, no more sim
140                return;
141            }
142        }
143        // check below and left
144        if let Some(&below_left) = self.world.get(x - 1, y - 1) {
145            if below_left.kind.density() < piece.kind.density() {
146                self.swap((x, y), (x - 1, y - 1));
147                self.has_moved[(x, y)] = true;
148                self.has_moved[(x - 1, y - 1)] = true;
149                // moved, no more sim
150                return;
151            }
152        }
153        // check right
154        if let Some(&right) = self.world.get(x + 1, y) {
155            // note: we are not checking densities anymore, since this is on the horizontal axis.
156            if right.kind == PieceKind::Air {
157                self.swap((x, y), (x + 1, y));
158                self.has_moved[(x, y)] = true;
159                self.has_moved[(x + 1, y)] = true;
160                // moved, no more sim
161                return;
162            }
163        }
164        // check left
165        if let Some(&left) = self.world.get(x - 1, y) {
166            // note: we are not checking densities anymore, since this is on the horizontal axis.
167            if left.kind == PieceKind::Air {
168                self.swap((x, y), (x - 1, y));
169                self.has_moved[(x, y)] = true;
170                self.has_moved[(x - 1, y)] = true;
171                // moved, no more sim
172                return;
173            }
174        }
175    }
176
177    fn resize_discard(&mut self, width: usize, height: usize) {
178        let bounds = Bounds {
179            min_x: 0,
180            max_x: width as i64 - 1,
181            min_y: 0,
182            max_y: height as i64 - 1,
183        };
184
185        self.world = PlanarVec::new(
186            bounds,
187            Piece {
188                kind: PieceKind::Air,
189            },
190        );
191        self.has_moved = PlanarVec::new(bounds, false);
192    }
193}
194
195pub struct FallingSimulationComponent {
196    hb_display: HalfBlockDisplayRender,
197    fixed_update_runner: FixedUpdateRunner,
198}
199
200impl FallingSimulationComponent {
201    const UPDATES_PER_SECOND: f64 = 100.0;
202    const UPDATE_INTERVAL: f64 = 1.0 / Self::UPDATES_PER_SECOND;
203
204    pub fn new() -> Self {
205        Self {
206            hb_display: HalfBlockDisplayRender::new(10, 10),
207            fixed_update_runner: FixedUpdateRunner::new_from_rate_per_second(Self::UPDATES_PER_SECOND),
208        }
209    }
210
211    fn update_render(&mut self, data: &FallingSimulationData, display_info: &DisplayInfo) {
212        // TODO: add display here
213
214        for x in data.world.x_range() {
215            for y in data.world.y_range() {
216                let piece = data.world[(x, y)];
217                let color = match piece.kind {
218                    PieceKind::Air => Color::Transparent,
219                    PieceKind::Sand => Color::Rgb([255, 255, 0]),
220                    PieceKind::Water => Color::Rgb([0, 0, 255]),
221                };
222                let d_x = x;
223                let d_y = y;
224                let d_y = 2 * display_info.height() as i64 - d_y;
225                let d_y = d_y - 1;
226                self.hb_display.set_color(d_x as usize, d_y as usize, color);
227            }
228        }
229    }
230
231    fn update_simulation(&mut self, shared_state: &mut SharedState<FallingSimulationData>) {
232        let data = &mut shared_state.custom;
233        data.secs_passed += Self::UPDATE_INTERVAL;
234
235        // std::mem::swap(&mut data.world, &mut data.old_world);
236        // data.world.clear(Piece { kind: PieceKind::Air });
237
238        data.has_moved.clear(false);
239
240        data.total_pieces = 0;
241
242        // go over every piece (that is not air) and update it
243        for x in data.world.x_range() {
244            for y in data.world.y_range().rev() {
245                if data.has_moved[(x, y)] {
246                    continue;
247                }
248                let piece = data.world[(x, y)];
249                if piece.kind == PieceKind::Air {
250                    continue;
251                }
252
253                match piece.kind {
254                    PieceKind::Air => {
255                        // do nothing
256                    }
257                    PieceKind::Sand => {
258                        data.sim_sand((x, y));
259                    }
260                    PieceKind::Water => {
261                        data.sim_water((x, y));
262                    }
263                }
264                data.has_moved[(x, y)] = true;
265            }
266        }
267
268        for x in data.world.x_range() {
269            for y in data.world.y_range() {
270                let piece = data.world[(x, y)];
271                if piece.kind != PieceKind::Air {
272                    data.total_pieces += 1;
273                }
274            }
275        }
276
277        self.update_render(data, &shared_state.display_info);
278    }
279}
280
281impl Component<FallingSimulationData> for FallingSimulationComponent {
282    fn setup(
283        &mut self,
284        setup_info: &SetupInfo,
285        shared_state: &mut SharedState<FallingSimulationData>,
286    ) {
287        self.on_resize(
288            setup_info.display_info.width(),
289            setup_info.display_info.height(),
290            shared_state,
291        );
292    }
293
294    fn on_resize(
295        &mut self,
296        width: usize,
297        height: usize,
298        shared_state: &mut SharedState<FallingSimulationData>,
299    ) {
300        self.hb_display.resize_discard(width, height * 2);
301        let data = &mut shared_state.custom;
302        data.resize_discard(width, height * 2);
303    }
304
305    fn update(
306        &mut self,
307        update_info: UpdateInfo,
308        shared_state: &mut SharedState<FallingSimulationData>,
309    ) {
310        self.fixed_update_runner.fuel(update_info.dt);
311
312        // add sand from mouse events
313        let data = &mut shared_state.custom;
314
315        if shared_state.mouse_info.left_mouse_down
316            || shared_state.mouse_info.right_mouse_down
317            || shared_state.mouse_info.middle_mouse_down
318        {
319            let (s_x, s_y) = shared_state.mouse_info.last_mouse_pos;
320
321            let x = s_x as i64;
322            // scale to two halfblocks per pixel and recenter to 0,0
323            let y = shared_state.display_info.height() as i64 - s_y as i64;
324            let y = 2 * y;
325            let y = y - 1;
326
327            if let Some(piece) = data.world.get_mut(x, y) {
328                let kind = if shared_state.mouse_info.left_mouse_down {
329                    PieceKind::Sand
330                } else if shared_state.mouse_info.right_mouse_down {
331                    PieceKind::Water
332                } else {
333                    PieceKind::Air
334                };
335                piece.kind = kind;
336            } else {
337                panic!("Mouse out of bounds: ({}, {})", x, y);
338            }
339        }
340
341        while self.fixed_update_runner.has_gas() {
342            self.fixed_update_runner.consume();
343            self.update_simulation(shared_state);
344        }
345    }
346
347    fn render(
348        &self,
349        renderer: &mut dyn Renderer,
350        shared_state: &SharedState<FallingSimulationData>,
351        depth_base: i32,
352    ) {
353        let depth_base = i32::MAX - 99;
354        let data = &shared_state.custom;
355        format!("FallingSimulationComponent: {}s", data.secs_passed)
356            .render(renderer, 0, 0, depth_base);
357        format!("sands: [{}]", data.total_pieces).render(renderer, 0, 1, depth_base);
358
359        self.hb_display.render(renderer, 0, 0, depth_base);
360    }
361}