tetris/
tetris.rs

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            // don't rotate squares
69            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}