summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock85
-rw-r--r--Cargo.toml10
-rw-r--r--src/main.rs277
4 files changed, 373 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..0eafa82
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,85 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "getrandom"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "puzzle"
+version = "0.1.0"
+dependencies = [
+ "rand",
+ "termios",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "termios"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..3be4420
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "puzzle"
+version = "0.1.0"
+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"
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..245c2b1
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,277 @@
+extern crate termios;
+
+use rand::Rng;
+use std::io::Read;
+
+const FIELD_COLS: usize = 10;
+const FIELD_ROWS_VISIBLE: usize = 20;
+const FIELD_ROWS: usize = FIELD_ROWS_VISIBLE + 20;
+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];
+
+#[derive(Clone)]
+#[derive(Debug)]
+enum FigureKind {
+ I,
+ L2,
+ L,
+ T,
+ B,
+ S,
+ Z,
+}
+
+#[derive(Clone)]
+struct Figure {
+ kind: FigureKind,
+ cells: [i8; FIGURE_SIZE],
+}
+
+impl Figure {
+ fn kind_random() -> Self {
+ return match rand::thread_rng().gen_range(0..7) {
+ 0 => Self::kind_i(),
+ 1 => Self::kind_l2(),
+ 2 => Self::kind_l(),
+ 3 => Self::kind_b(),
+ 4 => Self::kind_s(),
+ 5 => Self::kind_t(),
+ 6 => Self::kind_z(),
+ _ => unreachable!(),
+ };
+ }
+ const fn kind_i() -> Self { Self{ cells: CELLS_I, kind: FigureKind::I } }
+ const fn kind_l2() -> Self { Self{ cells: CELLS_L2, kind: FigureKind::L2 } }
+ const fn kind_l() -> Self { Self{ cells: CELLS_L, kind: FigureKind::L } }
+ const fn kind_b() -> Self { Self{ cells: CELLS_B, kind: FigureKind::B } }
+ const fn kind_s() -> Self { Self{ cells: CELLS_S, kind: FigureKind::S } }
+ 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();
+ for y in 0..4 {
+ for x in 0..4 {
+ tmp[y + (3 - x) * 4] = self.cells[x + y * 4];
+ }
+ }
+ self.cells = tmp;
+ }
+ fn rotate_cw_3x3(&mut self) {
+ let mut tmp: [i8; 3 * 3] = Default::default();
+ for y in 0..3 {
+ for x in 0..3 {
+ tmp[y + (2 - x) * 3] = self.cells[x + y * 4];
+ }
+ }
+ for y in 0..3 {
+ for x in 0..3 {
+ self.cells[x + y * 4] = tmp[x + y * 3];
+ }
+ }
+ }
+ fn rotate(&mut self) {
+ match self.kind {
+ FigureKind::I => self.rotate_cw_4x4(),
+ FigureKind::L2 => self.rotate_cw_3x3(),
+ FigureKind::L => self.rotate_cw_3x3(),
+ FigureKind::B => {},
+ FigureKind::S => self.rotate_cw_3x3(),
+ FigureKind::T => self.rotate_cw_3x3(),
+ FigureKind::Z => self.rotate_cw_3x3(),
+ }
+ }
+}
+
+#[derive(Clone)]
+struct Field {
+ cells: [i8; FIELD_SIZE],
+}
+
+impl Default for Field {
+ #[inline]
+ fn default() -> Self {
+ Self { cells: [0; FIELD_SIZE] }
+ }
+}
+
+impl Field {
+ fn render(&mut self, figure: &Figure, x: isize, y: isize) {
+ for i in 0..FIGURE_SIZE {
+ let f_x = (i % FIGURE_SIZE_COLS) as isize + x;
+ let f_y = (i / FIGURE_SIZE_ROWS) as isize + y;
+ if f_x < 0 {
+ continue;
+ } else if f_x >= FIELD_COLS as isize {
+ continue;
+ } else if f_y < 0 {
+ continue;
+ } else if f_y >= FIELD_ROWS as isize {
+ continue;
+ }
+ if figure.cells[i] != 0 {
+ self.cells[f_x as usize + f_y as usize * FIELD_COLS] = figure.cells[i];
+ }
+ }
+ }
+ fn has_collision(&self, figure: &Figure, x: isize, y: isize) -> bool {
+ for i in 0..FIGURE_SIZE {
+ let f_x = (i % FIGURE_SIZE_COLS) as isize + x;
+ let f_y = (i / FIGURE_SIZE_ROWS) as isize + y;
+ if figure.cells[i] != 0 {
+ if f_x < 0 {
+ return true;
+ } else if f_x >= FIELD_COLS as isize {
+ return true;
+ } else if f_y < 0 {
+ return true;
+ } else if f_y >= FIELD_ROWS as isize {
+ return true;
+ } else if self.cells[f_x as usize + f_y as usize * FIELD_COLS] != 0 {
+ return true;
+ }
+ }
+ }
+ false
+ }
+}
+
+struct Game {
+ field: Field,
+ figure: Figure,
+ x: isize,
+ y: isize,
+}
+
+impl Default for Game {
+ #[inline]
+ fn default() -> Self {
+ Self{ field: Field::default(), figure: Figure::kind_random(), x: 0, y: 0 }
+ }
+}
+
+impl Game {
+ fn place(&mut self) {
+ self.field.render(&self.figure, self.x, self.y);
+ self.figure = Figure::kind_random();
+ self.x = 0;
+ self.y = 0;
+ }
+ fn step(&mut self, fd: &mut std::io::Stdin) -> Option<()> {
+ let mut r: [u8;1]=[0];
+ fd.read(&mut r[..]).unwrap();
+ match r[0] as char {
+ 'q' => return None,
+ ' ' => {
+ if !self.field.has_collision(&self.figure, self.x, self.y) {
+ self.place()
+ }
+ },
+ 'h' => {
+ if self.field.has_collision(&self.figure, self.x, self.y) {
+ self.x -= 1
+ } else if !self.field.has_collision(&self.figure, self.x - 1, self.y) {
+ self.x -= 1
+ }
+ }
+ 'j' => {
+ 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' => {
+ 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
+ }
+ }
+ 'l' => {
+ if self.field.has_collision(&self.figure, self.x, self.y) {
+ self.x += 1
+ } else if !self.field.has_collision(&self.figure, self.x + 1, self.y) {
+ self.x += 1
+ }
+ }
+ ';' => {
+ let mut figure = self.figure.clone();
+ figure.rotate();
+ if !self.field.has_collision(&figure, self.x, self.y) {
+ self.figure.rotate()
+ }
+ }
+ _ => {},
+ }
+ Some(())
+ }
+}
+
+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!("██");
+ } else {
+ print!(" ");
+ }
+ }
+ print!("\r\x1b[1B");
+ }
+}
+
+fn display_game(game: &Game) {
+ cursor_to_home();
+ let mut field = game.field.clone();
+ field.render(&game.figure, game.x, game.y);
+ print_field(&field);
+ if game.field.has_collision(&game.figure, game.x, game.y) {
+ println!("Collision!");
+ } else {
+ clear_line();
+ }
+}
+
+fn clear_screen() {
+ print!("\x1B[2J");
+}
+
+fn cursor_to_home() {
+ print!("\x1B[H");
+}
+
+fn clear_line() {
+ println!("\x1B[2K");
+}
+
+fn main() {
+ let termios_state_initial = termios::Termios::from_fd(0).unwrap();
+ let mut termios_state = termios_state_initial.clone();
+ let c_lflag = termios_state.c_lflag;
+ termios_state.c_lflag &= !(termios::ICANON | termios::ECHO);
+ termios::tcsetattr(0, termios::TCSADRAIN, &termios_state).unwrap();
+ termios_state.c_lflag = c_lflag;
+ clear_screen();
+ let mut game = Game::default();
+ display_game(&game);
+ let mut stdin = std::io::stdin();
+ loop {
+ if let None = game.step(&mut stdin) {
+ break;
+ }
+ display_game(&game);
+ }
+ termios::tcsetattr(0, termios::TCSADRAIN, &termios_state_initial).unwrap();
+}