summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock16
-rw-r--r--Cargo.toml9
-rw-r--r--src/main.rs216
-rw-r--r--src/rand.rs17
-rw-r--r--src/sc.rs134
6 files changed, 393 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..6a2206f
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,16 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "libc"
+version = "0.2.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
+
+[[package]]
+name = "snake"
+version = "1.0.0"
+dependencies = [
+ "libc",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..0cc9539
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "snake"
+version = "1.0.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+libc = "0.2"
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..37adc6f
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,216 @@
+mod rand;
+mod sc;
+
+use rand::Gen;
+use sc::{Key, Screen};
+use std::error::Error;
+use std::io::{stderr, stdout, Write};
+use std::time::{Duration, SystemTime};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+struct Pos {
+ row: u16,
+ col: u16,
+}
+
+#[derive(Debug)]
+struct Snake {
+ body: Vec<Pos>,
+ direction: Key,
+ growing: i64,
+}
+
+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,
+ }
+ pos
+ }
+
+ fn erase(&self, screen: &Screen) -> Result<(), Box<dyn Error>> {
+ let pos = self.next();
+ if screen.in_bounds(pos.row, pos.col) {
+ sc::move_cursor(pos.row, pos.col)?;
+ write!(stdout(), " ")?;
+ }
+ for &pos in self.body.iter() {
+ sc::move_cursor(pos.row, pos.col)?;
+ write!(stdout(), " ")?;
+ }
+ Ok(())
+ }
+
+ fn print(&self, g: &mut Gen, screen: &Screen) -> Result<(), Box<dyn Error>> {
+ let next = self.next();
+ if (self.direction == Key::Left || self.direction == Key::Right)
+ && screen.in_bounds(next.row, next.col)
+ && g.rand(40) == 0
+ {
+ sc::move_cursor(next.row, next.col)?;
+ write!(stdout(), "~")?;
+ }
+ for (i, &pos) in self.body.iter().enumerate() {
+ sc::move_cursor(pos.row, pos.col)?;
+ if i == 0 {
+ write!(stdout(), "O")?;
+ continue;
+ }
+ let prev = self.body[i - 1];
+ if i == self.body.len() - 1 {
+ if prev.row < pos.row {
+ write!(stdout(), "v")?;
+ continue;
+ }
+ if prev.row > pos.row {
+ write!(stdout(), "^")?;
+ continue;
+ }
+ if prev.col < pos.col {
+ write!(stdout(), ">")?;
+ continue;
+ }
+ write!(stdout(), "<")?;
+ continue;
+ }
+ let next = self.body[i + 1];
+ if prev.col == next.col {
+ write!(stdout(), "|")?;
+ continue;
+ }
+ if prev.row == next.row {
+ write!(stdout(), "=")?;
+ continue;
+ }
+ let (a, b) = if prev.row == pos.row {
+ (prev, next)
+ } else {
+ (next, prev)
+ };
+ if (a.col < pos.col) == (b.row < pos.row) {
+ write!(stdout(), "/")?;
+ continue;
+ }
+ write!(stdout(), "\\")?;
+ }
+ Ok(())
+ }
+
+ fn step(&mut self) {
+ let pos = self.next();
+ if self.growing > 0 {
+ self.body.push(self.body[self.body.len() - 1]);
+ self.growing -= 1;
+ }
+ for i in (1..self.body.len()).rev() {
+ self.body[i] = self.body[i - 1];
+ }
+ self.body[0] = pos;
+ }
+}
+
+struct Fruit {
+ pos: Pos,
+}
+
+impl Fruit {
+ fn print(&self) -> Result<(), Box<dyn Error>> {
+ sc::move_cursor(self.pos.row, self.pos.col)?;
+ 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 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 snake = Snake {
+ body: vec![Pos {
+ row: screen.rows / 2,
+ col: screen.cols / 2,
+ }],
+ direction: Key::Right,
+ growing: 0,
+ };
+ let mut fruit = Fruit {
+ pos: Pos { row: 0, col: 0 },
+ };
+ fruit.new_pos(&mut g, &screen);
+ let mut score: i64 = 0;
+ let mut real_score: i64 = 0;
+ let mut delay = Duration::from_millis(200);
+
+ loop {
+ sc::move_cursor(0, 3)?;
+ write!(stdout(), " Score: {} ", score)?;
+ snake.print(&mut g, &screen)?;
+ fruit.print()?;
+
+ sc::move_cursor(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;
+ }
+ };
+ snake.step();
+
+ if snake.body[0] == fruit.pos {
+ snake.growing += 6;
+ loop {
+ fruit.new_pos(&mut g, &screen);
+ if !snake.body.contains(&fruit.pos) {
+ break;
+ }
+ }
+ let h = real_score / 2;
+ score += h * h * h * h * h - h + 1;
+ 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])
+ {
+ return Ok(score);
+ }
+ }
+}
+
+fn main() {
+ let score = match snake() {
+ Ok(s) => s,
+ Err(err) => {
+ let _ = writeln!(stderr(), "FAIL: {}", err);
+ std::process::exit(1);
+ }
+ };
+ let _ = sc::clear();
+ let _ = sc::move_cursor(0, 0);
+ let _ = writeln!(stdout(), "Score: {}", score);
+}
diff --git a/src/rand.rs b/src/rand.rs
new file mode 100644
index 0000000..f9aee46
--- /dev/null
+++ b/src/rand.rs
@@ -0,0 +1,17 @@
+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
new file mode 100644
index 0000000..cb20c68
--- /dev/null
+++ b/src/sc.rs
@@ -0,0 +1,134 @@
+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);
+ }
+ }
+}