1extern crate prototty;
2extern crate prototty_elements;
3extern crate cgmath;
4extern crate ansi_colour;
5extern crate rand;
6
7use std::mem;
8use std::thread;
9use std::time::{Duration, Instant};
10use rand::Rng;
11use cgmath::Vector2;
12use prototty::*;
13use prototty_elements::elements::*;
14use prototty_elements::common::*;
15use prototty_elements::menu::*;
16use ansi_colour::{Colour, colours};
17
18const BLANK_COLOUR: Colour = colours::DARK_GREY;
19const FOREGROUND_COLOUR: Colour = colours::DARK_GREY;
20const BORDER_COLOUR: Colour = colours::WHITE;
21const BORDER_BACKGROUND: Colour = colours::BLACK;
22const BLOCK_CHAR: char = '▯';
23
24const PIECE_SIZE: usize = 4;
25
26const WIDTH: u16 = 10;
27const HEIGHT: u16 = 12;
28
29const STEP_MILLIS: u64 = 500;
30
31const ESCAPE: char = '\u{1b}';
32const ETX: char = '\u{3}';
33
34#[derive(Clone)]
35struct Piece {
36 colour: Colour,
37 coords: [Vector2<i16>; PIECE_SIZE],
38 typ: PieceType,
39}
40
41impl Piece {
42 fn new(colour: Colour, coords: [(i16, i16); PIECE_SIZE], typ: PieceType) -> Self {
43 let coords = [
44 coords[0].into(),
45 coords[1].into(),
46 coords[2].into(),
47 coords[3].into(),
48 ];
49 Self { colour, coords, typ }
50 }
51
52 fn translate(&self, offset: Vector2<i16>) -> Self {
53 Self {
54 colour: self.colour,
55 coords: [
56 self.coords[0] + offset,
57 self.coords[1] + offset,
58 self.coords[2] + offset,
59 self.coords[3] + offset,
60 ],
61 typ: self.typ,
62 }
63 }
64
65 fn rotate(&self) -> Self {
66 use self::PieceType::*;
67 let offset = match self.typ {
68 Square => return self.clone(),
70 _ => self.coords[2],
71 };
72
73 Self {
74 colour: self.colour,
75 coords: [
76 Self::rotate_about(self.coords[0], offset),
77 Self::rotate_about(self.coords[1], offset),
78 Self::rotate_about(self.coords[2], offset),
79 Self::rotate_about(self.coords[3], offset),
80 ],
81 typ: self.typ,
82 }
83 }
84
85 fn rotate_about(coord: Vector2<i16>, offset: Vector2<i16>) -> Vector2<i16> {
86 let relative = coord - offset;
87 let relative = Vector2 {
88 x: relative.y,
89 y: 0 - relative.x,
90 };
91 relative + offset
92 }
93}
94
95#[derive(Clone, Copy)]
96enum PieceType {
97 L,
98 ReverseL,
99 S,
100 Z,
101 T,
102 Square,
103 Line,
104}
105
106impl PieceType {
107 fn piece(self) -> Piece {
108 use self::PieceType::*;
109 match self {
110 L => Piece::new(colours::RED, [(0, 0), (0, 1), (0, 2), (1, 2)], self),
111 ReverseL => Piece::new(colours::GREEN, [(1, 0), (1, 1), (1, 2), (0, 2)], self),
112 S => Piece::new(colours::BLUE, [(2, 0), (1, 0), (1, 1), (0, 1)], self),
113 Z => Piece::new(colours::YELLOW, [(0, 0), (1, 0), (1, 1), (2, 1)], self),
114 T => Piece::new(colours::MAGENTA, [(1, 0), (0, 1), (1, 1), (2, 1)], self),
115 Square => Piece::new(colours::CYAN, [(0, 0), (0, 1), (1, 0), (1, 1)], self),
116 Line => Piece::new(colours::BRIGHT_BLUE, [(0, 0), (0, 1), (0, 2), (0, 3)], self),
117 }
118 }
119}
120
121const PIECE_TYPES: &[PieceType] = &[
122 PieceType::L,
123 PieceType::ReverseL,
124 PieceType::S,
125 PieceType::Z,
126 PieceType::T,
127 PieceType::Square,
128 PieceType::Line,
129];
130
131fn random_piece_type<R: Rng>(rng: &mut R) -> PieceType {
132 PIECE_TYPES[rng.gen::<usize>() % PIECE_TYPES.len()]
133}
134
135fn random_piece<R: Rng>(rng: &mut R) -> Piece {
136 random_piece_type(rng).piece()
137}
138
139#[derive(Clone, Copy, Default)]
140struct Cell {
141 colour: Option<Colour>,
142}
143
144struct Row {
145 cells: Vec<Cell>,
146}
147
148impl Row {
149 fn new(width: u16) -> Self {
150 let mut cells = Vec::with_capacity(width as usize);
151 cells.resize(width as usize, Default::default());
152 Self { cells }
153 }
154 fn is_full(&self) -> bool {
155 self.cells.iter().all(|c| c.colour.is_some())
156 }
157 fn clear(&mut self) {
158 self.cells.iter_mut().for_each(|c| *c = Default::default());
159 }
160}
161
162struct Board {
163 size: Vector2<i16>,
164 rows: Vec<Row>,
165 rows_swap: Vec<Row>,
166 empty_swap: Vec<Row>,
167}
168
169impl Board {
170 fn new(width: u16, height: u16) -> Self {
171
172 let mut rows = Vec::with_capacity(height as usize);
173 for _ in 0..height {
174 rows.push(Row::new(width));
175 }
176
177 Self {
178 size: Vector2::new(width, height).cast(),
179 rows,
180 rows_swap: Vec::new(),
181 empty_swap: Vec::new(),
182 }
183 }
184
185 fn get(&self, c: Vector2<i16>) -> Option<&Cell> {
186 if c.x < 0 || c.y < 0 {
187 return None;
188 }
189 let c: Vector2<usize> = c.cast();
190 self.rows.get(c.y).and_then(|r| r.cells.get(c.x))
191 }
192
193 fn get_mut(&mut self, c: Vector2<i16>) -> Option<&mut Cell> {
194 if c.x < 0 || c.y < 0 {
195 return None;
196 }
197 let c: Vector2<usize> = c.cast();
198 self.rows.get_mut(c.y).and_then(|r| r.cells.get_mut(c.x))
199 }
200
201 fn connects(&self, piece: &Piece) -> bool {
202 piece.coords.iter().any(|c| {
203 if c.y == self.size.y - 1 {
204 return true;
205 }
206 self.get(c + Vector2::new(0, 1))
207 .map(|c| c.colour.is_some())
208 .unwrap_or(false)
209 })
210 }
211
212 fn collides(&self, piece: &Piece) -> bool {
213 piece.coords.iter().any(|c| {
214 c.x < 0 || c.x >= self.size.x ||
215 c.y >= self.size.y ||
216 self.get(*c)
217 .map(|c| c.colour.is_some())
218 .unwrap_or(false)
219 })
220 }
221
222 fn add_piece(&mut self, piece: Piece) {
223 for coord in piece.coords.iter().cloned() {
224 self.get_mut(coord).map(|c| c.colour = Some(piece.colour));
225 }
226 }
227
228 fn strip_full(&mut self) {
229 for mut row in self.rows.drain(..) {
230 if row.is_full() {
231 row.clear();
232 self.empty_swap.push(row);
233 } else {
234 self.rows_swap.push(row);
235 }
236 }
237 for row in self.empty_swap.drain(..) {
238 self.rows.push(row);
239 }
240 for row in self.rows_swap.drain(..) {
241 self.rows.push(row);
242 }
243 }
244
245 fn move_to_top(&self, piece: Piece) -> Piece {
246 piece.translate(Vector2::new(self.size.x as i16 / 2 - 1, 0))
247 }
248}
249
250enum StepResolution {
251 GameOver,
252 Continue,
253}
254
255struct Game {
256 board: Board,
257 piece: Piece,
258 next_piece: Piece,
259}
260
261impl Game {
262 fn new<R: Rng>(width: u16, height: u16, rng: &mut R) -> Self {
263 let board = Board::new(width, height);
264 Self {
265 piece: board.move_to_top(random_piece(rng)),
266 next_piece: random_piece(rng),
267 board,
268 }
269 }
270
271 fn step<R: Rng>(&mut self, rng: &mut R) -> StepResolution {
272
273 if self.board.connects(&self.piece) {
274 self.store_piece(rng);
275 self.board.strip_full();
276
277 let mut game_over = false;
278 while self.board.collides(&self.piece) {
279 game_over = true;
280 self.piece = self.piece.translate(Vector2::new(0, -1));
281 }
282
283 if game_over {
284 return StepResolution::GameOver;
285 }
286
287 } else {
288 self.piece = self.piece.translate(Vector2::new(0, 1));
289 }
290
291 StepResolution::Continue
292 }
293
294 fn try_move(&mut self, v: Vector2<i16>) {
295 let new_piece = self.piece.translate(v);
296 if !self.board.collides(&new_piece) {
297 self.piece = new_piece;
298 }
299 }
300
301 fn try_rotate(&mut self) {
302 let new_piece = self.piece.rotate();
303 if !self.board.collides(&new_piece) {
304 self.piece = new_piece;
305 }
306 }
307
308 fn store_piece<R: Rng>(&mut self, rng: &mut R) {
309 let next_piece = mem::replace(&mut self.next_piece, random_piece(rng));
310 let piece = mem::replace(&mut self.piece, self.board.move_to_top(next_piece));
311 self.board.add_piece(piece);
312 }
313
314 fn render(&self, canvas: &mut Canvas) {
315 for (coord, canvas_cell) in canvas.enumerate_mut() {
316 let board_cell = self.board.get(coord).unwrap();
317 if let Some(colour) = board_cell.colour {
318 canvas_cell.background_colour = colour;
319 canvas_cell.character = BLOCK_CHAR;
320 canvas_cell.foreground_colour = FOREGROUND_COLOUR;
321 } else {
322 canvas_cell.character = ' ';
323 canvas_cell.background_colour = BLANK_COLOUR;
324 }
325 }
326 for coord in self.piece.coords.iter().cloned() {
327 if let Some(canvas_cell) = canvas.get_mut(coord) {
328 canvas_cell.background_colour = self.piece.colour;
329 canvas_cell.foreground_colour = FOREGROUND_COLOUR;
330 canvas_cell.character = BLOCK_CHAR;
331 }
332 }
333 }
334
335 fn render_next(&self, canvas: &mut Canvas) {
336 for cell in canvas.iter_mut() {
337 cell.character = ' ';
338 cell.foreground_colour = BLANK_COLOUR;
339 cell.background_colour = BLANK_COLOUR;
340 }
341 for coord in self.next_piece.coords.iter().cloned() {
342 if let Some(cell) = canvas.get_mut(coord + Vector2::new(1, 0)) {
343 cell.character = BLOCK_CHAR;
344 cell.foreground_colour = FOREGROUND_COLOUR;
345 cell.background_colour = self.next_piece.colour;
346 }
347 }
348 }
349}
350
351#[derive(Debug, Clone, Copy)]
352enum MainMenuChoice {
353 Play,
354 Quit,
355}
356
357struct Model {
358 game_canvas: Border<Canvas>,
359 piece_canvas: Border<Canvas>,
360}
361
362impl View for Model {
363 fn view<G: ViewGrid>(&self, offset: Vector2<i16>, depth: i16, grid: &mut G) {
364 self.game_canvas.view(offset + Vector2::new(1, 1), depth, grid);
365 let piece_offset = Vector2::new(self.game_canvas.size().x + 1, 1).cast();
366 self.piece_canvas.view(offset + piece_offset, depth, grid);
367 }
368}
369
370struct Frontend {
371 context: Context,
372 model: Model,
373}
374
375impl Frontend {
376 fn new(width: u16, height: u16) -> Self {
377 let context = Context::new().unwrap();
378 let mut model = Model {
379 game_canvas: Border::new(Canvas::new((width, height))),
380 piece_canvas: Border::new(Canvas::new((6, 4))),
381 };
382
383 model.piece_canvas.title = Some("next".to_string());
384 model.piece_canvas.foreground_colour = BORDER_COLOUR;
385 model.piece_canvas.background_colour = BORDER_BACKGROUND;
386 model.game_canvas.foreground_colour = BORDER_COLOUR;
387 model.game_canvas.background_colour = BORDER_BACKGROUND;
388
389 Self {
390 context,
391 model,
392 }
393 }
394 fn display_end_text(&mut self) {
395 self.context.render(&RichText::one_line(
396 vec![("YOU DIED", TextInfo::default().bold().foreground_colour(colours::RED))])).unwrap();
397 }
398 fn render(&mut self, game: &Game) {
399 game.render(&mut self.model.game_canvas.child);
400 game.render_next(&mut self.model.piece_canvas.child);
401 self.context.render(&self.model).unwrap();
402 }
403 fn main_menu(&mut self) -> MainMenuChoice {
404 let mut menu = Border::new(MenuInstance::new(Menu::smallest(vec![
405 ("Play", MainMenuChoice::Play),
406 ("Quit", MainMenuChoice::Quit),
407 ])).unwrap());
408
409 match self.context.run_menu(&mut menu, |b| &mut b.child).unwrap() {
410 MenuChoice::Finalise(x) => x,
411 _ => MainMenuChoice::Quit,
412 }
413 }
414 fn wait_input_timeout(&mut self, duration: Duration) -> Option<Input> {
415 self.context.wait_input_timeout(duration).unwrap()
416 }
417}
418
419fn main() {
420 let mut frontend = Frontend::new(WIDTH, HEIGHT);
421 let mut rng = rand::thread_rng();
422 loop {
423 match frontend.main_menu() {
424 MainMenuChoice::Quit => break,
425 MainMenuChoice::Play => (),
426 }
427
428 let mut game = Game::new(WIDTH, HEIGHT, &mut rng);
429
430 let step_duration = Duration::from_millis(STEP_MILLIS);
431
432 let mut step_start = Instant::now();
433 let mut remaining_time = step_duration;
434
435 loop {
436 frontend.render(&game);
437
438 let input = match frontend.wait_input_timeout(remaining_time) {
439 None => {
440 match game.step(&mut rng) {
441 StepResolution::Continue => (),
442 StepResolution::GameOver => {
443 frontend.render(&game);
444 thread::sleep(Duration::from_millis(500));
445 frontend.display_end_text();
446 thread::sleep(Duration::from_millis(1000));
447
448 break;
449 }
450 }
451 step_start = Instant::now();
452 remaining_time = step_duration;
453 continue;
454 }
455 Some(input) => input,
456 };
457
458 let now = Instant::now();
459 let time_since_step_start = now - step_start;
460 if time_since_step_start >= step_duration {
461 remaining_time = Duration::from_millis(0);
462 continue;
463 }
464 remaining_time = step_duration - time_since_step_start;
465
466 match input {
467 Input::Char(ESCAPE) | Input::Char(ETX) => break,
468 Input::Left => game.try_move(Vector2::new(-1, 0)),
469 Input::Right => game.try_move(Vector2::new(1, 0)),
470 Input::Up => game.try_rotate(),
471 Input::Down => game.try_move(Vector2::new(0, 1)),
472 _ => (),
473 }
474 }
475 }
476}