Skip to content

Commit dceea80

Browse files
examples: add interpolated movement and input queue to snek/snek.v (#21798)
1 parent 9b6578e commit dceea80

File tree

1 file changed

+91
-96
lines changed

1 file changed

+91
-96
lines changed

examples/snek/snek.v

Lines changed: 91 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,30 @@
1-
import os
1+
import datatypes
22
import gg
33
import gx
4-
// import sokol.sapp
5-
import time
4+
import os
65
import rand
6+
import time
77

88
// constants
99
const top_height = 100
1010
const canvas_size = 700
1111
const game_size = 17
1212
const tile_size = canvas_size / game_size
1313
const tick_rate_ms = 100
14-
1514
const high_score_file_path = os.join_path(os.cache_dir(), 'v', 'examples', 'snek')
1615

1716
// types
18-
struct Pos {
17+
struct Vec {
1918
x int
2019
y int
2120
}
2221

23-
fn (a Pos) + (b Pos) Pos {
24-
return Pos{a.x + b.x, a.y + b.y}
22+
fn (a Vec) + (b Vec) Vec {
23+
return Vec{a.x + b.x, a.y + b.y}
2524
}
2625

27-
fn (a Pos) - (b Pos) Pos {
28-
return Pos{a.x - b.x, a.y - b.y}
29-
}
30-
31-
enum Direction {
32-
up
33-
down
34-
left
35-
right
26+
fn (a Vec) - (b Vec) Vec {
27+
return Vec{a.x - b.x, a.y - b.y}
3628
}
3729

3830
type HighScore = int
@@ -48,38 +40,36 @@ fn (mut h HighScore) load() {
4840

4941
struct App {
5042
mut:
51-
gg &gg.Context = unsafe { nil }
52-
score int
53-
best HighScore
54-
snake []Pos
55-
dir Direction
56-
last_dir Direction
57-
food Pos
58-
start_time i64
59-
last_tick i64
43+
gg &gg.Context = unsafe { nil }
44+
score int
45+
best HighScore
46+
snake []Vec
47+
dir Vec
48+
dir_queue datatypes.Queue[Vec]
49+
food Vec
50+
last_tick i64
6051
}
6152

6253
// utility
6354
fn (mut app App) reset_game() {
6455
app.score = 0
6556
app.snake = [
66-
Pos{3, 8},
67-
Pos{2, 8},
68-
Pos{1, 8},
69-
Pos{0, 8},
57+
Vec{3, 8},
58+
Vec{2, 8},
59+
Vec{1, 8},
60+
Vec{0, 8},
7061
]
71-
app.dir = .right
72-
app.last_dir = app.dir
73-
app.food = Pos{10, 8}
74-
app.start_time = time.ticks()
62+
app.dir = Vec{1, 0}
63+
app.dir_queue = datatypes.Queue[Vec]{}
64+
app.food = Vec{10, 8}
7565
app.last_tick = time.ticks()
7666
}
7767

7868
fn (mut app App) move_food() {
7969
for {
8070
x := rand.intn(game_size) or { 0 }
8171
y := rand.intn(game_size) or { 0 }
82-
app.food = Pos{x, y}
72+
app.food = Vec{x, y}
8373

8474
if app.food !in app.snake {
8575
return
@@ -89,81 +79,65 @@ fn (mut app App) move_food() {
8979

9080
// events
9181
fn on_keydown(key gg.KeyCode, mod gg.Modifier, mut app App) {
92-
match key {
82+
dir := match key {
9383
.w, .up {
94-
if app.last_dir != .down {
95-
app.dir = .up
96-
}
84+
Vec{0, -1}
9785
}
9886
.s, .down {
99-
if app.last_dir != .up {
100-
app.dir = .down
101-
}
87+
Vec{0, 1}
10288
}
10389
.a, .left {
104-
if app.last_dir != .right {
105-
app.dir = .left
106-
}
90+
Vec{-1, 0}
10791
}
10892
.d, .right {
109-
if app.last_dir != .left {
110-
app.dir = .right
111-
}
93+
Vec{1, 0}
94+
}
95+
else {
96+
return
11297
}
113-
else {}
11498
}
99+
app.dir_queue.push(dir)
115100
}
116101

117102
fn on_frame(mut app App) {
118-
app.gg.begin()
119-
120-
now := time.ticks()
121-
122-
if now - app.last_tick >= tick_rate_ms {
123-
app.last_tick = now
124-
125-
// finding delta direction
126-
delta_dir := match app.dir {
127-
.up { Pos{0, -1} }
128-
.down { Pos{0, 1} }
129-
.left { Pos{-1, 0} }
130-
.right { Pos{1, 0} }
131-
}
103+
// check if snake bit itself
104+
if app.snake[0] in app.snake[1..] {
105+
app.reset_game()
106+
}
132107

133-
// "snaking" along
134-
mut prev := app.snake[0]
135-
app.snake[0] = app.snake[0] + delta_dir
108+
// check if snake hit a wall
109+
if app.snake[0].x < 0 || app.snake[0].x >= game_size || app.snake[0].y < 0
110+
|| app.snake[0].y >= game_size {
111+
app.reset_game()
112+
}
136113

137-
for i in 1 .. app.snake.len {
138-
tmp := app.snake[i]
139-
app.snake[i] = prev
140-
prev = tmp
141-
}
114+
progress := f32_min(1, f32(time.ticks() - app.last_tick) / f32(tick_rate_ms))
115+
app.gg.begin()
142116

143-
// adding last segment
144-
if app.snake[0] == app.food {
145-
app.move_food()
146-
app.score++
147-
if app.score > app.best {
148-
app.best = app.score
149-
app.best.save()
150-
}
151-
app.snake << app.snake.last() + app.snake.last() - app.snake[app.snake.len - 2]
152-
}
117+
// draw food
118+
app.gg.draw_rect_filled(tile_size * app.food.x, tile_size * app.food.y + top_height,
119+
tile_size, tile_size, gx.red)
153120

154-
app.last_dir = app.dir
155-
}
156-
// drawing snake
157-
for pos in app.snake {
121+
// draw snake
122+
for pos in app.snake[..app.snake.len - 1] {
158123
app.gg.draw_rect_filled(tile_size * pos.x, tile_size * pos.y + top_height, tile_size,
159124
tile_size, gx.blue)
160125
}
161126

162-
// drawing food
163-
app.gg.draw_rect_filled(tile_size * app.food.x, tile_size * app.food.y + top_height,
164-
tile_size, tile_size, gx.red)
127+
// draw partial head
128+
head := app.snake[0]
129+
app.gg.draw_rect_filled(int(tile_size * (head.x + app.dir.x * progress)),
130+
int(tile_size * (head.y + app.dir.y * progress)) + top_height, tile_size, tile_size,
131+
gx.blue)
132+
133+
// draw partial tail
134+
tail := app.snake.last()
135+
tail_dir := app.snake[app.snake.len - 2] - tail
136+
app.gg.draw_rect_filled(int(tile_size * (tail.x + tail_dir.x * progress)),
137+
int(tile_size * (tail.y + tail_dir.y * progress)) + top_height, tile_size, tile_size,
138+
gx.blue)
165139

166-
// drawing top
140+
// draw score bar
167141
app.gg.draw_rect_filled(0, 0, canvas_size, top_height, gx.black)
168142
app.gg.draw_text(150, top_height / 2, 'Score: ${app.score}', gx.TextCfg{
169143
color: gx.white
@@ -178,14 +152,35 @@ fn on_frame(mut app App) {
178152
size: 65
179153
})
180154

181-
// checking if snake bit itself
182-
if app.snake[0] in app.snake[1..] {
183-
app.reset_game()
184-
}
185-
// checking if snake hit a wall
186-
if app.snake[0].x < 0 || app.snake[0].x >= game_size || app.snake[0].y < 0
187-
|| app.snake[0].y >= game_size {
188-
app.reset_game()
155+
if progress == 1 {
156+
// "snake" along
157+
mut prev := app.snake[0]
158+
app.snake[0] = app.snake[0] + app.dir
159+
160+
for i in 1 .. app.snake.len {
161+
tmp := app.snake[i]
162+
app.snake[i] = prev
163+
prev = tmp
164+
}
165+
166+
// add tail segment if food has been eaten
167+
if app.snake[0] == app.food {
168+
app.score++
169+
if app.score > app.best {
170+
app.best = app.score
171+
app.best.save()
172+
}
173+
app.snake << app.snake.last() + app.snake.last() - app.snake[app.snake.len - 2]
174+
app.move_food()
175+
}
176+
177+
if dir := app.dir_queue.pop() {
178+
if dir.x != -app.dir.x || dir.y != -app.dir.y {
179+
app.dir = dir
180+
}
181+
}
182+
183+
app.last_tick = time.ticks()
189184
}
190185

191186
app.gg.end()

0 commit comments

Comments
 (0)