This commit is contained in:
2024-04-03 01:52:34 +02:00
commit 25f568a74d
3 changed files with 243 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
Cargo.lock

8
Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "term_snake"
version = "0.1.0"
edition = "2021"
[dependencies]
crossterm = "0.27.0"
rand = "0.8.5"

233
src/main.rs Normal file
View File

@@ -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<char> {
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<CrashLocation> {
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<T: std::fmt::Display> 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)]);
}
}