use notan::draw::*;
use notan::prelude::*;
const TILE_SIZE: usize = 40;
const COLS: usize = 800 / TILE_SIZE;
const ROWS: usize = 600 / TILE_SIZE;
const BACKGROUND_COLOR: Color = Color::new(0.698, 0.792, 0.376, 1.0);
const SNAKE_COLOR: Color = Color::new(0.211, 0.298, 0.074, 1.0);
const LINE_COLOR: Color = Color::new(1.0, 1.0, 1.0, 0.1);
const MOVEMENT_MS: f32 = 0.2;
const MIN_MOVEMENT_MS: f32 = 0.02;
#[notan_main]
fn main() -> Result<(), String> {
let win_config = WindowConfig::new().set_size(800, 600).set_vsync(true);
notan::init_with(State::new)
.add_config(win_config)
.add_config(DrawConfig)
.update(update)
.draw(draw)
.build()
}
fn update(app: &mut App, state: &mut State) {
state.time += app.timer.delta_f32();
change_direction(&app.keyboard, state);
if state.can_move() {
state.movement();
state.time = 0.0;
}
}
fn draw(gfx: &mut Graphics, state: &mut State) {
let tile_size = TILE_SIZE as f32;
let mut draw = gfx.create_draw();
draw.clear(BACKGROUND_COLOR);
for i in 0..COLS * ROWS {
let (x, y) = xy(i);
let pos_x = (x * TILE_SIZE) as f32;
let pos_y = (y * TILE_SIZE) as f32;
draw.rect((pos_x, pos_y), (tile_size, tile_size))
.color(LINE_COLOR)
.stroke(1.0);
}
draw.text(&state.font, "Use WASD to move")
.position(10.0, 570.0)
.size(20.0);
let pos_x = (state.food.0 * TILE_SIZE) as f32;
let pos_y = (state.food.1 * TILE_SIZE) as f32;
draw.rect((pos_x, pos_y), (tile_size, tile_size))
.color(Color::from_hex(0xa0a0a0ff));
draw.rect((pos_x, pos_y), (tile_size, tile_size))
.color(Color::BLACK)
.stroke(2.0);
state.snake.iter().enumerate().for_each(|(i, (x, y))| {
let pos_x = (x * TILE_SIZE) as f32;
let pos_y = (y * TILE_SIZE) as f32;
let color = if i == state.snake.len() - 1 {
SNAKE_COLOR
} else {
SNAKE_COLOR.with_alpha(0.8)
};
draw.rect((pos_x, pos_y), (tile_size, tile_size))
.color(color);
draw.rect((pos_x, pos_y), (tile_size, tile_size))
.color(Color::BLACK)
.stroke(1.0);
});
draw.text(&state.font, &format!("Score: {}", state.score))
.position(10.0, 10.0)
.size(30.0);
if let Some(last_score) = state.last_score {
draw.text(&state.font, &format!("Last Score: {last_score}"))
.position(10.0, 50.0)
.size(20.0);
}
gfx.render(&draw);
}
#[derive(PartialEq)]
enum Direction {
Left,
Right,
Up,
Down,
}
#[derive(AppState)]
struct State {
snake: Vec<(usize, usize)>,
food: (usize, usize),
rng: Random,
dir: Direction,
time: f32,
score: i32,
last_score: Option<i32>,
accel: f32,
font: Font,
}
impl State {
fn new(gfx: &mut Graphics) -> Self {
let mut rng = Random::default();
let food = random_xy(&mut rng);
let font = gfx
.create_font(include_bytes!("assets/Ubuntu-B.ttf"))
.unwrap();
Self {
time: 0.0,
rng,
food,
snake: vec![(8, 7), (9, 7), (10, 7)],
dir: Direction::Right,
score: 0,
last_score: None,
accel: 0.0,
font,
}
}
fn hit(&self, x: usize, y: usize, only_body: bool) -> bool {
let range = if only_body {
0..self.snake.len() - 1
} else {
0..self.snake.len()
};
let mut exists_hit = false;
for i in range {
let (tile_x, tile_y) = self.snake[i];
if x == tile_x && y == tile_y {
exists_hit = true;
break;
}
}
exists_hit
}
fn reset(&mut self) {
self.time = 0.0;
self.snake = vec![(8, 7), (9, 7), (10, 7)];
self.dir = Direction::Right;
self.food = random_xy(&mut self.rng);
self.last_score = Some(self.score);
self.score = 0;
self.accel = 0.0;
}
fn set_food_pos(&mut self) {
loop {
let (fx, fy) = random_xy(&mut self.rng);
if !self.hit(fx, fy, false) {
self.food = (fx, fy);
break;
}
}
}
fn can_move(&self) -> bool {
self.time >= (MOVEMENT_MS - self.accel).max(MIN_MOVEMENT_MS)
}
fn movement(&mut self) {
if let Some((mut x, mut y)) = self.snake.last() {
if self.hit(x, y, true) {
self.reset();
return;
}
match self.dir {
Direction::Left => {
x = (COLS + x - 1) % COLS;
}
Direction::Right => {
x = (COLS + x + 1) % COLS;
}
Direction::Up => {
y = (ROWS + y - 1) % ROWS;
}
Direction::Down => {
y = (ROWS + y + 1) % ROWS;
}
}
self.snake.push((x, y));
if x == self.food.0 && y == self.food.1 {
self.score += 1;
self.accel += 0.01;
self.set_food_pos();
} else {
self.snake.remove(0);
}
}
}
}
fn random_xy(rng: &mut Random) -> (usize, usize) {
(rng.random_range(0..COLS), rng.random_range(0..ROWS))
}
fn xy(index: usize) -> (usize, usize) {
(index % COLS, index / COLS)
}
fn change_direction(keyboard: &Keyboard, state: &mut State) {
let up = keyboard.was_pressed(KeyCode::KeyW) || keyboard.was_pressed(KeyCode::ArrowUp);
let down = keyboard.was_pressed(KeyCode::KeyS) || keyboard.was_pressed(KeyCode::ArrowDown);
let left = keyboard.was_pressed(KeyCode::KeyA) || keyboard.was_pressed(KeyCode::ArrowLeft);
let right = keyboard.was_pressed(KeyCode::KeyD) || keyboard.was_pressed(KeyCode::ArrowRight);
if up && state.dir != Direction::Down {
state.dir = Direction::Up;
} else if down && state.dir != Direction::Up {
state.dir = Direction::Down;
} else if left && state.dir != Direction::Right {
state.dir = Direction::Left;
} else if right && state.dir != Direction::Left {
state.dir = Direction::Right;
}
}