diff --git a/src/actions.rs b/src/actions.rs index 9c5f961..829e0bb 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; #[derive(Hash, PartialEq, Eq)] -pub enum Controls { +pub enum Action { Left, Right, SoftDrop, @@ -11,17 +11,20 @@ pub enum Controls { RotateCcw, } -pub struct ControlsHeld(HashMap); +pub struct ActionsHeld(HashMap); -impl ControlsHeld { - pub fn just_pressed(&self, ticks: usize, control: &Controls) -> bool { +impl ActionsHeld { + pub fn new() -> Self { + Self(HashMap::new()) + } + pub fn just_pressed(&self, ticks: usize, control: &Action) -> bool { self.held_for(ticks, control, |held_for| held_for == 0) } pub fn held_for bool>( &self, ticks: usize, - control: &Controls, + control: &Action, functor: F, ) -> bool { self.get(control) @@ -30,15 +33,15 @@ impl ControlsHeld { } } -impl std::ops::Deref for ControlsHeld { - type Target = HashMap; +impl std::ops::Deref for ActionsHeld { + type Target = HashMap; fn deref(&self) -> &Self::Target { &self.0 } } -impl std::ops::DerefMut for ControlsHeld { +impl std::ops::DerefMut for ActionsHeld { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } diff --git a/src/game.rs b/src/game.rs index 6f030de..fa60d9a 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,4 +1,4 @@ -use crate::actions::{Controls, ControlsHeld}; +use crate::actions::{Action, ActionsHeld}; use crate::board::Board; use crate::tetromino::{Direction, DirectionDiff, Tetromino}; @@ -21,14 +21,14 @@ impl CurrentTetromino { } } -struct Game { - board: Board, - next_tetrominos: [Tetromino; 3], - current_tetromino: CurrentTetromino, - held_tetromino: Option, +pub struct Game { + pub board: Board, + pub next_tetrominos: [Tetromino; 3], + pub current_tetromino: CurrentTetromino, + pub held_tetromino: Option, has_swapped_held: bool, - score: Score, - ticks: usize, + pub score: Score, + pub ticks: usize, } struct Score { @@ -79,6 +79,17 @@ impl Score { } impl Game { + pub fn new() -> Self { + Self { + board: Board::new(), + next_tetrominos: std::array::from_fn(|_| Tetromino::random()), + current_tetromino: CurrentTetromino::new(Tetromino::random()), + held_tetromino: None, + has_swapped_held: false, + score: Score::new(), + ticks: 0, + } + } 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) @@ -86,8 +97,8 @@ impl Game { last } - fn hard_drop(&mut self, controls: &ControlsHeld) { - if !controls.just_pressed(self.ticks, &Controls::HardDrop) { + fn hard_drop(&mut self, actions: &ActionsHeld) { + if !actions.just_pressed(self.ticks, &Action::HardDrop) { return; } let start_y = self.current_tetromino.y; @@ -104,9 +115,9 @@ impl Game { } } - fn soft_drop(&mut self, controls: &ControlsHeld) { + fn soft_drop(&mut self, actions: &ActionsHeld) { let mut delay = 32 - self.score.level * 2; - if controls.contains_key(&Controls::SoftDrop) { + if actions.contains_key(&Action::SoftDrop) { delay /= 10; } @@ -119,21 +130,21 @@ impl Game { self.current_tetromino.y -= 1; self.place_current_tetromino(); self.check_line_clears(); - } else if controls.contains_key(&Controls::SoftDrop) { + } else if actions.contains_key(&Action::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); + fn move_horizontally(&mut self, actions: &ActionsHeld) { + for key in [Action::Left, Action::Right] { + let just_pressed = actions.just_pressed(self.ticks, &key); + let long_press = actions.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, + Action::Left => -1, + Action::Right => 1, _ => unreachable!(), }; self.current_tetromino.x += offset; @@ -173,21 +184,20 @@ impl Game { } } - 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); + pub fn step(&mut self, actions: &ActionsHeld) { + self.hard_drop(actions); + self.soft_drop(actions); + self.move_horizontally(actions); - if controls.just_pressed(self.ticks, &Controls::Swap) { + if actions.just_pressed(self.ticks, &Action::Swap) { self.try_swap_tetromino(); } for (control, direction) in [ - (Controls::RotateCw, DirectionDiff::Cw), - (Controls::RotateCcw, DirectionDiff::Ccw), + (Action::RotateCw, DirectionDiff::Cw), + (Action::RotateCcw, DirectionDiff::Ccw), ] { - if !controls.just_pressed(self.ticks, &control) { + if !actions.just_pressed(self.ticks, &control) { continue; } self.try_rotate(direction); diff --git a/src/main.rs b/src/main.rs index fa1d7b3..99ca36c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use tetromino::Tetromino; mod actions; mod board; mod game; +mod sdl_impl; mod tetromino; struct Rgb(u8, u8, u8); @@ -21,4 +22,6 @@ impl Rgb { } } -fn main() {} +fn main() { + sdl_impl::start_game(); +} diff --git a/src/sdl_impl.rs b/src/sdl_impl.rs new file mode 100644 index 0000000..edcb86e --- /dev/null +++ b/src/sdl_impl.rs @@ -0,0 +1,146 @@ +use crate::actions::{Action, ActionsHeld}; +use crate::board::Board; +use crate::game::Game; +use crate::Rgb; +use sdl2::event::Event; +use sdl2::keyboard::Keycode; +use sdl2::pixels::Color; +use sdl2::rect::Rect; +use sdl2::render::WindowCanvas; +use std::time::Duration; + +fn draw_board(canvas: &mut WindowCanvas, width: usize, height: usize, color: Rgb) { + canvas.set_draw_color(Color::RGB(color.0, color.1, color.2)); + canvas + .fill_rect(Rect::new( + (800 - 24 * Board::WIDTH as i32) / 2 + width as i32 * 24, + (600 - 24 * Board::HEIGHT as i32) / 2 + height as i32 * 24, + 24, + 24, + )) + .unwrap(); +} + +pub fn start_game() { + let mut game = Game::new(); + let mut actions = ActionsHeld::new(); + + let sdl_context = sdl2::init().unwrap(); + let video_subsystem = sdl_context.video().unwrap(); + + let window = video_subsystem + .window("reimtris2", 800, 600) + .position_centered() + .build() + .unwrap(); + + let mut canvas = window.into_canvas().build().unwrap(); + let mut event_pump = sdl_context.event_pump().unwrap(); + 'running: loop { + canvas.set_draw_color(Color::RGB(16, 16, 16)); + canvas.clear(); + for event in event_pump.poll_iter() { + match event { + Event::Quit { .. } + | Event::KeyDown { + keycode: Some(Keycode::Escape), + .. + } => break 'running, + Event::KeyDown { + keycode: Some(keycode), + .. + } => { + let keycode = match keycode { + Keycode::Left | Keycode::A => Action::Left, + Keycode::Right | Keycode::D => Action::Right, + Keycode::Down | Keycode::S => Action::SoftDrop, + Keycode::Space => Action::HardDrop, + Keycode::Z => Action::RotateCcw, + Keycode::X => Action::RotateCw, + Keycode::C => Action::Swap, + _ => continue, + }; + actions.insert(keycode, game.ticks); + } + Event::KeyUp { + keycode: Some(keycode), + .. + } => { + let keycode = match keycode { + Keycode::Left | Keycode::A => Action::Left, + Keycode::Right | Keycode::D => Action::Right, + Keycode::Down | Keycode::S => Action::SoftDrop, + Keycode::Space => Action::HardDrop, + Keycode::Z => Action::RotateCcw, + Keycode::X => Action::RotateCw, + Keycode::C => Action::Swap, + _ => continue, + }; + actions.remove(&keycode); + } + _ => {} + } + } + + canvas.set_draw_color(Color::WHITE); + canvas + .draw_rect(Rect::new( + (800 - 24 * Board::WIDTH as i32) / 2 - 1, + (600 - 24 * Board::HEIGHT as i32) / 2 - 1, + 24 * Board::WIDTH as u32 + 2, + 24 * Board::HEIGHT as u32 + 2, + )) + .unwrap(); + + for (y, row) in game.board.iter().enumerate() { + for (x, piece) in row.iter().enumerate() { + let color = match piece { + Some(t) => Rgb::from_tetromino(t), + None => Rgb(0, 0, 0), + }; + draw_board(&mut canvas, x, y, color) + } + } + + let pattern = game + .current_tetromino + .tetromino + .direction_pattern(&game.current_tetromino.direction); + + for (y, row) in pattern.iter().enumerate() { + for x in row + .iter() + .enumerate() + .filter(|(_, exists)| **exists) + .map(|(x, _)| x) + { + let x = x as i8 + game.current_tetromino.x; + let y = y as i8 + game.current_tetromino.y; + + if y < 0 { + continue; + } + + if y >= Board::HEIGHT as i8 { + continue; + } + + if x < 0 || x >= Board::WIDTH as i8 { + continue; + } + + draw_board( + &mut canvas, + x as usize, + y as usize, + Rgb::from_tetromino(&game.current_tetromino.tetromino), + ) + } + } + + game.step(&actions); + + canvas.present(); + ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60)); + } +}