haskellnetwire

Netwire 5 - bouncing object of the walls


I'm trying to understand FRP and Netwire. My best source of practical knowledge is this post, however it's a bit outdated, as it's written in Netwire 4, and I'm using version 5.0. I want to have player controlled square, that bounces off screen edges.

Based on that post I have this:

acceleration :: (Monad m, Monoid e) => Wire s e m (Set SDL.Keysym) Double
acceleration  =  pure (-80) . when (keyDown SDL.SDLK_LEFT)
                    <|> pure 80 . when (keyDown SDL.SDLK_RIGHT)
                    <|> pure 0

velocity :: (Monad m, HasTime t s, Monoid e) => Wire s e m (Double, Bool) Double
velocity = integralWith limit 0
    where limit collision v = let newV = if collision then -v else v in clampMax maxV newV


challenge2 :: (MonadFix m, HasTime t s) => Wire s () m (Set SDL.Keysym) Double
challenge2 = proc keysDown -> do
    a <- acceleration -< keysDown
    rec v <- velocity -< (a, colls)
        (pos, colls) <- position -< v
    returnA -< pos


position :: (Monad m, HasTime t s, Monoid e) => Wire s e m Double (Double, Bool)
position = what to put here?

I want position wire to integrate velocity, correct the position to stay within bounds of the screen and produce Bool indicating that collision occurred. Linked article uses accumT, which, in current version of Netwire, is (AFAIK) gone. And it's not too pretty - integrating by hand when there is a wire for that... I know, that I can limit position using integralWith, but it can't produce anything more than a Fractional. I tried like this:

position = mkSF_ bounds . integral 0
    where bounds pos = if trace (show pos) pos > 150 then (149, True) else if pos < 0 then (1, True) else (pos, False)

And forgive me that ;). Now I know that there is an internal state in integral wire, which I do not modify this way.

So what is the 'correct way' to achieve what I want?


Solution

  • I was following the same article along, trying to translate it to Netwire 5.0 as well. This was indeed a bit of a sticky point. I ended up creating a new integralWith' function similar in design to the integralWith but which takes as input a single value and produces two values.

    integralWith' ::
        (Fractional a, HasTime t s)
        => (a -> (a, o))  -- Function for potentially limiting the integration
                          -- and producing a secondary output.
        -> a              -- Integration constant (aka start value).
        -> Wire s e m a (a, o)
    integralWith' correct = loop
      where
        loop x' =
            mkPure $ \ds dx ->
                let dt = realToFrac (dtime ds)
                    (x,b)  = correct (x' + dt*dx)
                in x' `seq` (Right (x', b), loop x)
    

    This is almost directly copied from http://hackage.haskell.org/package/netwire-5.0.0/docs/src/FRP-Netwire-Move.html#integralWith, all I did was fiddle with the types to get it to work.

    My position function ended up looking like this.

    position :: (Monad m, HasTime t s) => Wire s e m Double (Double, Bool)  
    position = integralWith' clamp 0    
      where    
        clamp p | p < 0 || p > 150 = (max 1 (min 149 p), True)    
                | otherwise        = (p, False)
    

    As I am just getting into FRP and Haskell myself, I'm not sure if something like this already exists in the netwire library or not, or if it is even generally useful, or if there is a simpler way that I have not yet seen.