From f1877a1e8c7091c42b56b1a18b69f150b5bebe22 Mon Sep 17 00:00:00 2001 From: Theis Pieter Hollebeek Date: Mon, 3 Mar 2025 12:22:57 +0100 Subject: [PATCH] move game stuff into game --- src/game.rs | 280 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 288 ++--------------------------------------------- src/tetromino.rs | 196 ++++++++++++++++++++++++-------- 3 files changed, 443 insertions(+), 321 deletions(-) create mode 100644 src/game.rs diff --git a/src/game.rs b/src/game.rs new file mode 100644 index 0000000..b2eeb36 --- /dev/null +++ b/src/game.rs @@ -0,0 +1,280 @@ +use crate::actions::{Controls, ControlsHeld}; +use crate::board::Board; +use crate::tetromino::{Direction, DirectionDiff, Tetromino}; + +struct CurrentTetromino { + tetromino: Tetromino, + direction: Direction, + x: i8, + y: i8, +} + +impl CurrentTetromino { + fn new(tetromino: Tetromino) -> Self { + const PIECE_WIDTH: i8 = 2; + Self { + tetromino, + direction: Direction::Up, + x: (Board::WIDTH as i8 - PIECE_WIDTH) / 2, + y: -1, + } + } +} + +struct Game { + board: Board, + next_tetrominos: [Tetromino; 3], + current_tetromino: CurrentTetromino, + held_tetromino: Option, + has_swapped_held: bool, + score: Score, + ticks: usize, +} + +struct Score { + level: usize, + points: usize, + lines: usize, + combo: usize, + back_to_back: bool, +} + +impl Score { + const fn new() -> Self { + Self { + level: 0, + points: 0, + lines: 0, + combo: 0, + back_to_back: false, + } + } + + fn level_up(&mut self, lines_cleared: usize) { + self.lines += lines_cleared; + if self.lines > self.level * 5 { + self.level += 1; + self.lines = 0; + } + } + + fn point_multiplier_from_lines_cleared(lines_cleared: usize) -> f32 { + match lines_cleared { + 0 => 0.0, + 1 => 100.0, + 2 => 300.0, + 3 => 500.0, + 4 => 800.0, + _ => unreachable!("we cannot clear more than 4 lines"), + } + } + + fn combos(&self, lines_cleared: usize) -> usize { + if lines_cleared > 0 { + self.combo * 50 * self.level + } else { + 0 + } + } +} + +impl Game { + fn take_next_in_bag(&mut self, mut last: Tetromino) -> Tetromino { + for value in self.next_tetrominos.iter_mut().rev() { + std::mem::swap(value, &mut last) + } + last + } + + fn hard_drop(&mut self, controls: &ControlsHeld) { + if !controls.just_pressed(self.ticks, &Controls::HardDrop) { + return; + } + let start_y = self.current_tetromino.y; + loop { + self.current_tetromino.y += 1; + if !self.board.colliding(&self.current_tetromino) { + continue; + } + self.current_tetromino.y -= 1; + self.score.points += (self.current_tetromino.y - start_y) as usize * 2; + self.place_current_tetromino(); + self.check_line_clears(); + break; + } + } + + fn soft_drop(&mut self, controls: &ControlsHeld) { + let mut delay = 32 - self.score.level * 2; + if controls.contains_key(&Controls::SoftDrop) { + delay /= 10; + } + + if self.ticks % delay != 0 { + return; + } + + self.current_tetromino.y += 1; + if self.board.colliding(&self.current_tetromino) { + self.current_tetromino.y -= 1; + self.place_current_tetromino(); + self.check_line_clears(); + } else if controls.contains_key(&Controls::SoftDrop) { + self.score.points += 1; + } + } + + fn move_horizontally(&mut self, controls: &ControlsHeld) { + for key in [Controls::Left, Controls::Right] { + let just_pressed = controls.just_pressed(self.ticks, &key); + let long_press = controls.held_for(self.ticks, &key, |held_for| held_for > 15); + if !just_pressed && !long_press { + continue; + } + let offset = match key { + Controls::Left => -1, + Controls::Right => 1, + _ => unreachable!(), + }; + self.current_tetromino.x += offset; + if self.board.colliding(&self.current_tetromino) { + self.current_tetromino.x -= offset; + } + } + } + + fn check_line_clears(&mut self) { + let lines_cleared = self.board.lines_cleared(); + + self.score.level_up(lines_cleared); + + let mut points = + self.score.level as f32 * Score::point_multiplier_from_lines_cleared(lines_cleared); + + if self.score.back_to_back && lines_cleared == 4 { + points *= 1.5; + } + points += self.score.combos(lines_cleared) as f32; + + self.score.points += points as usize; + + if lines_cleared == 4 { + self.score.back_to_back = true; + } else if lines_cleared > 0 { + self.score.back_to_back = false; + } + + if lines_cleared > 0 { + self.score.combo += 1; + // play_line_clears_sound(); + } else { + self.score.combo = 0; + // play_hard_drop_sound(); + } + } + + fn step(&mut self, controls: &ControlsHeld) { + // TODO: ensure game is running at 60 ticks per second? (`if !check_update_time(context, 60) { return; }`) + self.hard_drop(controls); + self.soft_drop(controls); + self.move_horizontally(controls); + + if controls.just_pressed(self.ticks, &Controls::Swap) { + self.try_swap_tetromino(); + } + + for (control, direction) in [ + (Controls::RotateCw, DirectionDiff::Cw), + (Controls::RotateCcw, DirectionDiff::Ccw), + ] { + if !controls.just_pressed(self.ticks, &control) { + continue; + } + self.try_rotate(direction); + } + self.ticks += 1; + } + + fn try_rotate(&mut self, diff: DirectionDiff) -> bool { + let rotated = self.current_tetromino.direction.rotate(&diff); + let old_direction = std::mem::replace(&mut self.current_tetromino.direction, rotated); + if !self.board.colliding(&self.current_tetromino) { + return true; + } + let wall_kicks = self + .current_tetromino + .tetromino + .wall_kicks(&old_direction, &diff); + + for (x, y) in wall_kicks { + self.current_tetromino.x += x; + self.current_tetromino.y += y; + if !(self.board.colliding(&self.current_tetromino)) { + return true; + } + self.current_tetromino.x -= x; + self.current_tetromino.y -= y; + } + + self.current_tetromino.direction = old_direction; + false + } + + fn place_current_tetromino(&mut self) { + let next = CurrentTetromino::new(self.take_next_in_bag(Tetromino::random())); + let current = std::mem::replace(&mut self.current_tetromino, next); + let pattern = current.tetromino.direction_pattern(¤t.direction); + + for (y, row) in pattern.iter().enumerate() { + for x in row + .iter() + .enumerate() + .filter(|(_, exists)| **exists) + .map(|(x, _)| x) + { + let y = (current.y + y as i8) as usize; + let x = (current.x + x as i8) as usize; + self.board[y][x] = Some(current.tetromino.clone()); + } + } + + self.has_swapped_held = false; + } + + fn try_swap_tetromino(&mut self) { + if self.has_swapped_held { + return; + } + self.has_swapped_held = true; + let held_or_first_in_bag_tetromino = self + .held_tetromino + .take() + .unwrap_or_else(|| self.take_next_in_bag(Tetromino::random())); + let current_tetromino = CurrentTetromino::new(held_or_first_in_bag_tetromino); + let old_tetromino = std::mem::replace(&mut self.current_tetromino, current_tetromino); + self.held_tetromino.replace(old_tetromino.tetromino); + } +} + +#[cfg(test)] +mod test { + use crate::{Board, CurrentTetromino, Game, Score, Tetromino}; + + #[test] + fn advance_bag() { + let mut game = Game { + board: Board::new(), + score: Score::new(), + next_tetrominos: [Tetromino::I, Tetromino::J, Tetromino::O], + current_tetromino: CurrentTetromino::new(Tetromino::J), + held_tetromino: None, + has_swapped_held: false, + ticks: 0, + }; + assert_eq!(game.take_next_in_bag(Tetromino::S), Tetromino::I); + assert_eq!( + game.next_tetrominos, + [Tetromino::J, Tetromino::O, Tetromino::S] + ); + } +} diff --git a/src/main.rs b/src/main.rs index 48bdc28..fa1d7b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,288 +1,24 @@ -use actions::{Controls, ControlsHeld}; -use board::Board; -use tetromino::{Direction, DirectionDiff, Tetromino}; +use tetromino::Tetromino; mod actions; mod board; +mod game; mod tetromino; struct Rgb(u8, u8, u8); -struct CurrentTetromino { - tetromino: Tetromino, - direction: Direction, - x: i8, - y: i8, -} - -impl CurrentTetromino { - fn new(tetromino: Tetromino) -> Self { - const PIECE_WIDTH: i8 = 2; - Self { - tetromino, - direction: Direction::Up, - x: (Board::WIDTH as i8 - PIECE_WIDTH) / 2, - y: -1, +impl Rgb { + fn from_tetromino(tetromino: &Tetromino) -> Self { + match tetromino { + Tetromino::I => Self(0, 255, 255), + Tetromino::J => Self(0, 0, 255), + Tetromino::L => Self(255, 128, 0), + Tetromino::O => Self(255, 255, 0), + Tetromino::S => Self(0, 255, 0), + Tetromino::T => Self(255, 0, 255), + Tetromino::Z => Self(255, 0, 0), } } } -struct Game { - board: Board, - next_tetrominos: [Tetromino; 3], - current_tetromino: CurrentTetromino, - held_tetromino: Option, - has_swapped_held: bool, - score: Score, - ticks: usize, -} - -struct Score { - level: usize, - points: usize, - lines: usize, - combo: usize, - back_to_back: bool, -} - -impl Score { - const fn new() -> Self { - Self { - level: 0, - points: 0, - lines: 0, - combo: 0, - back_to_back: false, - } - } - - fn level_up(&mut self, lines_cleared: usize) { - self.lines += lines_cleared; - if self.lines > self.level * 5 { - self.level += 1; - self.lines = 0; - } - } - - fn point_multiplier_from_lines_cleared(lines_cleared: usize) -> f32 { - match lines_cleared { - 0 => 0.0, - 1 => 100.0, - 2 => 300.0, - 3 => 500.0, - 4 => 800.0, - _ => unreachable!("we cannot clear more than 4 lines"), - } - } - - fn combos(&self, lines_cleared: usize) -> usize { - if lines_cleared > 0 { - self.combo * 50 * self.level - } else { - 0 - } - } -} - -impl Game { - fn take_next_in_bag(&mut self, mut last: Tetromino) -> Tetromino { - for value in self.next_tetrominos.iter_mut().rev() { - std::mem::swap(value, &mut last) - } - last - } - - fn hard_drop(&mut self, controls: &ControlsHeld) { - if !controls.just_pressed(self.ticks, &Controls::HardDrop) { - return; - } - let start_y = self.current_tetromino.y; - loop { - self.current_tetromino.y += 1; - if !self.board.colliding(&self.current_tetromino) { - continue; - } - self.current_tetromino.y -= 1; - self.score.points += (self.current_tetromino.y - start_y) as usize * 2; - self.place_current_tetromino(); - self.check_line_clears(); - break; - } - } - - fn soft_drop(&mut self, controls: &ControlsHeld) { - let mut delay = 32 - self.score.level * 2; - if controls.contains_key(&Controls::SoftDrop) { - delay /= 10; - } - - if self.ticks % delay != 0 { - return; - } - - self.current_tetromino.y += 1; - if self.board.colliding(&self.current_tetromino) { - self.current_tetromino.y -= 1; - self.place_current_tetromino(); - self.check_line_clears(); - } else if controls.contains_key(&Controls::SoftDrop) { - self.score.points += 1; - } - } - - fn move_horizontally(&mut self, controls: &ControlsHeld) { - for key in [Controls::Left, Controls::Right] { - let just_pressed = controls.just_pressed(self.ticks, &key); - let long_press = controls.held_for(self.ticks, &key, |held_for| held_for > 15); - if !just_pressed && !long_press { - continue; - } - let offset = match key { - Controls::Left => -1, - Controls::Right => 1, - _ => unreachable!(), - }; - self.current_tetromino.x += offset; - if self.board.colliding(&self.current_tetromino) { - self.current_tetromino.x -= offset; - } - } - } - - fn check_line_clears(&mut self) { - let lines_cleared = self.board.lines_cleared(); - - self.score.level_up(lines_cleared); - - let mut points = - self.score.level as f32 * Score::point_multiplier_from_lines_cleared(lines_cleared); - - if self.score.back_to_back && lines_cleared == 4 { - points *= 1.5; - } - points += self.score.combos(lines_cleared) as f32; - - self.score.points += points as usize; - - if lines_cleared == 4 { - self.score.back_to_back = true; - } else if lines_cleared > 0 { - self.score.back_to_back = false; - } - - if lines_cleared > 0 { - self.score.combo += 1; - // play_line_clears_sound(); - } else { - self.score.combo = 0; - // play_hard_drop_sound(); - } - } - - fn step(&mut self, controls: &ControlsHeld) { - // TODO: ensure game is running at 60 ticks per second? (`if !check_update_time(context, 60) { return; }`) - self.hard_drop(controls); - self.soft_drop(controls); - self.move_horizontally(controls); - - if controls.just_pressed(self.ticks, &Controls::Swap) { - self.try_swap_tetromino(); - } - - for (control, direction) in [ - (Controls::RotateCw, DirectionDiff::Cw), - (Controls::RotateCcw, DirectionDiff::Ccw), - ] { - if !controls.just_pressed(self.ticks, &control) { - continue; - } - self.try_rotate(direction); - } - self.ticks += 1; - } - - fn try_rotate(&mut self, diff: DirectionDiff) -> bool { - let rotated = self.current_tetromino.direction.rotate(&diff); - let old_direction = std::mem::replace(&mut self.current_tetromino.direction, rotated); - if !self.board.colliding(&self.current_tetromino) { - return true; - } - let wall_kicks = self - .current_tetromino - .tetromino - .wall_kicks(&old_direction, &diff); - - for (x, y) in wall_kicks { - self.current_tetromino.x += x; - self.current_tetromino.y += y; - if !(self.board.colliding(&self.current_tetromino)) { - return true; - } - self.current_tetromino.x -= x; - self.current_tetromino.y -= y; - } - - self.current_tetromino.direction = old_direction; - false - } - - fn place_current_tetromino(&mut self) { - let next = CurrentTetromino::new(self.take_next_in_bag(Tetromino::random())); - let current = std::mem::replace(&mut self.current_tetromino, next); - let pattern = current.tetromino.direction_pattern(¤t.direction); - - for (y, row) in pattern.iter().enumerate() { - for x in row - .iter() - .enumerate() - .filter(|(_, exists)| **exists) - .map(|(x, _)| x) - { - let y = (current.y + y as i8) as usize; - let x = (current.x + x as i8) as usize; - self.board[y][x] = Some(current.tetromino.clone()); - } - } - - self.has_swapped_held = false; - } - - fn try_swap_tetromino(&mut self) { - if self.has_swapped_held { - return; - } - self.has_swapped_held = true; - let held_or_first_in_bag_tetromino = self - .held_tetromino - .take() - .unwrap_or_else(|| self.take_next_in_bag(Tetromino::random())); - let current_tetromino = CurrentTetromino::new(held_or_first_in_bag_tetromino); - let old_tetromino = std::mem::replace(&mut self.current_tetromino, current_tetromino); - self.held_tetromino.replace(old_tetromino.tetromino); - } -} - fn main() {} - -#[cfg(test)] -mod test { - use crate::{Board, CurrentTetromino, Game, Score, Tetromino}; - - #[test] - fn advance_bag() { - let mut game = Game { - board: Board::new(), - score: Score::new(), - next_tetrominos: [Tetromino::I, Tetromino::J, Tetromino::O], - current_tetromino: CurrentTetromino::new(Tetromino::J), - held_tetromino: None, - has_swapped_held: false, - ticks: 0, - }; - assert_eq!(game.take_next_in_bag(Tetromino::S), Tetromino::I); - assert_eq!( - game.next_tetrominos, - [Tetromino::J, Tetromino::O, Tetromino::S] - ); - } -} diff --git a/src/tetromino.rs b/src/tetromino.rs index 8ac8db4..546cac9 100644 --- a/src/tetromino.rs +++ b/src/tetromino.rs @@ -1,5 +1,3 @@ -use crate::Rgb; - #[derive(Clone, Debug, PartialEq)] pub enum Tetromino { I, @@ -53,65 +51,173 @@ impl Tetromino { } } - const fn color(&self) -> Rgb { - match self { - Self::I => Rgb(0, 255, 255), - Self::J => Rgb(0, 0, 255), - Self::L => Rgb(255, 128, 0), - Self::O => Rgb(255, 255, 0), - Self::S => Rgb(0, 255, 0), - Self::T => Rgb(255, 0, 255), - Self::Z => Rgb(255, 0, 0), - } - } - pub fn direction_pattern(&self, direction: &Direction) -> [[bool; 4]; 4] { let dir = match self { Self::I => match direction { - Direction::Up => [[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], - Direction::Right => [[0, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0]], - Direction::Down => [[0, 0, 0, 0], [0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0]], - Direction::Left => [[0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0]], + Direction::Up => [ + ['-', '-', '-', '-'], + ['#', '#', '#', '#'], + ['-', '-', '-', '-'], + ['-', '-', '-', '-'], + ], + Direction::Right => [ + ['-', '-', '#', '-'], + ['-', '-', '#', '-'], + ['-', '-', '#', '-'], + ['-', '-', '#', '-'], + ], + Direction::Down => [ + ['-', '-', '-', '-'], + ['-', '-', '-', '-'], + ['#', '#', '#', '#'], + ['-', '-', '-', '-'], + ], + Direction::Left => [ + ['-', '#', '-', '-'], + ['-', '#', '-', '-'], + ['-', '#', '-', '-'], + ['-', '#', '-', '-'], + ], }, Self::J => match direction { - Direction::Up => [[0, 0, 0, 0], [1, 0, 0, 0], [1, 1, 1, 0], [0, 0, 0, 0]], - Direction::Right => [[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 0, 0], [0, 1, 0, 0]], - Direction::Down => [[0, 0, 0, 0], [0, 0, 0, 0], [1, 1, 1, 0], [0, 0, 1, 0]], - Direction::Left => [[0, 0, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0], [1, 1, 0, 0]], + Direction::Up => [ + ['-', '-', '-', '-'], + ['#', '-', '-', '-'], + ['#', '#', '#', '-'], + ['-', '-', '-', '-'], + ], + Direction::Right => [ + ['-', '-', '-', '-'], + ['-', '#', '#', '-'], + ['-', '#', '-', '-'], + ['-', '#', '-', '-'], + ], + Direction::Down => [ + ['-', '-', '-', '-'], + ['-', '-', '-', '-'], + ['#', '#', '#', '-'], + ['-', '-', '#', '-'], + ], + Direction::Left => [ + ['-', '-', '-', '-'], + ['-', '#', '-', '-'], + ['-', '#', '-', '-'], + ['#', '#', '-', '-'], + ], }, Self::L => match direction { - Direction::Up => [[0, 0, 0, 0], [0, 0, 1, 0], [1, 1, 1, 0], [0, 0, 0, 0]], - Direction::Right => [[0, 0, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 1, 0]], - Direction::Down => [[0, 0, 0, 0], [0, 0, 0, 0], [1, 1, 1, 0], [1, 0, 0, 0]], - Direction::Left => [[0, 0, 0, 0], [1, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0]], - }, - Self::O => match direction { - Direction::Up => [[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]], - Direction::Right => [[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]], - Direction::Down => [[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]], - Direction::Left => [[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]], + Direction::Up => [ + ['-', '-', '-', '-'], + ['-', '-', '#', '-'], + ['#', '#', '#', '-'], + ['-', '-', '-', '-'], + ], + Direction::Right => [ + ['-', '-', '-', '-'], + ['-', '#', '-', '-'], + ['-', '#', '-', '-'], + ['-', '#', '#', '-'], + ], + Direction::Down => [ + ['-', '-', '-', '-'], + ['-', '-', '-', '-'], + ['#', '#', '#', '-'], + ['#', '-', '-', '-'], + ], + Direction::Left => [ + ['-', '-', '-', '-'], + ['#', '#', '-', '-'], + ['-', '#', '-', '-'], + ['-', '#', '-', '-'], + ], }, Self::S => match direction { - Direction::Up => [[0, 0, 0, 0], [0, 1, 1, 0], [1, 1, 0, 0], [0, 0, 0, 0]], - Direction::Right => [[0, 0, 0, 0], [0, 1, 0, 0], [0, 1, 1, 0], [0, 0, 1, 0]], - Direction::Down => [[0, 0, 0, 0], [0, 0, 0, 0], [0, 1, 1, 0], [1, 1, 0, 0]], - Direction::Left => [[0, 0, 0, 0], [1, 0, 0, 0], [1, 1, 0, 0], [0, 1, 0, 0]], + Direction::Up => [ + ['-', '-', '-', '-'], + ['-', '#', '#', '-'], + ['#', '#', '-', '-'], + ['-', '-', '-', '-'], + ], + Direction::Right => [ + ['-', '-', '-', '-'], + ['-', '#', '-', '-'], + ['-', '#', '#', '-'], + ['-', '-', '#', '-'], + ], + Direction::Down => [ + ['-', '-', '-', '-'], + ['-', '-', '-', '-'], + ['-', '#', '#', '-'], + ['#', '#', '-', '-'], + ], + Direction::Left => [ + ['-', '-', '-', '-'], + ['#', '-', '-', '-'], + ['#', '#', '-', '-'], + ['-', '#', '-', '-'], + ], }, Self::T => match direction { - Direction::Up => [[0, 0, 0, 0], [0, 1, 0, 0], [1, 1, 1, 0], [0, 0, 0, 0]], - Direction::Right => [[0, 0, 0, 0], [0, 1, 0, 0], [0, 1, 1, 0], [0, 1, 0, 0]], - Direction::Down => [[0, 0, 0, 0], [0, 0, 0, 0], [1, 1, 1, 0], [0, 1, 0, 0]], - Direction::Left => [[0, 0, 0, 0], [0, 1, 0, 0], [1, 1, 0, 0], [0, 1, 0, 0]], + Direction::Up => [ + ['-', '-', '-', '-'], + ['-', '#', '-', '-'], + ['#', '#', '#', '-'], + ['-', '-', '-', '-'], + ], + Direction::Right => [ + ['-', '-', '-', '-'], + ['-', '#', '-', '-'], + ['-', '#', '#', '-'], + ['-', '#', '-', '-'], + ], + Direction::Down => [ + ['-', '-', '-', '-'], + ['-', '-', '-', '-'], + ['#', '#', '#', '-'], + ['-', '#', '-', '-'], + ], + Direction::Left => [ + ['-', '-', '-', '-'], + ['-', '#', '-', '-'], + ['#', '#', '-', '-'], + ['-', '#', '-', '-'], + ], }, Self::Z => match direction { - Direction::Up => [[0, 0, 0, 0], [1, 1, 0, 0], [0, 1, 1, 0], [0, 0, 0, 0]], - Direction::Right => [[0, 0, 0, 0], [0, 0, 1, 0], [0, 1, 1, 0], [0, 1, 0, 0]], - Direction::Down => [[0, 0, 0, 0], [0, 0, 0, 0], [1, 1, 0, 0], [0, 1, 1, 0]], - Direction::Left => [[0, 0, 0, 0], [0, 1, 0, 0], [1, 1, 0, 0], [1, 0, 0, 0]], + Direction::Up => [ + ['-', '-', '-', '-'], + ['#', '#', '-', '-'], + ['-', '#', '#', '-'], + ['-', '-', '-', '-'], + ], + Direction::Right => [ + ['-', '-', '-', '-'], + ['-', '-', '#', '-'], + ['-', '#', '#', '-'], + ['-', '#', '-', '-'], + ], + Direction::Down => [ + ['-', '-', '-', '-'], + ['-', '-', '-', '-'], + ['#', '#', '-', '-'], + ['-', '#', '#', '-'], + ], + Direction::Left => [ + ['-', '-', '-', '-'], + ['-', '#', '-', '-'], + ['#', '#', '-', '-'], + ['#', '-', '-', '-'], + ], }, + Self::O => [ + ['-', '-', '-', '-'], + ['-', '#', '#', '-'], + ['-', '#', '#', '-'], + ['-', '-', '-', '-'], + ], }; - dir.map(|row| row.map(|v| v != 0)) + dir.map(|row| row.map(|v| v != '-')) } pub const fn wall_kicks(&self, direction: &Direction, diff: &DirectionDiff) -> [(i8, i8); 5] {