diff options
-rw-r--r-- | Cargo.lock | 48 | ||||
-rw-r--r-- | Cargo.toml | 7 | ||||
-rw-r--r-- | src/main.rs | 196 |
3 files changed, 201 insertions, 50 deletions
@@ -3,6 +3,18 @@ version = 3 [[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -26,6 +38,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", + "static_assertions", +] + +[[package]] name = "nonblock" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -35,6 +70,12 @@ dependencies = [ ] [[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -44,6 +85,7 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" name = "puzzle" version = "0.1.0" dependencies = [ + "nix", "nonblock", "rand", "termios", @@ -80,6 +122,12 @@ dependencies = [ ] [[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] name = "termios" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -rand = "0.8.5" -termios = "0.3.3" -nonblock = "0.2.0" +rand = "~0.8.5" +termios = "~0.3.3" +nonblock = "~0.2.0" +nix = { version = "~0.26.2", features = ["time"] } diff --git a/src/main.rs b/src/main.rs index fc8c0a5..e5a8d04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ extern crate termios; use rand::Rng; -use std::time::Duration; use nonblock::NonBlockingReader; const FIELD_COLS: usize = 10; @@ -11,18 +10,17 @@ const FIELD_SIZE: usize = FIELD_COLS * FIELD_ROWS; const FIGURE_SIZE_COLS: usize = 4; const FIGURE_SIZE_ROWS: usize = 4; const FIGURE_SIZE: usize = FIGURE_SIZE_COLS*FIGURE_SIZE_ROWS; -const CELLS_I: [i8; FIGURE_SIZE] = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0]; -const CELLS_L2:[i8; FIGURE_SIZE] = [0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0]; -const CELLS_L: [i8; FIGURE_SIZE] = [0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0]; -const CELLS_B: [i8; FIGURE_SIZE] = [0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0]; -const CELLS_S: [i8; FIGURE_SIZE] = [0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0]; -const CELLS_T: [i8; FIGURE_SIZE] = [0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0]; -const CELLS_Z: [i8; FIGURE_SIZE] = [0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0]; +const CELLS_I: [u8; FIGURE_SIZE] = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0]; +const CELLS_L2:[u8; FIGURE_SIZE] = [0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0]; +const CELLS_L: [u8; FIGURE_SIZE] = [0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0]; +const CELLS_B: [u8; FIGURE_SIZE] = [0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0]; +const CELLS_S: [u8; FIGURE_SIZE] = [0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0]; +const CELLS_T: [u8; FIGURE_SIZE] = [0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0]; +const CELLS_Z: [u8; FIGURE_SIZE] = [0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0]; enum StepResult { StateChanged, Quit, - Noop, } #[derive(Clone)] @@ -37,10 +35,37 @@ enum FigureKind { Z, } +struct Timeout { + beginning: nix::sys::time::TimeSpec, + duration: std::time::Duration, +} + +impl Default for Timeout { + #[inline] + fn default() -> Self { + Self { + beginning: nix::sys::time::TimeSpec::new(0, 0), + duration: std::time::Duration::default(), + } + } +} + +impl Timeout { + fn from_duration(d: std::time::Duration) -> Self { + Timeout{ + beginning: nix::time::clock_gettime(nix::time::ClockId::CLOCK_MONOTONIC).unwrap(), + duration: d, + } + } + fn is_elapsed(&self) -> bool { + std::time::Duration::from(self.beginning) + self.duration <= std::time::Duration::from(nix::time::clock_gettime(nix::time::ClockId::CLOCK_MONOTONIC).unwrap()) + } +} + #[derive(Clone)] struct Figure { kind: FigureKind, - cells: [i8; FIGURE_SIZE], + cells: [u8; FIGURE_SIZE], } impl Figure { @@ -64,7 +89,7 @@ impl Figure { const fn kind_t() -> Self { Self{ cells: CELLS_T, kind: FigureKind::T } } const fn kind_z() -> Self { Self{ cells: CELLS_Z, kind: FigureKind::Z } } fn rotate_cw_4x4(&mut self) { - let mut tmp: [i8; 4 * 4] = Default::default(); + let mut tmp: [u8; 4 * 4] = Default::default(); for y in 0..4 { for x in 0..4 { tmp[y + (3 - x) * 4] = self.cells[x + y * 4]; @@ -73,7 +98,7 @@ impl Figure { self.cells = tmp; } fn rotate_cw_3x3(&mut self) { - let mut tmp: [i8; 3 * 3] = Default::default(); + let mut tmp: [u8; 3 * 3] = Default::default(); for y in 0..3 { for x in 0..3 { tmp[y + (2 - x) * 3] = self.cells[x + y * 4]; @@ -100,7 +125,7 @@ impl Figure { #[derive(Clone)] struct Field { - cells: [i8; FIELD_SIZE], + cells: [u8; FIELD_SIZE], } impl Default for Field { @@ -149,6 +174,20 @@ impl Field { } false } + fn is_line_complete(&self, line: usize) -> bool { + assert!(line < FIELD_ROWS); + self.cells[FIELD_COLS * line..][..FIELD_COLS].iter().fold(1, |a, e| a & e) != 0 + } + fn kill_line(&mut self, line: usize) { + for row in line..FIELD_ROWS - 1 { + for col in 0..FIELD_COLS { + self.cells[col + row * FIELD_COLS] = self.cells[col + (row + 1) * FIELD_COLS]; + } + } + for col in 0..FIELD_COLS { + self.cells[col + (FIELD_ROWS - 1) * FIELD_COLS] = 0; + } + } } struct Game { @@ -156,33 +195,58 @@ struct Game { figure: Figure, x: isize, y: isize, + fall_timeout: Timeout, + place_timeout: Option<Timeout>, } impl Default for Game { #[inline] fn default() -> Self { - Self{ field: Field::default(), figure: Figure::kind_random(), x: 0, y: 0 } + Self{ + field: Field::default(), + figure: Figure::kind_random(), + x: 3, + y: 18, + fall_timeout: Timeout::from_duration(std::time::Duration::from_millis(500)), + place_timeout: None, + } } } impl Game { - fn place(&mut self) { + fn place_figure(&mut self) { self.field.render(&self.figure, self.x, self.y); self.figure = Figure::kind_random(); - self.x = 0; - self.y = 0; + (self.x, self.y) = (3, 18); + self.stop_place_timeout(); + for line in 0..FIELD_ROWS - 1 { + while self.field.is_line_complete(line) { + self.field.kill_line(line); + } + } + } + fn reset_fall_timeout(&mut self) { + self.fall_timeout = Timeout::from_duration(std::time::Duration::from_millis(500)); } - fn step(&mut self, c_optional: Option<char>) -> StepResult { - if let None = c_optional { - return StepResult::Noop; + fn start_place_timeout(&mut self) { + self.place_timeout = Some(Timeout::from_duration(std::time::Duration::from_millis(500))); + } + fn stop_place_timeout(&mut self) { + self.place_timeout = None + } + fn reset_place_timeout(&mut self) { + if let Some(_) = self.place_timeout { + self.start_place_timeout(); } - let c = c_optional.unwrap(); + } + fn handle_input(&mut self, c: char) -> Option<StepResult> { match c { - 'q' => return StepResult::Quit, + 'q' => return Some(StepResult::Quit), ' ' => { - if !self.field.has_collision(&self.figure, self.x, self.y) { - self.place() + while !self.field.has_collision(&self.figure, self.x, self.y - 1) { + self.y -= 1; } + self.place_figure() }, 'h' => { if self.field.has_collision(&self.figure, self.x, self.y) { @@ -198,11 +262,11 @@ impl Game { self.y -= 1 } } - 'k' => { - if self.field.has_collision(&self.figure, self.x, self.y) { - self.y += 1 - } else if !self.field.has_collision(&self.figure, self.x, self.y + 1) { - self.y += 1 + ';' | 'k' => { + let mut figure = self.figure.clone(); + figure.rotate(); + if !self.field.has_collision(&figure, self.x, self.y) { + self.figure.rotate() } } 'l' => { @@ -212,29 +276,58 @@ impl Game { self.x += 1 } } - ';' => { - let mut figure = self.figure.clone(); - figure.rotate(); - if !self.field.has_collision(&figure, self.x, self.y) { - self.figure.rotate() - } - } _ => {}, } - StepResult::StateChanged + self.reset_place_timeout(); + Some(StepResult::StateChanged) + } + fn advance_figure(&mut self) { + if self.field.has_collision(&self.figure, self.x, self.y - 1) { + self.place_figure(); + } else { + self.y -= 1; + } + if self.field.has_collision(&self.figure, self.x, self.y - 1) { + self.start_place_timeout(); + } + self.reset_fall_timeout(); + } + fn step(&mut self, c_optional: Option<char>) -> Option<StepResult> { + let handle_input_result = if let Some(c) = c_optional { + self.handle_input(c) + } else { + None + }; + let advance_figure_result = if let Some(t) = &self.place_timeout { + if t.is_elapsed() { + self.advance_figure(); + Some(StepResult::StateChanged) + } else { + None + } + } else if self.fall_timeout.is_elapsed() { + self.advance_figure(); + Some(StepResult::StateChanged) + } else { + None + }; + if let None = handle_input_result { + advance_figure_result + } else { + handle_input_result + } } } fn print_field(field: &Field) { cursor_to_home(); - clear_line(); for y in 0..FIELD_ROWS_VISIBLE { for x in 0..FIELD_COLS { let c = field.cells[(FIELD_ROWS_VISIBLE - 1 - y) * FIELD_COLS + x]; if c == 0 { - print!("██"); + print!(". "); } else { - print!(" "); + print!("██"); } } print!("\r\x1b[1B"); @@ -280,18 +373,27 @@ fn main() { let mut buf = String::new(); noblock_stdin.read_available_to_string(&mut buf).unwrap(); let mut changed = false; - for c in buf.bytes() { - let result = game.step(Some(c as char)); - match result { - StepResult::StateChanged => changed = true, - StepResult::Noop => {}, - StepResult::Quit => break 'outer, + if buf.len() > 0 { + for c in buf.bytes() { + if let Some(result) = game.step(Some(c as char)) { + match result { + StepResult::StateChanged => changed = true, + StepResult::Quit => break 'outer, + } + } + } + } else { + if let Some(result) = game.step(None) { + match result { + StepResult::StateChanged => changed = true, + StepResult::Quit => break 'outer, + } } } if changed { display_game(&game); } - std::thread::sleep(Duration::from_millis(1)); + std::thread::sleep(std::time::Duration::from_millis(1)); } termios::tcsetattr(0, termios::TCSADRAIN, &termios_state_initial).unwrap(); } |