Web-peli FRP:llä ja Elmillä

Instanssi 2013

Ville Tirronen

Ekaksi

FRP?

  • Functional
  • Reactive
  • Programming

Functional?

  • Ohjelmalla ei ole implisiittistä tilaa
    • Ei muuttuvia muuttujia
    • Ei silmukoita
  • Funktiot ovat arvoja
    • Funktion voi viedä parametrina
    • Funktion voi palauttaa paluuarvona

Reactive?

  • Callback / eventtipohjainen ohjelmointi on kurjaa

  • Runtime voi hoitaa että arvot päivittyvät paikalleen

  • Esitetään ohjelma funktiona signaalilta toiselle

  • Tätä varten on erillisiä ohjelmointikieliä. Kuten Elm.

Elm?

  • FRP-kieli.
  • Uusi ja karhea
  • Kääntyy webbisivuiksi ja javascriptiksi.
  • Yleiskäyttöinen!
  • Ks. kotisivu

Esimerkki


-- scene :: Signal (Int,Int) -> Signal Element
scene signal = lift . plainText . show $ signal
main = scene Mouse.position

Signaalimuunnin


-- scene :: Signal (Int,Int) -> Signal Element
scene signal = lift . plainText . show $ signal
main = scene (sampleOn Mouse.clicks Mouse.position)

Toinen signaalimuunnin


-- scene :: Signal [(Int,Int)] -> Signal Element
scene = collage 300 300 . box . solid green . line
main = lift scene (foldp (:) [] Mouse.position)

box x = [x]

Mitäs näillä sitten voi tehdä?

Speksi!

Speksi!

Pelikenttä


main = collage 500 200 
    [filled lightBlue (rect 500 200 (250,100))  -- Taivas
    ,filled green (maakappale (maankorkeus 0))] -- Kukkulat 

maakappale f =
    polygon ((500,f 500):(500,500):(0,500):(0, f 0)
             :map (\x -> (x*10,f (x*10))) [1 .. 50]) (0,0)

maankorkeus kuljettuMatka = \x ->  
    160 + round (30*sin (toFloat x/40+(toFloat kuljettuMatka)/10))

Liikettä


scene kuljettuMatka = collage 500 200 
    [filled lightBlue (rect 500 200 (250,100))
    ,filled green (maakappale (maankorkeus kuljettuMatka))]

main = lift scene (count (fps 40)) 

Uhkia kentälle..


vesi kuljettuMatka = \x -> 
    170 + round (5*sin(kuljettuMatka/5)*sin (x/5))

scene kuljettuMatka = collage 500 200 
    [filled lightBlue (rect 500 200 (250,100)) 
    ,filled blue  (maakappale (vesi  kuljettuMatka))  -- Vettä!
    ,filled green (maakappale (maankorkeus kuljettuMatka))] 

Hahmo


main = lift scene $ foldp paivita {kuljettuMatka = 0} (fps 40)

juokse tila = {tila| kuljettuMatka <- tila.kuljettuMatka + 1}
paivita s  ig = juokse

scene tila = collage 500 200 
    [...
    ,toForm (50,100) (image 40 40 "mario_walk_right.gif")
    ,filled green (maakappale (maankorkeus tila.kuljettuMatka))  
    ,filled blue  (maakappale (vesi        tila.kuljettuMatka))   
     ...]   

Painovoima


main = lift scene $ foldp paivita {kuljettuMatka=0, y=100, dy=0} 
                                  (fps 40)
juokse tila = {tila| kuljettuMatka <- tila.kuljettuMatka + 1
                   , y <- tila.y+tila.dy}

putoa  tila = {tila| dy <- p.dy + 0.3}

paivita sig = putoa . juokse

scene tilanne = collage 500 200 
    [...
    ,toForm (50,tila.y-16) (image 40 40 "mario_walk_right.gif") 
     ...]    

Maakosketus


maassa tila = tila.y >= maankorkeus tila.kuljettuMatka 50
osuMaahan tila = if maassa tila && tila.dy >= 0 
               then {tila| y <- maankorkeus tila.kuljettuMatka 50
                           , dy <- 0} 
               else tila
paivita sig = osuMaahan . putoa . juokse

Napit ja pomppu


delta = lift inSeconds (fps 50)
main = lift scene $ foldp paivita {kuljettuMatka = 0, y=100, dy=0}
                          (sampleOn delta (Keyboard.arrows))

hyppaa sig tila = if maassa {tila|y<-tila.y+5} && sig.y == 1  
                  then {tila| dy <- (0-7)} else tila

paivita sig = osuMaahan . putoa . juokse . hyppaa sig

Pisteet


main = lift scene $ foldp paivita 
                      {kuljettuMatka = 0, y=100, dy=0,pisteet=0} 
                      (sampleOn delta (Keyboard.arrows))
pisteet tila = if tila.y>175 
        then {tila| points <- 0}
        else {tila| points <- tila.points + 1}

paivita sig = pisteet . osuMaahan . putoa . juokse . hyppaa sig

scene tila = collage 500 200
    [...
    ,toForm (420,40) (plainText $ "Pisteet:"++show tila.points) 
     ...]

maakappale f = polygon ((500,f 500):(500,500):(0,500):(0, f 0)
                            :map (\x -> (x*10,f (x*10))) [1..50]) 
                            (0,0)

maankorkeus kuljettuMatka x = 
    160 + round (30*sin (toFloat x/40+(toFloat kuljettuMatka)/10))
vesi  kuljettuMatka x = 170 + round (5*sin(kuljettuMatka/5)*sin (x/5))

onGround p = p.y >= maankorkeus p.x 50

hitGround p = if onGround p && p.dy >= 0 
                then {p| y <- maankorkeus p.x 50, dy <- 0} else p
move p      = {p| x  <- p.x  + 1.2, y <- p.y + p.dy}
gravity p   = {p| dy <- p.dy + 0.3}
jump sig p  = if onGround {p|y<-p.y+5} && sig.y == 1 
                then {p| dy <- (0-7)} else p
score p     = if p.y>175 then {p| points <- 0} 
                         else {p| points <- p.points + 1}

update sig = score . hitGround . gravity . move . jump sig

scene game = collage 500 200
        [filled lightBlue $ rect 500 200 (250,100)  
        ,toForm (50,game.y-16) $ image 40 40 
           (if onGround {game | y <- game.y+5} 
              then "mario_walk_right.gif" 
              else "mario_jump_right.gif")
        ,filled blue  $ maakappale (vesi  game.x) 
        ,filled green $ maakappale (maankorkeus game.x)
        ,toForm (110,10) (plainText "Putkijuoksu v0.1")
        ,toForm (130,40) (plainText $ "Pisteet:"++show game.points)
        ]

delta = lift inSeconds (fps 50)
main =  lift scene $ foldp (update) {x=0, y=0, dy=0,points=0} 
                               (sampleOn delta (Keyboard.arrows))

Se siitä