RosettaCodeData/Task/Snake/Julia/snake.julia

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()