tetris_game/
lib.rs

1#![no_std]
2
3use core::fmt;
4
5const BOARD_WIDTH: usize = 10;
6const BOARD_HEIGHT: usize = 20;
7
8// Define colors
9#[derive(Clone, Copy)]
10pub enum Color {
11    Red,
12    Green,
13    Blue,
14    Yellow,
15    Cyan,
16    Magenta,
17    White,
18}
19
20// Tetromino struct
21#[derive(Clone, Copy)]
22pub struct Tetromino {
23    pub shape: [(u8, u8); 4],
24    pub color: Color,
25}
26
27// Tetromino shapes with their rotations
28const TETROMINOS: &[Tetromino; 7] = &[
29    Tetromino {
30        shape: [(0, 1), (1, 1), (2, 1), (3, 1)],
31        color: Color::Cyan,
32    }, // I
33    Tetromino {
34        shape: [(0, 0), (0, 1), (1, 0), (1, 1)],
35        color: Color::Yellow,
36    }, // O
37    Tetromino {
38        shape: [(0, 1), (1, 1), (2, 1), (1, 0)],
39        color: Color::Magenta,
40    }, // T
41    Tetromino {
42        shape: [(0, 0), (1, 0), (2, 0), (2, 1)],
43        color: Color::Green,
44    }, // L
45    Tetromino {
46        shape: [(0, 1), (1, 1), (2, 1), (2, 0)],
47        color: Color::Red,
48    }, // J
49    Tetromino {
50        shape: [(0, 0), (1, 0), (1, 1), (2, 1)],
51        color: Color::Blue,
52    }, // S
53    Tetromino {
54        shape: [(0, 1), (1, 1), (1, 0), (2, 0)],
55        color: Color::White,
56    }, // Z
57];
58
59// Game state
60pub struct Tetris<R: RandomGenerator> {
61    pub board: [[Option<Color>; BOARD_WIDTH]; BOARD_HEIGHT],
62    pub current_piece: Tetromino,
63    pub piece_pos: (i8, i8),
64    pub score: u32,
65    pub game_over: bool,
66    rng: R,
67}
68
69pub trait RandomGenerator {
70    fn next_random(&mut self) -> usize;
71}
72
73impl<R: RandomGenerator> Tetris<R> {
74    pub fn new(rng: R) -> Self {
75        let mut game = Tetris {
76            board: [[None; BOARD_WIDTH]; BOARD_HEIGHT],
77            current_piece: TETROMINOS[0].clone(),
78            piece_pos: (3, 0),
79            score: 0,
80            game_over: false,
81            rng,
82        };
83        // Check initial spawn
84        if !game.can_place(&game.current_piece.shape, game.piece_pos) {
85            game.game_over = true;
86        }
87        game
88    }
89
90    pub fn is_game_over(&self) -> bool {
91        self.game_over
92    }
93
94    // Control functions
95    pub fn move_left(&mut self) -> bool {
96        self.try_move((-1, 0))
97    }
98
99    pub fn move_right(&mut self) -> bool {
100        self.try_move((1, 0))
101    }
102
103    pub fn move_down(&mut self) -> bool {
104        if self.game_over {
105            return false;
106        }
107        if !self.try_move((0, 1)) {
108            self.lock_piece();
109            self.spawn_new_piece();
110            true
111        } else {
112            false
113        }
114    }
115
116    pub fn rotate(&mut self) -> bool {
117        if self.game_over {
118            return false;
119        }
120        let mut rotated = [(0, 0); 4];
121        for i in 0..4 {
122            rotated[i] = (
123                self.current_piece.shape[i].1,
124                3 - self.current_piece.shape[i].0,
125            );
126        }
127
128        if self.can_place(&rotated, self.piece_pos) {
129            self.current_piece.shape = rotated;
130            true
131        } else {
132            false
133        }
134    }
135
136    fn try_move(&mut self, delta: (i8, i8)) -> bool {
137        if self.game_over {
138            return false;
139        }
140        let new_pos = (self.piece_pos.0 + delta.0, self.piece_pos.1 + delta.1);
141        if self.can_place(&self.current_piece.shape, new_pos) {
142            self.piece_pos = new_pos;
143            true
144        } else {
145            false
146        }
147    }
148
149    fn can_place(&self, piece: &[(u8, u8); 4], pos: (i8, i8)) -> bool {
150        for &(dx, dy) in piece {
151            let x = pos.0 + dx as i8;
152            let y = pos.1 + dy as i8;
153            if x < 0
154                || x >= BOARD_WIDTH as i8
155                || y >= BOARD_HEIGHT as i8
156                || (y >= 0 && self.board[y as usize][x as usize].is_some())
157            {
158                return false;
159            }
160        }
161        true
162    }
163
164    fn lock_piece(&mut self) {
165        if self.game_over {
166            return;
167        }
168        for &(dx, dy) in &self.current_piece.shape {
169            let x = (self.piece_pos.0 + dx as i8) as usize;
170            let y = (self.piece_pos.1 + dy as i8) as usize;
171            self.board[y][x] = Some(self.current_piece.color);
172        }
173        self.check_lines();
174    }
175
176    // fn select_new_piece(tetrominos) {
177
178    fn spawn_new_piece(&mut self) {
179        if self.game_over {
180            return;
181        }
182        // Simple random selection (in real impl would need RNG)
183        let idx = self.rng.next_random() % 7;
184        self.current_piece = TETROMINOS[idx].clone();
185        self.piece_pos = (3, 0);
186
187        // Check if new piece can be placed, if not, game over
188        if !self.can_place(&self.current_piece.shape, self.piece_pos) {
189            self.game_over = true;
190        }
191    }
192
193    fn check_lines(&mut self) {
194        if self.game_over {
195            return;
196        }
197        for y in 0..BOARD_HEIGHT {
198            if self.board[y].iter().all(|&cell| cell.is_some()) {
199                // Clear line
200                for yy in (1..=y).rev() {
201                    self.board[yy] = self.board[yy - 1];
202                }
203                self.board[0] = [None; BOARD_WIDTH];
204                self.score += 100;
205            }
206        }
207    }
208}
209
210// Example drawing function
211pub fn draw_on_screen<R: RandomGenerator>(
212    tetris: &Tetris<R>,
213    f: &mut impl fmt::Write,
214) -> fmt::Result {
215    for y in 0..BOARD_HEIGHT {
216        write!(f, "|")?;
217        for x in 0..BOARD_WIDTH {
218            let mut occupied = tetris.board[y][x].is_some();
219            if !tetris.game_over {
220                for &(dx, dy) in &tetris.current_piece.shape {
221                    if (tetris.piece_pos.0 + dx as i8) as usize == x
222                        && (tetris.piece_pos.1 + dy as i8) as usize == y
223                    {
224                        occupied = true;
225                    }
226                }
227            }
228            write!(f, "{}", if occupied { "#" } else { " " })?;
229        }
230        writeln!(f, "|")?;
231    }
232    if tetris.game_over {
233        writeln!(f, "GAME OVER - Score: {}", tetris.score)
234    } else {
235        writeln!(f, "Score: {}", tetris.score)
236    }
237}