examples: add interpolated movement and input queue to snek/snek.v (#21798)

This commit is contained in:
shadow 2024-07-03 15:17:46 -04:00 committed by GitHub
parent 9b6578e883
commit dceea80d85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,9 +1,9 @@
import os import datatypes
import gg import gg
import gx import gx
// import sokol.sapp import os
import time
import rand import rand
import time
// constants // constants
const top_height = 100 const top_height = 100
@ -11,28 +11,20 @@ const canvas_size = 700
const game_size = 17 const game_size = 17
const tile_size = canvas_size / game_size const tile_size = canvas_size / game_size
const tick_rate_ms = 100 const tick_rate_ms = 100
const high_score_file_path = os.join_path(os.cache_dir(), 'v', 'examples', 'snek') const high_score_file_path = os.join_path(os.cache_dir(), 'v', 'examples', 'snek')
// types // types
struct Pos { struct Vec {
x int x int
y int y int
} }
fn (a Pos) + (b Pos) Pos { fn (a Vec) + (b Vec) Vec {
return Pos{a.x + b.x, a.y + b.y} return Vec{a.x + b.x, a.y + b.y}
} }
fn (a Pos) - (b Pos) Pos { fn (a Vec) - (b Vec) Vec {
return Pos{a.x - b.x, a.y - b.y} return Vec{a.x - b.x, a.y - b.y}
}
enum Direction {
up
down
left
right
} }
type HighScore = int type HighScore = int
@ -51,11 +43,10 @@ mut:
gg &gg.Context = unsafe { nil } gg &gg.Context = unsafe { nil }
score int score int
best HighScore best HighScore
snake []Pos snake []Vec
dir Direction dir Vec
last_dir Direction dir_queue datatypes.Queue[Vec]
food Pos food Vec
start_time i64
last_tick i64 last_tick i64
} }
@ -63,15 +54,14 @@ mut:
fn (mut app App) reset_game() { fn (mut app App) reset_game() {
app.score = 0 app.score = 0
app.snake = [ app.snake = [
Pos{3, 8}, Vec{3, 8},
Pos{2, 8}, Vec{2, 8},
Pos{1, 8}, Vec{1, 8},
Pos{0, 8}, Vec{0, 8},
] ]
app.dir = .right app.dir = Vec{1, 0}
app.last_dir = app.dir app.dir_queue = datatypes.Queue[Vec]{}
app.food = Pos{10, 8} app.food = Vec{10, 8}
app.start_time = time.ticks()
app.last_tick = time.ticks() app.last_tick = time.ticks()
} }
@ -79,7 +69,7 @@ fn (mut app App) move_food() {
for { for {
x := rand.intn(game_size) or { 0 } x := rand.intn(game_size) or { 0 }
y := rand.intn(game_size) or { 0 } y := rand.intn(game_size) or { 0 }
app.food = Pos{x, y} app.food = Vec{x, y}
if app.food !in app.snake { if app.food !in app.snake {
return return
@ -89,81 +79,65 @@ fn (mut app App) move_food() {
// events // events
fn on_keydown(key gg.KeyCode, mod gg.Modifier, mut app App) { fn on_keydown(key gg.KeyCode, mod gg.Modifier, mut app App) {
match key { dir := match key {
.w, .up { .w, .up {
if app.last_dir != .down { Vec{0, -1}
app.dir = .up
}
} }
.s, .down { .s, .down {
if app.last_dir != .up { Vec{0, 1}
app.dir = .down
}
} }
.a, .left { .a, .left {
if app.last_dir != .right { Vec{-1, 0}
app.dir = .left
}
} }
.d, .right { .d, .right {
if app.last_dir != .left { Vec{1, 0}
app.dir = .right }
else {
return
} }
} }
else {} app.dir_queue.push(dir)
}
} }
fn on_frame(mut app App) { fn on_frame(mut app App) {
// check if snake bit itself
if app.snake[0] in app.snake[1..] {
app.reset_game()
}
// check if snake hit a wall
if app.snake[0].x < 0 || app.snake[0].x >= game_size || app.snake[0].y < 0
|| app.snake[0].y >= game_size {
app.reset_game()
}
progress := f32_min(1, f32(time.ticks() - app.last_tick) / f32(tick_rate_ms))
app.gg.begin() app.gg.begin()
now := time.ticks() // draw food
app.gg.draw_rect_filled(tile_size * app.food.x, tile_size * app.food.y + top_height,
tile_size, tile_size, gx.red)
if now - app.last_tick >= tick_rate_ms { // draw snake
app.last_tick = now for pos in app.snake[..app.snake.len - 1] {
// finding delta direction
delta_dir := match app.dir {
.up { Pos{0, -1} }
.down { Pos{0, 1} }
.left { Pos{-1, 0} }
.right { Pos{1, 0} }
}
// "snaking" along
mut prev := app.snake[0]
app.snake[0] = app.snake[0] + delta_dir
for i in 1 .. app.snake.len {
tmp := app.snake[i]
app.snake[i] = prev
prev = tmp
}
// adding last segment
if app.snake[0] == app.food {
app.move_food()
app.score++
if app.score > app.best {
app.best = app.score
app.best.save()
}
app.snake << app.snake.last() + app.snake.last() - app.snake[app.snake.len - 2]
}
app.last_dir = app.dir
}
// drawing snake
for pos in app.snake {
app.gg.draw_rect_filled(tile_size * pos.x, tile_size * pos.y + top_height, tile_size, app.gg.draw_rect_filled(tile_size * pos.x, tile_size * pos.y + top_height, tile_size,
tile_size, gx.blue) tile_size, gx.blue)
} }
// drawing food // draw partial head
app.gg.draw_rect_filled(tile_size * app.food.x, tile_size * app.food.y + top_height, head := app.snake[0]
tile_size, tile_size, gx.red) app.gg.draw_rect_filled(int(tile_size * (head.x + app.dir.x * progress)),
int(tile_size * (head.y + app.dir.y * progress)) + top_height, tile_size, tile_size,
gx.blue)
// drawing top // draw partial tail
tail := app.snake.last()
tail_dir := app.snake[app.snake.len - 2] - tail
app.gg.draw_rect_filled(int(tile_size * (tail.x + tail_dir.x * progress)),
int(tile_size * (tail.y + tail_dir.y * progress)) + top_height, tile_size, tile_size,
gx.blue)
// draw score bar
app.gg.draw_rect_filled(0, 0, canvas_size, top_height, gx.black) app.gg.draw_rect_filled(0, 0, canvas_size, top_height, gx.black)
app.gg.draw_text(150, top_height / 2, 'Score: ${app.score}', gx.TextCfg{ app.gg.draw_text(150, top_height / 2, 'Score: ${app.score}', gx.TextCfg{
color: gx.white color: gx.white
@ -178,14 +152,35 @@ fn on_frame(mut app App) {
size: 65 size: 65
}) })
// checking if snake bit itself if progress == 1 {
if app.snake[0] in app.snake[1..] { // "snake" along
app.reset_game() mut prev := app.snake[0]
app.snake[0] = app.snake[0] + app.dir
for i in 1 .. app.snake.len {
tmp := app.snake[i]
app.snake[i] = prev
prev = tmp
} }
// checking if snake hit a wall
if app.snake[0].x < 0 || app.snake[0].x >= game_size || app.snake[0].y < 0 // add tail segment if food has been eaten
|| app.snake[0].y >= game_size { if app.snake[0] == app.food {
app.reset_game() app.score++
if app.score > app.best {
app.best = app.score
app.best.save()
}
app.snake << app.snake.last() + app.snake.last() - app.snake[app.snake.len - 2]
app.move_food()
}
if dir := app.dir_queue.pop() {
if dir.x != -app.dir.x || dir.y != -app.dir.y {
app.dir = dir
}
}
app.last_tick = time.ticks()
} }
app.gg.end() app.gg.end()