From 25f568a74d9445957f205c13009ca976f971161c Mon Sep 17 00:00:00 2001 From: Vegard Matthey Date: Wed, 3 Apr 2024 01:52:34 +0200 Subject: [PATCH] inital --- .gitignore | 2 + Cargo.toml | 8 ++ src/main.rs | 233 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9993771 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "term_snake" +version = "0.1.0" +edition = "2021" + +[dependencies] +crossterm = "0.27.0" +rand = "0.8.5" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..3b95c10 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,233 @@ +use crossterm::{ + event::{poll, read, Event, KeyCode}, + terminal::{disable_raw_mode, enable_raw_mode}, +}; +use rand::prelude::*; +use std::collections::VecDeque; +use std::time::{Duration, Instant}; + +const WIDTH: usize = 20; +const HEIGHT: usize = 20; +const FRAME_TIME: Duration = Duration::from_millis(100); +const SNAKE_COLOR: Color = Color { r: 0, g: 255, b: 0 }; +const APPLE_COLOR: Color = Color { r: 255, g: 0, b: 0 }; + +#[derive(PartialEq)] +enum Direction { + Up, + Down, + Left, + Right, +} + +enum CrashLocation { + Snake, + North, + South, + East, + West, +} + +fn main() { + let mut snake: VecDeque<(usize, usize)> = vec![(0, 0), (1, 0), (2, 0)].into(); + let mut direction = Direction::Right; + let mut length = snake.len(); + let mut rng = thread_rng(); + let mut apple = (rng.gen_range(0..WIDTH), rng.gen_range(0..WIDTH)); + + loop { + let now = Instant::now(); + + render(&snake, apple, length); + enable_raw_mode().unwrap(); + change_dir(&mut direction); + disable_raw_mode().unwrap(); + if let Some(crash) = move_snake(&mut snake, &direction, length) { + match crash { + CrashLocation::North => println!("You crashed in the North wall!"), + CrashLocation::South => println!("You crashed in the South wall!"), + CrashLocation::East => println!("You crashed in the East wall!"), + CrashLocation::West => println!("You crashed in the West wall!"), + CrashLocation::Snake => println!("You crashed in yourself!"), + } + break; + } + eat(&snake, &mut apple, &mut length, &mut rng); + + while now.elapsed() < FRAME_TIME {} + } +} + +fn eat( + snake: &VecDeque<(usize, usize)>, + apple: &mut (usize, usize), + length: &mut usize, + rng: &mut ThreadRng, +) { + if snake.contains(&apple) { + *apple = (rng.gen_range(0..WIDTH), rng.gen_range(0..WIDTH)); + *length += 1; + } +} + +fn change_dir(direction: &mut Direction) { + if let Some(k) = key_input(FRAME_TIME) { + match k { + 'd' => { + if *direction != Direction::Left { + *direction = Direction::Right; + } + } + 'a' => { + if *direction != Direction::Right { + *direction = Direction::Left; + } + } + 'w' => { + if *direction != Direction::Down { + *direction = Direction::Up; + } + } + 's' => { + if *direction != Direction::Up { + *direction = Direction::Down; + } + } + 'q' => { + disable_raw_mode().unwrap(); + std::process::exit(0); + } + _ => (), + } + } +} + +fn key_input(timeout: Duration) -> Option { + if poll(timeout).ok()? { + if let Event::Key(k) = read().ok()? { + if let KeyCode::Char(c) = k.code { + return Some(c); + } + } + } + None +} + +fn move_snake( + snake: &mut VecDeque<(usize, usize)>, + direction: &Direction, + length: usize, +) -> Option { + if length == snake.len() { + snake.pop_front(); + } + let mut a = *snake.iter().last().unwrap(); + + match direction { + Direction::Right => { + if a.0 + 1 < WIDTH { + a.0 += 1 + } else { + return Some(CrashLocation::East); + } + } + Direction::Left => { + if a.0 > 0 { + a.0 -= 1 + } else { + return Some(CrashLocation::West); + } + } + Direction::Up => { + if a.1 + 1 < WIDTH { + a.1 += 1 + } else { + return Some(CrashLocation::North); + } + } + Direction::Down => { + if a.1 > 0 { + a.1 -= 1 + } else { + return Some(CrashLocation::South); + } + } + } + + if snake.contains(&a) { + return Some(CrashLocation::Snake); + } + + snake.push_back(a); + None +} + +fn render(snake: &VecDeque<(usize, usize)>, apple: (usize, usize), length: usize) { + let mut s = String::new(); + s.push_str("\u{001b}c"); + s.push_str(&format!("length: {length}\n")); + s.push('┏'); + s.push_str(&"━".repeat(WIDTH * 2)); + s.push_str("┓\n"); + for y in (0..HEIGHT).rev() { + s.push('┃'); + for x in 0..WIDTH { + if snake.contains(&(x, y)) { + s.push_str(&" ".bg(SNAKE_COLOR)); + } else if (x, y) == apple { + s.push_str(&" ".bg(APPLE_COLOR)); + } else { + s.push_str(" "); + } + } + s.push_str("┃\n"); + } + s.push('┗'); + s.push_str(&"━".repeat(WIDTH * 2)); + s.push('┛'); + println!("{s}"); +} + +struct Color { + r: u8, + g: u8, + b: u8, +} + +trait Colorize { + fn bg(&self, color: Color) -> String; +} + +impl Colorize for T { + fn bg(&self, color: Color) -> String { + format!( + "\x1b[48;2;{};{};{}m{self}\x1b[0m", + color.r, color.g, color.b + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn move_test() { + let mut snake: VecDeque<(usize, usize)> = vec![(16, 16), (17, 16), (18, 16)].into(); + let mut direction = Direction::Up; + let length = snake.len(); + move_snake(&mut snake, &direction, length); + assert_eq!(snake, vec![(17, 16), (18, 16), (18, 17)]); + + direction = Direction::Right; + move_snake(&mut snake, &direction, length); + assert_eq!(snake, vec![(18, 16), (18, 17), (19, 17)]); + + direction = Direction::Down; + move_snake(&mut snake, &direction, length); + assert_eq!(snake, vec![(18, 17), (19, 17), (19, 16)]); + + direction = Direction::Left; + move_snake(&mut snake, &direction, length); + assert_eq!(snake, vec![(19, 17), (19, 16), (18, 16)]); + } +}