Snake: Difference between revisions
Content deleted Content added
→{{header|C}}: c99 feature removed |
|||
Line 851: | Line 851: | ||
=={{header|Julia}}== |
=={{header|Julia}}== |
||
Makie version in 99 lines. |
|||
Gtk GUI library version. |
|||
<lang julia>using |
<lang julia>using Makie |
||
mutable struct SnakeGame |
|||
@enum Direction Up Right Down Left |
|||
height |
|||
const TITLE = "Snake Game" |
|||
width |
|||
const WIDTHPIXELS, HEIGHTPIXELS, RPIXELS = 640, 480, 10 |
|||
snake |
|||
const TICKTIME = 0.2 |
|||
food |
|||
end |
|||
opposite(d::Direction) = d == Up ? Down : d == Down ? Up : d == Right ? Left : Right |
|||
randpoint(width, height) = Point(rand(2:width-1), rand(2:height-1)) |
|||
colors = Dict("black" => colorant"black", "red" => colorant"red", |
|||
"silver" => colorant"silver", "wheat" => colorant"wheat") |
|||
randcolor() = rand([ |
|||
colorant"rgb(0,255,4)", |
|||
colorant"rgb(0,81,255)", |
|||
colorant"rgb(75,0,130)", |
|||
colorant"rgb(255,141,0)", |
|||
colorant"rgb(227,255,0)", |
|||
]) |
|||
function SnakeGame(;height=6, width=8) |
|||
mutable struct Snake |
|||
snake = [rand(CartesianIndices((height, width)))] |
|||
facing::Direction |
|||
food = rand(CartesianIndices((height, width))) |
|||
lastdirection::Direction |
|||
while food == snake[1] |
|||
head::Point |
|||
food = rand(CartesianIndices((height, width))) |
|||
tail::Vector{Point} |
|||
fruit::Point |
|||
width::Int |
|||
height::Int |
|||
score::Int |
|||
running::Bool |
|||
taildelta::Int |
|||
startcolor::String |
|||
fruitcolor::String |
|||
wallcolor::String |
|||
wall_collide_ends::Bool |
|||
function Snake(width, height, collidemode) |
|||
return new(Right, Right, randpoint(width, height), Point[], |
|||
randpoint(width, height), width, height, 0, true, 0, |
|||
"black", "red", "silver", collidemode) |
|||
end |
end |
||
SnakeGame(height, width, snake, food) |
|||
end |
end |
||
function |
function step!(game, direction) |
||
next_head = game.snake[1] + direction |
|||
if !snake.running |
|||
next_head = CartesianIndex(mod.(next_head.I, Base.OneTo.((game.height, game.width)))) # allow crossing boundry |
|||
snake.score = 0; |
|||
if is_valid(game, next_head) |
|||
snake.running = true |
|||
pushfirst!(game.snake, next_head) |
|||
if next_head == game.food |
|||
snake.fruit = randpoint(snake.width, snake.height) |
|||
length(game.snake) < game.height * game.width && init_food!(game) |
|||
else |
|||
pop!(game.snake) |
|||
end |
|||
true |
|||
else |
|||
false |
|||
end |
end |
||
end |
end |
||
is_valid(game, position) = position ∉ game.snake |
|||
turn(snake, d::Direction) = if snake.lastdirection != opposite(d) snake.facing = d end |
|||
function |
function init_food!(game) |
||
p = rand(CartesianIndices((game.height, game.width))) |
|||
if snake.wall_collide_ends |
|||
while !is_valid(game, p) |
|||
snake.running = false |
|||
p = rand(CartesianIndices((game.height, game.width))) |
|||
return false |
|||
end |
end |
||
game.food = p |
|||
end |
end |
||
function |
function play(;n=10,t=0.5) |
||
game = Node(SnakeGame(;width=n,height=n)) |
|||
if snake.running |
|||
scene = Scene(resolution = (1000, 1000), raw = true, camera = campixel!) |
|||
# start of tail becomes where head was before |
|||
display(scene) |
|||
pushfirst!(snake.tail, snake.head) |
|||
snake.lastdirection = snake.facing |
|||
area = scene.px_area |
|||
# move head, tail will follow |
|||
poly!(scene, area) |
|||
f = snake.facing |
|||
if f == Up |
|||
grid_size = @lift((widths($area)[1] / $game.height, widths($area)[2] / $game.width)) |
|||
snake.head = Point(snake.head.x, snake.head.y - 1) |
|||
elseif f == Right |
|||
snake_boxes = @lift([FRect2D((p.I .- (1,1)) .* $grid_size , $grid_size) for p in $game.snake]) |
|||
snake.head = Point(snake.head.x + 1, snake.head.y) |
|||
poly!(scene, snake_boxes, color=:blue, strokewidth = 5, strokecolor = :black) |
|||
elseif f == Down |
|||
snake.head = Point(snake.head.x, snake.head.y + 1) |
|||
snake_head_box = @lift(FRect2D(($game.snake[1].I .- (1,1)) .* $grid_size , $grid_size)) |
|||
elseif f == Left |
|||
poly!(scene, snake_head_box, color=:black) |
|||
snake.head = Point(snake.head.x - 1, snake.head.y) |
|||
snake_head = @lift((($game.snake[1].I .- 0.5) .* $grid_size)) |
|||
end |
|||
scatter!(scene, snake_head, marker='◉', color=:blue, markersize=@lift(minimum($grid_size))) |
|||
# hit a wall? End if collidemode, or wrap to opposite wall |
|||
if snake.head.x > snake.width - 1 |
|||
food_position = @lift(($game.food.I .- (0.5,0.5)) .* $grid_size) |
|||
collided(snake) && (snake.head = Point(2, snake.head.y)) |
|||
scatter!(scene, food_position, color=:red, marker='♥', markersize=@lift(minimum($grid_size))) |
|||
elseif snake.head.x < 2 |
|||
collided(snake) && (snake.head = Point(snake.width - 1, snake.head.y)) |
|||
score_text = @lift("Score: $(length($game.snake)-1)") |
|||
text!(scene, score_text, color=:gray, position = @lift((widths($area)[1]/2, widths($area)[2])), textsize = 50, align = (:center, :top)) |
|||
collided(snake) && (snake.head = Point(snake.head.x, 2)) |
|||
elseif snake.head.y < 2 |
|||
direction = Ref{Any}(nothing) |
|||
collided(snake) && (snake.head = Point(snake.head.x, snake.height - 1)) |
|||
# hit own tail? End game |
|||
on(scene.events.keyboardbuttons) do but |
|||
elseif snake.head in snake.tail |
|||
if ispressed(but, Keyboard.left) |
|||
direction[] = CartesianIndex(-1,0) |
|||
# hit fruit? Score, make another fruit, make tail longer |
|||
elseif |
elseif ispressed(but, Keyboard.up) |
||
direction[] = CartesianIndex(0,1) |
|||
elseif ispressed(but, Keyboard.down) |
|||
direction[] = CartesianIndex(0,-1) |
|||
elseif ispressed(but, Keyboard.right) |
|||
snake.fruit = randpoint(snake.width, snake.height) |
|||
direction[] = CartesianIndex(1,0) |
|||
snake.taildelta = 3 |
|||
# add to tail by not removing the end if taildelta is positive |
|||
elseif snake.taildelta > 0 |
|||
snake.taildelta -= 1 |
|||
# end of tail has moved forward, so remove prior end |
|||
else |
|||
pop!(snake.tail) |
|||
end |
end |
||
end |
end |
||
end |
|||
last_dir = nothing |
|||
function snakeapp(collide=true) |
|||
while true |
|||
win = GtkWindow(TITLE, WIDTHPIXELS, HEIGHTPIXELS) |> (can = GtkCanvas()) |
|||
# avoid turn back |
|||
snake = Snake(WIDTHPIXELS ÷ RPIXELS, HEIGHTPIXELS ÷ RPIXELS, collide) |
|||
if !isnothing(direction[]) && (isnothing(last_dir) || direction[] != -last_dir) |
|||
@guarded draw(can) do widget |
|||
last_dir = direction[] |
|||
buf = fill(colors["wheat"], WIDTHPIXELS, HEIGHTPIXELS) |
|||
r = RPIXELS - 1 |
|||
for x in 1:snake.width, y in 1:snake.height |
|||
px, py, gridpos = (x - 1) * RPIXELS + 1, (y - 1) * RPIXELS + 1, Point(x, y) |
|||
if x == 1 || x == snake.width || y == 1 || y == snake.height |
|||
# wall |
|||
buf[px:px+r, py:py+r] .= colors[snake.wallcolor] |
|||
elseif gridpos == snake.head || gridpos in snake.tail |
|||
# snake parts |
|||
if snake.running |
|||
buf[px:px+r, py:py+r] .= randcolor() |
|||
else |
|||
buf[px:px+r, py:py+r] .= colors[snake.startcolor] |
|||
end |
|||
elseif gridpos == snake.fruit |
|||
# fruit |
|||
buf[px:px+r, py:py+r] .= colors[snake.fruitcolor] |
|||
end |
|||
end |
end |
||
if !isnothing(last_dir) |
|||
if step!(game[], last_dir) |
|||
end |
|||
game[] = game[] |
|||
function kbinput(w, event) |
|||
else |
|||
break |
|||
help() |
|||
elseif snake.running |
|||
if k in (65362, 119, 87) |
|||
turn(snake, Left) |
|||
elseif k in (65364, 115, 83) |
|||
turn(snake, Right) |
|||
elseif k in (65361, 97, 56) |
|||
turn(snake, Up) |
|||
elseif k in (65363, 100, 68) |
|||
turn(snake, Down) |
|||
end |
end |
||
elseif k == 32 |
|||
restart(snake) |
|||
end |
end |
||
sleep(t) |
|||
function tick() |
|||
next(snake) |
|||
set_gtk_property!(win, :title, TITLE * " " * |
|||
(snake.running ? "(Current score: $(snake.score))" : "Press Space to Restart") * |
|||
" (H for Help)") |
|||
sleep(TICKTIME * (0.95^(snake.score/10))) |
|||
end |
|||
function help() |
|||
helptext = """ |
|||
SNAKE GAME |
|||
UP: \u2191 | W | w |
|||
DOWN: \u2193 | S | s |
|||
RIGHT: \u2192 | D | d |
|||
LEFT: \u2190 | A | a |
|||
Scoring: Each fruit eaten is worth 10 points. |
|||
In collision mode, hitting a wall ends the game. |
|||
In non-collision mode, hitting wall adds one segment to tail. |
|||
SPACEBAR TO RESTART |
|||
""" |
|||
warn_dialog(helptext, win) |
|||
end |
|||
inputhid = signal_connect(kbinput, win, "key-press-event") |
|||
showall(win) |
|||
while true |
|||
tick() |
|||
draw(can) |
|||
show(can) |
|||
showall(win) |
|||
end |
end |
||
end |
end |
||
play() |
|||
snakeapp(false) |
|||
</lang> |
</lang> |
||