packages feed

reactive-thread-0.3.2.1: examples/sdl/Main.hs

module Main where

-- import Control.Applicative
import Control.Concurrent.Chan
import Control.Concurrent hiding (yield)
import Control.Monad hiding (forever)
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.State
import Data.List

import qualified Graphics.UI.SDL as SDL

import Reactive.Thread

data UpOrDown = Up | Down
  deriving (Eq, Ord, Show)

type KeyboardChan = Chan (UpOrDown, SDL.Keysym)

heldOf :: KeyboardChan -> [SDL.SDLKey] -> Update o (UpdateVar [SDL.SDLKey])
heldOf origKeyChan keys = parallel [] $ do
  keyChan <- liftIO $ dupChan origKeyChan
  evalStateT (foreverT $ go keyChan) []
  where
    go keyChan = do
      (upOrDown, sym) <- liftIO $ readChan keyChan
      let key = SDL.symKey sym
      when (key `elem` keys) $ do
        held <- get
        let held' = case upOrDown of
              Up -> delete key held
              Down -> key : held
        put held'
        lift $ yield held'

keyboard
  :: KeyboardChan
  -> a
  -> [(SDL.SDLKey, a)]
  -> Update o (UpdateVar a)
keyboard keyChan z keys = parallel z $ do
  held <- heldOf keyChan (map fst keys)
  forever $ do
    heldKeys <- query held
    case heldKeys of
      [] -> yield z
      (key:_) -> maybe (return ()) yield $ lookup key keys

type Thread o = Update o ()

type Time = Double

constantTick :: Double {-FPS-} -> Update o (UpdateVar Time)
constantTick fps = parallel 0 $ evalStateT (foreverT go) 0
  where
    go = do
      liftIO . threadDelay $ floor (1000 * 1000 / fps)

      t' <- gets succ
      put t'
      lift $ yield t'

velocity
  :: Double            -- ^ Initial position.
  -> UpdateVar Time    -- ^ Time.
  -> UpdateVar Double  -- ^ Velocity.
  -> Update o (UpdateVar Double)
velocity z timeVar velVar = parallel z $ do
  zeroTime <- query timeVar
  evalStateT (foreverT go) (z, zeroTime)
  where
    go = do
      (pos, time) <- get
      time' <- lift $ query timeVar
      vel <- lift $ query velVar

      let dt = time' - time
      pos' <- if dt > 0 && vel /= 0
        then do
          let pos' = pos + dt * vel
          lift $ yield pos'
          return pos'
        else return pos

      put (pos', time')

data Sprite = Sprite !Double !Double
  deriving (Show)

player
  :: UpdateVar Time
  -> KeyboardChan
  -> Update o (UpdateVar Sprite)
player timer keyChan = parallel (Sprite 0 0) $ do
  xVel <- keyboard keyChan 0 [(SDL.SDLK_LEFT, -1), (SDL.SDLK_RIGHT, 1)]
  yVel <- keyboard keyChan 0 [(SDL.SDLK_UP, -1), (SDL.SDLK_DOWN, 1)]
  x <- velocity 0 timer xVel
  y <- velocity 0 timer yVel

  forever $ do
    sprite <- Sprite
      `liftM` query x
      `ap` query y
    yield sprite

gameLoop :: SDL.Surface -> KeyboardChan -> Thread ()
gameLoop surface keyChan = do
  timer <- constantTick 60
  p <- player timer keyChan
  forever $ do
    sprite <- query p

    liftIO $ do
      let format = SDL.surfaceGetPixelFormat surface
      black <- SDL.mapRGB format 0 0 0
      void $ SDL.fillRect surface Nothing black

      drawSprite surface sprite
      SDL.flip surface

drawSprite :: SDL.Surface -> Sprite -> IO ()
drawSprite surface (Sprite x y) = do
  let format = SDL.surfaceGetPixelFormat surface
  color <- SDL.mapRGB format 0 128 255
  void $ SDL.fillRect surface (Just region) color

  where
    region = SDL.Rect
      (floor x) (floor y)
      width height

    width = 32
    height = 32

main :: IO ()
main = SDL.withInit [SDL.InitEverything] $ do
  surface <- SDL.setVideoMode initialWidth initialHeight 32
    [SDL.HWSurface, SDL.Resizable, SDL.RLEAccel, SDL.DoubleBuf]

  keyboardChan <- newChan
  gameThreadID <- forkIO $ runUpdate (gameLoop surface keyboardChan)
  _ <- eventLoop keyboardChan
  killThread gameThreadID

  where
    initialWidth = 640
    initialHeight = 480

    eventLoop keyboardChan = pollEvent
      where
        pollEvent = SDL.pollEvent >>= handleEvent

        handleEvent event = case event of
          SDL.NoEvent -> SDL.waitEvent >>= handleEvent
          SDL.KeyDown SDL.Keysym { SDL.symKey = SDL.SDLK_ESCAPE }
            -> return ()  -- Stop.
          SDL.KeyDown sym -> do
            writeChan keyboardChan (Down, sym)
            pollEvent
          SDL.KeyUp sym -> do
            writeChan keyboardChan (Up, sym)
            pollEvent
          _ -> pollEvent