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};
11
12fn main() -> std::io::Result<()> {
13 terminal_setup()?;
14 install_panic_handler();
15
16 let mut game = Game::new(stdout());
17 game.install_recommended_components();
18 game.add_component(Box::new(FallingSimulationComponent::new()));
19 game.run()?;
20
21 terminal_cleanup()?;
22
23 Ok(())
24}
25
26#[derive(Debug, Clone, Copy, PartialEq)]
27enum PieceKind {
28 Air,
29 Sand,
30 Water,
31}
32
33impl PieceKind {
34 fn density(&self) -> f64 {
35 match self {
36 PieceKind::Air => 0.0,
37 PieceKind::Sand => 2.0,
38 PieceKind::Water => 1.0,
39 }
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq)]
44struct Piece {
45 kind: PieceKind,
46}
47
48#[derive(Default)]
49struct FallingSimulationData {
50 secs_passed: f64,
51 total_pieces: usize,
52 world: PlanarVec<Piece>,
53 has_moved: PlanarVec<bool>,
54}
55
56impl FallingSimulationData {
57 fn new() -> Self {
58 let bounds = Bounds {
59 min_x: -100,
60 max_x: 100,
61 min_y: -100,
62 max_y: 100,
63 };
64
65 Self {
66 secs_passed: 0.0,
67 total_pieces: 0,
68 world: PlanarVec::new(
69 bounds,
70 Piece {
71 kind: PieceKind::Air,
72 },
73 ),
74 has_moved: PlanarVec::new(bounds, false),
75 }
76 }
77
78 fn swap(&mut self, (x1, y1): (i64, i64), (x2, y2): (i64, i64)) {
79 let temp = self.world[(x1, y1)];
80 self.world[(x1, y1)] = self.world[(x2, y2)];
81 self.world[(x2, y2)] = temp;
82 }
83
84 fn sim_sand(&mut self, (x, y): (i64, i64)) {
85 let piece = self.world[(x, y)];
86
87 if let Some(&below) = self.world.get(x, y - 1) {
89 if below.kind.density() < piece.kind.density() {
90 self.swap((x, y), (x, y - 1));
91 self.has_moved[(x, y)] = true;
92 self.has_moved[(x, y - 1)] = true;
93 return;
95 }
96 }
97 if let Some(&below_right) = self.world.get(x + 1, y - 1) {
99 if below_right.kind.density() < piece.kind.density() {
100 self.swap((x, y), (x + 1, y - 1));
101 self.has_moved[(x, y)] = true;
102 self.has_moved[(x + 1, y - 1)] = true;
103 return;
105 }
106 }
107 if let Some(&below_left) = self.world.get(x - 1, y - 1) {
109 if below_left.kind.density() < piece.kind.density() {
110 self.swap((x, y), (x - 1, y - 1));
111 self.has_moved[(x, y)] = true;
112 self.has_moved[(x - 1, y - 1)] = true;
113 return;
115 }
116 }
117 }
118
119 fn sim_water(&mut self, (x, y): (i64, i64)) {
120 let piece = self.world[(x, y)];
121
122 if let Some(&below) = self.world.get(x, y - 1) {
124 if below.kind.density() < piece.kind.density() {
125 self.swap((x, y), (x, y - 1));
126 self.has_moved[(x, y)] = true;
127 self.has_moved[(x, y - 1)] = true;
128 return;
130 }
131 }
132 if let Some(&below_right) = self.world.get(x + 1, y - 1) {
134 if below_right.kind.density() < piece.kind.density() {
135 self.swap((x, y), (x + 1, y - 1));
136 self.has_moved[(x, y)] = true;
137 self.has_moved[(x + 1, y - 1)] = true;
138 return;
140 }
141 }
142 if let Some(&below_left) = self.world.get(x - 1, y - 1) {
144 if below_left.kind.density() < piece.kind.density() {
145 self.swap((x, y), (x - 1, y - 1));
146 self.has_moved[(x, y)] = true;
147 self.has_moved[(x - 1, y - 1)] = true;
148 return;
150 }
151 }
152 if let Some(&right) = self.world.get(x + 1, y) {
154 if right.kind == PieceKind::Air {
156 self.swap((x, y), (x + 1, y));
157 self.has_moved[(x, y)] = true;
158 self.has_moved[(x + 1, y)] = true;
159 return;
161 }
162 }
163 if let Some(&left) = self.world.get(x - 1, y) {
165 if left.kind == PieceKind::Air {
167 self.swap((x, y), (x - 1, y));
168 self.has_moved[(x, y)] = true;
169 self.has_moved[(x - 1, y)] = true;
170 return;
172 }
173 }
174 }
175
176 fn resize_discard(&mut self, width: usize, height: usize) {
177 let bounds = Bounds {
178 min_x: 0,
179 max_x: width as i64 - 1,
180 min_y: 0,
181 max_y: height as i64 - 1,
182 };
183
184 self.world = PlanarVec::new(
185 bounds,
186 Piece {
187 kind: PieceKind::Air,
188 },
189 );
190 self.has_moved = PlanarVec::new(bounds, false);
191 }
192}
193
194pub struct FallingSimulationComponent {
195 dt_budget: f64,
196 hb_display: HalfBlockDisplayRender,
197}
198
199impl FallingSimulationComponent {
200 const UPDATES_PER_SECOND: f64 = 100.0;
201 const UPDATE_INTERVAL: f64 = 1.0 / Self::UPDATES_PER_SECOND;
202
203 pub fn new() -> Self {
204 Self {
205 dt_budget: 0.0,
206 hb_display: HalfBlockDisplayRender::new(10, 10),
207 }
208 }
209
210 fn update_render(&mut self, data: &FallingSimulationData, display_info: &DisplayInfo) {
211 for x in data.world.x_range() {
214 for y in data.world.y_range() {
215 let piece = data.world[(x, y)];
216 let color = match piece.kind {
217 PieceKind::Air => Color::Transparent,
218 PieceKind::Sand => Color::Rgb([255, 255, 0]),
219 PieceKind::Water => Color::Rgb([0, 0, 255]),
220 };
221 let d_x = x;
222 let d_y = y;
223 let d_y = 2 * display_info.height() as i64 - d_y;
224 let d_y = d_y - 1;
225 self.hb_display.set_color(d_x as usize, d_y as usize, color);
226 }
227 }
228 }
229
230 fn update_simulation(&mut self, shared_state: &mut SharedState<FallingSimulationData>) {
231 let data = &mut shared_state.custom;
232 data.secs_passed += Self::UPDATE_INTERVAL;
233
234 data.has_moved.clear(false);
238
239 data.total_pieces = 0;
240
241 for x in data.world.x_range() {
243 for y in data.world.y_range().rev() {
244 if data.has_moved[(x, y)] {
245 continue;
246 }
247 let piece = data.world[(x, y)];
248 if piece.kind == PieceKind::Air {
249 continue;
250 }
251
252 match piece.kind {
253 PieceKind::Air => {
254 }
256 PieceKind::Sand => {
257 data.sim_sand((x, y));
258 }
259 PieceKind::Water => {
260 data.sim_water((x, y));
261 }
262 }
263 data.has_moved[(x, y)] = true;
264 }
265 }
266
267 for x in data.world.x_range() {
268 for y in data.world.y_range() {
269 let piece = data.world[(x, y)];
270 if piece.kind != PieceKind::Air {
271 data.total_pieces += 1;
272 }
273 }
274 }
275
276 self.update_render(data, &shared_state.display_info);
277 }
278}
279
280impl Component<FallingSimulationData> for FallingSimulationComponent {
281 fn setup(
282 &mut self,
283 setup_info: &SetupInfo,
284 shared_state: &mut SharedState<FallingSimulationData>,
285 ) {
286 self.on_resize(
287 setup_info.display_info.width(),
288 setup_info.display_info.height(),
289 shared_state,
290 );
291 }
292
293 fn on_resize(
294 &mut self,
295 width: usize,
296 height: usize,
297 shared_state: &mut SharedState<FallingSimulationData>,
298 ) {
299 self.hb_display.resize_discard(width, height * 2);
300 let data = &mut shared_state.custom;
301 data.resize_discard(width, height * 2);
302 }
303
304 fn update(
305 &mut self,
306 update_info: UpdateInfo,
307 shared_state: &mut SharedState<FallingSimulationData>,
308 ) {
309 let dt = update_info.dt;
310 self.dt_budget += 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.dt_budget >= Self::UPDATE_INTERVAL {
342 self.update_simulation(shared_state);
343 self.dt_budget -= Self::UPDATE_INTERVAL;
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}