Untitled

mail@pastecode.io avatar
unknown
plain_text
6 days ago
4.6 kB
2
Indexable
Never
package main

import "core:math/linalg"
import "core:fmt"
import "core:time"
import "core:math/rand"
import rl "vendor:raylib"

// MARK: Consts

WINDOW_SIZE :: 500
BLOCK_SIZE :: 25
UPDATE_INTERVAL_MS :: 100

// MARK: Structs

GameState :: struct {
	// Snake
	head: Point,
	body: [dynamic]Point,
	direction: Direction,
	food: Point,

	last_update_ts: time.Time,
	is_game_over: bool
}

Direction :: enum {
	LEFT,
	RIGHT,
	DOWN,
	UP
}

Point :: [2]int

// MARK: Globals

g_game_state: ^GameState


// MARK: Main

update :: proc() {

	if g_game_state.is_game_over {
		if rl.IsKeyPressed(.R) {
			g_game_state.is_game_over = false
			mem_free()
			reset_state()
		}

		return
	}

	// Input handling
	dir := g_game_state.direction
	if rl.IsKeyDown(.W) && dir != .DOWN 	do g_game_state.direction = .UP
	if rl.IsKeyDown(.A) && dir != .RIGHT do g_game_state.direction = .LEFT
	if rl.IsKeyDown(.S) && dir != .UP 	do g_game_state.direction = .DOWN
	if rl.IsKeyDown(.D) && dir != .LEFT 	do g_game_state.direction = .RIGHT

	// Snake update
	elapsed_ts := time.duration_milliseconds(time.since(g_game_state.last_update_ts))
	if elapsed_ts <= UPDATE_INTERVAL_MS {
		return
	}

	g_game_state.last_update_ts = time.now()
	prev_pos := g_game_state.head
	new_head := g_game_state.head + dir_to_point(g_game_state.direction)

	// Wall collision
	grid_size := WINDOW_SIZE / BLOCK_SIZE
	is_x_valid := new_head.x < 0 || new_head.x >= grid_size
	is_y_valid := new_head.y < 0 || new_head.y >= grid_size
	if is_x_valid || is_y_valid {
		g_game_state.is_game_over = true
		return
	}

	// Snake collision
	for pt in g_game_state.body {
		if new_head == pt {
			g_game_state.is_game_over = true
			return
		}
	}

	// Snake movement
	g_game_state.head = new_head
	for &pt in g_game_state.body {
		temp := pt
		pt = prev_pos
		prev_pos = temp
	}

	// Food collision
	if g_game_state.head == g_game_state.food {
		append(&g_game_state.body, g_game_state.food)
		g_game_state.food = get_rand_pos()
	}
}

draw :: proc() {
	rl.BeginDrawing()
	defer rl.EndDrawing()

	bg_color := rl.DARKGRAY if g_game_state.is_game_over else rl.DARKBROWN
	rl.ClearBackground(bg_color)

	// Draw grid
	num_rows := WINDOW_SIZE / BLOCK_SIZE
	grid_color := rl.ColorAlpha(rl.GRAY, 0.5)
	for i in 0..<num_rows {
		x := f32(i * BLOCK_SIZE)
		rl.DrawLineV({x, 0}, {x, WINDOW_SIZE}, grid_color)
		rl.DrawLineV({0, x}, {WINDOW_SIZE, x}, grid_color)
	}

	// Draw score
	score := fmt.caprint(len(g_game_state.body))
	SCORE_FONT_SIZE :: 150
	score_width := rl.MeasureText(score, SCORE_FONT_SIZE)
	score_color := rl.ColorAlpha(rl.BEIGE, 0.8)
	rl.DrawText(score, (WINDOW_SIZE / 2) - score_width / 2, (WINDOW_SIZE / 2) - 70, SCORE_FONT_SIZE, score_color)

	// Draw snake
	{
		// Head
		snake_head := point_to_vec2(g_game_state.head * BLOCK_SIZE)
		rl.DrawRectangleV(snake_head, {BLOCK_SIZE, BLOCK_SIZE}, rl.PINK)

		// Body
		for pt in g_game_state.body {
			point := point_to_vec2(pt * BLOCK_SIZE)
			rl.DrawRectangleV(point, {BLOCK_SIZE, BLOCK_SIZE}, rl.WHITE)
		}
	}

	// Draw Food
	food_pos := point_to_vec2(g_game_state.food * BLOCK_SIZE)
	rl.DrawRectangleV(food_pos, {BLOCK_SIZE, BLOCK_SIZE}, rl.SKYBLUE)

	// Draw restart text
	if g_game_state.is_game_over {
		rl.DrawText("Press 'R' to restart", 10, 10, 30, rl.WHITE)
	}
}

// MARK: Utils

point_to_vec2 :: proc(point: Point) -> rl.Vector2 {
	return { f32(point.x), f32(point.y) }
}

dir_to_point :: proc(dir: Direction) -> (ret: Point) {
	switch(dir) {
		case .LEFT:
			ret = {-1, 0}
		case .RIGHT:
			ret = {1, 0}
		case .DOWN:
			ret = {0, 1}
		case .UP:
			ret = {0, -1}
	}

	return ret
}

get_rand_pos :: proc() -> Point {
	range := i32(WINDOW_SIZE / BLOCK_SIZE)
	return {
		int(rand.int31_max(range)),
		int(rand.int31_max(range)),
	}
}

// MARK: Exports

@(export)
init_window :: proc() {
	rl.SetConfigFlags({.WINDOW_RESIZABLE})
	rl.InitWindow(WINDOW_SIZE, WINDOW_SIZE, "snake")
	rl.SetTargetFPS(120)
}

@(export)
tick :: proc() {
	update()
	draw()
}

@(export)
mem_free :: proc() {
	free(g_game_state)
}

@(export)
reset_state :: proc() {
	half_screen := (WINDOW_SIZE / BLOCK_SIZE) / 2

	g_game_state = new(GameState)
	g_game_state^ = GameState {
		head = {half_screen - 1, half_screen - 1},
		food = get_rand_pos()
	}
}

@(export)
state :: proc() -> rawptr {
	return g_game_state
}

@(export)
reload_state :: proc(mem: rawptr) {
	g_game_state = (^GameState)(mem)
}

@(export)
is_restart :: proc(mem: rawptr) -> bool {
	return rl.IsKeyDown(.LEFT_SHIFT) && rl.IsKeyPressed(.R)
}

@(export)
is_close_window :: proc() -> bool {
	return rl.WindowShouldClose()
}

@(export)
close_window :: proc() { 
	rl.CloseWindow()
}
Leave a Comment