diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7395ade --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "snake-rs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +crossterm = "0.26.1" +rand = "0.8.5" diff --git a/README.md b/README.md index 4829892..28109f4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ # snake-rs -Classic snake game with terminal graphics written in rust. \ No newline at end of file +Classic snake game with terminal graphics written in rust. + +This is my first project in rust so the code is probably great. + +Play the game: +``` +cargo run +``` diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1128800 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,195 @@ +use crossterm::{*, cursor::*, event::*, style::*, terminal::*}; +use std::io::{stdout, Stdout, Write}; +use std::time::Duration; +use rand::{Rng, thread_rng}; + +#[derive(Copy, Clone)] +struct Point { + x: u16, + y: u16 +} + +#[derive(Copy, Clone, std::fmt::Debug, PartialEq, Eq)] +enum Direction { + Up, + Down, + Left, + Right +} + +fn init_screen() -> Result { + let mut stdout: Stdout = stdout(); + + enable_raw_mode()?; + stdout.queue(Clear(ClearType::All))?; + stdout.queue(Hide)?; + stdout.queue(MoveTo(0, 0))?; + + stdout.flush()?; + + Ok(stdout) +} + +fn cleanup(stdout: &mut Stdout) -> Result<()> { + stdout.queue(Clear(ClearType::All))?; + stdout.queue(Show)?; + stdout.queue(MoveTo(0, 0))?; + disable_raw_mode()?; + + stdout.flush()?; + + Ok(()) +} + +fn move_snake(snake: &mut Vec, direction: Direction, cols: u16, rows: u16) { + for i in (0..(*snake).len()).rev() { + if i == 0 { + match direction { + Direction::Up => { + if snake[0].y == 0 { + snake[0].y = rows-1; + } else { + snake[0].y -= 1; + } + }, + Direction::Down => { + if snake[0].y == rows-1 { + snake[0].y = 0; + } else { + snake[0].y += 1; + } + }, + Direction::Left => { + if snake[0].x == 0 { + snake[0].x = cols-1; + } else { + snake[0].x -= 1; + } + }, + Direction::Right => { + if snake[0].x == cols-1 { + snake[0].x = 0; + } else { + snake[0].x += 1; + } + } + } + } else { + snake[i] = snake[i-1]; + } + } + +} + +fn print_snake(snake: &Vec, screen: &mut Stdout) -> Result<()> { + for seg in (*snake).iter() { + (*screen).queue(MoveTo(seg.x, seg.y))?; + (*screen).queue(Print("#".to_string()))?; + } + Ok(()) +} + +fn main() -> Result<()> { + let mut screen = init_screen()?; + let (cols, rows) = size()?; + + let mut snake: Vec = Vec::new(); + let mut apples: Vec = Vec::new(); + + let inital_length = 5; + let num_apples = 10; + let mut head_x = cols/2 - inital_length/2; + let mut head_y = rows/2; + let mut direction: Direction = Direction::Left; + let mut score = 0; + let mut lost = false; + + screen.queue(MoveTo(head_x, head_y))?; + + // create snake + for i in 0..inital_length { + snake.push(Point { x: head_x + i, y: head_y }); + screen.queue(Print("#".to_string()))?; + } + + // create apples + for i in 0..num_apples { + apples.push(Point { x: thread_rng().gen_range(0..cols), y: thread_rng().gen_range(0..rows-1)}); + screen.queue(MoveTo(apples[i].x, apples[i].y))?; + screen.queue(Print("*".to_string()))?; + } + + 'main: loop { + screen.queue(MoveTo(head_x, head_y))?; + + match direction { + Direction::Up => {move_snake(&mut snake, direction, cols, rows);}, + Direction::Down => {move_snake(&mut snake, direction, cols, rows);}, + Direction::Left => {move_snake(&mut snake, direction, cols, rows);}, + Direction::Right => {move_snake(&mut snake, direction, cols, rows);} + } + + head_x = snake[0].x; + head_y = snake[0].y; + + for i in 1..snake.len() { + if head_x == snake[i].x && head_y == snake[i].y { + lost = true; + break 'main; + } + } + + + for i in 0..num_apples { + if head_x == apples[i].x && head_y == apples[i].y { + score += 1; + apples[i] = Point { x: thread_rng().gen_range(0..cols), y: thread_rng().gen_range(0..rows)}; + snake.push(Point { x: 0, y: 0 }); + } + screen.queue(MoveTo(apples[i].x, apples[i].y))?; + screen.queue(Print("*".to_string()))?; + } + + print_snake(&snake, &mut screen)?; + + screen.queue(MoveTo(0, rows))?; + + screen.queue(Print(format!("SCORE: {} | to quit | arrow keys to move", score)))?; + + screen.flush()?; + screen.queue(Clear(ClearType::All))?; + + if poll(Duration::from_millis(150))? { + match read()? { + Event::Key(e) => match e.code { + KeyCode::Char('q') => break 'main, + KeyCode::Up => direction = + if direction != Direction::Down { Direction::Up } + else { direction }, + KeyCode::Down => direction = + if direction != Direction::Up { Direction::Down } + else { direction }, + KeyCode::Left => direction = + if direction != Direction::Right { Direction::Left } + else { direction }, + KeyCode::Right => direction = + if direction != Direction::Left { Direction::Right } + else { direction }, + _ => () + }, + _ => () + } + } + } + + cleanup(&mut screen)?; + + if lost { + println!("You loose. Final Score: {}", score); + } else { + println!("Goodbye! Final Score: {}", score); + } + + Ok(()) + +} diff --git a/src/snake.rs b/src/snake.rs new file mode 100644 index 0000000..e69de29