packages feed

sodium-0.9.0.0: examples/games/poodle.hs

{-# LANGUAGE DoRec, GeneralizedNewtypeDeriving #-}
-- Package dependencies:
--     random
--     stb-image
--     OpenGL
--     GLUT
import FRP.Sodium hiding (split)
import Control.Applicative
import Control.Monad.Trans
import Data.Maybe
import Engine
import System.Random

poodleSprite :: Point -> Sprite
poodleSprite pt = ((pt,(120,120)), "poodle.png")

-- | Active poodle logic (which could be made much more interesting).
poodle :: PoodleID
       -> Point
       -> Event MouseEvent
       -> Behaviour Double
       -> Reactive (Behaviour (PoodleID, Sprite))
poodle iD pos@(x0,y0) eMouse time = do
    t0 <- sample time
    let dt = subtract t0 <$> time
        bounce dt =
            let t = snd $ properFraction dt
            in  800 * t + (-800) * t^2
        sprite = (\dt -> (iD, poodleSprite (x0, y0 + bounce dt))) <$> dt
    return sprite

-- | Peel a new item off the list each time the event fires.
peelList :: Event x -> [a] -> Reactive (Behaviour a)
peelList ev xs0 =
    hold (head xs0)
            =<< collectE (\_ (x:xs) -> (x, xs)) (tail xs0) ev

-- | Generate events at random intervals.
randomTimes :: StdGen -> Behaviour Double -> Reactive (Event Double)
randomTimes rng time = do
    -- Infinite list of random intervals from 0.25 to 1.2 seconds.
    let intervals = randomRs (0.25, 1.2) rng
    rec
        tLast <- hold 0 eAppear
        interval <- peelList eAppear intervals
        let eTime = value time
            eAppear = filterJust $ snapshotWith (\t (tLast, interval) ->
                    if t >= tLast + interval then Just t else Nothing
                ) eTime ((,) <$> tLast <*> interval)
    return eAppear

newtype PoodleID = PoodleID Int deriving (Eq, Enum, Show)
data Action = Create PoodleID (Behaviour (PoodleID, Sprite)) | Destroy PoodleID

poodleGame :: StdGen -> Game
poodleGame rng eMouse time = do

    -- Random times for appearance of new poodles
    let (rng1, rng2_) = split rng
        (rng2, rng3) = split rng2_
    eAppear <- do
        randomTimes rng1 time

    -- Pick a position for each new poodle
    eNewPosition <- do
        -- Infinite list of random poodle positions
        let xs = randomRs (-900, 900) rng2
            ys = randomRs (-900, 500) rng3
        let idsAndPoses = zip [PoodleID 1..] (zip xs ys)
        -- Peel an item off the list for each new poodle
        collectE (\_ ((iD, pos):xs) -> ((iD, pos), xs)) idsAndPoses eAppear

    -- Construct a new active poodle for each new position
    let eCreations = execute $ (\(iD, pos) -> do
                beh <- poodle iD pos eMouse time
                return $ Create iD beh
            ) <$> eNewPosition

    rec
        -- Destroy poodles that are clicked on
        let eDestructions = filterJust $ snapshotWith (\mev poodles ->
                    case mev of
                        MouseDown clickPos -> listToMaybe
                            [ Destroy iD | (iD, (rect, _)) <- poodles,
                                   clickPos `inside` rect]
                        _ -> Nothing
                ) eMouse poodles

        -- Handle creations and destructions giving a behaviour containing a
        -- list of poodle behaviours.
        poodleBehs <- hold [] =<< collectE (\change poodles ->
                let poodles' = case change of
                        Create iD beh -> (iD, beh) : poodles
                        Destroy iD    -> filter (\(thisID, _) -> iD /= thisID) poodles
                in  (map snd poodles', poodles')
            ) [] (eCreations `merge` eDestructions)

        -- Convert list of behaviours into a behaviour containing a list,
        -- then flatten behaviour within behaviour down to a single behaviour of
        -- poodle sprites.
        poodles <- switch $ foldr (\ba bt -> (:) <$> ba <*> bt) (pure []) <$> poodleBehs

    -- Return poodle sprites without their ids
    return (map snd <$> poodles)

main = do
    rng <- newStdGen
    runGame "Poodle invasion - click the poodles to keep them under control" (poodleGame rng)