retris/
tetris.rs

1use crate::board::Board;
2use crate::manual::Manual;
3use crate::pause::Pause;
4use crate::score::Score;
5use crate::timer::Timer;
6use crate::queue::Queue;
7use cursive::{
8    event::{Callback, Event, EventResult, Key},
9    views::Dialog,
10    Printer, Vec2, View,
11    theme::{Color, ColorStyle},
12};
13
14const SLOW_SPEED: usize = 30;
15const NORMAL_SPEED: usize = 10;
16const FAST_SPEED: usize = 1;
17pub struct Tetris {
18    score: Score,
19    timer: Timer,
20    manual: Manual,
21    board: Board,
22    queue: Queue,
23    score_size: Vec2,
24    timer_size: Vec2,
25    manual_size: Vec2,
26    board_size: Vec2,
27    is_paused: bool,
28    hit_bottom: bool,
29    frame_idx: usize,
30    max_frame_idx: usize,
31    gameover: bool,
32}
33
34impl Default for Tetris {
35    fn default() -> Self {
36        Self::new()
37    }
38}
39
40impl Tetris {
41    pub fn new() -> Tetris {
42        let background_color = (
43            ColorStyle::new(Color::Rgb(0,0,0), Color::Rgb(0,0,0)),
44            ColorStyle::new(Color::Rgb(0,0,0), Color::Rgb(30,30,30)),
45        );
46        let warning_color = ColorStyle::new(Color::Rgb(0,0,0), Color::Rgb(200,200,0));
47        let mut score = Score::new();
48        let mut timer = Timer::new();
49        let mut manual = Manual::new();
50        let mut board = Board::new(background_color, warning_color, 10, 20);
51        let score_size = score.required_size(Vec2::new(0,0));
52        let timer_size = timer.required_size(Vec2::new(0,0));
53        let manual_size = manual.required_size(Vec2::new(0,0));
54        let board_size = board.required_size(Vec2::new(0,0));
55        Tetris {
56            score,
57            timer,
58            manual,
59            board,
60            queue: Queue::new(),
61            score_size,
62            timer_size,
63            manual_size,
64            board_size,
65            is_paused: false,
66            hit_bottom: false,
67            frame_idx: 0,
68            max_frame_idx: SLOW_SPEED,
69            gameover: false,
70        }
71    }
72
73    fn on_down(&mut self, is_drop: bool, is_begin: bool) -> EventResult {
74        if self.is_paused {
75            return EventResult::Consumed(None);
76        }
77        let (gameover, hit_bottom) = self.board.on_down(is_drop, is_begin);
78        let gameover = gameover || self.score.is_gameover();
79        if gameover {
80            self.gameover = true;
81            self.toggle_pause();
82            return EventResult::Consumed(Some(Callback::from_fn(move |s| {
83                s.add_layer(Dialog::info("Game Over!"));
84            })));
85        }
86        if hit_bottom {
87            if is_drop {
88                self.merge_block();
89            } else {
90                self.hit_bottom = hit_bottom;
91                self.frame_idx = 0;
92                self.max_frame_idx = NORMAL_SPEED;
93            }
94        }
95        EventResult::Consumed(None)
96    }
97
98    fn new_game(&mut self) -> EventResult {
99        self.score.renew();
100        self.board.renew();
101        self.queue.renew();
102        self.timer.renew();
103        self.is_paused = false;
104        self.hit_bottom = false;
105        self.frame_idx = 0;
106        self.max_frame_idx = SLOW_SPEED;
107        self.gameover = false;
108        EventResult::Consumed(None)
109    }
110
111    fn stop_and_resume(&mut self) -> EventResult {
112        self.toggle_pause();
113        if self.is_paused {
114            EventResult::Consumed(Some(Callback::from_fn(move |s| {
115                s.add_layer(Pause::new());
116            })))
117        } else {
118            EventResult::Consumed(None)
119        }
120    }
121
122    fn toggle_pause(&mut self) {
123        self.is_paused = !self.is_paused;
124        self.timer.toggle_pause();
125    }
126
127    fn handle_merge_and_pass(&mut self, event: Event) -> EventResult {
128        if self.gameover && event != Event::Char('n') {
129            return EventResult::Consumed(None);
130        }
131        let is_begin = self.hit_bottom;
132        if self.hit_bottom {
133            self.merge_block();
134        }
135        match event {
136            Event::Key(Key::Down) => self.speed_up(),
137            Event::Refresh => self.on_down(false, is_begin),
138            Event::Char(' ') => self.on_down(true, is_begin),
139            Event::Char('n') => self.new_game(),
140            Event::Char('m') => self.stop_and_resume(),
141            _ => EventResult::Ignored,
142        }
143    }
144
145    fn merge_block(&mut self) {
146            let score = self.board.merge_block();
147            self.score.add(score);
148            let block = self.queue.pop_and_spawn_new_block();
149            self.board.insert(block);
150            self.hit_bottom = false;
151            self.max_frame_idx = SLOW_SPEED;
152            self.frame_idx = 0;
153    }
154
155    fn speed_up(&mut self) -> EventResult {
156        self.max_frame_idx = FAST_SPEED;
157        self.frame_idx = 0;
158        EventResult::Consumed(None)
159    }
160
161    fn pass_event_to_board(&mut self, event: Event) -> EventResult {
162        if self.is_paused || self.gameover {
163            return EventResult::Consumed(None)
164        }
165        let moved = self.board.handle_event(event, self.hit_bottom);
166        if self.hit_bottom && moved {
167            self.max_frame_idx = std::cmp::min(3 + self.max_frame_idx, 2 * NORMAL_SPEED);
168        }
169        EventResult::Consumed(None)
170    }
171}
172
173impl View for Tetris {
174    fn draw(&self, printer: &Printer) {
175        let x_padding = 4;
176        let y_padding = 4;
177        let score_padding = Vec2::new(x_padding, y_padding);
178        let timer_padding = Vec2::new(x_padding,  y_padding + 1 + self.score_size.y);
179        let manual_padding = Vec2::new(x_padding, y_padding + self.score_size.y + self.timer_size.y);
180        let first_column_x_padding = std::cmp::max(std::cmp::max(self.manual_size.x, self.score_size.x), self.timer_size.x);
181        let board_padding = Vec2::new(x_padding + first_column_x_padding + 2, y_padding);
182        let queue_padding = Vec2::new(x_padding + first_column_x_padding + self.board_size.x, y_padding);
183
184        let score_printer = printer.offset(score_padding);
185        let timer_printer = printer.offset(timer_padding);
186        let manual_printer = printer.offset(manual_padding);
187        let board_printer = printer.offset(board_padding);
188        let queue_printer = printer.offset(queue_padding);
189
190        self.score.draw(&score_printer);
191        self.timer.draw(&timer_printer);
192        self.manual.draw(&manual_printer);
193        self.board.draw(&board_printer);
194        self.queue.draw(&queue_printer);
195    }
196
197    fn required_size(&mut self, constraints: Vec2) -> Vec2 {
198        let score_size = self.score.required_size(constraints);
199        let timer_size = self.timer.required_size(constraints);
200        let manual_size = self.manual.required_size(constraints);
201        let board_size = self.board.required_size(constraints);
202        let queue_size = self.queue.required_size(constraints);
203        Vec2::new(std::cmp::max(std::cmp::max(manual_size.x, score_size.x), timer_size.x) + board_size.x + queue_size.x + 10, board_size.y)
204    }
205
206    fn on_event(&mut self, event: Event) -> EventResult {
207        if event == Event::Refresh {
208            self.frame_idx += 1;
209            if self.frame_idx == self.max_frame_idx {
210                self.frame_idx = 0;
211            } else {
212                return EventResult::Ignored;
213            }
214        }
215
216        match event {
217            Event::Refresh | Event::Key(Key::Down) | Event::Char(' ') | Event::Char('n') | Event::Char('m') => self.handle_merge_and_pass(event),
218            _ => self.pass_event_to_board(event),
219        }
220    }
221}