1use std::panic;
2
3use crossterm::AlternateScreen;
4use specs::{Builder, Dispatcher, DispatcherBuilder, World, WorldExt};
5
6use crate::components::prelude::*;
7use crate::geo::prelude::*;
8use crate::helpers::*;
9use crate::resources::prelude::*;
10use crate::settings::prelude::*;
11
12pub fn run() {
13 use std::thread::sleep;
14 use std::time::Duration;
15
16 panic::set_hook(Box::new(on_panic));
17
18 let final_scores;
19
20 {
23 let (mut world, mut dispatcher) = setup();
24
25 world.insert(Running(true));
26
27 let sleep_duration = Duration::from_millis(
28 world.read_resource::<Settings>().update_delay_ms,
29 );
30
31 while world.read_resource::<Running>().0 {
32 dispatcher.dispatch(&mut world);
33 world.maintain();
34 flush_stdout();
35 sleep(sleep_duration);
36 }
37
38 final_scores = (&*world.read_resource::<Scores>()).clone();
39 cleanup(Some(&world));
40 }
41
42 print_scores(&final_scores);
43}
44
45fn print_scores(scores: &Scores) {
46 #[cfg(feature = "style")]
47 use crossterm::{style, Attribute, Color};
48
49 const PADDING: &str = " ";
50
51 #[cfg(feature = "style")]
52 let header_msg = style("Final Scores")
53 .with(Color::Blue)
54 .attr(Attribute::Bold)
55 .attr(Attribute::Underlined);
56
57 #[cfg(not(feature = "style"))]
58 let header_msg = "Final Scores";
59
60 println!(
61 "{}\n{}",
62 header_msg,
63 format!("{}", scores).replace("\n", format!("{}\n", PADDING).as_str())
64 );
65}
66
67fn on_panic(panic_info: &panic::PanicInfo) {
68 cleanup(None);
69 eprintln!("{:#}", panic_info);
70}
71
72fn cleanup(world: Option<&World>) {
73 if let Some(world) = world {
74 world.read_resource::<TerminalCursor>().show().unwrap();
75 world.read_resource::<AlternateScreen>().to_main().unwrap();
76 } else {
77 use crossterm::{execute, LeaveAlternateScreen};
78 use std::io::{stdout, Write};
79
80 TerminalCursor::new().show().unwrap();
81 execute!(stdout(), LeaveAlternateScreen).unwrap();
82 }
83}
84
85fn setup<'a, 'b>() -> (World, Dispatcher<'a, 'b>) {
86 const RAW_MODE: bool = true;
87
88 let mut world = World::new();
89 let dispatcher = new_dispatcher();
90
91 world.register::<Paddle>();
93 world.register::<Position>();
94 world.register::<Size>();
95 world.register::<Drawable>();
96 world.register::<Velocity>();
97 world.register::<Collider>();
98 world.register::<Collision>();
99 world.register::<PaddleAi>();
100 world.register::<Ball>();
101 world.register::<Confined>();
102
103 let settings = load_settings();
105 let cursor = TerminalCursor::new();
106 cursor.hide().unwrap();
107 world.insert(Deltatime::default());
108 world.insert(InputManager::new(settings.bindings.clone()));
109 world.insert(AlternateScreen::to_alternate(RAW_MODE).unwrap());
110 world.insert(cursor);
111 world.insert(TerminalInput::new());
112 world.insert(Scores::from(&settings.chars.score));
113 world.insert(ShouldReset::default());
114 world.insert(ShouldResetBallSpawns::default());
115 world.insert(settings);
116
117 create_paddles(&mut world);
119 create_vertical_walls(&mut world);
120
121 (world, dispatcher)
122}
123
124fn new_dispatcher<'a, 'b>() -> Dispatcher<'a, 'b> {
125 use crate::systems::prelude::*;
126
127 DispatcherBuilder::new()
128 .with(DeltatimeSystem::default(), "deltatime_system", &[])
129 .with(InputSystem::default(), "input_system", &[])
130 .with(
131 ControlPaddlesSystem::default(),
132 "control_paddles_system",
133 &["input_system"],
134 )
135 .with(PaddleAiSystem::default(), "paddle_ai_system", &[
136 "input_system",
137 ])
138 .with(MovePaddlesSystem::default(), "move_paddles_system", &[
139 "control_paddles_system",
140 "paddle_ai_system",
141 ])
142 .with(MoveEntitiesSystem::default(), "move_entities_system", &[
143 "deltatime_system",
144 "move_paddles_system",
145 ])
146 .with(
147 ConfineEntitiesSystem::default(),
148 "confine_entities_system",
149 &["move_entities_system"],
150 )
151 .with(BallBounceSystem::default(), "ball_bounce_system", &[
152 "move_entities_system",
153 ])
154 .with(BallScoreSystem::default(), "ball_score_system", &[
155 "move_entities_system",
156 "ball_bounce_system",
157 ])
158 .with(ResetSystem::default(), "reset_system", &[
159 "ball_score_system",
160 ])
161 .with(DrawRoomSystem::default(), "draw_room_system", &[
162 "move_entities_system",
163 "confine_entities_system",
164 ])
165 .with(DrawEntitiesSystem::default(), "draw_entities_system", &[
166 "move_entities_system",
167 "draw_room_system",
168 ])
169 .with(DrawScoresSystem::default(), "draw_scores_system", &[
170 "ball_score_system",
171 "draw_room_system",
172 "draw_entities_system",
173 ])
174 .with(SpawnBallSystem::default(), "spawn_ball_system", &[])
175 .build()
176}
177
178fn create_paddles(world: &mut World) {
179 let settings = (*world.read_resource::<Settings>()).clone();
180
181 let paddle_x = 1.0 + settings.paddle.size.0 * 0.5;
182 let paddle_y = settings.room.height as f32 * 0.5;
183 let paddle_size = Size::new(settings.paddle.size.0, settings.paddle.size.1);
184 let paddle_char = &settings.chars.paddle;
185 let room_rect = Rect {
186 top: 1.0,
187 bottom: (settings.room.height - 1) as f32,
188 left: 1.0,
189 right: (settings.room.width - 1) as f32,
190 };
191
192 let drawable: Drawable = paddle_char.into();
193
194 let mut left_paddle = world
196 .create_entity()
197 .with(Paddle::new(Side::Left))
198 .with(drawable.clone())
199 .with(position_for_paddle(&settings, &Side::Left))
200 .with(paddle_size.clone())
201 .with(Velocity::default())
202 .with(Collision::new(CollisionType::Paddle(Side::Left)))
203 .with(Confined::new(room_rect.clone()));
204 if settings.paddle.ai.left {
206 left_paddle = left_paddle.with(PaddleAi::default());
207 }
208 left_paddle.build();
209
210 let mut right_paddle = world
212 .create_entity()
213 .with(Paddle::new(Side::Right))
214 .with(drawable)
215 .with(Position::new(
216 settings.room.width as f32 - paddle_x,
217 paddle_y,
218 ))
219 .with(paddle_size.clone())
220 .with(Velocity::default())
221 .with(Collision::new(CollisionType::Paddle(Side::Right)))
222 .with(Confined::new(room_rect));
223 if settings.paddle.ai.right {
225 right_paddle = right_paddle.with(PaddleAi::default());
226 }
227 right_paddle.build();
228}
229
230fn create_vertical_walls(world: &mut World) {
231 const WALL_HEIGHT: f32 = 8.0;
232 const WALL_Y_PADDING: f32 = 1.0;
233
234 let settings = (*world.read_resource::<Settings>()).clone();
235 let room_size = (settings.room.width as f32, settings.room.height as f32);
236 let half_room_size = (room_size.0 * 0.5, room_size.1 * 0.5);
237 let size = (room_size.0, WALL_HEIGHT);
238 let half_size = (half_room_size.0, size.1 * 0.5);
239
240 world
242 .create_entity()
243 .with(Position::new(
244 half_room_size.0,
245 -half_size.1 + WALL_Y_PADDING,
246 ))
247 .with(Size::new(size.0, size.1))
248 .with(Collision::new(CollisionType::Wall(Side::Top)))
249 .build();
250
251 world
253 .create_entity()
254 .with(Position::new(
255 half_room_size.0,
256 room_size.1 + half_size.1 - WALL_Y_PADDING,
257 ))
258 .with(Size::new(size.0, size.1))
259 .with(Collision::new(CollisionType::Wall(Side::Bottom)))
260 .build();
261}