diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Cargo.lock | 85 | ||||
-rw-r--r-- | Cargo.toml | 10 | ||||
-rw-r--r-- | src/main.rs | 277 |
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(); +} |