packages feed

pang-a-lambda-0.2.0.0: Experiments/splitballs/Input.hs

-- | Defines an abstraction for the game controller and the functions to read
-- it.
--
-- Lower-level devices replicate the higher-level API, and should accomodate to
-- it. Each device should:
--
--    - Upon initialisation, return any necessary information to poll it again.
--
--    - Update the controller with its own values upon sensing.
--
-- In this case, we only have one:  mouse/keyboard combination.
--
module Input where

-- External imports
import Data.IORef
import Graphics.UI.SDL as SDL

-- Internal imports
import Control.Extra.Monad
import Graphics.UI.Extra.SDL

-- * Game controller

-- | Controller info at any given point.
data Controller = Controller
  { controllerPos        :: (Double, Double)
  , controllerClick      :: Bool
  , controllerStop       :: Bool
  , controllerPause      :: Bool
  , controllerExit       :: Bool
  , controllerFast       :: Bool
  , controllerSlow       :: Bool
  , controllerSuperSlow  :: Bool
  , controllerFullscreen :: Bool
  }

-- | Controller info at any given point, plus a pointer
-- to poll the main device again. This is safe,
-- since there is only one writer at a time (the device itself).
newtype ControllerRef =
  ControllerRef { controllerData :: (IORef Controller, Controller -> IO Controller) }

-- * General API

-- | Initialize the available input devices. This operation
-- returns a reference to a controller, which enables
-- getting its state as many times as necessary. It does
-- not provide any information about its nature, abilities, etc.
initializeInputDevices :: IO ControllerRef
initializeInputDevices = do
  nr <- newIORef defaultInfo
  return $ ControllerRef (nr, sdlGetController)
 where defaultInfo = Controller (0,0) False False False False False False False False

-- | Sense from the controller, providing its current
-- state. This should return a new Controller state
-- if available, or the last one there was.
--
-- It is assumed that the sensing function is always
-- callable, and that it knows how to update the
-- Controller info if necessary.
senseInput :: ControllerRef -> IO Controller
senseInput (ControllerRef (cref, sensor)) =
  modifyIORefIO cref sensor

type ControllerDev = IO (Maybe (Controller -> IO Controller))

-- * SDL API (mid-level)

-- ** Sensing

-- | Sense the SDL keyboard and mouse and update
-- the controller. It only senses the mouse position,
-- the primary mouse button, and the p key to pause
-- the game.
--
-- We need a non-blocking controller-polling function.
-- TODO: Check http://gameprogrammer.com/fastevents/fastevents1.html
sdlGetController :: Controller -> IO Controller
sdlGetController info =
  foldLoopM info pollEvent (not.isEmptyEvent) ((return .) . handleEvent)

handleEvent :: Controller -> SDL.Event -> Controller
handleEvent c e =
  case e of
    MouseMotion x y _ _                      -> c { controllerPos        = (fromIntegral x, fromIntegral y)}
    MouseButtonDown _ _ ButtonLeft           -> c { controllerClick      = True }
    MouseButtonUp   _ _ ButtonLeft           -> c { controllerClick      = False}
    KeyUp (Keysym { symKey = SDLK_p })       -> c { controllerPause      = not (controllerPause c) }
    KeyUp (Keysym { symKey = SDLK_f })       -> c { controllerFullscreen = not (controllerFullscreen c) }
    KeyDown (Keysym { symKey = SDLK_w })     -> c { controllerSuperSlow  = True  }
    KeyUp   (Keysym { symKey = SDLK_w })     -> c { controllerSuperSlow  = False }
    KeyDown (Keysym { symKey = SDLK_s })     -> c { controllerSlow       = True  }
    KeyUp   (Keysym { symKey = SDLK_s })     -> c { controllerSlow       = False }
    KeyDown (Keysym { symKey = SDLK_x })     -> c { controllerFast       = True  }
    KeyUp   (Keysym { symKey = SDLK_x })     -> c { controllerFast       = False }
    KeyDown (Keysym { symKey = SDLK_h })     -> c { controllerStop       = True  }
    KeyUp   (Keysym { symKey = SDLK_h })     -> c { controllerStop       = False }
    KeyDown (Keysym { symKey = SDLK_SPACE }) -> c { controllerClick      = True  }
    KeyUp (Keysym { symKey = SDLK_SPACE })   -> c { controllerClick      = False }
    KeyDown (Keysym { symKey = SDLK_ESCAPE}) -> c { controllerExit       = True  }
    _                                        -> c


-- * Aux IOREf
modifyIORefIO :: IORef a -> (a -> IO a) -> IO a
modifyIORefIO ref modify = do
  v <- readIORef ref
  new <- modify v
  writeIORef ref new
  return new