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 Colors, Gtk, GtkUtilities
<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 restart(snake)
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
snake.head = randpoint(snake.width, snake.height)
pushfirst!(game.snake, next_head)
if next_head == game.food
snake.fruit = randpoint(snake.width, snake.height)
empty!(snake.tail)
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 collided(snake)
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
return true
game.food = p
end
end


function next(snake)
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))
elseif snake.head.y > snake.height - 1
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
snake.running = false
if ispressed(but, Keyboard.left)
direction[] = CartesianIndex(-1,0)
# hit fruit? Score, make another fruit, make tail longer
elseif snake.head == snake.fruit
elseif ispressed(but, Keyboard.up)
snake.score += 10
direction[] = CartesianIndex(0,1)
# new fruit
elseif ispressed(but, Keyboard.down)
while (snake.fruit == snake.head) || (snake.fruit in snake.tail)
direction[] = CartesianIndex(0,-1)
elseif ispressed(but, Keyboard.right)
snake.fruit = randpoint(snake.width, snake.height)
end
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
ctx = Gtk.getgc(can)
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
copy!(can, buf)
if !isnothing(last_dir)
if step!(game[], last_dir)
end
game[] = game[]
function kbinput(w, event)
k = event.keyval
else
if k == 74 || k == 104
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
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>