1#![no_std]
2
3use core::fmt;
4
5const BOARD_WIDTH: usize = 10;
6const BOARD_HEIGHT: usize = 20;
7
8#[derive(Clone, Copy)]
10pub enum Color {
11 Red,
12 Green,
13 Blue,
14 Yellow,
15 Cyan,
16 Magenta,
17 White,
18}
19
20#[derive(Clone, Copy)]
22pub struct Tetromino {
23 pub shape: [(u8, u8); 4],
24 pub color: Color,
25}
26
27const TETROMINOS: &[Tetromino; 7] = &[
29 Tetromino {
30 shape: [(0, 1), (1, 1), (2, 1), (3, 1)],
31 color: Color::Cyan,
32 }, Tetromino {
34 shape: [(0, 0), (0, 1), (1, 0), (1, 1)],
35 color: Color::Yellow,
36 }, Tetromino {
38 shape: [(0, 1), (1, 1), (2, 1), (1, 0)],
39 color: Color::Magenta,
40 }, Tetromino {
42 shape: [(0, 0), (1, 0), (2, 0), (2, 1)],
43 color: Color::Green,
44 }, Tetromino {
46 shape: [(0, 1), (1, 1), (2, 1), (2, 0)],
47 color: Color::Red,
48 }, Tetromino {
50 shape: [(0, 0), (1, 0), (1, 1), (2, 1)],
51 color: Color::Blue,
52 }, Tetromino {
54 shape: [(0, 1), (1, 1), (1, 0), (2, 0)],
55 color: Color::White,
56 }, ];
58
59pub 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 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 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 spawn_new_piece(&mut self) {
179 if self.game_over {
180 return;
181 }
182 let idx = self.rng.next_random() % 7;
184 self.current_piece = TETROMINOS[idx].clone();
185 self.piece_pos = (3, 0);
186
187 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 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
210pub 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}