From 3603d42087fa9399fa7f3730a0d3c7c714d6dccc Mon Sep 17 00:00:00 2001 From: Rose Hogenson Date: Mon, 15 Jan 2024 09:36:15 -0800 Subject: Clean up and simplify the code. --- src/main.rs | 256 +++++++++++++++++++++++++++++++++++++++++++++--------------- src/rand.rs | 17 ---- src/sc.rs | 134 ------------------------------- 3 files changed, 193 insertions(+), 214 deletions(-) delete mode 100644 src/rand.rs delete mode 100644 src/sc.rs (limited to 'src') diff --git a/src/main.rs b/src/main.rs index 37adc6f..ffa6320 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,172 @@ -mod rand; -mod sc; - -use rand::Gen; -use sc::{Key, Screen}; +use libc::termios; use std::error::Error; -use std::io::{stderr, stdout, Write}; +use std::io::{stderr, stdout, Read, Write}; +use std::sync::mpsc::{Receiver, SyncSender, TryRecvError}; use std::time::{Duration, SystemTime}; +#[derive(Debug)] +struct Gen { + state: u64, +} + +impl Gen { + fn new() -> Gen { + Gen { + state: SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_nanos() as u64, + } + } + + fn rand(&mut self, max: i32) -> i32 { + // https://en.wikipedia.org/wiki/Linear_congruential_generator + self.state = self.state.wrapping_mul(6364136223846793005).wrapping_add(1); + (self.state >> 33 & 0x7fffffff) as i32 % max + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] struct Pos { row: u16, col: u16, } +fn pos(row: u16, col: u16) -> Pos { + Pos { row, col } +} + +// https://en.wikipedia.org/wiki/ANSI_escape_code +fn move_cursor(pos: Pos) -> Result<(), Box> { + write!(stdout(), "\x1b[{};{}H", pos.row + 1, pos.col + 1)?; + Ok(()) +} + +fn clear() -> Result<(), Box> { + write!(stdout(), "\x1b[2J")?; + Ok(()) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Dir { + Up, + Down, + Left, + Right, +} + +impl Dir { + fn opposite(self) -> Dir { + match self { + Dir::Up => Dir::Down, + Dir::Down => Dir::Up, + Dir::Left => Dir::Right, + Dir::Right => Dir::Left, + } + } +} + +struct Screen { + old_attr: termios, + keys: Receiver, + cancel: SyncSender<()>, +} + +impl Screen { + const ROWS: u16 = 25; + const COLS: u16 = 50; + + fn init() -> Result> { + // Get current terminal attrs. + let mut attr; + unsafe { + attr = std::mem::zeroed(); + if libc::tcgetattr(1, &mut attr) < 0 { + return Err(Box::from("tcgetattr failed")); + } + } + + // Put the terminal into raw mode. + unsafe { + let mut raw = std::mem::zeroed(); + libc::cfmakeraw(&mut raw); + raw.c_cc[libc::VMIN] = 0; + raw.c_cc[libc::VTIME] = 1; // 100ms timeout + if libc::tcsetattr(1, libc::TCSANOW, &raw) < 0 { + return Err(Box::from("tcsetattr failed")); + } + } + + let (key_tx, key_rx) = std::sync::mpsc::sync_channel(4); + let (cancel_tx, cancel_rx) = std::sync::mpsc::sync_channel(0); + std::thread::spawn(move || { + let mut stdin = std::io::stdin().lock(); + let mut buf = [0; 3]; + while let Err(TryRecvError::Empty) = cancel_rx.try_recv() { + let Ok(n) = stdin.read(&mut buf) else { + continue; + }; + if n < 3 || buf[0] != 27 || buf[1] != b'[' { + continue; + } + let key = match buf[2] { + b'A' => Dir::Up, + b'B' => Dir::Down, + b'C' => Dir::Right, + b'D' => Dir::Left, + _ => { + continue; + } + }; + let _ = key_tx.try_send(key); + } + }); + + clear()?; + move_cursor(pos(0, 0))?; + for _ in 0..Screen::COLS { + write!(stdout(), "-")?; + } + for i in 1..Screen::ROWS - 1 { + move_cursor(pos(i, 0))?; + write!(stdout(), "|")?; + move_cursor(pos(i, Screen::COLS - 1))?; + write!(stdout(), "|")?; + } + move_cursor(pos(Screen::ROWS - 1, 0))?; + for _ in 0..Screen::COLS { + write!(stdout(), "-")?; + } + + Ok(Screen { + old_attr: attr, + keys: key_rx, + cancel: cancel_tx, + }) + } + + fn read_key(&self) -> Option { + self.keys.try_recv().ok() + } +} + +impl Drop for Screen { + fn drop(&mut self) { + self.cancel.send(()).unwrap(); + unsafe { + libc::tcsetattr(1, libc::TCSANOW, &self.old_attr); + } + } +} + +fn in_bounds(pos: Pos) -> bool { + (1..Screen::ROWS - 1).contains(&pos.row) && (1..Screen::COLS - 1).contains(&pos.col) +} + #[derive(Debug)] struct Snake { body: Vec, - direction: Key, + direction: Dir, growing: i64, } @@ -24,38 +174,38 @@ impl Snake { fn next(&self) -> Pos { let mut pos = self.body[0]; match self.direction { - Key::Up => pos.row -= 1, - Key::Down => pos.row += 1, - Key::Left => pos.col -= 1, - Key::Right => pos.col += 1, + Dir::Up => pos.row -= 1, + Dir::Down => pos.row += 1, + Dir::Left => pos.col -= 1, + Dir::Right => pos.col += 1, } pos } - fn erase(&self, screen: &Screen) -> Result<(), Box> { + fn erase(&self) -> Result<(), Box> { let pos = self.next(); - if screen.in_bounds(pos.row, pos.col) { - sc::move_cursor(pos.row, pos.col)?; + if in_bounds(pos) { + move_cursor(pos)?; write!(stdout(), " ")?; } for &pos in self.body.iter() { - sc::move_cursor(pos.row, pos.col)?; + move_cursor(pos)?; write!(stdout(), " ")?; } Ok(()) } - fn print(&self, g: &mut Gen, screen: &Screen) -> Result<(), Box> { + fn print(&self, g: &mut Gen) -> Result<(), Box> { let next = self.next(); - if (self.direction == Key::Left || self.direction == Key::Right) - && screen.in_bounds(next.row, next.col) + if (self.direction == Dir::Left || self.direction == Dir::Right) + && in_bounds(next) && g.rand(40) == 0 { - sc::move_cursor(next.row, next.col)?; + move_cursor(next)?; write!(stdout(), "~")?; } for (i, &pos) in self.body.iter().enumerate() { - sc::move_cursor(pos.row, pos.col)?; + move_cursor(pos)?; if i == 0 { write!(stdout(), "O")?; continue; @@ -113,70 +263,52 @@ impl Snake { } } +#[derive(Debug)] struct Fruit { pos: Pos, } impl Fruit { fn print(&self) -> Result<(), Box> { - sc::move_cursor(self.pos.row, self.pos.col)?; + move_cursor(self.pos)?; write!(stdout(), "@")?; Ok(()) } - fn new_pos(&mut self, g: &mut Gen, screen: &Screen) { - self.pos.row = g.rand(i32::from(screen.rows) - 4) as u16 + 2; - self.pos.col = g.rand(i32::from(screen.cols) - 4) as u16 + 2; + fn new_pos(&mut self, g: &mut Gen) { + self.pos.row = g.rand(i32::from(Screen::ROWS) - 4) as u16 + 2; + self.pos.col = g.rand(i32::from(Screen::COLS) - 4) as u16 + 2; } } fn snake() -> Result> { let screen = Screen::init()?; - let mut g = rand::new( - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_nanos() as i64, - ); + let mut g = Gen::new(); let mut snake = Snake { - body: vec![Pos { - row: screen.rows / 2, - col: screen.cols / 2, - }], - direction: Key::Right, + body: vec![pos(Screen::ROWS / 2, Screen::COLS / 2)], + direction: Dir::Right, growing: 0, }; - let mut fruit = Fruit { - pos: Pos { row: 0, col: 0 }, - }; - fruit.new_pos(&mut g, &screen); + let mut fruit = Fruit { pos: pos(0, 0) }; + fruit.new_pos(&mut g); let mut score: i64 = 0; let mut real_score: i64 = 0; let mut delay = Duration::from_millis(200); loop { - sc::move_cursor(0, 3)?; + move_cursor(pos(0, 3))?; write!(stdout(), " Score: {} ", score)?; - snake.print(&mut g, &screen)?; + snake.print(&mut g)?; fruit.print()?; - sc::move_cursor(screen.rows - 1, screen.cols - 1)?; + move_cursor(pos(Screen::ROWS - 1, Screen::COLS - 1))?; stdout().flush()?; - let mut d = delay; - if snake.direction == Key::Up || snake.direction == Key::Down { - d = d * 5 / 4; - } - std::thread::sleep(d); - - snake.erase(&screen)?; - if let Some(key) = screen.read_key() { - if !(snake.direction == Key::Up && key == Key::Down - || snake.direction == Key::Down && key == Key::Up - || snake.direction == Key::Left && key == Key::Right - || snake.direction == Key::Right && key == Key::Left) - || snake.body.len() == 1 - { - snake.direction = key; + std::thread::sleep(delay); + + snake.erase()?; + if let Some(dir) = screen.read_key() { + if dir != snake.direction.opposite() || snake.body.len() == 1 { + snake.direction = dir; } }; snake.step(); @@ -184,7 +316,7 @@ fn snake() -> Result> { if snake.body[0] == fruit.pos { snake.growing += 6; loop { - fruit.new_pos(&mut g, &screen); + fruit.new_pos(&mut g); if !snake.body.contains(&fruit.pos) { break; } @@ -194,9 +326,7 @@ fn snake() -> Result> { real_score += 1; delay = delay * 14 / 15; } - if !screen.in_bounds(snake.body[0].row, snake.body[0].col) - || snake.body[1..].contains(&snake.body[0]) - { + if !in_bounds(snake.body[0]) || snake.body[1..].contains(&snake.body[0]) { return Ok(score); } } @@ -210,7 +340,7 @@ fn main() { std::process::exit(1); } }; - let _ = sc::clear(); - let _ = sc::move_cursor(0, 0); + let _ = clear(); + let _ = move_cursor(pos(0, 0)); let _ = writeln!(stdout(), "Score: {}", score); } diff --git a/src/rand.rs b/src/rand.rs deleted file mode 100644 index f9aee46..0000000 --- a/src/rand.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub struct Gen { - state: i64, -} - - -pub fn new(seed: i64) -> Gen { - Gen{ - state: seed, - } -} - -impl Gen { - pub fn rand(&mut self, max: i32) -> i32 { - self.state = self.state.wrapping_mul(6364136223846793005).wrapping_add(1); - (self.state >> 33 & 0x7fffffff) as i32 % max - } -} diff --git a/src/sc.rs b/src/sc.rs deleted file mode 100644 index cb20c68..0000000 --- a/src/sc.rs +++ /dev/null @@ -1,134 +0,0 @@ -use libc::{termios, winsize}; -use std::error::Error; -use std::io::{stdin, stdout, Read, Write}; -use std::sync::mpsc::{Receiver, SyncSender, TryRecvError}; - -pub fn move_cursor(row: u16, col: u16) -> Result<(), Box> { - write!(stdout(), "\x1b[{};{}H", row + 1, col + 1)?; - Ok(()) -} - -pub fn clear() -> Result<(), Box> { - write!(stdout(), "\x1b[2J")?; - Ok(()) -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Key { - Up, - Down, - Left, - Right, -} - -pub struct Screen { - pub rows: u16, - pub cols: u16, - old_attr: termios, - cancel: SyncSender<()>, - keys: Receiver, -} - -impl Screen { - pub fn init() -> Result> { - let mut size: winsize; - unsafe { - size = std::mem::zeroed(); - if libc::ioctl(1, libc::TIOCGWINSZ, &mut size) < 0 { - return Err(Box::from("ioctl failed")); - } - } - if size.ws_col > 60 { - size.ws_col = 60; - } - if size.ws_row > 30 { - size.ws_row = 30; - } - - let mut attr; - unsafe { - attr = std::mem::zeroed(); - if libc::tcgetattr(1, &mut attr) < 0 { - return Err(Box::from("tcgetattr failed")); - } - } - - unsafe { - let mut raw = std::mem::zeroed(); - libc::cfmakeraw(&mut raw); - raw.c_cc[libc::VMIN] = 0; - raw.c_cc[libc::VTIME] = 1; - if libc::tcsetattr(1, libc::TCSANOW, &raw) < 0 { - return Err(Box::from("tcsetattr failed")); - } - } - - clear()?; - move_cursor(0, 0)?; - for _ in 0..size.ws_col { - write!(stdout(), "-")?; - } - for i in 1..size.ws_row - 1 { - move_cursor(i, 0)?; - write!(stdout(), "|")?; - move_cursor(i, size.ws_col - 1)?; - write!(stdout(), "|")?; - } - move_cursor(size.ws_row - 1, 0)?; - for _ in 0..size.ws_col { - write!(stdout(), "-")?; - } - - let (cancel_tx, cancel_rx) = std::sync::mpsc::sync_channel(0); - let (key_tx, key_rx) = std::sync::mpsc::sync_channel(4); - std::thread::spawn(move || { - let mut buf = [0; 3]; - while let Err(TryRecvError::Empty) = cancel_rx.try_recv() { - let Ok(n) = stdin().read(&mut buf) else { - continue; - }; - if n < 3 || buf[0] != 27 || buf[1] != b'[' { - continue; - } - let key = match buf[2] { - b'A' => Key::Up, - b'B' => Key::Down, - b'C' => Key::Right, - b'D' => Key::Left, - _ => { - continue; - } - }; - let _ = key_tx.try_send(key); - } - }); - - Ok(Screen { - rows: size.ws_row, - cols: size.ws_col, - old_attr: attr, - cancel: cancel_tx, - keys: key_rx, - }) - } - - pub fn read_key(&self) -> Option { - if let Ok(key) = self.keys.try_recv() { - return Some(key); - } - None - } - - pub fn in_bounds(&self, row: u16, col: u16) -> bool { - (1..self.rows - 1).contains(&row) && (1..self.cols - 1).contains(&col) - } -} - -impl Drop for Screen { - fn drop(&mut self) { - self.cancel.send(()).unwrap(); - unsafe { - libc::tcsetattr(1, libc::TCSANOW, &self.old_attr); - } - } -} -- cgit v1.3.1