103 lines
3.2 KiB
Plaintext
103 lines
3.2 KiB
Plaintext
using GLMakie, GeometryBasics
|
|
|
|
mutable struct SnakeGame
|
|
height::Int
|
|
width::Int
|
|
snake::Vector{CartesianIndex{2}}
|
|
food::CartesianIndex{2}
|
|
end
|
|
|
|
function SnakeGame(;height=6, width=8)
|
|
snake = [rand(CartesianIndices((height, width)))]
|
|
food = rand(CartesianIndices((height, width)))
|
|
while food == snake[1]
|
|
food = rand(CartesianIndices((height, width)))
|
|
end
|
|
SnakeGame(height, width, snake, food)
|
|
end
|
|
|
|
function step!(game, direction)
|
|
next_head = game.snake[1] + direction
|
|
next_head = CartesianIndex(mod.(next_head.I, Base.OneTo.((game.height, game.width)))) # allow crossing boundry
|
|
if is_valid(game, next_head)
|
|
pushfirst!(game.snake, next_head)
|
|
if next_head == game.food
|
|
length(game.snake) < game.height * game.width && init_food!(game)
|
|
else
|
|
pop!(game.snake)
|
|
end
|
|
true
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
is_valid(game, position) = position ∉ game.snake
|
|
|
|
function init_food!(game)
|
|
p = rand(CartesianIndices((game.height, game.width)))
|
|
while !is_valid(game, p)
|
|
p = rand(CartesianIndices((game.height, game.width)))
|
|
end
|
|
game.food = p
|
|
end
|
|
|
|
function play(;n=10,t=0.5)
|
|
game = Observable(SnakeGame(;width=n,height=n))
|
|
scene = Scene(resolution = (1000, 1000), raw = true, camera = campixel!)
|
|
display(scene)
|
|
|
|
area = scene.px_area
|
|
poly!(scene, area)
|
|
|
|
grid_size = @lift((widths($area)[1] / $game.height, widths($area)[2] / $game.width))
|
|
|
|
snake_boxes = @lift([FRect2D((p.I .- (1,1)) .* $grid_size , $grid_size) for p in $game.snake])
|
|
poly!(scene, snake_boxes, color=:blue, strokewidth = 5, strokecolor = :black)
|
|
|
|
snake_head_box = @lift(FRect2D(($game.snake[1].I .- (1,1)) .* $grid_size , $grid_size))
|
|
poly!(scene, snake_head_box, color=:black)
|
|
snake_head = @lift((($game.snake[1].I .- 0.5) .* $grid_size))
|
|
scatter!(scene, snake_head, marker='◉', color=:blue, markersize=@lift(minimum($grid_size)))
|
|
|
|
food_position = @lift(($game.food.I .- (0.5,0.5)) .* $grid_size)
|
|
scatter!(scene, food_position, color=:red, marker='♥', markersize=@lift(minimum($grid_size)))
|
|
|
|
score_text = @lift("Score: $(length($game.snake)-1)")
|
|
text!(scene, score_text, color=:gray, position = @lift((widths($area)[1]/2, widths($area)[2])), fontsize = 50, align = (:center, :top))
|
|
|
|
direction = Ref{Any}(nothing)
|
|
|
|
on(events(scene).keyboardbutton) do but
|
|
if but.action == Keyboard.press || but.action == Keyboard.repeat
|
|
if but.key == Keyboard.left
|
|
direction[] = CartesianIndex(-1,0)
|
|
elseif but.key == Keyboard.up
|
|
direction[] = CartesianIndex(0,1)
|
|
elseif but.key == Keyboard.down
|
|
direction[] = CartesianIndex(0,-1)
|
|
elseif but.key == Keyboard.right
|
|
direction[] = CartesianIndex(1,0)
|
|
end
|
|
end
|
|
end
|
|
|
|
last_dir = nothing
|
|
while true
|
|
# avoid turn back
|
|
if !isnothing(direction[]) && (isnothing(last_dir) || direction[] != -last_dir)
|
|
last_dir = direction[]
|
|
end
|
|
if !isnothing(last_dir)
|
|
if step!(game[], last_dir)
|
|
game[] = game[]
|
|
else
|
|
break
|
|
end
|
|
end
|
|
sleep(t)
|
|
end
|
|
end
|
|
|
|
play()
|