blockstorm/
lib.rs

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    //
49    (1, 0),
50    (1, 1),
51    (1, -1),
52    (1, -2),
53    (1, 2),
54    //
55    (-1, 0),
56    (-1, 1),
57    (-1, -1),
58    (-1, -2),
59    (-1, 2),
60    //
61    (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// TODO: actually, make this a normal enum and let errors be its variants (so no more implementing
112// the error trait and what not)
113#[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    // Clockwise,
131    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                    // can't do modulo because current_rotation_id is unsigned
268                    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    // TODO: remove pub
475    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    // TODO: make private
496    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        // Move the ghost piece up one step, since we lowered it one step too far
546        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        // Re-draw the moving piece, since we might have overwritten it
555        // While drawing the ghost piece, and then gave up
556        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        // TODO: this should return either OutOfBoundsError or OverlappingMinoesError
593    ) -> 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    // There is no need for a separate lock function, since a lock is really a hard drop from
608    // lowest possible height
609    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                    // Pieces
737                    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                    // Pieces already placed
745                    n if n.rem_euclid(10) == 8 => Color::DarkGray,
746                    // Ghost piece
747                    // Empty tile
748                    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            // You can set the style of the entire Table.
756            .style(Style::default().fg(Color::White))
757            // As any other widget, a Table can be wrapped in a Block.
758            // Columns widths are constrained in the same way as Layout...
759            .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                    // Pieces
799                    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                    // Pieces already placed
807                    n if n.rem_euclid(10) == 8 => Color::DarkGray,
808                    // Ghost piece
809                    // Empty tile
810                    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            // You can set the style of the entire Table.
818            .style(Style::default().fg(Color::White))
819            // As any other widget, a Table can be wrapped in a Block.
820            // Columns widths are constrained in the same way as Layout...
821            .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                    // Pieces
850                    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                    // Pieces already placed
858                    n if n.rem_euclid(10) == 8 => Color::DarkGray,
859                    // Ghost piece
860                    n if n < 0 => Color::White,
861                    // Empty tile
862                    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            // You can set the style of the entire Table.
870            .style(Style::default().fg(Color::White))
871            // As any other widget, a Table can be wrapped in a Block.
872            // Columns widths are constrained in the same way as Layout...
873            .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}