summaryrefslogtreecommitdiffstats
path: root/src/main.rs
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2024-01-14 16:01:32 -0800
committerRose Hogenson <rosehogenson@posteo.net>2024-01-14 16:01:32 -0800
commit680fdffb350b2409ac1709774d3cd81144d808a4 (patch)
treeea9b7c2a321a89a2919bbbf1d7012a9e97f73c58 /src/main.rs
downloadsnake-680fdffb350b2409ac1709774d3cd81144d808a4.tar.zst
Write a simple snake game.
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs216
1 files changed, 216 insertions, 0 deletions
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);
+}