summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2024-01-15 09:36:15 -0800
committerRose Hogenson <rosehogenson@posteo.net>2024-01-15 09:36:15 -0800
commit3603d42087fa9399fa7f3730a0d3c7c714d6dccc (patch)
treeda6ea53261f52ba754a21c3b5e2590ca633207fb /src
parent680fdffb350b2409ac1709774d3cd81144d808a4 (diff)
downloadsnake-3603d42087fa9399fa7f3730a0d3c7c714d6dccc.tar.zst
Clean up and simplify the code.
Diffstat (limited to 'src')
-rw-r--r--src/main.rs254
-rw-r--r--src/rand.rs17
-rw-r--r--src/sc.rs134
3 files changed, 192 insertions, 213 deletions
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<dyn Error>> {
+ write!(stdout(), "\x1b[{};{}H", pos.row + 1, pos.col + 1)?;
+ Ok(())
+}
+
+fn clear() -> Result<(), Box<dyn Error>> {
+ 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<Dir>,
+ cancel: SyncSender<()>,
+}
+
+impl Screen {
+ const ROWS: u16 = 25;
+ const COLS: u16 = 50;
+
+ fn init() -> Result<Screen, Box<dyn Error>> {
+ // 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<Dir> {
+ 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<Pos>,
- 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<dyn Error>> {
+ fn erase(&self) -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
+ fn print(&self, g: &mut Gen) -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
- 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<i64, Box<dyn Error>> {
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);
+ std::thread::sleep(delay);
- 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;
+ 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<i64, Box<dyn Error>> {
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<i64, Box<dyn Error>> {
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<dyn Error>> {
- write!(stdout(), "\x1b[{};{}H", row + 1, col + 1)?;
- Ok(())
-}
-
-pub fn clear() -> Result<(), Box<dyn Error>> {
- 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<Key>,
-}
-
-impl Screen {
- pub fn init() -> Result<Screen, Box<dyn Error>> {
- 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<Key> {
- 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);
- }
- }
-}