packages feed

pang-a-lambda-0.2.0.0: Experiments/player/Main.hs

{-# LANGUAGE Arrows #-}
import Control.Monad
import Data.IORef
import Data.Maybe
import Debug.Trace
import FRP.Yampa                  as Yampa
import FRP.Yampa.Switches         as Yampa
import Graphics.UI.SDL            as SDL
import Graphics.UI.SDL.Primitives as SDL

width  = 640
height = 480

main = do
  timeRef <- newIORef (0 :: Int)
  controllerRef <- newIORef defaultController
  reactimate (initGraphs >> readIORef controllerRef)
             (\_ -> do
                dtSecs <- yampaSDLTimeSense timeRef
                mInput <- sdlGetController controllerRef
                -- print (mInput)
                return (dtSecs, Just mInput)
             )
             (\_ e -> display e >> return False)
             (dlSwitch [player] >>> arr composeWorld)

-- | Updates the time in an IO Ref and returns the time difference
updateTime :: IORef Int -> Int -> IO Int
updateTime timeRef newTime = do
  previousTime <- readIORef timeRef
  writeIORef timeRef newTime
  return (newTime - previousTime)

yampaSDLTimeSense :: IORef Int -> IO Yampa.DTime
yampaSDLTimeSense timeRef = do
  -- Get time passed since SDL init
  newTime <- fmap fromIntegral SDL.getTicks

  -- Obtain time difference
  dt <- updateTime timeRef newTime
  let dtSecs = fromIntegral dt / 100
  return dtSecs

-- We need a non-blocking controller-polling function.
sdlGetController :: IORef Controller -> IO Controller
sdlGetController controllerState = do
  state <- readIORef controllerState
  e    <- pollEvent
  case e of

    -- Update position
    MouseMotion x y _ _ -> do
      writeIORef controllerState (state { controllerPos = (fromIntegral x, fromIntegral y)})
      sdlGetController controllerState

    -- Fire 1
    MouseButtonDown x y ButtonLeft -> do
      writeIORef controllerState (state { controllerFire1 = True })
      sdlGetController controllerState

    MouseButtonUp x y ButtonLeft -> do
      writeIORef controllerState (state { controllerFire1 = False })
      sdlGetController controllerState

    MouseButtonDown x y ButtonRight -> do
      writeIORef controllerState (state { controllerFire2 = True })
      sdlGetController controllerState

    MouseButtonUp x y ButtonRight -> do
      writeIORef controllerState (state { controllerFire2 = False })
      sdlGetController controllerState

    _                   -> return state

data Controller = Controller
 { controllerPos   :: (Double, Double)
 , controllerFire1 :: Bool
 , controllerFire2 :: Bool
 }

defaultController :: Controller
defaultController = Controller (0, 0) False False

initGraphs :: IO ()
initGraphs = do
  -- Initialise SDL
  SDL.init [InitVideo]

  -- Create window
  screen <- SDL.setVideoMode width height 16 [SWSurface]
  SDL.setCaption "Test" ""

  -- Important if we want the keyboard to work right (I don't know
  -- how to make it work otherwise)
  SDL.enableUnicode True

display :: World -> IO()
display world = do
  -- Obtain surface
  screen <- getVideoSurface

  -- Necessary colors
  let format = surfaceGetPixelFormat screen
  red      <- mapRGB format  0xFF 0    0
  green    <- mapRGB format  0x87 0xFF 0x87
  blue     <- mapRGB format  0    0    0xFF
  otherRed <- mapRGBA format 0xFF 0xFF 0x00 0xFF

  -- Paint screen green
  fillRect screen Nothing green

  -- Paint small red square
  case worldPlayer world of
    Just (Player (playerX, playerY)) -> void $ do
      let side = 10
          x = round playerX
          y = round playerY
      fillRect screen (Just (Rect x y side side)) red
    _ -> return ()

  -- Paint vertical lines for all fire arrows
  let paintFire f = do
        let fireColor = if fireSticky f then blue else otherRed
            (x0,y0)   = fireOrigin f
            (x1,y1)   = fireTip    f
            (dx, dy)  = (3, y0-y1)
            (x1', y1') = (round x1, round y1)
            (x0', y0', dx', dy') = (round x0 - 1, round y0, round dx, round dy)
        fillRect screen (Just (Rect x1' y1' dx' dy')) fireColor
        print f
        -- vLine screen x0'     y0' y1' fireColor
        -- vLine screen (x0'+1) y0' y1' fireColor
        -- vLine screen (x0'+2) y0' y1' fireColor

  mapM_ paintFire (worldFires world)

  -- Double buffering
  SDL.flip screen

data World = World
  { worldPlayer :: Maybe Player
  , worldFires  :: [Fire]
  }

composeWorld :: [Object] -> World
composeWorld objs = World pl fs
 where pl = listToMaybe $ mapMaybe objectPlayer objs
       fs = mapMaybe objectFire objs

objectPlayer :: Object -> Maybe Player
objectPlayer (ObjectPlayer pl) = Just pl
objectPlayer _                 = Nothing

objectFire :: Object -> Maybe Fire
objectFire (ObjectFire pl) = Just pl
objectFire _               = Nothing

data Object = ObjectPlayer Player
            | ObjectFire   Fire
 deriving Show

data Player = Player { playerPos :: (Double, Double) }
 deriving Show

data Fire = Fire   { fireOrigin :: (Double, Double)
                   , fireTip    :: (Double, Double)
                   , fireSticky :: Bool
                   }
 deriving Show

-- | A player that may die or spawn new objects.
player :: ListSF Controller Object
player = ListSF $ proc (Controller p f1 f2) -> do

  let this = ObjectPlayer $ Player p
  newF1 <- isEvent ^<< edge -< f1
  newF2 <- isEvent ^<< edge -< f2

  let newF1Arrows = [ fire p False | newF1 ]
      newF2Arrows = [ fire p True  | newF2 ]
      allArrows   = newF1Arrows ++ newF2Arrows

  returnA -< (this, False, allArrows)

 where initialPos = (fromIntegral width / 2, fromIntegral height / 2)

-- | This produces bullets that die when they hit the top of the screen.
-- There's sticky bullets and normal bullets. Sticky bullets get stuck for a
-- while before they die.
fire :: (Double, Double) -> Bool -> ListSF Controller Object
fire (x0, y0) sticky = ListSF $ proc _ -> do
  let v0 = -10

  -- Calculate tip of arrow
  yT <- (y0+) ^<< integral -< v0
  let y = max 0 yT

  -- Delay death if the fire is "sticky"
  hit <- switch (constant noEvent &&& (arr (<= 0) >>> edge))
                (\_ -> stickyDeath sticky)
      -< y
  let dead = isEvent hit

  let object = ObjectFire $ Fire (x0, y0) (x0, y) sticky

  returnA -< (object, dead, [])

stickyDeath True  = after 30 ()
stickyDeath False = constant (Event ())