1use rand::seq::SliceRandom;
2use std::{
3 error::{self, Error},
4 fmt, io,
5 sync::mpsc::{self, Receiver, Sender},
6 thread::{self, sleep},
7 time::{Duration, Instant},
8};
9use termion::{event::Key, input::TermRead};
10
11use termion::raw::RawTerminal;
12use tui::{
13 backend::TermionBackend,
14 layout::{Alignment, Constraint, Direction, Layout},
15 style::{Color, Modifier, Style},
16 text::{Span, Spans},
17 widgets::{Block, Borders, Cell, Paragraph, Row, Table, Wrap},
18 Terminal,
19};
20
21#[derive(Debug)]
22pub enum TetrisDirection {
23 Up,
24 Down,
25 Left,
26 Right,
27}
28impl TetrisDirection {
29 pub fn opposite_direction(&self) -> TetrisDirection {
30 match self {
31 TetrisDirection::Up => TetrisDirection::Down,
32 TetrisDirection::Down => TetrisDirection::Up,
33 TetrisDirection::Left => TetrisDirection::Right,
34 TetrisDirection::Right => TetrisDirection::Left,
35 }
36 }
37}
38pub const DOWN: TetrisDirection = TetrisDirection::Down;
39pub const LEFT: TetrisDirection = TetrisDirection::Left;
40pub const RIGHT: TetrisDirection = TetrisDirection::Right;
41
42pub const KICKS: [(i16, i16); 20] = [
43 (0, 0),
44 (0, 1),
45 (0, -1),
46 (0, -2),
47 (0, 2),
48 (1, 0),
50 (1, 1),
51 (1, -1),
52 (1, -2),
53 (1, 2),
54 (-1, 0),
56 (-1, 1),
57 (-1, -1),
58 (-1, -2),
59 (-1, 2),
60 (2, 0),
62 (2, 1),
63 (2, -1),
64 (2, -2),
65 (2, 2),
66];
67
68#[derive(Debug)]
69pub enum Rotation {
70 Clockwise,
71 CounterClockwise,
72}
73impl Rotation {
74 pub fn other_direction(&self) -> Rotation {
75 match self {
76 Rotation::Clockwise => Rotation::CounterClockwise,
77 Rotation::CounterClockwise => Rotation::Clockwise,
78 }
79 }
80}
81pub const COUNTER_CLOCKWISE: Rotation = Rotation::CounterClockwise;
82pub const CLOCKWISE: Rotation = Rotation::Clockwise;
83
84#[derive(Debug, Default, Clone, Copy)]
85pub struct Point(pub i16, pub i16);
86
87#[derive(Debug, Default, Clone, Copy)]
88pub struct RelPoint(pub i16, pub i16);
89
90#[derive(Debug, Default)]
91pub struct OutOfBoundsError;
92impl Error for OutOfBoundsError {}
93impl fmt::Display for OutOfBoundsError {
94 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95 write!(f, "Out of bounds")
96 }
97}
98#[derive(Debug, Default)]
99pub struct OverlappingMinoesError;
100impl Error for OverlappingMinoesError {}
101impl fmt::Display for OverlappingMinoesError {
102 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
103 write!(f, "Out of bounds")
104 }
105}
106impl From<OutOfBoundsError> for MinoesError {
107 fn from(_: OutOfBoundsError) -> Self {
108 MinoesError::OutOfBounds(OutOfBoundsError)
109 }
110}
111#[derive(Debug)]
114pub enum MinoesError {
115 OutOfBounds(OutOfBoundsError),
116 OverlappingMinoes(OverlappingMinoesError),
117}
118
119#[derive(Debug, Default, Clone, Copy)]
120pub struct Piece {
121 piece_type: PieceType,
122 center: Point,
123 current_rotation_id: usize,
124 rotations: [[RelPoint; 3]; 4],
125 pub color: i16,
126}
127
128#[derive(Clone, Debug, Copy)]
129pub enum PieceType {
130 I,
132 J,
133 L,
134 O,
135 S,
136 T,
137 Z,
138}
139
140impl Default for PieceType {
141 fn default() -> PieceType {
142 PieceType::I
143 }
144}
145
146impl Piece {
147 pub fn new(piece_type: PieceType) -> Piece {
148 let center: Point;
149 let pos_0: [RelPoint; 3];
150 let pos_1: [RelPoint; 3];
151 let pos_2: [RelPoint; 3];
152 let pos_3: [RelPoint; 3];
153 let color: i16;
154 match piece_type {
155 PieceType::I => {
156 center = Point(0, 5);
157 pos_0 = [RelPoint(0, -2), RelPoint(0, -1), RelPoint(0, 1)];
158 pos_1 = [RelPoint(-2, 0), RelPoint(-1, 0), RelPoint(1, 0)];
159 pos_2 = [RelPoint(0, -2), RelPoint(0, -1), RelPoint(0, 1)];
160 pos_3 = [RelPoint(-2, 0), RelPoint(-1, 0), RelPoint(1, 0)];
161 color = 1;
162 }
163 PieceType::J => {
164 center = Point(1, 5);
165 pos_0 = [RelPoint(0, -1), RelPoint(0, 1), RelPoint(-1, 1)];
166 pos_1 = [RelPoint(-1, -1), RelPoint(-1, 0), RelPoint(1, 0)];
167 pos_2 = [RelPoint(1, -1), RelPoint(0, -1), RelPoint(0, 1)];
168 pos_3 = [RelPoint(-1, 0), RelPoint(1, 0), RelPoint(1, 1)];
169 color = 2;
170 }
171 PieceType::L => {
172 center = Point(1, 4);
173 pos_0 = [RelPoint(-1, -1), RelPoint(0, -1), RelPoint(0, 1)];
174 pos_1 = [RelPoint(-1, 0), RelPoint(1, 0), RelPoint(1, -1)];
175 pos_2 = [RelPoint(0, -1), RelPoint(0, 1), RelPoint(1, 1)];
176 pos_3 = [RelPoint(-1, 1), RelPoint(-1, 0), RelPoint(1, 0)];
177 color = 3;
178 }
179 PieceType::O => {
180 center = Point(1, 4);
181 pos_0 = [RelPoint(-1, 0), RelPoint(0, 1), RelPoint(-1, 1)];
182 pos_1 = [RelPoint(-1, 0), RelPoint(0, 1), RelPoint(-1, 1)];
183 pos_2 = [RelPoint(-1, 0), RelPoint(0, 1), RelPoint(-1, 1)];
184 pos_3 = [RelPoint(-1, 0), RelPoint(0, 1), RelPoint(-1, 1)];
185 color = 4;
186 }
187 PieceType::S => {
188 center = Point(0, 4);
189 pos_0 = [RelPoint(1, -1), RelPoint(1, 0), RelPoint(0, 1)];
190 pos_1 = [RelPoint(-1, 0), RelPoint(0, 1), RelPoint(1, 1)];
191 pos_2 = [RelPoint(1, -1), RelPoint(1, 0), RelPoint(0, 1)];
192 pos_3 = [RelPoint(-1, 0), RelPoint(0, 1), RelPoint(1, 1)];
193 color = 5;
194 }
195 PieceType::T => {
196 center = Point(1, 4);
197 pos_0 = [RelPoint(-1, 0), RelPoint(0, -1), RelPoint(0, 1)];
198 pos_1 = [RelPoint(-1, 0), RelPoint(0, -1), RelPoint(1, 0)];
199 pos_2 = [RelPoint(0, -1), RelPoint(1, 0), RelPoint(0, 1)];
200 pos_3 = [RelPoint(-1, 0), RelPoint(1, 0), RelPoint(0, 1)];
201 color = 6;
202 }
203 PieceType::Z => {
204 center = Point(0, 4);
205 pos_0 = [RelPoint(0, -1), RelPoint(1, 0), RelPoint(1, 1)];
206 pos_1 = [RelPoint(-1, 1), RelPoint(0, 1), RelPoint(1, 0)];
207 pos_2 = [RelPoint(0, -1), RelPoint(1, 0), RelPoint(1, 1)];
208 pos_3 = [RelPoint(-1, 1), RelPoint(0, 1), RelPoint(1, 0)];
209 color = 7;
210 }
211 };
212 Piece {
213 piece_type,
214 center,
215 current_rotation_id: 0,
216 rotations: [pos_0, pos_1, pos_2, pos_3],
217 color,
218 }
219 }
220
221 pub fn get_piece_points(&self) -> Result<[Point; 4], OutOfBoundsError> {
222 let mut points = [Point(0, 0); 4];
223 let current_rotation = self.rotations[self.current_rotation_id];
224 for i in 0..3 {
225 let rel_point = current_rotation[i];
226 if self.center.0 as i16 + rel_point.0 < 0 {
227 return Err(OutOfBoundsError);
228 }
229 let point = Point(
230 self.center.0 as i16 + rel_point.0,
231 self.center.1 as i16 + rel_point.1,
232 );
233 points[i] = point;
234 }
235 points[3] = self.center;
236 Ok(points)
237 }
238
239 fn move_piece_by(&mut self, x: i16, y: i16) {
240 self.center = Point(self.center.0 + x, self.center.1 + y);
241 }
242
243 pub fn move_piece(&mut self, direction: &TetrisDirection) {
244 match direction {
245 TetrisDirection::Up => {
246 self.move_piece_by(-1, 0);
247 }
248 TetrisDirection::Down => {
249 self.move_piece_by(1, 0);
250 }
251 TetrisDirection::Left => {
252 self.move_piece_by(0, -1);
253 }
254 TetrisDirection::Right => {
255 self.move_piece_by(0, 1);
256 }
257 }
258 }
259
260 pub fn rotate_piece(&mut self, rotation: &Rotation) {
261 match rotation {
262 Rotation::CounterClockwise => {
263 self.current_rotation_id = (self.current_rotation_id + 1) % 4;
264 }
265 Rotation::Clockwise => {
266 if self.current_rotation_id == 0 {
267 self.current_rotation_id = 3;
269 } else {
270 self.current_rotation_id -= 1;
271 }
272 }
273 }
274 }
275}
276
277#[derive(Debug)]
278pub enum Event {
279 TimePassed,
280 MovePiece(TetrisDirection),
281 RotatePiece(Rotation),
282 HardDropPiece,
283 HoldPiece,
284 TogglePause,
285 Quit,
286}
287
288#[derive(Debug)]
289pub struct Game {
290 pub event_receiver: Receiver<Event>,
291 pub ghost_piece: Option<Piece>,
292 pub held_piece: Option<Piece>,
293 pub hold_used: bool,
294 pub level: u16,
295 pub lines_cleared: u16,
296 pub moving_piece: Piece,
297 pub next_pieces: Vec<Piece>,
298 pub playfield: [[i16; 10]; 22],
299 pub score: u16,
300 pub speed_info_sender: Sender<u64>,
301}
302
303impl Default for Game {
304 fn default() -> Game {
305 let (event_sender, event_receiver) = mpsc::channel();
306 let (speed_info_sender, speed_info_receiver) = mpsc::channel::<u64>();
307 let key_sender = event_sender.clone();
308 event_sender
309 .send(Event::TimePassed)
310 .expect("Could not send message");
311
312 thread::spawn(move || {
313 let mut last_action_time = Instant::now();
314 let mut time_per_row = 1000;
315 loop {
316 if let Ok(speed) = speed_info_receiver.try_recv() {
317 time_per_row = speed;
318 }
319 let elapsed = Instant::now().duration_since(last_action_time);
320 let time_elapsed = elapsed >= Duration::from_millis(time_per_row);
321
322 if time_elapsed {
323 event_sender
324 .send(Event::TimePassed)
325 .expect("Could not send message");
326 last_action_time = Instant::now();
327 }
328
329 sleep(Duration::from_millis(10));
330 }
331 });
332
333 thread::spawn(move || {
334 for event in io::stdin().keys() {
335 match event {
336 Ok(Key::Char('q')) => {
337 key_sender
338 .send(Event::Quit)
339 .expect("Could not send message");
340 }
341 Ok(Key::Char('j')) => {
342 key_sender
343 .send(Event::MovePiece(TetrisDirection::Down))
344 .expect("Could not send message");
345 }
346 Ok(Key::Char('k')) => {
347 key_sender
348 .send(Event::RotatePiece(CLOCKWISE))
349 .expect("Could not send message");
350 }
351 Ok(Key::Char('h')) => {
352 key_sender
353 .send(Event::MovePiece(TetrisDirection::Left))
354 .expect("Could not send message");
355 }
356 Ok(Key::Char('l')) => {
357 key_sender
358 .send(Event::MovePiece(TetrisDirection::Right))
359 .expect("Could not send message");
360 }
361 Ok(Key::Char('d')) => {
362 key_sender
363 .send(Event::HardDropPiece)
364 .expect("Could not send message");
365 }
366 Ok(Key::Char('c')) => {
367 key_sender
368 .send(Event::HoldPiece)
369 .expect("Could not send message");
370 }
371 Ok(Key::Char('p')) => {
372 key_sender
373 .send(Event::TogglePause)
374 .expect("Could not send message");
375 }
376
377 _ => (),
378 }
379 }
380 });
381
382 let mut rng = rand::thread_rng();
383 let mut next_pieces = vec![
384 Piece::new(PieceType::I),
385 Piece::new(PieceType::J),
386 Piece::new(PieceType::L),
387 Piece::new(PieceType::O),
388 Piece::new(PieceType::S),
389 Piece::new(PieceType::T),
390 Piece::new(PieceType::Z),
391 ];
392 next_pieces.shuffle(&mut rng);
393
394 return Self {
395 event_receiver,
396 ghost_piece: None,
397 held_piece: None,
398 hold_used: false,
399 level: 1,
400 lines_cleared: 0,
401 moving_piece: next_pieces.pop().unwrap(),
402 next_pieces,
403 playfield: [[0; 10]; 22],
404 score: 0,
405 speed_info_sender,
406 };
407 }
408}
409
410impl Game {
411 pub fn get_next_piece_in_queue(&mut self, pop: bool) -> Piece {
412 let mut next_bag = Vec::new();
413 if self.next_pieces.last().is_none() {
414 let mut rng = rand::thread_rng();
415 next_bag = vec![
416 Piece::new(PieceType::I),
417 Piece::new(PieceType::J),
418 Piece::new(PieceType::L),
419 Piece::new(PieceType::O),
420 Piece::new(PieceType::S),
421 Piece::new(PieceType::T),
422 Piece::new(PieceType::Z),
423 ];
424
425 next_bag.shuffle(&mut rng);
426 }
427 self.next_pieces.append(&mut next_bag);
428
429 if pop {
430 self.next_pieces.pop().unwrap()
431 } else {
432 self.next_pieces.last().unwrap().clone()
433 }
434 }
435
436 fn fill_piece_points(&mut self, piece: &Piece) -> Result<(), MinoesError> {
437 let piece_points = piece.get_piece_points().unwrap();
438 for point in piece_points.iter() {
439 if point.0 < 0 || point.1 < 0 {
440 return Err(MinoesError::OutOfBounds(OutOfBoundsError));
441 }
442 if point.0 >= self.playfield.len() as i16 || point.1 >= self.playfield[0].len() as i16 {
443 return Err(MinoesError::OutOfBounds(OutOfBoundsError));
444 }
445 if self.playfield[point.0 as usize][point.1 as usize] == piece.color {
446 continue;
447 }
448
449 if self.playfield[point.0 as usize][point.1 as usize] > 0 {
450 return Err(MinoesError::OverlappingMinoes(OverlappingMinoesError));
451 }
452 self.playfield[point.0 as usize][point.1 as usize] = piece.color;
453 }
454 Ok(())
455 }
456
457 fn clear_piece_points(&mut self, piece: &Piece) -> Result<(), OutOfBoundsError> {
458 let piece_points = piece.get_piece_points().unwrap();
459 for point in piece_points.iter() {
460 if point.0 < 0 || point.1 < 0 {
461 return Err(OutOfBoundsError);
462 }
463 if point.0 >= self.playfield.len() as i16 || point.1 >= self.playfield[0].len() as i16 {
464 return Err(OutOfBoundsError);
465 }
466 if self.playfield[point.0 as usize][point.1 as usize] != piece.color {
467 continue;
468 }
469 self.playfield[point.0 as usize][point.1 as usize] = 0;
470 }
471 Ok(())
472 }
473
474 pub fn piece_is_in_allowed_position(&self, piece: &Piece) -> Result<(), MinoesError> {
476 let piece_points = match piece.get_piece_points() {
477 Ok(points) => points,
478 Err(_) => return Err(MinoesError::OutOfBounds(OutOfBoundsError)),
479 };
480
481 for point in piece_points.iter() {
482 if point.0 < 0 || point.1 < 0 {
483 return Err(MinoesError::OutOfBounds(OutOfBoundsError));
484 }
485 if point.0 >= self.playfield.len() as i16 || point.1 >= self.playfield[0].len() as i16 {
486 return Err(MinoesError::OutOfBounds(OutOfBoundsError));
487 }
488 if self.playfield[point.0 as usize][point.1 as usize] > 0 {
489 return Err(MinoesError::OverlappingMinoes(OverlappingMinoesError));
490 }
491 }
492 return Ok(());
493 }
494
495 pub fn piece_is_out_of_bounds(&self, piece: &Piece) -> bool {
497 let piece_points = match piece.get_piece_points() {
498 Ok(points) => points,
499 Err(_) => return true,
500 };
501
502 for point in piece_points.iter() {
503 if point.0 < 0 || point.1 < 0 {
504 return true;
505 }
506 if point.0 >= self.playfield.len() as i16 || point.1 >= self.playfield[0].len() as i16 {
507 return true;
508 }
509 }
510 return false;
511 }
512
513 pub fn piece_is_overlapping_with(&self, piece: &Piece, checked: i16) -> bool {
514 let piece_points = piece.get_piece_points().unwrap();
515
516 for point in piece_points.iter() {
517 if self.playfield[point.0 as usize][point.1 as usize] == checked {
518 return true;
519 }
520 }
521 return false;
522 }
523
524 fn fill_field_with_dropped_points(&mut self, points: [Point; 4]) {
525 for point in points.iter() {
526 self.playfield[point.0 as usize][point.1 as usize] = 8;
527 }
528 }
529
530 fn update_ghost_piece(&mut self) {
531 if let Some(ghost_piece) = &self.ghost_piece {
532 self.clear_piece_points(&ghost_piece.clone()).unwrap();
533 }
534 let mut ghost_piece = self.moving_piece.clone();
535 ghost_piece.color = -1;
536
537 let mut lowered_counter = 0;
538 while !self.piece_is_out_of_bounds(&ghost_piece)
539 && !self.piece_is_overlapping_with(&ghost_piece, 8)
540 {
541 ghost_piece.move_piece(&TetrisDirection::Down);
542 lowered_counter += 1;
543 }
544
545 if lowered_counter != 0 {
547 ghost_piece.move_piece(&TetrisDirection::Up);
548 }
549 match self.fill_piece_points(&ghost_piece) {
550 Ok(_) => {}
551 Err(_) => {}
552 }
553 self.ghost_piece = Some(ghost_piece);
554 self.fill_piece_points(&self.moving_piece.clone()).unwrap();
557 }
558
559 pub fn add_piece_to_field(&mut self, piece: Piece) -> Result<(), MinoesError> {
560 self.moving_piece = piece;
561 self.fill_piece_points(&self.moving_piece.clone())?;
562 self.update_ghost_piece();
563 return Ok(());
564 }
565
566 pub fn rotate_moving_piece(&mut self, direction: &Rotation) -> Result<(), OutOfBoundsError> {
567 let mut ok = false;
568 let old_piece = self.moving_piece.clone();
569 self.clear_piece_points(&old_piece).unwrap();
570 for (kick_x, kick_y) in KICKS.iter() {
571 self.moving_piece.move_piece_by(*kick_x, *kick_y);
572 self.moving_piece.rotate_piece(direction);
573 if let Ok(_) = self.piece_is_in_allowed_position(&self.moving_piece.clone()) {
574 ok = true;
575 break;
576 }
577 self.moving_piece.move_piece_by(-*kick_x, -*kick_y);
578 self.moving_piece.rotate_piece(&direction.other_direction());
579 }
580 if !ok {
581 self.fill_piece_points(&old_piece).unwrap();
582 return Err(OutOfBoundsError);
583 }
584 self.fill_piece_points(&self.moving_piece.clone()).unwrap();
585 self.update_ghost_piece();
586 return Ok(());
587 }
588
589 pub fn move_moving_piece(
590 &mut self,
591 direction: TetrisDirection,
592 ) -> Result<(), OutOfBoundsError> {
594 self.clear_piece_points(&self.moving_piece.clone()).unwrap();
595 self.moving_piece.move_piece(&direction);
596 if let Err(_) = self.piece_is_in_allowed_position(&self.moving_piece.clone()) {
597 self.moving_piece
598 .move_piece(&direction.opposite_direction());
599 self.fill_piece_points(&self.moving_piece.clone()).unwrap();
600 return Err(OutOfBoundsError);
601 }
602 self.update_ghost_piece();
603 self.fill_piece_points(&self.moving_piece.clone()).unwrap();
604 return Ok(());
605 }
606
607 pub fn hard_drop_moving_piece(&mut self) -> Result<(), MinoesError> {
610 self.clear_piece_points(&self.moving_piece.clone())?;
611 self.clear_piece_points(&self.ghost_piece.unwrap().clone())?;
612 self.moving_piece = self.ghost_piece.unwrap().clone();
613 self.ghost_piece = None;
614 self.fill_field_with_dropped_points(self.moving_piece.get_piece_points().unwrap());
615 let cleared_lines_count = self.clear_filled_lines();
616 self.adjust_level(cleared_lines_count);
617 self.adjust_score(cleared_lines_count);
618 let next_piece = self.get_next_piece_in_queue(true);
619 self.add_piece_to_field(next_piece)?;
620 self.hold_used = false;
621 return Ok(());
622 }
623
624 pub fn hold_moving_piece(&mut self) -> Result<(), MinoesError> {
625 self.clear_piece_points(&self.moving_piece.clone())?;
626 self.clear_piece_points(&self.ghost_piece.unwrap().clone())?;
627 match self.held_piece {
628 Some(piece) => {
629 let old_held_piece = piece;
630 self.held_piece = Some(self.moving_piece.clone());
631 self.moving_piece = Piece::new(old_held_piece.piece_type);
632 }
633 None => {
634 self.held_piece = Some(self.moving_piece.clone());
635 self.moving_piece = self.get_next_piece_in_queue(true);
636 }
637 }
638 self.fill_piece_points(&self.moving_piece.clone()).unwrap();
639 self.add_piece_to_field(self.moving_piece)?;
640 self.hold_used = true;
641 return Ok(());
642 }
643
644 fn adjust_level(&mut self, cleared_lines: usize) {
645 match cleared_lines {
646 1 => self.lines_cleared += 1,
647 2 => self.lines_cleared += 3,
648 3 => self.lines_cleared += 5,
649 4 => self.lines_cleared += 8,
650 _ => {}
651 }
652 if self.lines_cleared >= self.level * 5 {
653 self.level += 1;
654 self.lines_cleared = 0;
655 let new_speed: u64 = ((0.8 - ((self.level - 1) as f64 * 0.007)).powi(self.level as i32)
656 * 1000.0)
657 .round() as u64;
658 self.speed_info_sender.send(new_speed).unwrap();
659 }
660 }
661
662 fn adjust_score(&mut self, cleared_lines: usize) {
663 match cleared_lines {
664 1 => self.score += 100 * self.level,
665 2 => self.score += 300 * self.level,
666 3 => self.score += 500 * self.level,
667 4 => self.score += 800 * self.level,
668 _ => {}
669 }
670 }
671
672 #[allow(unused_assignments)]
673 pub fn clear_filled_lines(&mut self) -> usize {
674 let mut cleared_lines: usize = 0;
675 for last_line in (0..self.playfield.len()).rev() {
676 while self.playfield[last_line].iter().all(|x| *x == 8) {
677 cleared_lines += 1;
678 for i in (1..=last_line).rev() {
679 self.playfield[i] = self.playfield[i - 1];
680 }
681 }
682 }
683 return cleared_lines;
684 }
685}
686
687pub fn draw_game(
688 terminal: &mut Terminal<TermionBackend<RawTerminal<io::Stdout>>>,
689 game: &mut Game,
690) -> Result<(), Box<dyn error::Error>> {
691 terminal.draw(|f| {
692 let vertical_chunk = Layout::default()
693 .direction(Direction::Vertical)
694 .constraints(
695 [
696 Constraint::Length((game.playfield.len() + 2).try_into().unwrap()),
697 Constraint::Min(0),
698 ]
699 .as_ref(),
700 )
701 .split(f.size());
702 let chunks = Layout::default()
703 .direction(Direction::Horizontal)
704 .constraints(
705 [
706 Constraint::Length(20),
707 Constraint::Length((game.playfield[0].len() * 2 + 2).try_into().unwrap()),
708 Constraint::Length(21),
709 Constraint::Min(0),
710 ]
711 .as_ref(),
712 )
713 .split(vertical_chunk[0]);
714 let piece_info_section = Layout::default()
715 .direction(Direction::Vertical)
716 .constraints(
717 [
718 Constraint::Percentage(45),
719 Constraint::Percentage(10),
720 Constraint::Percentage(45),
721 ]
722 .as_ref(),
723 )
724 .split(chunks[0]);
725
726 let mut next_piece_field = game.playfield.clone().map(|row| row.map(|_cell| 0));
727 let piece = game.get_next_piece_in_queue(false);
728 let points = piece.get_piece_points().unwrap();
729 for point in points {
730 next_piece_field[(point.0 - piece.center.0 + 3) as usize]
731 [(point.1 - piece.center.1 + 4) as usize] = piece.color;
732 }
733 let next_piece_rows = next_piece_field.map(|row| {
734 Row::new(row.map(|el| {
735 let color = match el {
736 n if n.rem_euclid(10) == 1 => Color::Cyan,
738 n if n.rem_euclid(10) == 2 => Color::Blue,
739 n if n.rem_euclid(10) == 3 => Color::Red,
740 n if n.rem_euclid(10) == 4 => Color::Yellow,
741 n if n.rem_euclid(10) == 5 => Color::Green,
742 n if n.rem_euclid(10) == 6 => Color::Magenta,
743 n if n.rem_euclid(10) == 7 => Color::LightRed,
744 n if n.rem_euclid(10) == 8 => Color::DarkGray,
746 n if n.rem_euclid(10) == 0 => Color::Reset,
749 _ => Color::White,
750 };
751 Cell::from("").style(Style::default().bg(color))
752 }))
753 });
754 let next_piece_table = Table::new(next_piece_rows)
755 .style(Style::default().fg(Color::White))
757 .widths(&[
760 Constraint::Length(2),
761 Constraint::Length(2),
762 Constraint::Length(2),
763 Constraint::Length(2),
764 Constraint::Length(2),
765 Constraint::Length(2),
766 Constraint::Length(2),
767 Constraint::Length(2),
768 Constraint::Length(2),
769 Constraint::Length(2),
770 Constraint::Length(2),
771 Constraint::Length(2),
772 Constraint::Length(2),
773 ])
774 .column_spacing(0)
775 .highlight_style(Style::default().add_modifier(Modifier::BOLD))
776 .block(
777 Block::default()
778 .title("Next piece")
779 .title_alignment(tui::layout::Alignment::Center)
780 .borders(Borders::ALL),
781 );
782 f.render_widget(next_piece_table, piece_info_section[0]);
783
784 let mut held_piece_field = game.playfield.clone().map(|row| row.map(|_cell| 0));
785 match game.held_piece {
786 Some(piece) => {
787 let points = piece.get_piece_points().unwrap();
788 for point in points {
789 held_piece_field[(point.0 - piece.center.0 + 3) as usize]
790 [(point.1 - piece.center.1 + 4) as usize] = piece.color;
791 }
792 }
793 None => {}
794 };
795 let held_piece_rows = held_piece_field.map(|row| {
796 Row::new(row.map(|el| {
797 let color = match el {
798 n if n.rem_euclid(10) == 1 => Color::Cyan,
800 n if n.rem_euclid(10) == 2 => Color::Blue,
801 n if n.rem_euclid(10) == 3 => Color::Red,
802 n if n.rem_euclid(10) == 4 => Color::Yellow,
803 n if n.rem_euclid(10) == 5 => Color::Green,
804 n if n.rem_euclid(10) == 6 => Color::Magenta,
805 n if n.rem_euclid(10) == 7 => Color::LightRed,
806 n if n.rem_euclid(10) == 8 => Color::DarkGray,
808 n if n.rem_euclid(10) == 0 => Color::Reset,
811 _ => Color::White,
812 };
813 Cell::from("").style(Style::default().bg(color))
814 }))
815 });
816 let held_piece_table = Table::new(held_piece_rows)
817 .style(Style::default().fg(Color::White))
819 .widths(&[
822 Constraint::Length(2),
823 Constraint::Length(2),
824 Constraint::Length(2),
825 Constraint::Length(2),
826 Constraint::Length(2),
827 Constraint::Length(2),
828 Constraint::Length(2),
829 Constraint::Length(2),
830 Constraint::Length(2),
831 Constraint::Length(2),
832 Constraint::Length(2),
833 Constraint::Length(2),
834 Constraint::Length(2),
835 ])
836 .column_spacing(0)
837 .highlight_style(Style::default().add_modifier(Modifier::BOLD))
838 .block(
839 Block::default()
840 .title("Hold")
841 .title_alignment(tui::layout::Alignment::Center)
842 .borders(Borders::ALL),
843 );
844 f.render_widget(held_piece_table, piece_info_section[2]);
845
846 let field_rows = game.playfield.map(|row| {
847 Row::new(row.map(|el| {
848 let color = match el {
849 n if n.rem_euclid(10) == 1 => Color::Cyan,
851 n if n.rem_euclid(10) == 2 => Color::Blue,
852 n if n.rem_euclid(10) == 3 => Color::Red,
853 n if n.rem_euclid(10) == 4 => Color::Yellow,
854 n if n.rem_euclid(10) == 5 => Color::Green,
855 n if n.rem_euclid(10) == 6 => Color::Magenta,
856 n if n.rem_euclid(10) == 7 => Color::LightRed,
857 n if n.rem_euclid(10) == 8 => Color::DarkGray,
859 n if n < 0 => Color::White,
861 n if n.rem_euclid(10) == 0 => Color::Reset,
863 _ => Color::White,
864 };
865 Cell::from("").style(Style::default().bg(color))
866 }))
867 });
868 let playfield_table = Table::new(field_rows)
869 .style(Style::default().fg(Color::White))
871 .widths(&[
874 Constraint::Length(2),
875 Constraint::Length(2),
876 Constraint::Length(2),
877 Constraint::Length(2),
878 Constraint::Length(2),
879 Constraint::Length(2),
880 Constraint::Length(2),
881 Constraint::Length(2),
882 Constraint::Length(2),
883 Constraint::Length(2),
884 Constraint::Length(2),
885 ])
886 .column_spacing(0)
887 .highlight_style(Style::default().add_modifier(Modifier::BOLD))
888 .block(Block::default().borders(Borders::ALL));
889 f.render_widget(playfield_table, chunks[1]);
890 let text = vec![
891 Spans::from(vec![Span::raw("")]),
892 Spans::from(vec![Span::raw("")]),
893 Spans::from(vec![Span::raw("Score")]),
894 Spans::from(Span::styled(
895 game.score.to_string(),
896 Style::default().fg(Color::Red),
897 )),
898 Spans::from(vec![Span::raw("")]),
899 Spans::from(vec![Span::raw("Level")]),
900 Spans::from(Span::styled(
901 game.level.to_string(),
902 Style::default().fg(Color::Red),
903 )),
904 ];
905 let score_section = Layout::default()
906 .direction(Direction::Vertical)
907 .constraints(
908 [
909 Constraint::Length(10),
910 Constraint::Min(0),
911 Constraint::Length(11),
912 ]
913 .as_ref(),
914 )
915 .split(chunks[2]);
916 let score_paragraph = Paragraph::new(text)
917 .style(Style::default().fg(Color::White))
918 .alignment(Alignment::Center)
919 .wrap(Wrap { trim: true })
920 .block(
921 Block::default()
922 .title("Score")
923 .title_alignment(Alignment::Center)
924 .borders(Borders::ALL),
925 );
926 f.render_widget(score_paragraph, score_section[0]);
927
928 let button_style = Style::default()
929 .fg(Color::Yellow)
930 .add_modifier(Modifier::ITALIC);
931 let help_text = vec![
932 Spans::from(vec![Span::raw("")]),
933 Spans::from(vec![
934 Span::styled("K", button_style),
935 Span::raw(" - Rotate"),
936 ]),
937 Spans::from(vec![
938 Span::styled("H", button_style),
939 Span::raw(" - Move left"),
940 ]),
941 Spans::from(vec![
942 Span::styled("L", button_style),
943 Span::raw(" - Move right"),
944 ]),
945 Spans::from(vec![
946 Span::styled("J", button_style),
947 Span::raw(" - Move down"),
948 ]),
949 Spans::from(vec![
950 Span::styled("D", button_style),
951 Span::raw(" - Hard drop"),
952 ]),
953 Spans::from(vec![
954 Span::styled("C", button_style),
955 Span::raw(" - Hold piece"),
956 ]),
957 ];
958 let help_paragraph = Paragraph::new(help_text)
959 .style(Style::default().fg(Color::White))
960 .alignment(Alignment::Left)
961 .wrap(Wrap { trim: true })
962 .block(
963 Block::default()
964 .title("Buttons")
965 .title_alignment(Alignment::Center)
966 .borders(Borders::ALL),
967 );
968
969 f.render_widget(help_paragraph, score_section[2]);
970 })?;
971 Ok(())
972}
973
974pub fn draw_game_over(
975 terminal: &mut Terminal<TermionBackend<RawTerminal<io::Stdout>>>,
976 game: &mut Game,
977) -> Result<(), Box<dyn error::Error>> {
978 terminal.draw(|f| {
979 let game_over_layout_v = Layout::default()
980 .direction(Direction::Vertical)
981 .constraints(
982 [
983 Constraint::Length(((game.playfield.len() + 2) / 3).try_into().unwrap()),
984 Constraint::Length(((game.playfield.len() + 2) / 3).try_into().unwrap()),
985 Constraint::Min(0),
986 ]
987 .as_ref(),
988 )
989 .split(f.size());
990 let game_over_layout = Layout::default()
991 .direction(Direction::Horizontal)
992 .constraints(
993 [
994 Constraint::Length(20),
995 Constraint::Length((game.playfield[0].len() * 2 + 2).try_into().unwrap()),
996 Constraint::Min(0),
997 ]
998 .as_ref(),
999 )
1000 .split(game_over_layout_v[1]);
1001 let game_over_paragraph = Paragraph::new(vec![
1002 Spans::from(""),
1003 Spans::from(""),
1004 Spans::from("Score"),
1005 Spans::from(Span::styled(
1006 game.score.to_string(),
1007 Style::default().fg(Color::Red),
1008 )),
1009 Spans::from(""),
1010 Spans::from("Press q to quit"),
1011 ])
1012 .style(Style::default().fg(Color::White))
1013 .alignment(Alignment::Center)
1014 .wrap(Wrap { trim: true })
1015 .block(
1016 Block::default()
1017 .title("Game over")
1018 .title_alignment(Alignment::Center)
1019 .borders(Borders::ALL),
1020 );
1021
1022 f.render_widget(game_over_paragraph, game_over_layout[1]);
1023 })?;
1024 Ok(())
1025}