v/examples/snek/snek.v
Eliyaan (Nopana) bbb61ab368
Some checks failed
Graphics CI / gg-regressions (push) Waiting to run
vlib modules CI / build-module-docs (push) Waiting to run
native backend CI / native-backend-ubuntu (push) Waiting to run
vab CI / v-compiles-os-android (push) Waiting to run
native backend CI / native-backend-windows (push) Waiting to run
Shy and PV CI / v-compiles-puzzle-vibes (push) Waiting to run
Sanitized CI / sanitize-undefined-clang (push) Waiting to run
Sanitized CI / sanitize-undefined-gcc (push) Waiting to run
Sanitized CI / tests-sanitize-address-clang (push) Waiting to run
Sanitized CI / sanitize-address-msvc (push) Waiting to run
Sanitized CI / sanitize-address-gcc (push) Waiting to run
Sanitized CI / sanitize-memory-clang (push) Waiting to run
sdl CI / v-compiles-sdl-examples (push) Waiting to run
Time CI / time-linux (push) Waiting to run
Time CI / time-macos (push) Waiting to run
Time CI / time-windows (push) Waiting to run
toml CI / toml-module-pass-external-test-suites (push) Waiting to run
Tools CI / tools-linux (clang) (push) Waiting to run
Tools CI / tools-linux (gcc) (push) Waiting to run
Tools CI / tools-linux (tcc) (push) Waiting to run
Tools CI / tools-macos (clang) (push) Waiting to run
Tools CI / tools-windows (gcc) (push) Waiting to run
Tools CI / tools-windows (msvc) (push) Waiting to run
Tools CI / tools-windows (tcc) (push) Waiting to run
Tools CI / tools-docker-ubuntu-musl (push) Waiting to run
vab CI / vab-compiles-v-examples (push) Waiting to run
wasm backend CI / wasm-backend (ubuntu-22.04) (push) Waiting to run
wasm backend CI / wasm-backend (windows-2022) (push) Waiting to run
Workflow Lint / lint-yml-workflows (push) Has been cancelled
gg, gx: deprecate gx and replace all occurences with gg (which now contains all the functionality of gx) (#24966)
2025-08-14 19:53:56 +03:00

181 lines
4.1 KiB
V

import gg
import os
import rand
import time
import math.vec { Vec2 }
// constants
const font = $embed_file('../assets/fonts/RobotoMono-Regular.ttf')
const top_height = 100
const canvas_size = 700
const game_size = 17
const tile_size = canvas_size / game_size
const tick_rate_ms = 100
const high_score_file_path = os.join_path(os.cache_dir(), 'v', 'examples', 'snek')
// types
type HighScore = int
type Vec = Vec2[int]
fn (mut h HighScore) save() {
os.mkdir_all(os.dir(high_score_file_path)) or { return }
os.write_file(high_score_file_path, (*h).str()) or { return }
}
fn (mut h HighScore) load() {
h = (os.read_file(high_score_file_path) or { '' }).int()
}
struct App {
mut:
gg &gg.Context = unsafe { nil }
score int
best HighScore
snake []Vec
dir Vec
dir_queue []Vec
food Vec
last_tick i64
}
// utility
fn (mut app App) reset_game() {
app.score = 0
app.snake = [Vec{3, 8}, Vec{2, 8}, Vec{1, 8}, Vec{0, 8}]
app.dir = Vec{1, 0}
app.dir_queue = []
app.food = Vec{10, 8}
app.last_tick = time.ticks()
}
fn (mut app App) move_food() {
for {
x := rand.intn(game_size) or { 0 }
y := rand.intn(game_size) or { 0 }
app.food = Vec{x, y}
if app.food !in app.snake {
return
}
}
}
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))
// draw everything:
app.gg.begin()
// draw food
app.gg.draw_rect_filled(tile_size * app.food.x, tile_size * app.food.y + top_height,
tile_size, tile_size, gg.red)
// draw snake
for pos in app.snake[..app.snake.len - 1] {
app.gg.draw_rect_filled(tile_size * pos.x, tile_size * pos.y + top_height, tile_size,
tile_size, gg.blue)
}
// draw partial head
head := app.snake[0]
app.gg.draw_rect_filled(tile_size * (head.x + app.dir.x * progress), tile_size * (head.y +
app.dir.y * progress) + top_height, tile_size, tile_size, gg.blue)
// draw partial tail
tail := app.snake.last()
tail_dir := app.snake[app.snake.len - 2] - tail
app.gg.draw_rect_filled(tile_size * (tail.x + tail_dir.x * progress), tile_size * (tail.y +
tail_dir.y * progress) + top_height, tile_size, tile_size, gg.blue)
// draw score bar
app.gg.draw_rect_filled(0, 0, canvas_size, top_height, gg.black)
app.gg.draw_text(150, top_height / 2, 'Score: ${app.score}', gg.TextCfg{
color: gg.white
align: .center
vertical_align: .middle
size: 65
})
app.gg.draw_text(canvas_size - 150, top_height / 2, 'Best: ${app.best}', gg.TextCfg{
color: gg.white
align: .center
vertical_align: .middle
size: 65
})
if progress == 1 {
// "snake" along
mut prev := app.snake[0]
app.snake[0] = app.snake[0] + app.dir
for i in 1 .. app.snake.len {
app.snake[i], prev = prev, app.snake[i]
}
// add tail segment if food has been eaten
if app.snake[0] == app.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.move_food()
}
if app.dir_queue.len > 0 {
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()
}
// events
fn on_keydown(key gg.KeyCode, mod gg.Modifier, mut app App) {
app.dir_queue << match key {
.w, .up {
Vec{0, -1}
}
.s, .down {
Vec{0, 1}
}
.a, .left {
Vec{-1, 0}
}
.d, .right {
Vec{1, 0}
}
else {
return
}
}
}
mut app := App{}
app.reset_game()
app.best.load()
mut font_copy := font
app.gg = gg.new_context(
bg_color: gg.white
frame_fn: on_frame
keydown_fn: on_keydown
user_data: &app
width: canvas_size
height: top_height + canvas_size
window_title: 'snek'
font_bytes_normal: unsafe { font_copy.data().vbytes(font_copy.len) }
)
app.gg.run()