extern crate termios; use rand::Rng; use nonblock::NonBlockingReader; 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: [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, } #[derive(Clone)] #[derive(Debug)] enum FigureKind { I, L2, L, T, B, S, 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: [u8; 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: [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]; } } self.cells = tmp; } fn rotate_cw_3x3(&mut self) { 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]; } } 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: [u8; 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 } 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 { field: Field, figure: Figure, x: isize, y: isize, fall_timeout: Timeout, place_timeout: Option, } impl Default for Game { #[inline] fn default() -> Self { Self{ field: Field::default(), figure: Figure::kind_random(), x: 3, y: 17, fall_timeout: Timeout::from_duration(std::time::Duration::from_millis(500)), place_timeout: None, } } } impl Game { fn place_figure(&mut self) { self.field.render(&self.figure, self.x, self.y); self.figure = Figure::kind_random(); (self.x, self.y) = (3, 17); 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 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(); } } fn handle_input(&mut self, c: char) -> Option { match c { 'q' => return Some(StepResult::Quit), ' ' => { 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) { 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' => { let mut figure = self.figure.clone(); figure.rotate(); if !self.field.has_collision(&figure, self.x, self.y) { self.figure.rotate() } } '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 } } _ => {}, } 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) -> Option { 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(); for y in 0..FIELD_ROWS_VISIBLE { print!("|"); 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 noblock_stdin = NonBlockingReader::from_fd(std::io::stdin()).unwrap(); 'outer: while !noblock_stdin.is_eof() { let mut buf = String::new(); noblock_stdin.read_available_to_string(&mut buf).unwrap(); let mut changed = false; 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(std::time::Duration::from_millis(1)); } termios::tcsetattr(0, termios::TCSADRAIN, &termios_state_initial).unwrap(); }