1use crate::{Board, Orientation, Piece, Tetromino};
6
7#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash, Debug, Default)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub enum RotationSystem {
11 Raw,
15 #[default]
17 Ocular,
18 ClassicL,
20 ClassicR,
22 Super,
24}
25
26impl RotationSystem {
27 pub fn rotate(&self, piece: &Piece, board: &Board, right_turns: i8) -> Option<Piece> {
35 match self {
36 RotationSystem::Raw => raw_rotate(piece, board, right_turns),
37 RotationSystem::Ocular => ocular_rotate(piece, board, right_turns),
38 RotationSystem::ClassicL => classic_rotate(piece, board, right_turns, false),
39 RotationSystem::ClassicR => classic_rotate(piece, board, right_turns, true),
40 RotationSystem::Super => super_rotate(piece, board, right_turns),
41 }
42 }
43}
44
45fn raw_rotate(piece: &Piece, board: &Board, right_turns: i8) -> Option<Piece> {
46 piece.reoriented_offset_on(board, right_turns, (0, 0)).ok()
47}
48
49#[rustfmt::skip]
78fn ocular_rotate(piece: &Piece, board: &Board, right_turns: i8) -> Option<Piece> {
79 use Orientation::*;
80 match right_turns.rem_euclid(4) {
82 0 => piece.offset_on(board, (0, 0)).ok(),
84
85 2 => {
87 let mut lookup_tetromino = piece.tetromino;
88 let mut lookup_orientation = piece.orientation;
89 let mut apply_mirror = false;
90 let reorient_horizontally = match piece.orientation { N => N, E => W, S => S, W => E };
92
93 let kick_table = 'lookup: loop {
94 break match lookup_tetromino {
95
96 Tetromino::O | Tetromino::I => &[( 0, 0)][..],
98
99 Tetromino::S => match lookup_orientation {
102 N | S => &[(-1,-1), ( 0, 0)][..],
103 E | W => &[( 1,-1), ( 0, 0)][..],
104 },
105
106 Tetromino::Z => {
107 lookup_tetromino = Tetromino::S;
109 apply_mirror = true;
110 continue 'lookup;
111 },
112
113 Tetromino::T => match lookup_orientation {
114 N => &[( 0,-1), ( 0, 0)][..],
115 E => &[(-1, 0), ( 0, 0), (-1,-1)][..],
116 S => &[( 0, 1), ( 0, 0), ( 0,-1)][..],
117 W => {
118 lookup_orientation = reorient_horizontally;
120 apply_mirror = true;
121 continue 'lookup;
122 },
123 },
124
125 Tetromino::L => match lookup_orientation {
126 N => &[( 0,-1), ( 1,-1), (-1,-1), ( 0, 0), ( 1, 0)][..],
127 E => &[(-1, 0), (-1,-1), ( 0, 0), ( 0,-1)][..],
128 S => &[( 0, 1), ( 0, 0), (-1, 1), (-1, 0)][..],
129 W => &[( 1, 0), ( 0, 0), ( 1,-1), ( 1, 1), ( 0, 1)][..],
130 },
131
132 Tetromino::J => {
133 lookup_tetromino = Tetromino::L;
135 lookup_orientation = reorient_horizontally;
136 apply_mirror = true;
137 continue 'lookup;
138 }
139 }
140 };
141
142 let offsets = kick_table.iter().copied().map(|(x, y)| if apply_mirror { (-x, y) } else { (x, y) });
144 piece.find_reoriented_offset_on(board, right_turns, offsets)
146 }
147
148 rot => {
150 let mut lookup_leftrot = rot == 3;
152 let mut lookup_tetromino = piece.tetromino;
153 let mut lookup_orientation = piece.orientation;
154 let mut apply_mirror = None;
156 let reorient_horizontally = match lookup_orientation { N => N, E => W, S => S, W => E };
158
159 let kick_table = 'lookup: loop {
160 match lookup_tetromino {
161 Tetromino::O => {
162 if lookup_leftrot {
163 break 'lookup &[(-1, 0), (-1,-1), (-1, 1), ( 0, 0)][..];
164 } else {
165 apply_mirror = Some(0);
167 lookup_leftrot = true;
168 continue 'lookup;
169 }
170 },
171
172 Tetromino::I => {
173 if lookup_leftrot {
174 break 'lookup match lookup_orientation {
175 N | S => &[( 1,-1), ( 1,-2), ( 1,-3), ( 0,-1), ( 0,-2), ( 0,-3), ( 1, 0), ( 0, 0), ( 2,-1), ( 2,-2)][..],
176 E | W => &[(-2, 1), (-3, 1), (-2, 0), (-3, 0), (-1, 1), (-1, 0), ( 0, 1), ( 0, 0)][..],
177 };
178 } else {
179 let dx = match lookup_orientation { N | S => 3, E | W => -3 };
182 apply_mirror = Some(dx);
183 lookup_leftrot = true;
184 continue 'lookup;
185 }
186 },
187
188 Tetromino::S => break 'lookup match lookup_orientation {
189 N | S => if lookup_leftrot { &[( 0, 0), ( 0,-1), ( 1, 0), (-1,-1)][..] }
190 else { &[( 1, 0), ( 1,-1), ( 1, 1), ( 0, 0), ( 0,-1)][..] },
191 E | W => if lookup_leftrot { &[(-1, 0), ( 0, 0), (-1,-1), (-1, 1), ( 0, 1)][..] }
192 else { &[( 0, 0), (-1, 0), ( 0,-1), ( 1, 0), ( 0, 1), (-1, 1)][..] },
193 },
194
195 Tetromino::Z => {
196 let dx = match lookup_orientation { N | S => 1, E | W => -1 };
199 apply_mirror = Some(dx);
200 lookup_tetromino = Tetromino::S;
201 lookup_leftrot = !lookup_leftrot;
202 continue 'lookup;
203 },
204
205 Tetromino::T => {
206 if lookup_leftrot {
207 break 'lookup match lookup_orientation {
208 N => &[( 0,-1), ( 0, 0), (-1,-1), ( 1,-1), (-1,-2), ( 1, 0)][..],
209 E => &[(-1, 1), (-1, 0), ( 0, 1), ( 0, 0), (-1,-1), (-1, 2)][..],
210 S => &[( 1, 0), ( 0, 0), ( 1,-1), ( 0,-1), ( 1,-2), ( 2, 0)][..],
211 W => &[( 0, 0), (-1, 0), ( 0,-1), (-1,-1), ( 1,-1), ( 0, 1), (-1, 1)][..],
212 };
213 } else {
214 let dx = match lookup_orientation { N | S => 1, E | W => -1 };
216 apply_mirror = Some(dx);
217 lookup_orientation = reorient_horizontally;
218 lookup_leftrot = true;
219 continue 'lookup;
220 }
221 },
222
223 Tetromino::L => break match lookup_orientation {
224 N => if lookup_leftrot { &[( 0,-1), ( 1,-1), ( 0,-2), ( 1,-2), ( 0, 0), ( 1, 0)][..] }
225 else { &[( 1,-1), ( 1, 0), ( 1,-1), ( 2, 0), ( 0,-1), ( 0, 0)][..] },
226 E => if lookup_leftrot { &[(-1, 1), (-1, 0), (-2, 1), (-2, 0), ( 0, 0), ( 0, 1)][..] }
227 else { &[(-1, 0), ( 0, 0), ( 0,-1), (-1,-1), ( 0, 1), (-1, 1)][..] },
228 S => if lookup_leftrot { &[( 1, 0), ( 0, 0), ( 1,-1), ( 0,-1), ( 0, 1), ( 1, 1)][..] }
229 else { &[( 0, 0), ( 0,-1), ( 1,-1), (-1,-1), ( 1, 0), (-1, 0), ( 0, 1)][..] },
230 W => if lookup_leftrot { &[( 0, 0), (-1, 0), ( 0, 1), ( 1, 0), (-1, 1), ( 1, 1), ( 0,-1), (-1,-1)][..] }
231 else { &[( 0, 1), (-1, 1), ( 0, 0), (-1, 0), ( 0, 2), (-1, 2)][..] },
232 },
233
234 Tetromino::J => {
235 let dx = match lookup_orientation { N | S => 1, E | W => -1 };
237 apply_mirror = Some(dx);
238 lookup_tetromino = Tetromino::L;
239 lookup_orientation = reorient_horizontally;
240 lookup_leftrot = !lookup_leftrot;
241 continue 'lookup;
242 }
243 }
244 };
245
246 let offsets = kick_table.iter().copied().map(|(x, y)| if let Some(mx) = apply_mirror { (mx - x, y) } else { (x, y) });
248 piece.find_reoriented_offset_on(board, right_turns, offsets)
250 },
251 }
252}
253
254fn classic_rotate(
255 piece: &Piece,
256 board: &Board,
257 right_turns: i8,
258 is_r_not_l: bool,
259) -> Option<Piece> {
260 let r_variant_offset = if is_r_not_l { 1 } else { 0 };
261 #[rustfmt::skip]
262 let kick = match right_turns.rem_euclid(4) {
263 0 => (0, 0),
265 2 => {
267 use Orientation::*;
268 match piece.tetromino {
269 Tetromino::O | Tetromino::I | Tetromino::S | Tetromino::Z => (0, 0),
270 Tetromino::T | Tetromino::L | Tetromino::J => match piece.orientation {
271 N => (0, -1),
272 S => (0, 1),
273 E => (-1, 0),
274 W => (1, 0),
275 },
276 }
277 }
278 r => {
280 use Orientation::*;
281 match piece.tetromino {
282 Tetromino::O => (0, 0), Tetromino::I => match piece.orientation {
284 N | S => (1+r_variant_offset, -1), E | W => (-1-r_variant_offset, 1), },
287 Tetromino::S | Tetromino::Z => match piece.orientation {
288 N | S => (r_variant_offset, 0), E | W => (-r_variant_offset, 0), },
291 Tetromino::T | Tetromino::L | Tetromino::J => match piece.orientation {
292 N => if r == 3 { ( 0,-1) } else { ( 1,-1) }, E => if r == 3 { (-1, 1) } else { (-1, 0) }, S => if r == 3 { ( 1, 0) } else { ( 0, 0) }, W => if r == 3 { ( 0, 0) } else { ( 0, 1) }, },
297 }
298 },
299 };
300
301 piece.reoriented_offset_on(board, right_turns, kick).ok()
302}
303
304fn super_rotate(piece: &Piece, board: &Board, right_turns: i8) -> Option<Piece> {
305 let left = match right_turns.rem_euclid(4) {
306 0 => return piece.offset_on(board, (0, 0)).ok(),
308 1 => false,
310 2 => {
312 #[rustfmt::skip]
313 let kick_table = match piece.tetromino {
314 Tetromino::O | Tetromino::I | Tetromino::S | Tetromino::Z => &[(0, 0)][..],
315 Tetromino::T | Tetromino::L | Tetromino::J => match piece.orientation {
316 N => &[( 0,-1), ( 0, 0)][..],
317 E => &[(-1, 0), ( 0, 0)][..],
318 S => &[( 0, 1), ( 0, 0)][..],
319 W => &[( 1, 0), ( 0, 0)][..],
320 },
321 };
322 return piece.find_reoriented_offset_on(board, 2, kick_table.iter().copied());
323 }
324 3 => true,
326 _ => unreachable!(),
327 };
328 use Orientation::*;
329 #[rustfmt::skip]
330 let kick_table = match piece.tetromino {
331 Tetromino::O => &[(0, 0)][..],
332 Tetromino::I => match piece.orientation {
333 N => if left { &[( 1,-2), ( 0,-2), ( 3,-2), ( 0, 0), ( 3,-3)][..] }
334 else { &[( 2,-2), ( 0,-2), ( 3,-2), ( 0,-3), ( 3, 0)][..] },
335 E => if left { &[(-2, 2), ( 0, 2), (-3, 2), ( 0, 3), (-3, 0)][..] }
336 else { &[(-2, 1), (-3, 1), ( 0, 1), (-3, 3), ( 0, 0)][..] },
337 S => if left { &[( 2,-1), ( 3,-1), ( 0,-1), ( 3,-3), ( 0, 0)][..] }
338 else { &[( 1,-1), ( 3,-1), ( 0,-1), ( 3, 0), ( 0,-3)][..] },
339 W => if left { &[(-1, 1), (-3, 1), ( 0, 1), (-3, 0), ( 0, 3)][..] }
340 else { &[(-1, 2), ( 0, 2), (-3, 2), ( 0, 0), (-3, 3)][..] },
341 },
342 Tetromino::S | Tetromino::Z | Tetromino::T | Tetromino::L | Tetromino::J => match piece.orientation {
343 N => if left { &[( 0,-1), ( 1,-1), ( 1, 0), ( 0,-3), ( 1,-3)][..] }
344 else { &[( 1,-1), ( 0,-1), ( 0, 0), ( 1,-3), ( 0,-3)][..] },
345 E => if left { &[(-1, 1), ( 0, 1), ( 0, 0), (-1, 3), ( 0, 3)][..] }
346 else { &[(-1, 0), ( 0, 0), ( 0,-1), (-1, 2), ( 0, 2)][..] },
347 S => if left { &[( 1, 0), ( 0, 0), (-1, 1), ( 1,-2), ( 0,-2)][..] }
348 else { &[( 0, 0), ( 1, 0), ( 1, 1), ( 0,-2), ( 1,-2)][..] },
349 W => if left { &[( 0, 0), (-1, 0), (-1,-1), ( 0, 2), (-1, 2)][..] }
350 else { &[( 0, 1), (-1, 1), (-1, 0), ( 0, 3), (-1, 3)][..] },
351 },
352 };
353 piece.find_reoriented_offset_on(board, right_turns, kick_table.iter().copied())
354}