summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock48
-rw-r--r--Cargo.toml7
-rw-r--r--src/main.rs196
3 files changed, 201 insertions, 50 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0d52afd..190e987 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 624495f..11fd4dd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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();
}