From 680fdffb350b2409ac1709774d3cd81144d808a4 Mon Sep 17 00:00:00 2001 From: Rose Hogenson Date: Sun, 14 Jan 2024 16:01:32 -0800 Subject: Write a simple snake game. --- src/main.rs | 216 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 src/main.rs (limited to 'src/main.rs') 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, + 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> { + 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> { + 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> { + 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> { + 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); +} -- cgit v1.3.1