-
-
Save raimohanska/8639343 to your computer and use it in GitHub Desktop.
module PrincessVsLion where | |
import Keyboard | |
-- character positions | |
princess = foldp inc 4 (pressesOf Keyboard.space) | |
lion = foldp inc 0 (fps 2) | |
-- combined game state | |
gameState = lift2 makeState princess lion | |
-- main function | |
main = lift asText gameState | |
-- helpers | |
pressesOf key = keepIf id False key | |
inc _ prev = prev + 1 | |
makeState p l = { princess= p, lion= l } |
In Bacon.js you could
gameResults = spacePress.flatMapFirst ->
princessPos = spacePress.scan 4, inc
lionPos = Bacon.interval(500).scan 0, inc
both = Bacon.combineTemplate { princessPos, lionPos }
lionWon = both.filter ({princessPos, lionPos}) -> princessPos == lionPos
princessWon = princessPos.filter (pos) -> pos >= 13
endResult = lionWon.map("Lion won").merge(princessWon.map("Princess won"))
princessPos.takeUntil(endResult).onValue(drawPrincess)
lionPos.takeUntil(endResult).onValue(drawLion)
return endResult
gameResults.log()
The stuff inside the function passed to flatMapFirst
is probably doable in Elm. Except you cannot have signals without initial value like the lionWon
and princessWon
signals above.
But restarting is a no-do, right? To me it seems it makes it much harder to compose stuff cleanly in Elm.
Please prove me wrong, I'll be mega happy!
If I understand Elm right, you just end up having single "whole world state", if you want to be able to alter it as single entity (e.g. reset to defaults). Using Bacon.js
vocabulary: you work (almost) only with Properties. You don't really have flatMap
for Properties there either.
And you can accumulate state with Applicative
instance of Signal
only.
import Keyboard
pressesOf key = keepIf id False (Keyboard.isDown key)
spaces = lift (\_ -> Just 10) (pressesOf 32) -- Space
resets = lift (\_ -> Nothing) (pressesOf 82) -- R
incs = lift (\_ -> Just 1) (pressesOf 65) -- A
events : Signal (Maybe number)
events = merges [spaces, incs, resets]
count : Signal number
count = foldp countUpdate 0 events
countUpdate : Maybe number -> number -> number
countUpdate next prev =
let c = maybe 0 ((+) prev) next
in if c > 100 then 0 else c -- over 100, reset as well
main = lift asText count
I guess monadic bind would make sense in use cases if eg HTTP.sendGet
would be of type string -> Signal (Response String)
, but it's cleverly made into Signal string -> Signal (Response String)
. Applicative
is enough there as well.
Now it works!
@phadej you're right, there has to be a function proceeding the "whole world state". And you have to forget almost everything you've learn about Rx/Bacon.js conventions. There's no temporal composition. Just a big state machine. Yet, the end result is quite nice. Except it's still not restartable.
module PrincessVsLion where
import Keyboard
data GameState = Ongoing Int Int | LionWon | PrincessWon
proceedGame keyPresses state = case state of
LionWon -> LionWon
PrincessWon -> PrincessWon
Ongoing princess lion ->
check ((keyPresses `div` 2) + 4) (lion + 1)
check princess lion =
if | lion == princess -> LionWon
| princess >= 13 -> PrincessWon
| otherwise -> Ongoing princess lion
input = sampleOn (fps 2) (count Keyboard.space)
initState = Ongoing 4 0
gameState = foldp proceedGame initState input
main = lift asText gameState
Pow pow pow, now it's restartable.
Trying to model a simple game for my daugher in Elm.
The idea is that Lion is chasing Princess in a 1-dimensional world. Lion starts at position 0 and Princess at position 4. Lion wins if he catches Princess. Princess wins if she reaches position 13. Lion moves twice a second. Princess moves when spacebar is pressed. Simple!
In the above snippet, I've declared the positions of the characters as Signals in a nice FRP way. I could probably define the end result of the game based on the two signals. Except you cannot have a Signal with undefined initial value, but that should be manageable. The real problem is that I cannot think of a way to cleanly restart the game while re-using the code above.
All the examples I've seen use a single update function that updates the whole game state. Is there a way to re-use simple Signals to compose more complex ones in Elm?
The fact that there's no flatMap makes me a bit pessimistic. You just cannot restart a stream, can you?