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 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 return;
96 }
97 }
98 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 return;
106 }
107 }
108 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 return;
116 }
117 }
118 }
119
120 fn sim_water(&mut self, (x, y): (i64, i64)) {
121 let piece = self.world[(x, y)];
122
123 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 return;
131 }
132 }
133 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 return;
141 }
142 }
143 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 return;
151 }
152 }
153 if let Some(&right) = self.world.get(x + 1, y) {
155 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 return;
162 }
163 }
164 if let Some(&left) = self.world.get(x - 1, y) {
166 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 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 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 data.has_moved.clear(false);
239
240 data.total_pieces = 0;
241
242 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 }
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 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 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}