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}