main
korin 3 years ago
parent 3b72b7f45c
commit 7f17c14012
  1. 2
      .gitignore
  2. 162
      Cargo.lock
  3. 13
      Cargo.toml
  4. 16
      LICENSE
  5. 14
      README
  6. 3
      README.md
  7. 3
      run
  8. 404
      src/main.rs

2
.gitignore vendored

@ -0,0 +1,2 @@
/target
high_score

162
Cargo.lock generated

@ -0,0 +1,162 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "libc"
version = "0.2.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "ncurses"
version = "5.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e2c5d34d72657dc4b638a1c25d40aae81e4f1c699062f72f467237920752032"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "pancurses"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0352975c36cbacb9ee99bfb709b9db818bed43af57751797f8633649759d13db"
dependencies = [
"libc",
"log",
"ncurses",
"pdcurses-sys",
"winreg",
]
[[package]]
name = "pdcurses-sys"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "084dd22796ff60f1225d4eb6329f33afaf4c85419d51d440ab6b8c6f4529166b"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "pkg-config"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "snek-game"
version = "1.0.0"
dependencies = [
"pancurses",
"rand",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winreg"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a"
dependencies = [
"winapi",
]

@ -0,0 +1,13 @@
[package]
name = "snek-game"
version = "1.0.0"
authors = ["voluminum"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.release]
opt-level = 3
[dependencies]
pancurses = "0.17.0"
rand = "0.8.5"

@ -1,3 +1,4 @@
<<<<<<< HEAD
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
@ -9,3 +10,18 @@ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
=======
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
>>>>>>> c2f2f11 (snek game)

@ -0,0 +1,14 @@
# Snake
This is a game of Snake that is designed to be played in the terminal. It is written in Rust with zero unsafe code and uses the pancurses library. The game tracks your high score, and has variable speed and difficulty.
## Building and running
This game can be built and ran with
``` sh
./run
```
Additionally, more options to the game can be found by typing
``` sh
./run -h
```

@ -1,3 +0,0 @@
# snek-game
A game of Snake written in Rust designed to be played in the terminal

3
run

@ -0,0 +1,3 @@
#!/usr/bin/env bash
cargo run --release -- $@

@ -0,0 +1,404 @@
use std::{env, fs, fs::File, io::{Error, Read, Write}, process::exit, thread, time};
use rand::Rng;
use pancurses::{initscr, endwin, Input, noecho /*, flushinp */};
enum GameEnd {
PlayerQuit,
AteSelf,
HitWall,
}
/// Different ways for the game to end
impl GameEnd {
fn tell(&self) -> &'static str {
match self {
GameEnd::PlayerQuit => "quit",
GameEnd::AteSelf => "ate yourself",
GameEnd::HitWall => "hit the wall",
}
}
}
#[derive(Debug)]
struct Vec2 {
x: i32,
y: i32,
}
impl Vec2 {
/// Initialize a Vec2 with the values you provide
/// Syntax: Vec2::new(X, Y)
/// Returns: Vec2
fn new(x: i32, y: i32) -> Vec2 {
Vec2 { x, y }
}
/// Check if the X and Y of one Vec2 is equal to another
/// Syntax: Vec2_1.equal_xy(&Vec2_2)
/// Returns: bool
fn equal_xy(&self, cmp_vec: &Vec2) -> bool {
self.x == cmp_vec.x && self.y == cmp_vec.y
}
}
/// Sets the direction of the snake head safely
/// as to not run into itself and end the game
fn set_direction(dir: &mut Vec2, new_dir: Vec2) {
if new_dir.x != -dir.x || new_dir.y != -dir.y {
dir.x = new_dir.x;
dir.y = new_dir.y;
}
}
/// Places the food in a random spot on the screen
fn place_food(food: &mut Vec2, window_size: &Vec2, snake: &Vec<Vec2>) {
// Place the food
food.x = rand::thread_rng().gen_range(1..window_size.x-1);
food.y = rand::thread_rng().gen_range(1..window_size.y-1);
// Check if food is under snake, recursively try again if so
for s in snake {
if food.equal_xy(s) {
place_food(food, window_size, snake)
}
}
}
/// Retrieve the high score from a high_score file
/// If the file doesn't exist, create one
fn get_hi_score() -> usize {
// Check if the high_score file exists
let mut file = match File::open("high_score") {
Ok(file) => file,
// No? Create one
Err(_) => {
match File::create("high_score") {
Ok(mut file) => {
match file.write_all(b"5") {
Ok(_) => (),
// And if writing to it fails... "fuck it".
Err(err) => panic!("Failed to write to hi_score: {}", err),
}
file
},
// If creating it fails... "fuck it"
Err(err) => panic!("Failed to create high_score: {:?}:", err),
}
},
};
// Grab high_score contents if found
let mut contents = String::new();
match file.read_to_string(&mut contents) {
Ok(_) => contents.parse().unwrap(),
// ReCURSEDively attempt to open file if it fails
// primarily due to the "bad file descriptor" error
Err(err) => {
println!("Couldn't read high_score: {}. Trying again...", err);
get_hi_score()
},
}
}
/// Write the high score to the high_score file
fn set_hi_score(score: usize) {
fn create(score: usize) -> Result<(), Error> {
let mut new_file = fs::OpenOptions::new().write(true).truncate(true).open("high_score")?;
new_file.write_fmt(format_args!("{}", score))?;
Ok(())
}
match create(score) {
Ok(_) => (),
Err(err) => println!("Failed to write file: {:?}:", err),
}
}
/// Run the snake game loop
fn game(rate: u64, bounce: bool, vert_slow: bool) {
// Set game update rate
let mut rate_div = 1.0;
// Get high score, create a file for it if there isn't one
let hi_score = get_hi_score();
// Initialize window
let window = initscr();
window.refresh();
window.keypad(true);
window.nodelay(true);
noecho();
let window_size = Vec2::new(
window.get_max_x(),
window.get_max_y()
);
// Initialize game
// Initialize and place snake sections
let mut snake: Vec<Vec2> = vec![];
let mut snake_len: usize = 5;
let center_x = window_size.x / 2;
let center_y = window_size.y / 2;
for x in -2..3 {
snake.push(Vec2::new(center_x + x, center_y));
}
// Get the snake moving
let mut direction = Vec2::new(1, 0);
// Place food
let mut food = Vec2::new(0, 0);
place_food(&mut food, &window_size, &snake);
let game_end;
let mut paused: bool = false;
// Start game loop
'game: loop {
let tick = time::Duration::from_millis(
(rate as f32 * rate_div) as u64
);
let snake_last = match snake.last() {
Some(_vec) => _vec,
None => panic!("Game crash: Snake has no sections. How did this happen?"),
};
// Check if snake is eating food
if snake_last.equal_xy(&food) {
place_food(&mut food, &window_size, &snake);
snake_len += 1;
}
// Check if snake is eating itself
for s in 0..snake.len()-1 {
if snake_last.equal_xy(&snake[s]) {
game_end = GameEnd::AteSelf;
break 'game;
}
}
// Check if snake is hitting wall
if
(snake_last.x < 1 || snake_last.y < 1
|| snake_last.x >= window_size.x-1
|| snake_last.y >= window_size.y-1)
&& !bounce
{
game_end = GameEnd::HitWall;
break 'game;
}
// Bounce the head off the wall if bounce is enabled
// FIXME: Snake stops when hitting center, causing game to end
// FIXME: When the snake heads towards the wall, this statement will forcibly
// steer the snake towards the center, even if the player turns away from it
if bounce {
// Vertical wall bounce
if snake_last.x < 2 || snake_last.x >= window_size.x-2 {
set_direction(&mut direction, Vec2::new(0, i32::signum(center_y - snake_last.y)));
// println!("{:?}", direction);
}
// Horizontal wall bounce
if snake_last.y < 2 || snake_last.y >= window_size.y-2 {
set_direction(&mut direction, Vec2::new(i32::signum(center_x - snake_last.x), 0));
// println!("{:?}", direction);
}
}
// Check user input
// TODO: Find a way to clear input queue
// TODO: so input does not lag from holding controls down
match window.getch() {
Some(Input::KeyUp) |
Some(Input::Character('w')) |
Some(Input::Character('k')) => {
set_direction(&mut direction, Vec2::new(0, -1));
if vert_slow { rate_div = 1.5; }
},
Some(Input::KeyLeft) |
Some(Input::Character('a')) |
Some(Input::Character('h')) => {
set_direction(&mut direction, Vec2::new(-1, 0));
rate_div = 1.0;
},
Some(Input::KeyDown) |
Some(Input::Character('s')) |
Some(Input::Character('j')) => {
set_direction(&mut direction, Vec2::new(0, 1));
if vert_slow { rate_div = 1.5; }
},
Some(Input::KeyRight) |
Some(Input::Character('d')) |
Some(Input::Character('l')) => {
set_direction(&mut direction, Vec2::new(1, 0));
rate_div = 1.0;
},
Some(Input::Character('p')) |
Some(Input::KeyExit) |
Some(Input::KeyBreak) => {
paused = !paused;
window.nodelay(!paused);
},
Some(Input::Character('q')) => {
game_end = GameEnd::PlayerQuit;
break 'game;
},
Some(_) => (),
None => (),
}
// Unintended behavior, doesn't allow for two movements within a single tick
// flushinp();
// Move snake
snake.push(Vec2::new(snake_last.x + direction.x, snake_last.y + direction.y));
if snake.len() > snake_len { snake.remove(0); }
// Clear screen, draw food
window.erase();
window.mvaddch(food.y, food.x, '$');
// Draw snake
for s in &snake { window.mvaddch(s.y, s.x, '@'); }
// Draw wall
window.draw_box('#','#');
// Draw score and high score
let score = format!(" Score {:4} ", snake_len);
window.mvaddstr(0, 2, score);
let score = format!(" Hi Score {:4} ", hi_score);
window.mvaddstr(0, 16, score);
// Draw paused if paused
if paused {
window.mvaddstr(window_size.y-1, 2, String::from(" [PAUSED] "));
}
thread::sleep(tick);
}
// Stop game
// Display end
let end_msg = format!("## GAME OVER ##\nYou {}!", game_end.tell());
let congrats = format!("[ X ] New high score! Previous: {}", hi_score);
let score_msg = format!("[ X ] Your score: {:4}", snake_len);
/* // Should this even be a feature?
window.clear();
window.mvaddstr(0, 3, end_msg.clone());
window.mvaddstr(3, 3, score_msg);
if snake_len > hi_score {
window.mvaddstr(6, 3, &*congrats);
}
window.refresh();
thread::sleep(time::Duration::from_millis(2000));
*/
// Exit game
endwin();
println!("{}", end_msg);
println!("{}", score_msg);
if snake_len > hi_score {
println!("{}", congrats);
set_hi_score(snake_len);
}
}
/// Print help information when -h flag is used
fn help() {
println!();
println!(
"CONTROLS:
To move the snake, either use:
- Arrow keys
- WASD keys
- HJKL keys
To pause or unpause, press p
OPTIONS
--bounce, -b [BROKEN]
Allow the snake's head to bounce off the wall, instead of
ending the game when hit. By default, this is disabled.
--help, -h
Shows this screen.
--rate, -r
Modifies rate of the game in milliseconds. (Default 150)
--no-vertical-slow, -v
By default, the game slows the snake when moving vertically
due to most terminal fonts being taller than wide.
If your font for whatever reason is equal aspect ratio,
or you don't like the effect, this option may be a good idea.
");
}
fn main() {
let args: Vec<String> = env::args().collect();
let start_time = time::Instant::now();
// Defaults
let mut rate: u64 = 150;
let mut bounce: bool = false;
let mut vert_slow: bool = true;
// Arguments
print!("Starting snake with: ");
for arg in 0..args.len() {
match Some(&*args[arg].to_string()) {
Some("--bounce") | Some("-b") => {
print!("{} ", args[arg]);
bounce = true
}
Some("--help") | Some("-h") => {
print!("{} ", args[arg]);
help();
exit(0);
},
Some("--rate") | Some("-r") => {
print!("{} ", args[arg]);
print!("{} ", args[arg+1]);
rate = args[arg+1].parse().unwrap();
},
Some("--no-vertical-slow") | Some("-v") => {
print!("{} ", args[arg]);
vert_slow = false;
}
_ => (),
}
}
println!();
// Start game
game(rate, bounce, vert_slow);
let elapsed = start_time.elapsed().as_millis()/* - 2000*/;
println!("Game lasted {}m {:.1}s",
elapsed / 60000,
(elapsed as f32 / 1000.0) % 60.0
);
}
Loading…
Cancel
Save