Taken from the tutorial at http://t.co/7rH5lfyAvt and fixing for a few of the changes in Elm since it was filmed.
import Mouse
import Window
import Random
import Text
(width, height) = (400, 400)
(hWidth, hHeight) = (width / 2, height / 2)
relativeMouse : (Int, Int) -> (Int, Int) -> (Int, Int)
relativeMouse (ox, oy) (x, y) = (x - ox, -(y - oy))
center : (Int, Int) -> (Int, Int)
center (w, h) = ( w // 2, h // 2)
type Vec = (Float, Float)
vecAdd : Vec -> Vec -> Vec
vecAdd (ax, ay) (bx, by) = (ax + bx, ay + by)
vecSub : Vec -> Vec -> Vec
vecSub (ax, ay) (bx, by) = (ax - bx, ay - by)
vecLen : Vec -> Float
vecLen (x, y) = sqrt (x * x + y * y)
vecMulS : Vec -> Time -> Vec
vecMulS (x, y) t = (x * t, y * t)
type Pill = {pos:Vec, vel:Vec, rad:Float, col:Color}
defaultPill = { pos = (0, hHeight)
,vel = (0, -500)
,rad = 15
,col = lightRed
}
newPill : Float -> Color -> Pill
newPill x col = { defaultPill | pos <- (x, hHeight)
, col <- col }
defaultPlayer = { defaultPill | pos <- (0, (-hHeight - defaultPill.rad))
, col <- black }
data State = Play | GameOver | Start
type Game = {player: Pill, pills: [Pill], score: Int, state: State}
defaultGame = { player = defaultPlayer
, pills = []
, score = 0
, state = Start
}
data Event = Tick (Time, (Int, Int)) | Add Pill | Click
stepPlay : Event -> Game -> Game
stepPlay event g =
case event of
Tick (t, mp) -> let hit pill = (vecLen <| vecSub g.player.pos pill.pos) < g.player.rad + pill.rad
unculled = filter (\ {pos} -> snd pos > -hHeight ) g.pills
untouched = filter (not << hit) unculled
touched = filter hit unculled
hitCol c = not <| isEmpty <| filter (\{col} -> col == c) touched
hitBlue = hitCol lightBlue
hitRed = hitCol lightRed
out = let (x, y) = mp in abs (toFloat x) > hWidth || abs (toFloat y) > hHeight
g' = { g | player <- stepPlayer mp g.player
, pills <- map (stepPill t) untouched
, score <- if hitBlue then g.score + 1 else g.score }
in if hitRed || out then { defaultGame | score <- g'.score
, state <- GameOver } else g'
Add p -> { g | pills <- p :: g.pills }
Click -> g
click : Event -> Bool
click event =
case event of
Click -> True
_ -> False
stepGame : Event -> Game -> Game
stepGame event ({state} as g) =
let playGame = { defaultGame | state <- Play }
toPlay = if click event then playGame else g
in case state of
Play -> stepPlay event g
GameOver -> toPlay
Start -> toPlay
stepPill : Time -> Pill -> Pill
stepPill t p = { p | pos <- vecAdd p.pos <| vecMulS p.vel t }
stepPlayer : (Int, Int) -> Pill -> Pill
stepPlayer (x, y) p = { p | pos <- (toFloat x, toFloat y) }
tf : Float -> Float -> String -> Form
tf y scl str =
toText str |> Text.color grey
|> centered
|> toForm
|> scale scl
|> move (0, y)
render : (Int, Int) -> Game -> Element
render (w, h) game =
let formPill {rad, col, pos} = circle rad |> filled col
|> move pos
txts = case game.state of
Start -> [
tf 70 4 "BluePiLL",
tf 0 2 "Click to Start"
]
Play -> [ tf 0 4 (show game.score) ]
GameOver -> [
tf 70 4 "Game Over",
tf 0 4 (show game.score),
tf -50 2 "Click to Restart"
]
forms = txts ++ (map formPill <| game.player :: game.pills)
in color grey <| container w h middle
<| color white
<| collage width height forms
rand fn sig = lift fn (Random.float sig)
randX = rand (\r -> (width * r) - hWidth)
randCol = rand (\r -> if r < 0.1 then lightBlue else defaultPill.col)
interval = (every (second * 0.5))
event = merges [ lift Tick input
, lift2 (\x col -> Add (newPill x col )) (randX interval) (randCol interval)
, lift (\_ -> Click) Mouse.isDown
]
delta = (fps 30)
input = (,) <~ lift inSeconds delta
~ sampleOn delta (lift2 relativeMouse (lift center Window.dimensions) Mouse.position)
main = render <~ Window.dimensions ~ foldp stepGame defaultGame event