Listing: giraffe
Dit is een simpel spelletje gemaakt door Rob van den Bogaard. Het idee kwam van zijn toen 4-jarige zoon, die zei dat hij wel een spelletje wilde met "een giraf die balletjes eet" :D
Wat jammer is aan deze versie, is dat het niet werkt op tablet..hoe zouden we dat het beste kunnen aanpassen?
module Main exposing (main)
import Playground exposing (..)
type Health
= NeedVitamins Int
| Dead
| ReadyForNextLevel
main =
let
memory =
{ balls =
[ { color = green, size = 12, x = 100, y = 50 }
, { color = blue, size = 10, x = 900, y = -50 }
]
, spots =
[]
, x = 0
, y = 0
, aim = 0
, velocity = ( 0, 0 )
, sunlight = rgb 250 245 255
, health = NeedVitamins 0
}
in
game view updateWhenHealthy memory
updateWhenHealthy computer memory =
case memory.health of
NeedVitamins _ ->
update computer memory
Dead ->
memory
ReadyForNextLevel ->
update computer memory
update computer memory =
let
-- aim 0 means zero degrees rotation of the face; in this case the eye
-- and mouth are aiming 45 degrees downwards relative to the horizon
-- we would like the giraffe to aim in the direction of the mouse cursor
-- tan aim = (c.y - y) / (c.x - x)
-- atan (tan aim) = aim in radians minus the 45 degree offset
-- atan2 is to help sort out the various negative value cases
newAim =
45
+ (180 / pi)
* atan2 (computer.mouse.y - memory.y) (abs (computer.mouse.x - memory.x))
pull =
( computer.mouse.x - memory.x - 50, computer.mouse.y - memory.y )
( ( newX, newY ), newVelocity ) =
moveGiraffe ( memory.x, memory.y ) newAim memory.velocity pull
movedBalls =
List.map (moveBall computer.time) memory.balls
( ballsEaten, ballsAfterEating ) =
List.foldl
(maybeEatBall memory.x memory.y memory.aim)
( 0, [] )
movedBalls
spotsAfterEating =
if ballsEaten > 0 then
let
random1 =
cos (spin 1 computer.time)
random2 =
sin (spin 1 computer.time)
newSpots =
( random1, random2 ) :: List.reverse memory.spots
in
List.reverse newSpots
else
memory.spots
newHealth =
case memory.health of
NeedVitamins amount ->
if List.length spotsAfterEating > 9 then
ReadyForNextLevel
else if ballsEaten > 0 then
NeedVitamins 0
else if amount < 1000 then
NeedVitamins (amount + 1)
else
Dead
Dead ->
Dead
ReadyForNextLevel ->
ReadyForNextLevel
in
{ memory
| aim = newAim
, x = newX
, y = newY
, velocity = newVelocity
, balls = ballsAfterEating
, spots = spotsAfterEating
, health = newHealth
}
moveGiraffe ( x, y ) aim ( vx, vy ) ( pullX, pullY ) =
let
drag =
2
vx_ =
(vx + pullX / 100) / drag
vy_ =
(vy + pullY / 100) / drag
x_ =
clamp -1000 1000 (x + vx_)
y_ =
clamp -150 200 (y + vy_)
in
( ( x_, y_ ), ( vx_, vy_ ) )
moveBall time b =
let
x_ =
b.x - 2
in
{ b
| size = b.size + wave -0.1 0.1 1 time
, x =
if x_ > -1000 then
x_
else
1000
, y = b.y + wave -0.2 0.2 20 time
}
maybeEatBall x y aim b ( eaten, ballsSoFar ) =
-- if the ball is behind the giraffe it can't be eaten; in that case we just
-- return the amount eaten so far and add the ball unchanged to the list
-- of balls we checked so far
-- we compare with x + 5, just to the right of the position of the giraffe
-- because its mouth is on the right side of its head, and to avoid division
-- by zero in further calculations
if b.x < x + 5 then
( eaten, b :: ballsSoFar )
else
let
distanceToBall =
sqrt ((b.x - x) ^ 2 + (b.y - y) ^ 2)
aimToBall =
45 + (180 / pi) * atan2 (b.y - y) (b.x - x)
in
if distanceToBall > 50 || distanceToBall < 5 then
-- this ball is too far away or to close by to get eaten; just
-- return the number of eaten balls so far and add the ball
-- unchanged to the list of balls already checked
( eaten, b :: ballsSoFar )
else if abs (aimToBall - aim) > 20 then
-- the ball is near but the giraffe is not aiming its mouth in its
-- direction, so it won't get eaten
( eaten, b :: ballsSoFar )
else
-- yesss! we can eat the ball; increase the "eaten" score and move
-- the ball out of sight to the right so it'll reappear as a new one
( eaten + 1, { b | x = 1000 } :: ballsSoFar )
view computer memory =
[ background memory.sunlight
, giraffe computer.time memory.sunlight memory.health memory.spots memory.aim
|> move memory.x memory.y
, balls memory.balls
]
background sunlight =
group
[ rectangle sunlight 2000 2000
, rectangle lightBrown 2000 400
|> moveDown 400
]
giraffe time sunlight health listOfSpots nod =
let
color =
case health of
NeedVitamins amount ->
if amount < 500 then
yellow
else
lightYellow
Dead ->
gray
ReadyForNextLevel ->
rgb (wave 0 255 3 time) (wave 0 255 5 time) (wave 0 255 1 time)
in
group
[ head sunlight color nod
, legs color 40 4
|> move 0 (-40 * 8)
, spots health listOfSpots
|> move -20 -60
]
|> (if health == ReadyForNextLevel then
moveUp (wave 0 50 0.5 time)
else
identity
)
head sunlight color nod =
group
[ headNeck color 40 8
, face sunlight
|> rotate nod
]
face sunlight =
group
[ circle black 15
|> move 10 10
, circle sunlight 10
|> move 25 -25
, headNeck orange 10 3
|> moveUp 60
|> moveLeft 10
]
headNeck color size neckLength =
group
[ circle color size
, rectangle color size (size * neckLength)
|> moveDown (size * (neckLength / 2))
|> moveLeft (size / 2)
]
legs color size legLength =
let
leg =
rectangle color (size * 2 / 3) (size * legLength)
|> moveLeft (size / 3)
|> moveDown (size * legLength / 2)
in
group
[ moveLeft (size * 4 / 3) leg
, leg
, circle color size
|> moveLeft size
|> moveDown (size / 6)
]
spot health i ( x, y ) =
let
color =
case health of
NeedVitamins amount ->
if amount < 500 then
brown
else
lightBrown
Dead ->
darkGray
ReadyForNextLevel ->
brown
in
circle color 12
|> move (x * 8) -(toFloat i * 25 + y * 5)
spots health =
group << List.indexedMap (spot health)
balls =
group << List.map ball
ball { color, size, x, y } =
move x y (circle color size)