Initial commit
This commit is contained in:
commit
c5d1d1dfbf
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
*.lock
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "snakers"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tui = "0.16"
|
||||||
|
termion = "1.5"
|
||||||
|
rand = "0.8.4"
|
|
@ -0,0 +1,49 @@
|
||||||
|
use std::io;
|
||||||
|
use std::thread;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use termion::input::TermRead;
|
||||||
|
use termion::event::Key;
|
||||||
|
|
||||||
|
pub struct Events {
|
||||||
|
rx: mpsc::Receiver<Key>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Events {
|
||||||
|
pub fn new() -> Events {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
let stdin = io::stdin();
|
||||||
|
|
||||||
|
for key in stdin.keys() {
|
||||||
|
match key {
|
||||||
|
Ok(key) => if let Err(_) = tx.send(key) { return; },
|
||||||
|
Err(_) => panic!("Failed to read stdin"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Events {
|
||||||
|
rx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&self) -> Option<Key> {
|
||||||
|
let key = self.rx.try_recv();
|
||||||
|
match key {
|
||||||
|
Ok(key) => Some(key),
|
||||||
|
Err(err) => match err {
|
||||||
|
mpsc::TryRecvError::Empty => None,
|
||||||
|
_ => panic!("Channel closed")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn last(&self) -> Option<Key> {
|
||||||
|
let mut key = self.next();
|
||||||
|
while let Some(k) = self.next() {
|
||||||
|
key = Some(k);
|
||||||
|
}
|
||||||
|
key
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
use crate::vec2::Vec2;
|
||||||
|
use rand::prelude::*;
|
||||||
|
use tui::widgets::canvas::{Context};
|
||||||
|
use tui::style::Color;
|
||||||
|
|
||||||
|
use crate::SCREEN_WIDTH;
|
||||||
|
use crate::SCREEN_HEIGHT;
|
||||||
|
|
||||||
|
pub struct Food {
|
||||||
|
pos: Vec2,
|
||||||
|
spoil_in: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Food {
|
||||||
|
pub fn draw(&self, ctx: &mut Context) {
|
||||||
|
if self.spoiled() {
|
||||||
|
ctx.print(self.pos.x, self.pos.y, "*", Color::Green);
|
||||||
|
} else {
|
||||||
|
ctx.print(self.pos.x, self.pos.y, "*", Color::Red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let x = rng.gen_range(-(SCREEN_WIDTH/2.0) as i32..(SCREEN_WIDTH/2.0) as i32);
|
||||||
|
let y = rng.gen_range(-(SCREEN_HEIGHT/2.0) as i32..(SCREEN_HEIGHT/2.0) as i32);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
pos: Vec2::new(x as f64, y as f64),
|
||||||
|
spoil_in: rng.gen_range(50..300),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spoiled(&self) -> bool {
|
||||||
|
self.spoil_in >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rotten(&self) -> bool {
|
||||||
|
self.spoil_in <= -100
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick_spoil(&mut self) {
|
||||||
|
self.spoil_in -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pos(&self) -> &Vec2 {
|
||||||
|
&self.pos
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
mod snake;
|
||||||
|
mod vec2;
|
||||||
|
mod event;
|
||||||
|
mod food;
|
||||||
|
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::io;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tui::Terminal;
|
||||||
|
use tui::backend::TermionBackend;
|
||||||
|
use tui::widgets::{Block, Borders};
|
||||||
|
use tui::style::Color;
|
||||||
|
use tui::widgets::canvas::{Canvas};
|
||||||
|
use termion::screen::AlternateScreen;
|
||||||
|
use termion::raw::IntoRawMode;
|
||||||
|
use termion::event::Key;
|
||||||
|
|
||||||
|
use event::Events;
|
||||||
|
use snake::Snake;
|
||||||
|
use vec2::Vec2;
|
||||||
|
use food::Food;
|
||||||
|
|
||||||
|
const SCREEN_WIDTH: f64 = 360.0;
|
||||||
|
const SCREEN_HEIGHT: f64 = 180.0;
|
||||||
|
|
||||||
|
fn main() -> Result<(), io::Error> {
|
||||||
|
let stdout = io::stdout().into_raw_mode()?;
|
||||||
|
let stdout = AlternateScreen::from(stdout);
|
||||||
|
let backend = TermionBackend::new(stdout);
|
||||||
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
|
||||||
|
let mut snake = Snake::new(0.0, 0.0, Color::LightBlue);
|
||||||
|
let mut food_pool: Vec<Food> = Vec::new();
|
||||||
|
|
||||||
|
while food_pool.len() < 15 {
|
||||||
|
food_pool.push(Food::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let food_pool = Mutex::new(food_pool);
|
||||||
|
|
||||||
|
let events = Events::new();
|
||||||
|
loop {
|
||||||
|
thread::sleep(Duration::from_millis(15));
|
||||||
|
|
||||||
|
terminal.draw(|f| {
|
||||||
|
let canvas = Canvas::default()
|
||||||
|
.block(Block::default()
|
||||||
|
.title("Canvas")
|
||||||
|
.borders(Borders::ALL))
|
||||||
|
.x_bounds([-SCREEN_WIDTH/2.0, SCREEN_WIDTH/2.0])
|
||||||
|
.y_bounds([-SCREEN_HEIGHT/2.0, SCREEN_HEIGHT/2.0])
|
||||||
|
.paint(|ctx| {
|
||||||
|
//ctx.draw(&snake);
|
||||||
|
snake.draw_big(ctx);
|
||||||
|
|
||||||
|
for food in &*food_pool.lock().unwrap() {
|
||||||
|
food.draw(ctx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
f.render_widget(canvas, f.size());
|
||||||
|
})?;
|
||||||
|
|
||||||
|
snake.advance();
|
||||||
|
|
||||||
|
if snake.collides_with(&snake) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in events.last() {
|
||||||
|
match key {
|
||||||
|
Key::Ctrl(c) => if c == 'c' { return Ok(()); },
|
||||||
|
Key::Up => snake.change_direction(Vec2::new(0.0, 1.0)),
|
||||||
|
Key::Left => snake.change_direction(Vec2::new(-1.0, 0.0)),
|
||||||
|
Key::Down => snake.change_direction(Vec2::new(0.0, -1.0)),
|
||||||
|
Key::Right => snake.change_direction(Vec2::new(1.0, 0.0)),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut food_pool = food_pool.lock().unwrap();
|
||||||
|
snake.try_eat(&mut food_pool);
|
||||||
|
|
||||||
|
for food in &mut *food_pool {
|
||||||
|
food.tick_spoil();
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..food_pool.len() {
|
||||||
|
if food_pool[i].rotten() {
|
||||||
|
food_pool.swap_remove(i);
|
||||||
|
food_pool.push(Food::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
use std::ptr;
|
||||||
|
use std::collections::LinkedList;
|
||||||
|
use tui::widgets::canvas::{Shape, Painter, Line, Context};
|
||||||
|
use tui::style::Color;
|
||||||
|
use crate::vec2::Vec2;
|
||||||
|
use crate::food::Food;
|
||||||
|
|
||||||
|
pub struct Snake {
|
||||||
|
body: LinkedList<Vec2>,
|
||||||
|
color: Color,
|
||||||
|
direction: Vec2,
|
||||||
|
speed: Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Shape for Snake {
|
||||||
|
fn draw(&self, painter: &mut Painter) {
|
||||||
|
let mut body = self.body.iter();
|
||||||
|
let mut last = body.next().unwrap();
|
||||||
|
|
||||||
|
for node in body {
|
||||||
|
let line = Line {
|
||||||
|
x1: last.x,
|
||||||
|
y1: last.y,
|
||||||
|
x2: node.x,
|
||||||
|
y2: node.y,
|
||||||
|
color: self.color,
|
||||||
|
};
|
||||||
|
line.draw(painter);
|
||||||
|
last = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
let head = self.get_head();
|
||||||
|
let head_point = Line {
|
||||||
|
x1: head.x,
|
||||||
|
y1: head.y,
|
||||||
|
x2: head.x,
|
||||||
|
y2: head.y,
|
||||||
|
color: Color::LightYellow
|
||||||
|
};
|
||||||
|
|
||||||
|
head_point.draw(painter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Snake {
|
||||||
|
fn draw_segment(&self, node1: Vec2, node2: Vec2, ctx: &mut Context) {
|
||||||
|
let x1 = node1.x as i32;
|
||||||
|
let x2 = node2.x as i32;
|
||||||
|
let y1 = node1.y as i32;
|
||||||
|
let y2 = node2.y as i32;
|
||||||
|
|
||||||
|
let x_range = if x2 > x1 {
|
||||||
|
x1..=x2
|
||||||
|
} else {
|
||||||
|
x2..=x1
|
||||||
|
};
|
||||||
|
let y_range = if y2 > y1 {
|
||||||
|
y1..=y2
|
||||||
|
} else {
|
||||||
|
y2..=y1
|
||||||
|
};
|
||||||
|
|
||||||
|
if x2 == x1 {
|
||||||
|
for y in y_range {
|
||||||
|
ctx.print(node1.x as f64, y as f64, "|", self.color);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for x in x_range {
|
||||||
|
ctx.print(x as f64, node2.y as f64, "-", self.color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_big(&self, ctx: &mut Context) {
|
||||||
|
let mut body = self.body.iter();
|
||||||
|
let mut last = body.next().unwrap();
|
||||||
|
|
||||||
|
for node in body {
|
||||||
|
self.draw_segment(*last, *node, ctx);
|
||||||
|
last = node;
|
||||||
|
}
|
||||||
|
ctx.layer();
|
||||||
|
|
||||||
|
let head = self.get_head();
|
||||||
|
ctx.print(head.x, head.y, "*", Color::LightYellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(x: f64, y: f64, color: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
body: LinkedList::from([Vec2::new(x, y), Vec2::new(x-5.0, y)]),
|
||||||
|
color,
|
||||||
|
direction: Vec2::new(1.0, 0.0),
|
||||||
|
speed: Vec2::from_single(1.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn change_direction(&mut self, direction: Vec2) {
|
||||||
|
if self.direction != direction && self.direction != -direction {
|
||||||
|
self.direction = direction;
|
||||||
|
|
||||||
|
// Create a new edge at current head position
|
||||||
|
let head = self.body.pop_front().unwrap();
|
||||||
|
self.body.push_front(head);
|
||||||
|
self.body.push_front(head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn grow(&mut self, multiplier: Vec2) {
|
||||||
|
let mut prev_tail = self.body.pop_back().unwrap();
|
||||||
|
let tail = self.body.pop_back().unwrap();
|
||||||
|
|
||||||
|
if tail == prev_tail {
|
||||||
|
self.body.push_back(tail);
|
||||||
|
self.grow(multiplier);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev_tail += tail.direction_to(&prev_tail) * multiplier;
|
||||||
|
self.body.push_back(tail);
|
||||||
|
self.body.push_back(prev_tail);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self) {
|
||||||
|
let mut head = self.body.pop_front().unwrap();
|
||||||
|
head += self.direction * self.speed;
|
||||||
|
self.body.push_front(head);
|
||||||
|
|
||||||
|
self.grow(-self.speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nodes_collide(target: &Vec2, node1: &Vec2, node2: &Vec2) -> bool {
|
||||||
|
if node1.x == node2.x && node1.x == target.x {
|
||||||
|
if node1.y < node2.y {
|
||||||
|
target.y >= node1.y && target.y <= node2.y
|
||||||
|
} else {
|
||||||
|
target.y <= node1.y && target.y >= node2.y
|
||||||
|
}
|
||||||
|
} else if node1.y == node2.y && node1.y == target.y {
|
||||||
|
if node1.x < node2.x {
|
||||||
|
target.x >= node1.x && target.x <= node2.x
|
||||||
|
} else {
|
||||||
|
target.x <= node1.x && target.x >= node2.x
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collides_with(&self, other: &Self) -> bool {
|
||||||
|
let my_head = self.get_head();
|
||||||
|
|
||||||
|
let mut nodes = other.body.iter();
|
||||||
|
let mut last = nodes.next().unwrap();
|
||||||
|
|
||||||
|
// If checking against itself, skip first section
|
||||||
|
if ptr::eq(self, other) {
|
||||||
|
last = nodes.next().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
for node in nodes {
|
||||||
|
if Self::nodes_collide(my_head, last, node) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
last = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_head(&self) -> &Vec2 {
|
||||||
|
self.body.iter().next().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_eat(&mut self, food_pool: &mut Vec<Food>) {
|
||||||
|
for i in 0..food_pool.len() {
|
||||||
|
if (*food_pool[i].get_pos() - *self.get_head()).len() <= self.speed.x + 2.0 {
|
||||||
|
food_pool.swap_remove(i);
|
||||||
|
self.grow(Vec2::from_single(12.0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,167 @@
|
||||||
|
use std::ops::{Add, AddAssign, Neg, Mul, Div, Sub, SubAssign};
|
||||||
|
use std::cmp::PartialEq;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct Vec2 {
|
||||||
|
pub x: f64,
|
||||||
|
pub y: f64
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vec2 {
|
||||||
|
pub fn new(x: f64, y: f64) -> Self {
|
||||||
|
Self { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_single(a: f64) -> Self {
|
||||||
|
Self { x: a, y: a }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn origin() -> Self {
|
||||||
|
Self { x: 0.0, y: 0.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> f64 {
|
||||||
|
(self.x.powf(2.0) + self.y.powf(2.0)).sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normal(&self) -> Self {
|
||||||
|
let hipothenuse = self.len();
|
||||||
|
if hipothenuse > 0.0 {
|
||||||
|
Self {
|
||||||
|
x: self.x / hipothenuse,
|
||||||
|
y: self.y / hipothenuse,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn direction_to(&self, other: &Self) -> Self {
|
||||||
|
(*other - *self).normal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for Vec2 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self {
|
||||||
|
Self { x: self.x + rhs.x, y: self.y + rhs.y }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub for Vec2 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn sub(self, rhs: Self) -> Self {
|
||||||
|
Self {x: self.x - rhs.x, y: self.y - rhs.y}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign for Vec2 {
|
||||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
|
self.x += rhs.x;
|
||||||
|
self.y += rhs.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubAssign for Vec2 {
|
||||||
|
fn sub_assign(&mut self, rhs: Self) {
|
||||||
|
self.x -= rhs.x;
|
||||||
|
self.y -= rhs.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Vec2 {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.x == other.x && self.y == other.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul for Vec2 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn mul(self, rhs: Self) -> Self {
|
||||||
|
Self { x: self.x * rhs.x, y: self.y * rhs.y }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Div for Vec2 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn div(self, rhs: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
x: if rhs.x > 0.0 { self.x / rhs.x } else { 0.0 },
|
||||||
|
y: if rhs.y > 0.0 { self.y / rhs.y } else { 0.0 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Neg for Vec2 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn neg(self) -> Self {
|
||||||
|
Self {
|
||||||
|
x: -self.x,
|
||||||
|
y: -self.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Vec2;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn direction_0deg() {
|
||||||
|
let v1 = Vec2::origin();
|
||||||
|
let v2 = Vec2::new(2.0, 0.0);
|
||||||
|
let result = Vec2::new(1.0, 0.0);
|
||||||
|
|
||||||
|
assert_eq!(result, v1.direction_to(&v2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn direction_45deg() {
|
||||||
|
let v1 = Vec2::origin();
|
||||||
|
let v2 = Vec2::from_single(1.0);
|
||||||
|
let result = Vec2::from_single(1.0/(2.0_f64).sqrt());
|
||||||
|
|
||||||
|
assert_eq!(result, v1.direction_to(&v2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn direction_90deg() {
|
||||||
|
let v1 = Vec2::origin();
|
||||||
|
let v2 = Vec2::new(0.0, 23.121);
|
||||||
|
let result = Vec2::new(0.0, 1.0);
|
||||||
|
|
||||||
|
assert_eq!(result, v1.direction_to(&v2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn direction_180deg() {
|
||||||
|
let v1 = Vec2::origin();
|
||||||
|
let v2 = Vec2::new(-82.1, 0.0);
|
||||||
|
let result = Vec2::new(-1.0, 0.0);
|
||||||
|
|
||||||
|
assert_eq!(result, v1.direction_to(&v2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn direction_270deg() {
|
||||||
|
let v1 = Vec2::origin();
|
||||||
|
let v2 = Vec2::new(0.0, -2.8);
|
||||||
|
let result = Vec2::new(0.0, -1.0);
|
||||||
|
|
||||||
|
assert_eq!(result, v1.direction_to(&v2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn direction_not_origin() {
|
||||||
|
let v1 = Vec2::from_single(1.0);
|
||||||
|
let v2 = Vec2::from_single(3.0);
|
||||||
|
let result = Vec2::from_single(1.0/(2.0_f64).sqrt());
|
||||||
|
|
||||||
|
assert_eq!(result, v1.direction_to(&v2));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue