spice 0.3.0.0 → 0.3.2.0
raw patch · 26 files changed
+1040/−962 lines, 26 filesdep +spicedep ~data-defaultsetup-changednew-component:exe:spicePVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: spice
Dependency ranges changed: data-default
API changes (from Hackage documentation)
- FRP.Spice.Graphics: Render :: (IO ()) -> Render
- FRP.Spice.Graphics: class Renderable a
- FRP.Spice.Graphics: fromRenderables :: Renderable a => [a] -> Scene
- FRP.Spice.Graphics: newtype Render
- FRP.Spice.Graphics: runRender :: Render -> IO ()
- FRP.Spice.Graphics: toRender :: Renderable a => a -> Render
+ FRP.Spice.Graphics: bindColor :: Color -> Scene
+ FRP.Spice.Graphics: fromElements :: [Element] -> Scene
- FRP.Spice.Graphics: black :: Scene
+ FRP.Spice.Graphics: black :: Color
- FRP.Spice.Graphics: blue :: Scene
+ FRP.Spice.Graphics: blue :: Color
- FRP.Spice.Graphics: color3f :: Float -> Float -> Float -> Scene
+ FRP.Spice.Graphics: color3f :: Float -> Float -> Float -> Color
- FRP.Spice.Graphics: color3i :: Int -> Int -> Int -> Scene
+ FRP.Spice.Graphics: color3i :: Int -> Int -> Int -> Color
- FRP.Spice.Graphics: color4f :: Float -> Float -> Float -> Float -> Scene
+ FRP.Spice.Graphics: color4f :: Float -> Float -> Float -> Float -> Color
- FRP.Spice.Graphics: color4i :: Int -> Int -> Int -> Int -> Scene
+ FRP.Spice.Graphics: color4i :: Int -> Int -> Int -> Int -> Color
- FRP.Spice.Graphics: green :: Scene
+ FRP.Spice.Graphics: green :: Color
- FRP.Spice.Graphics: red :: Scene
+ FRP.Spice.Graphics: red :: Color
- FRP.Spice.Graphics: white :: Scene
+ FRP.Spice.Graphics: white :: Color
Files
- LICENSE +20/−20
- Setup.hs +2/−2
- spice.cabal +59/−50
- src/FRP/Spice.hs +22/−22
- src/FRP/Spice/Config.hs +41/−41
- src/FRP/Spice/Engine.hs +86/−90
- src/FRP/Spice/Engine/Driver.hs +26/−24
- src/FRP/Spice/Engine/Network.hs +31/−32
- src/FRP/Spice/Engine/RenderWrapper.hs +31/−31
- src/FRP/Spice/Engine/RunInput.hs +31/−31
- src/FRP/Spice/Game.hs +27/−27
- src/FRP/Spice/Graphics.hs +15/−17
- src/FRP/Spice/Graphics/Color.hs +97/−100
- src/FRP/Spice/Graphics/Element.hs +36/−0
- src/FRP/Spice/Graphics/Geometry.hs +52/−69
- src/FRP/Spice/Graphics/Renderable.hs +0/−22
- src/FRP/Spice/Graphics/Scene.hs +64/−64
- src/FRP/Spice/Graphics/Utils.hs +18/−18
- src/FRP/Spice/Input.hs +26/−26
- src/FRP/Spice/Input/Backend.hs +98/−96
- src/FRP/Spice/Input/Keyboard.hs +40/−40
- src/FRP/Spice/Input/Mouse.hs +50/−50
- src/FRP/Spice/Input/MousePosition.hs +31/−31
- src/FRP/Spice/Math.hs +9/−9
- src/FRP/Spice/Math/Vector.hs +50/−50
- src/test/Main.hs +78/−0
LICENSE view
@@ -1,20 +1,20 @@-Copyright (c) 2014 Cerek Hillen--Permission is hereby granted, free of charge, to any person obtaining-a copy of this software and associated documentation files (the-"Software"), to deal in the Software without restriction, including-without limitation the rights to use, copy, modify, merge, publish,-distribute, sublicense, and/or sell copies of the Software, and to-permit persons to whom the Software is furnished to do so, subject to-the following conditions:--The above copyright notice and this permission notice shall be included-in all copies or substantial portions of the Software.--THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.+Copyright (c) 2014 Cerek Hillen + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Setup.hs view
@@ -1,2 +1,2 @@-import Distribution.Simple-main = defaultMain+import Distribution.Simple +main = defaultMain
spice.cabal view
@@ -1,50 +1,59 @@-name: spice-version: 0.3.0.0-synopsis: An FRP-based game engine written in Haskell.-description: An FRP-based game engine written in Haskell. - See the homepage for more information.-homepage: http://github.com/crockeo/spice-license: MIT-license-file: LICENSE-author: Cerek Hillen-maintainer: Cerek Hillen <cerekh@gmail.com>-copyright: (c) 2014, Cerek Hillen-category: FRP-build-type: Simple-cabal-version: >=1.10--library- ghc-options: -Wall -fno-warn-unused-do-bind-- exposed-modules: FRP.Spice- FRP.Spice.Graphics- FRP.Spice.Math-- other-modules: FRP.Spice.Config- FRP.Spice.Engine- FRP.Spice.Engine.Driver- FRP.Spice.Engine.Network- FRP.Spice.Engine.RenderWrapper- FRP.Spice.Engine.RunInput- FRP.Spice.Game- FRP.Spice.Graphics.Color- FRP.Spice.Graphics.Geometry- FRP.Spice.Graphics.Renderable- FRP.Spice.Graphics.Scene- FRP.Spice.Graphics.Utils- FRP.Spice.Input- FRP.Spice.Input.Backend- FRP.Spice.Input.Keyboard- FRP.Spice.Input.Mouse- FRP.Spice.Input.MousePosition- FRP.Spice.Math.Vector-- hs-source-dirs: src/-- build-depends: base == 4.7.*- , elerea == 2.7.*- , GLFW == 0.5.*- , containers == 0.5.*- , data-default == 0.5.*- , OpenGL-- default-language: Haskell2010+name: spice +version: 0.3.2.0 +synopsis: An FRP-based game engine written in Haskell. +description: An FRP-based game engine written in Haskell. - See the homepage for more information. +homepage: http://github.com/crockeo/spice +license: MIT +license-file: LICENSE +author: Cerek Hillen +maintainer: Cerek Hillen <cerekh@gmail.com> +copyright: (c) 2014, Cerek Hillen +category: FRP +build-type: Simple +cabal-version: >=1.10 + +library + ghc-options: -Wall -fno-warn-unused-do-bind + + exposed-modules: FRP.Spice + FRP.Spice.Graphics + FRP.Spice.Math + + other-modules: FRP.Spice.Config + FRP.Spice.Engine + FRP.Spice.Engine.Driver + FRP.Spice.Engine.Network + FRP.Spice.Engine.RenderWrapper + FRP.Spice.Engine.RunInput + FRP.Spice.Game + FRP.Spice.Graphics.Color + FRP.Spice.Graphics.Element + FRP.Spice.Graphics.Geometry + FRP.Spice.Graphics.Scene + FRP.Spice.Graphics.Utils + FRP.Spice.Input + FRP.Spice.Input.Backend + FRP.Spice.Input.Keyboard + FRP.Spice.Input.Mouse + FRP.Spice.Input.MousePosition + FRP.Spice.Math.Vector + + hs-source-dirs: src/ + + build-depends: base == 4.7.* + , elerea == 2.7.* + , GLFW == 0.5.* + , containers == 0.5.* + , data-default == 0.5.* + , OpenGL + + default-language: Haskell2010 + +executable spice + ghc-options: -rtsopts + main-is: Main.hs + hs-source-dirs: src/test/ + build-depends: base == 4.7.* + , spice + , data-default + default-language: Haskell2010
src/FRP/Spice.hs view
@@ -1,22 +1,22 @@-{-|- This module re-exports some other modules in the spice library so that you- needn't import all of them explicitly.--}-module FRP.Spice ( module Graphics.UI.GLFW- , module Data.Map.Strict-- , module FRP.Spice.Config- , module FRP.Spice.Engine- , module FRP.Spice.Input- , module FRP.Spice.Game- ) where---------------------------- Rexported Imports ---import Graphics.UI.GLFW (Key (..), SpecialKey (..), MouseButton (..))-import Data.Map.Strict ((!))--import FRP.Spice.Config-import FRP.Spice.Engine-import FRP.Spice.Input-import FRP.Spice.Game+{-| + This module re-exports some other modules in the spice library so that you + needn't import all of them explicitly. +-} +module FRP.Spice ( module Graphics.UI.GLFW + , module Data.Map.Strict + + , module FRP.Spice.Config + , module FRP.Spice.Engine + , module FRP.Spice.Input + , module FRP.Spice.Game + ) where + +----------------------- +-- Rexported Imports -- +import Graphics.UI.GLFW (Key (..), SpecialKey (..), MouseButton (..)) +import Data.Map.Strict ((!)) + +import FRP.Spice.Config +import FRP.Spice.Engine +import FRP.Spice.Input +import FRP.Spice.Game
src/FRP/Spice/Config.hs view
@@ -1,41 +1,41 @@-{-|- This module handles the configuration for the project.--}-module FRP.Spice.Config where------------------------- Global Imports ---import Data.Default--------------- Code ----{-|- A datatype to configure the window settings when creating an OpenGL context- using @'FRP.Spice.Engine.startEngine'@ in the engine.--}-data WindowConfig = WindowConfig { getWindowWidth :: Int- , getWindowHeight :: Int- , getWindowFullscreen :: Bool- , getWindowTitle :: String- }- deriving (Eq, Show, Read)--{-|- The default for @'WindowConfig'@-- > getWindowWidth = 640- > getWindowHeight = 480- > getWindowFullscreen = False- > getWindowResizeable = False- > getWindowTitle = "Spice Application"--}-defaultWindowConfig :: WindowConfig-defaultWindowConfig = WindowConfig { getWindowWidth = 640- , getWindowHeight = 480- , getWindowFullscreen = False- , getWindowTitle = "Spice Application"- }--instance Default WindowConfig where- def = defaultWindowConfig+{-| + This module handles the configuration for the project. +-} +module FRP.Spice.Config where + +-------------------- +-- Global Imports -- +import Data.Default + +---------- +-- Code -- + +{-| + A datatype to configure the window settings when creating an OpenGL context + using @'FRP.Spice.Engine.startEngine'@ in the engine. +-} +data WindowConfig = WindowConfig { getWindowWidth :: Int + , getWindowHeight :: Int + , getWindowFullscreen :: Bool + , getWindowTitle :: String + } + deriving (Eq, Show, Read) + +{-| + The default for @'WindowConfig'@ + + > getWindowWidth = 640 + > getWindowHeight = 480 + > getWindowFullscreen = False + > getWindowResizeable = False + > getWindowTitle = "Spice Application" +-} +defaultWindowConfig :: WindowConfig +defaultWindowConfig = WindowConfig { getWindowWidth = 640 + , getWindowHeight = 480 + , getWindowFullscreen = False + , getWindowTitle = "Spice Application" + } + +instance Default WindowConfig where + def = defaultWindowConfig
src/FRP/Spice/Engine.hs view
@@ -1,90 +1,86 @@-{-|- This module handles starting the engine. This is done via the use of the- @'startEngine'@ function.--}-module FRP.Spice.Engine (startEngine) where------------------------- Global Imports ---import Graphics.Rendering.OpenGL-import Graphics.UI.GLFW as GLFW-import FRP.Elerea.Param-import Data.IORef------------------------ Local Imports ---import FRP.Spice.Engine.RunInput-import FRP.Spice.Engine.Network-import FRP.Spice.Engine.Driver-import FRP.Spice.Input.Backend-import FRP.Spice.Config-import FRP.Spice.Game--------------- Code ------ Containing if the engine has been made before-madeRef :: IO (IORef Bool)-madeRef = newIORef False---- Making the size from a WindowConfig-makeSize :: WindowConfig -> Size-makeSize wc = Size (fromIntegral $ getWindowWidth wc) (fromIntegral $ getWindowHeight wc)---- Making the displaybits from a WindowConfig-makeDisplayBits :: [DisplayBits]-makeDisplayBits = [DisplayRGBBits 8 8 8, DisplayAlphaBits 8, DisplayDepthBits 24]---- Making the window mode from a WindowConfig-makeWindowMode :: WindowConfig -> WindowMode-makeWindowMode wc =- if getWindowFullscreen wc- then FullScreen- else Window--{-|- Starting the spice engine with the parameters prescribed in the- @'WindowConfig'@. It updates and renders the @'Game'@ automatically so all- you need to to is set up the @'WindowConfig'@ and make a datatype with an- instance of @'Game'@.--}-startEngine :: Game a => WindowConfig -> a -> IO ()-startEngine wc game = do- made <- madeRef- isMade <- readIORef made- if isMade- then error "You cannot run 'startEngine' more than once per program."- else do- writeIORef made True-- -- Opening the window- initialize- openWindow (makeSize wc) makeDisplayBits (makeWindowMode wc)- windowTitle $= getWindowTitle wc-- -- Checking for the window being closed- closed <- newIORef False- windowCloseCallback $= do- writeIORef closed True- return True-- -- Getting an external of the game- (gameSignal, gameSink) <- external game-- -- Getting the input container- ic <- makeInputContainer-- -- Updating the input- mousePosCallback $= makeMousePositionCallback wc ic- keyCallback $= makeKeyboardCallback ic- mouseButtonCallback $= makeMouseCallback ic-- -- Creating the network- network <- makeNetwork (getInput ic) gameSignal gameSink-- -- Driving the network- GLFW.time $= 0- driveNetwork network $ runInput closed-- -- Closing the window, after all is said and done- closeWindow+{-| + This module handles starting the engine. This is done via the use of the + @'startEngine'@ function. +-} +module FRP.Spice.Engine (startEngine) where + +-------------------- +-- Global Imports -- +import Graphics.Rendering.OpenGL +import Graphics.UI.GLFW as GLFW +import Data.IORef + +------------------- +-- Local Imports -- +import FRP.Spice.Engine.RunInput +import FRP.Spice.Engine.Network +import FRP.Spice.Engine.Driver +import FRP.Spice.Input.Backend +import FRP.Spice.Config +import FRP.Spice.Game +import FRP.Spice.Math + +---------- +-- Code -- + +-- Making the size from a WindowConfig +makeSize :: WindowConfig -> Size +makeSize wc = Size (fromIntegral $ getWindowWidth wc) (fromIntegral $ getWindowHeight wc) + +-- Making the displaybits from a WindowConfig +makeDisplayBits :: [DisplayBits] +makeDisplayBits = [DisplayRGBBits 8 8 8, DisplayAlphaBits 8, DisplayDepthBits 24] + +-- Making the window mode from a WindowConfig +makeWindowMode :: WindowConfig -> WindowMode +makeWindowMode wc = + if getWindowFullscreen wc + then FullScreen + else Window + +-- Resizing the window +resizeCallback :: IORef (Vector Int) -> WindowSizeCallback +resizeCallback wSize size@(Size w h) = do + writeIORef wSize $ Vector (fromIntegral w) (fromIntegral h) + viewport $= (Position 0 0, size) + +{-| + Starting the spice engine with the parameters prescribed in the + @'WindowConfig'@. It updates and renders the @'Game'@ automatically so all + you need to to is set up the @'WindowConfig'@ and make a datatype with an + instance of @'Game'@. +-} +startEngine :: Game a => WindowConfig -> a -> IO () +startEngine wc game = do + -- Opening the window + initialize + openWindow (makeSize wc) makeDisplayBits (makeWindowMode wc) + windowTitle $= getWindowTitle wc + + -- Checking for the window being closed + closed <- newIORef False + windowCloseCallback $= do + writeIORef closed True + return True + + -- Function to run on window resize + wSizeRef <- newIORef $ Vector (getWindowWidth wc) (getWindowHeight wc) + windowSizeCallback $= resizeCallback wSizeRef + + -- Getting the input container + ic <- makeInputContainer + + -- Updating the input + mousePosCallback $= makeMousePositionCallback ic wSizeRef + keyCallback $= makeKeyboardCallback ic + mouseButtonCallback $= makeMouseCallback ic + + -- Creating the network + network <- makeNetwork (getInput ic) game + + -- Driving the network + GLFW.time $= 0 + driveNetwork network $ runInput closed + + -- Closing the window, after all is said and done + closeWindow
src/FRP/Spice/Engine/Driver.hs view
@@ -1,24 +1,26 @@-{-|- This module need not be used directly. Refer to @'FRP.Spice.Engine'@ instead.--}-module FRP.Spice.Engine.Driver where------------------------- Global Imports ---import Control.Monad--------------- Code ----{-|- Driving a network created with the @'FRP.Spice.Engine.Network.makeNetwork'@- function and a function such as @'FRP.Spice.Engine.RunInput.runInput'@.--}-driveNetwork :: (a -> IO (IO ())) -> IO (Maybe a) -> IO ()-driveNetwork network iomdriver = do- mdriver <- iomdriver-- case mdriver of- Just driver -> do join $ network driver- driveNetwork network iomdriver- Nothing -> return ()+{-| + This module need not be used directly. Refer to @'FRP.Spice.Engine'@ instead. +-} +module FRP.Spice.Engine.Driver where + +-------------------- +-- Global Imports -- +import FRP.Spice.Engine.RenderWrapper +import FRP.Spice.Game + +---------- +-- Code -- + +{-| + Driving a network created with the @'FRP.Spice.Engine.Network.makeNetwork'@ + function and a function such as @'FRP.Spice.Engine.RunInput.runInput'@. +-} +driveNetwork :: Game a => (Float -> IO a) -> IO (Maybe Float) -> IO () +driveNetwork network iomdriver = do + mdriver <- iomdriver + + case mdriver of + Just driver -> do state <- network driver + renderWrapper $ render state + driveNetwork network iomdriver + Nothing -> return ()
src/FRP/Spice/Engine/Network.hs view
@@ -1,32 +1,31 @@-{-|- This module need not be used directly. Refer to- @'FRP.Spice.Engine.startEngine'@ instead.--}-module FRP.Spice.Engine.Network where------------------------- Global Imports ---import FRP.Elerea.Param------------------------ Local Imports ---import FRP.Spice.Engine.RenderWrapper-import FRP.Spice.Input-import FRP.Spice.Game--------------- Code ----{-|- Creating a network to be used with the- @'FRP.Spice.Engine.Driver.driveNetwork'@ function.--}-makeNetwork :: Game a => Signal Input -> Signal a -> (a -> IO ()) -> IO (Float -> IO (IO ()))-makeNetwork inputSignal gameSignal gameSink =- start $ memo $ do- input <- inputSignal- game <- gameSignal-- return $ do- gameSink $ update 0.01 input game- renderWrapper $ render game+{-| + This module need not be used directly. Refer to + @'FRP.Spice.Engine.startEngine'@ instead. +-} +module FRP.Spice.Engine.Network where + +-------------------- +-- Global Imports -- +import FRP.Elerea.Param + +------------------- +-- Local Imports -- +import FRP.Spice.Input +import FRP.Spice.Game + +---------- +-- Code -- + +{-| + The raw update function that @'makeNetwork'@ calls. +-} +updateFn :: Game a => Float -> Input -> a -> a +updateFn = update + +{-| + Creating a network to be used with the + @'FRP.Spice.Engine.Driver.driveNetwork'@ function. +-} +makeNetwork :: Game a => Signal Input -> a -> IO (Float -> IO a) +makeNetwork inputSignal game = + start $ transfer game updateFn inputSignal
src/FRP/Spice/Engine/RenderWrapper.hs view
@@ -1,31 +1,31 @@-{-|- This module provides a wrapper to use with a given @'FRP.Spice.Game.Game'@'s- render function.--}-module FRP.Spice.Engine.RenderWrapper where------------------------- Global Imports ---import Graphics.Rendering.OpenGL-import Graphics.UI.GLFW------------------------- Global Imports ---import FRP.Spice.Graphics.Scene--------------- Code ----{-|- A function to be ran on a @'FRP.Spice.Game.Game'@'s render function to- provide a bit of a framework around it. It runs @'clear'@ before the render- function, and @'flush'@ / @'swapBuffers'@ afterwards.--}-renderWrapper :: Scene -> IO ()-renderWrapper scene = do- clear [ColorBuffer]-- renderScene scene-- flush- swapBuffers+{-| + This module provides a wrapper to use with a given @'FRP.Spice.Game.Game'@'s + render function. +-} +module FRP.Spice.Engine.RenderWrapper where + +-------------------- +-- Global Imports -- +import Graphics.Rendering.OpenGL +import Graphics.UI.GLFW + +-------------------- +-- Global Imports -- +import FRP.Spice.Graphics.Scene + +---------- +-- Code -- + +{-| + A function to be ran on a @'FRP.Spice.Game.Game'@'s render function to + provide a bit of a framework around it. It runs @'clear'@ before the render + function, and @'flush'@ / @'swapBuffers'@ afterwards. +-} +renderWrapper :: Scene -> IO () +renderWrapper scene = do + clear [ColorBuffer] + + renderScene scene + + flush + swapBuffers
src/FRP/Spice/Engine/RunInput.hs view
@@ -1,31 +1,31 @@-{-|- This module need not be used directly. Refer to @'FRP.Spice.Engine'@ instead.--}-module FRP.Spice.Engine.RunInput where------------------------- Global Imports ---import Graphics.Rendering.OpenGL-import Graphics.UI.GLFW as GLFW-import Data.IORef-import GHC.Float--------------- Code ----{-|- Given an @'IORef'@ indicating whether or not the program should close, it- either returns the delta time since the last call, or a @'Nothing'@,- indicating that the program should close.--}-runInput :: IORef Bool -> IO (Maybe Float)-runInput closed = do- pollEvents- c <- readIORef closed-- if c- then return Nothing- else do- dt <- get GLFW.time- GLFW.time $= 0- return $ Just $ double2Float (dt / 1000)+{-| + This module need not be used directly. Refer to @'FRP.Spice.Engine'@ instead. +-} +module FRP.Spice.Engine.RunInput where + +-------------------- +-- Global Imports -- +import Graphics.Rendering.OpenGL +import Graphics.UI.GLFW as GLFW +import Data.IORef +import GHC.Float + +---------- +-- Code -- + +{-| + Given an @'IORef'@ indicating whether or not the program should close, it + either returns the delta time since the last call, or a @'Nothing'@, + indicating that the program should close. +-} +runInput :: IORef Bool -> IO (Maybe Float) +runInput closed = do + pollEvents + c <- readIORef closed + + if c + then return Nothing + else do + dt <- get GLFW.time + GLFW.time $= 0 + return $ Just $ double2Float dt
src/FRP/Spice/Game.hs view
@@ -1,27 +1,27 @@-{-|- This module provides the API which people must use to define a game within- the scope of Spice.--}-module FRP.Spice.Game where------------------------ Local Imports ---import FRP.Spice.Graphics.Scene-import FRP.Spice.Input--------------- Code ----{-|- A synonym to make the update function more self-documenting.--}-type DeltaTime = Float--{-|- The class which is to be used in the @'FRP.Spice.Engine.startEngine'@- function. @'update'@ provides the API to update on every tick (purely), an- @'render'@ provides the API to render every frame.--}-class Game a where- update :: DeltaTime -> Input -> a -> a- render :: a -> Scene+{-| + This module provides the API which people must use to define a game within + the scope of Spice. +-} +module FRP.Spice.Game where + +------------------- +-- Local Imports -- +import FRP.Spice.Graphics.Scene +import FRP.Spice.Input + +---------- +-- Code -- + +{-| + A synonym to make the update function more self-documenting. +-} +type DeltaTime = Float + +{-| + The class which is to be used in the @'FRP.Spice.Engine.startEngine'@ + function. @'update'@ provides the API to update on every tick (purely), an + @'render'@ provides the API to render every frame. +-} +class Game a where + update :: DeltaTime -> Input -> a -> a + render :: a -> Scene
src/FRP/Spice/Graphics.hs view
@@ -1,17 +1,15 @@-{-|- This module re-exports a number of modules having to do with graphics.--}-module FRP.Spice.Graphics ( module FRP.Spice.Graphics.Renderable- , module FRP.Spice.Graphics.Geometry- , module FRP.Spice.Graphics.Color- , module FRP.Spice.Graphics.Scene- , module FRP.Spice.Graphics.Utils- ) where---------------------------- Rexported Imports ---import FRP.Spice.Graphics.Renderable-import FRP.Spice.Graphics.Geometry-import FRP.Spice.Graphics.Color-import FRP.Spice.Graphics.Scene-import FRP.Spice.Graphics.Utils+{-| + This module re-exports a number of modules having to do with graphics. +-} +module FRP.Spice.Graphics ( module FRP.Spice.Graphics.Geometry + , module FRP.Spice.Graphics.Color + , module FRP.Spice.Graphics.Scene + , module FRP.Spice.Graphics.Utils + ) where + +----------------------- +-- Rexported Imports -- +import FRP.Spice.Graphics.Geometry +import FRP.Spice.Graphics.Color +import FRP.Spice.Graphics.Scene +import FRP.Spice.Graphics.Utils
src/FRP/Spice/Graphics/Color.hs view
@@ -1,100 +1,97 @@-{-|- This module provides an abstraction over the default representations of color- in the Haskell OpenGL bindings.--}-module FRP.Spice.Graphics.Color where------------------------- Global Imports ---import Graphics.Rendering.OpenGL (Color4 (..), color)------------------------ Local Imports ---import FRP.Spice.Graphics.Renderable-import FRP.Spice.Graphics.Scene-import FRP.Spice.Graphics.Utils--------------- Code ----{-|- Representing a Color using four @'Float'@ representing reg, green, blue, and- the alpha mask respectively. The @'Float'@s are in a range from 0-1,- representing @'Int'@s from 0-255.--}-data Color = Color { getRed :: Float- , getGreen :: Float- , getBlue :: Float- , getAlpha :: Float- }- deriving (Eq, Show, Read)--{-|- Changing the current OpenGL context's @'Color4'@ to whatever @'Color'@- specified.--}-instance Renderable Color where- toRender (Color r g b a) =- Render $- color $ Color4 (togl r) (togl g) (togl b) (togl a)--{-|- A synonym for the @'Color'@ constructor.--}-color4f :: Float -> Float -> Float -> Float -> Scene-color4f r g b a = fromRenderables [Color r g b a]--{-|- Constructing a @'Color'@ from 3 @'Float'@s, defaulting the alpha mask to 1.0.--}-color3f :: Float -> Float -> Float -> Scene-color3f r g b = color4f r g b 1.0--{-|- Creating a @'Color'@ from 4 @'Int'@s. The ints, similarly to @'color4f'@- represent red, green, blue, and the alpha mask respectively. The ints should- be in the range of 0-255. (Note: @'color4i'@ is functionally equivalent (and- also equivalent in source code) to calling color4f with each of its arguments- divided by 255.)--}-color4i :: Int -> Int -> Int -> Int -> Scene-color4i r g b a = color4f (fromIntegral r / 255)- (fromIntegral g / 255)- (fromIntegral b / 255)- (fromIntegral a / 255)--{-|- Constructing a @'Color'@ from 3 @'Int'@s, defaulting the alpha mask to 255.--}-color3i :: Int -> Int -> Int -> Scene-color3i r g b = color4i r g b 255--{-|- The color black.--}-black :: Scene-black = color3i 0 0 0--{-|- The color white.--}-white :: Scene-white = color3i 255 255 255--{-|- The color red.--}-red :: Scene-red = color3i 255 0 0--{-|- The color green.--}-green :: Scene-green = color3i 0 255 0--{-|- The color blue.--}-blue :: Scene-blue = color3i 0 0 255+{-| + This module provides an abstraction over the default representations of color + in the Haskell OpenGL bindings. +-} +module FRP.Spice.Graphics.Color where + +-------------------- +-- Global Imports -- +import Graphics.Rendering.OpenGL (Color4 (..), color) + +------------------- +-- Local Imports -- +import FRP.Spice.Graphics.Element +import FRP.Spice.Graphics.Scene +import FRP.Spice.Graphics.Utils + +---------- +-- Code -- + +{-| + Representing a Color using four @'Float'@ representing reg, green, blue, and + the alpha mask respectively. The @'Float'@s are in a range from 0-1, + representing @'Int'@s from 0-255. +-} +data Color = Color { getRed :: Float + , getGreen :: Float + , getBlue :: Float + , getAlpha :: Float + } + deriving (Eq, Show, Read) + +{-| + Converting a color to an action in a @'Scene'@. +-} +bindColor :: Color -> Scene +bindColor (Color r g b a) = fromElements [SetColor r g b a] + +{-| + A synonym for the @'Color'@ constructor. +-} +color4f :: Float -> Float -> Float -> Float -> Color +color4f = Color + +{-| + Constructing a @'Color'@ from 3 @'Float'@s, defaulting the alpha mask to 1.0. +-} +color3f :: Float -> Float -> Float -> Color +color3f r g b = color4f r g b 1.0 + +{-| + Creating a @'Color'@ from 4 @'Int'@s. The ints, similarly to @'color4f'@ + represent red, green, blue, and the alpha mask respectively. The ints should + be in the range of 0-255. (Note: @'color4i'@ is functionally equivalent (and + also equivalent in source code) to calling color4f with each of its arguments + divided by 255.) +-} +color4i :: Int -> Int -> Int -> Int -> Color +color4i r g b a = color4f (fromIntegral r / 255) + (fromIntegral g / 255) + (fromIntegral b / 255) + (fromIntegral a / 255) + +{-| + Constructing a @'Color'@ from 3 @'Int'@s, defaulting the alpha mask to 255. +-} +color3i :: Int -> Int -> Int -> Color +color3i r g b = color4i r g b 255 + +{-| + The color black. +-} +black :: Color +black = color3i 0 0 0 + +{-| + The color white. +-} +white :: Color +white = color3i 255 255 255 + +{-| + The color red. +-} +red :: Color +red = color3i 255 0 0 + +{-| + The color green. +-} +green :: Color +green = color3i 0 255 0 + +{-| + The color blue. +-} +blue :: Color +blue = color3i 0 0 255
+ src/FRP/Spice/Graphics/Element.hs view
@@ -0,0 +1,36 @@+module FRP.Spice.Graphics.Element ( Element (..) + , renderElement + ) where + +-------------------- +-- Global Imports -- +import Graphics.Rendering.OpenGL +import Control.Monad + +------------------- +-- Local Imports -- +import FRP.Spice.Graphics.Utils +import FRP.Spice.Math + +---------- +-- Code -- + +{-| + A type to purely contain the information for any kind of render call (that + has been implemented so far.) +-} +data Element = RenderPrimitive PrimitiveMode [Vector Float] + | SetColor Float Float Float Float + +{-| + Converting an @'Element'@ into its matching OpenGL call. +-} +renderElement :: Element -> IO () + +renderElement (RenderPrimitive mode vertecies) = + renderPrimitive mode $ + forM_ vertecies $ \(Vector x y) -> + vertex $ Vertex2 (togl x) (togl y) + +renderElement (SetColor r g b a) = + color $ Color4 (togl r) (togl g) (togl b) (togl a)
src/FRP/Spice/Graphics/Geometry.hs view
@@ -1,69 +1,52 @@-{-|- This module provides a cleaner API to render a number of shapes in the- current OpenGL context.--}-module FRP.Spice.Graphics.Geometry ( renderPoint- , renderRectangle- , renderSquare- ) where------------------------- Global Imports ---import Graphics.Rendering.OpenGL ( PrimitiveMode (..)- , Vertex2 (..)- , renderPrimitive- , vertex- )-import Control.Monad------------------------ Local Imports ---import FRP.Spice.Graphics.Renderable-import FRP.Spice.Graphics.Scene-import FRP.Spice.Graphics.Utils-import FRP.Spice.Math.Vector--------------- Code ----{-|- The information necessary to construct any given OpenGL primitive. The- @'PrimitiveMode'@ represents -- of course -- the kind of GL primitive to- render. The list of @'Vector'@s directly represents the vertecies to bind.--}-data Primitive = Primitive PrimitiveMode [Vector Float]--{-|- Rendering whatever OpenGL primitive is specified.--}-instance Renderable Primitive where- toRender (Primitive mode vertecies) =- Render $- renderPrimitive mode $- forM_ vertecies $ \(Vector x y) ->- vertex $ Vertex2 (togl x) (togl y)--{-|- Rendering a point.--}-renderPoint :: Vector Float -> Scene-renderPoint pos =- fromRenderables [Primitive Points [pos]]--{-|- Rendering a rectangle.--}-renderRectangle :: Vector Float -> Vector Float -> Scene-renderRectangle (Vector x y) (Vector w h) = do- fromRenderables [ Primitive Quads [ Vector (x ) (y )- , Vector (x + w) (y )- , Vector (x + w) (y + h)- , Vector (x ) (y + h)- ]- ]--{-|- Rendering a square.--}-renderSquare :: Vector Float -> Float -> Scene-renderSquare pos size = renderRectangle pos $ Vector size size+{-| + This module provides a cleaner API to render a number of shapes in the + current OpenGL context. +-} +module FRP.Spice.Graphics.Geometry ( renderPoint + , renderRectangle + , renderSquare + ) where + +-------------------- +-- Global Imports -- +import Graphics.Rendering.OpenGL ( PrimitiveMode (..) + , Vertex2 (..) + , renderPrimitive + , vertex + ) +import Control.Monad + +------------------- +-- Local Imports -- +import FRP.Spice.Graphics.Element +import FRP.Spice.Graphics.Scene +import FRP.Spice.Graphics.Utils +import FRP.Spice.Math.Vector + +---------- +-- Code -- + +{-| + Rendering a point. +-} +renderPoint :: Vector Float -> Scene +renderPoint pos = + fromElements [RenderPrimitive Points [pos]] + +{-| + Rendering a rectangle. +-} +renderRectangle :: Vector Float -> Vector Float -> Scene +renderRectangle (Vector x y) (Vector w h) = do + fromElements [ RenderPrimitive Quads [ Vector (x ) (y ) + , Vector (x + w) (y ) + , Vector (x + w) (y + h) + , Vector (x ) (y + h) + ] + ] + +{-| + Rendering a square. +-} +renderSquare :: Vector Float -> Float -> Scene +renderSquare pos size = renderRectangle pos $ Vector size size
− src/FRP/Spice/Graphics/Renderable.hs
@@ -1,22 +0,0 @@-module FRP.Spice.Graphics.Renderable where--------------- Code ----{-|- A container around IO to suggest that one should perform rendering in a- rendering function.--}-newtype Render = Render (IO ())--{-|- Performing the IO contained in a @'Render'@.--}-runRender :: Render -> IO ()-runRender (Render action) = action--{-|- A class that provides an API to convert a datatype into a @'Render'@.--}-class Renderable a where- toRender :: a -> Render
src/FRP/Spice/Graphics/Scene.hs view
@@ -1,64 +1,64 @@-module FRP.Spice.Graphics.Scene ( Scene- , fromRenderables- , renderScene- ) where------------------------- Global Imports ---import Control.Applicative-import Control.Monad------------------------ Local Imports ---import FRP.Spice.Graphics.Renderable--------------- Code ----{-|- For composing a scene out of a set of renderables.--}-data SceneT a = SceneT [Render] a--{-|- The commonly used instance of SceneT--}-type Scene = SceneT ()--{-|- Functor instance to satisfy applicative instance.--}-instance Functor SceneT where- fmap fn (SceneT renderables v) = SceneT renderables $ fn v--{-|- Applicative instance to satisfy the monad instance. Not advised to use--}-instance Applicative SceneT where- pure = return- (<*>) = ap--{-|- Used for being able to compose Scenes in a do-notation. Not very useful- outside of that.--}-instance Monad SceneT where- return a = SceneT [] a- (SceneT renderables v) >>= fn =- let (SceneT renderables' v') = fn v in- SceneT (renderables ++ renderables') v'--{-|- Constructing a SceneT from a list of renderables.--}-fromRenderables :: Renderable a => [a] -> Scene-fromRenderables renderables =- SceneT (map toRender renderables) ()--{-|- Rendering a whole @'Scene'@ (renders each @'Element'@ from first in list to last in- list.)--}-renderScene :: Scene -> IO ()-renderScene (SceneT renderables _) =- forM_ renderables runRender+module FRP.Spice.Graphics.Scene ( Scene + , fromElements + , renderScene + ) where + +-------------------- +-- Global Imports -- +import Control.Applicative +import Control.Monad + +------------------- +-- Local Imports -- +import FRP.Spice.Graphics.Element + +---------- +-- Code -- + +{-| + For composing a scene out of a set of elements. +-} +data SceneT a = SceneT [Element] a + +{-| + The commonly used instance of SceneT +-} +type Scene = SceneT () + +{-| + Functor instance to satisfy applicative instance. +-} +instance Functor SceneT where + fmap fn (SceneT elements v) = SceneT elements $ fn v + +{-| + Applicative instance to satisfy the monad instance. Not advised to use +-} +instance Applicative SceneT where + pure = return + (<*>) = ap + +{-| + Used for being able to compose Scenes in a do-notation. Not very useful + outside of that. +-} +instance Monad SceneT where + return a = SceneT [] a + (SceneT elements v) >>= fn = + let (SceneT elements' v') = fn v in + SceneT (elements ++ elements') v' + +{-| + Constructing a SceneT from a list of elements. +-} +fromElements :: [Element] -> Scene +fromElements elements = + SceneT elements () + +{-| + Rendering a whole @'Scene'@ (renders each @'Element'@ from first in list to last in + list.) +-} +renderScene :: Scene -> IO () +renderScene (SceneT elements _) = + forM_ elements renderElement
src/FRP/Spice/Graphics/Utils.hs view
@@ -1,18 +1,18 @@-{-|- This module provides a set of utilities to ease the burden of writing OpenGL- rendering functions.--}-module FRP.Spice.Graphics.Utils where------------------------- Global Imports ---import Graphics.Rendering.OpenGL--------------- Code ----{-|- Converting a @'Float'@ to a @'GLfloat'@--}-togl :: Float -> GLfloat-togl = realToFrac+{-| + This module provides a set of utilities to ease the burden of writing OpenGL + rendering functions. +-} +module FRP.Spice.Graphics.Utils where + +-------------------- +-- Global Imports -- +import Graphics.Rendering.OpenGL + +---------- +-- Code -- + +{-| + Converting a @'Float'@ to a @'GLfloat'@ +-} +togl :: Float -> GLfloat +togl = realToFrac
src/FRP/Spice/Input.hs view
@@ -1,26 +1,26 @@-{-|- This module provides everything relating to input in the scope of spice.--}-module FRP.Spice.Input where------------------------- Global Imports ---import Graphics.UI.GLFW-import Data.Map.Strict------------------------ Local Imports ---import FRP.Spice.Math--------------- Code ----{-|- A container for all of the states themselves. It is used as a @'Signal'@- @'Input'@ in the @'InputContainer'@ (which is necessary to use it within- Elerea's FRP network).--}-data Input = Input { mousePosition :: Vector Float- , keyboard :: Map Key Bool- , mouse :: Map MouseButton Bool- }+{-| + This module provides everything relating to input in the scope of spice. +-} +module FRP.Spice.Input where + +-------------------- +-- Global Imports -- +import Graphics.UI.GLFW +import Data.Map.Strict + +------------------- +-- Local Imports -- +import FRP.Spice.Math + +---------- +-- Code -- + +{-| + A container for all of the states themselves. It is used as a @'Signal'@ + @'Input'@ in the @'InputContainer'@ (which is necessary to use it within + Elerea's FRP network). +-} +data Input = Input { mousePosition :: Vector Float + , keyboard :: Map Key Bool + , mouse :: Map MouseButton Bool + }
src/FRP/Spice/Input/Backend.hs view
@@ -1,96 +1,98 @@-{-|- The backend to be used with the input.--}-module FRP.Spice.Input.Backend where------------------------- Global Imports ---import Graphics.Rendering.OpenGL-import Graphics.UI.GLFW as GLFW-import FRP.Elerea.Param-import Data.Map.Strict------------------------ Local Imports ---import qualified FRP.Spice.Input.MousePosition as MousePosition-import qualified FRP.Spice.Input.Keyboard as Keyboard-import qualified FRP.Spice.Input.Mouse as Mouse-import FRP.Spice.Config-import FRP.Spice.Input-import FRP.Spice.Math--------------- Code ----{-|- A wrapper around the sinks for the mouse position, the key states, and the- mouse button states.--}-data Sinks = Sinks { mousePositionSinks :: Vector Float -> IO ()- , keyboardSinks :: Map Key (Bool -> IO ())- , mouseSinks :: Map MouseButton (Bool -> IO ())- }----{-|- A container around @'Sinks'@ and @'Signal'@ @'Input'@ so that one needn't- pass around a tuple.--}-data InputContainer = InputContainer { getSinks :: Sinks- , getInput :: Signal Input- }--{-|- Making an @'InputContainer'@ filled with all necessary externals.--}-makeInputContainer :: IO InputContainer-makeInputContainer = do- mousePositionExternals <- MousePosition.externals- keyboardExternals <- Keyboard.externals- mouseExternals <- Mouse.externals-- let sinks = Sinks { mousePositionSinks = MousePosition.sinks mousePositionExternals- , keyboardSinks = Keyboard.sinks keyboardExternals- , mouseSinks = Mouse.sinks mouseExternals- }-- let input = do mps <- fst mousePositionExternals- kbs <- Keyboard.signals keyboardExternals- ms <- Mouse.signals mouseExternals-- return Input { mousePosition = mps- , keyboard = kbs- , mouse = ms- }-- return InputContainer { getSinks = sinks- , getInput = input- }--{-|- Creating a callback to update the mouse position's state.--}-makeMousePositionCallback :: WindowConfig -> InputContainer -> MousePosCallback-makeMousePositionCallback wc ic (Position x y) =- mousePositionSinks (getSinks ic) $- Vector ( fromIntegral x / ((fromIntegral $ getWindowWidth wc) / 2) - 1)- ((-fromIntegral y) / ((fromIntegral $ getWindowHeight wc) / 2) + 1)--{-|- Creating a callback to update the keyboard's states.--}-makeKeyboardCallback :: InputContainer -> KeyCallback-makeKeyboardCallback ic key state =- keyboardSinks (getSinks ic) ! key $ case state of- Press -> True- Release -> False--{-|- Creating a callback to update the mouse buttons' states.--}-makeMouseCallback :: InputContainer -> MouseButtonCallback-makeMouseCallback ic button state =- mouseSinks (getSinks ic) ! button $ case state of- Press -> True- Release -> False+{-| + The backend to be used with the input. +-} +module FRP.Spice.Input.Backend where + +-------------------- +-- Global Imports -- +import Graphics.Rendering.OpenGL +import Graphics.UI.GLFW as GLFW +import FRP.Elerea.Param +import Data.Map.Strict +import Data.IORef + +------------------- +-- Local Imports -- +import qualified FRP.Spice.Input.MousePosition as MousePosition +import qualified FRP.Spice.Input.Keyboard as Keyboard +import qualified FRP.Spice.Input.Mouse as Mouse +import FRP.Spice.Config +import FRP.Spice.Input +import FRP.Spice.Math + +---------- +-- Code -- + +{-| + A wrapper around the sinks for the mouse position, the key states, and the + mouse button states. +-} +data Sinks = Sinks { mousePositionSinks :: Vector Float -> IO () + , keyboardSinks :: Map Key (Bool -> IO ()) + , mouseSinks :: Map MouseButton (Bool -> IO ()) + } + + + +{-| + A container around @'Sinks'@ and @'Signal'@ @'Input'@ so that one needn't + pass around a tuple. +-} +data InputContainer = InputContainer { getSinks :: Sinks + , getInput :: Signal Input + } + +{-| + Making an @'InputContainer'@ filled with all necessary externals. +-} +makeInputContainer :: IO InputContainer +makeInputContainer = do + mousePositionExternals <- MousePosition.externals + keyboardExternals <- Keyboard.externals + mouseExternals <- Mouse.externals + + let sinks = Sinks { mousePositionSinks = MousePosition.sinks mousePositionExternals + , keyboardSinks = Keyboard.sinks keyboardExternals + , mouseSinks = Mouse.sinks mouseExternals + } + + let input = do mps <- fst mousePositionExternals + kbs <- Keyboard.signals keyboardExternals + ms <- Mouse.signals mouseExternals + + return Input { mousePosition = mps + , keyboard = kbs + , mouse = ms + } + + return InputContainer { getSinks = sinks + , getInput = input + } + +{-| + Creating a callback to update the mouse position's state. +-} +makeMousePositionCallback :: InputContainer -> IORef (Vector Int) -> MousePosCallback +makeMousePositionCallback ic wSizeRef (Position x y) = do + (Vector w h) <- readIORef wSizeRef + mousePositionSinks (getSinks ic) $ + Vector ( fromIntegral x / ((fromIntegral $ w) / 2) - 1) + ((-fromIntegral y) / ((fromIntegral $ h) / 2) + 1) + +{-| + Creating a callback to update the keyboard's states. +-} +makeKeyboardCallback :: InputContainer -> KeyCallback +makeKeyboardCallback ic key state = + keyboardSinks (getSinks ic) ! key $ case state of + Press -> True + Release -> False + +{-| + Creating a callback to update the mouse buttons' states. +-} +makeMouseCallback :: InputContainer -> MouseButtonCallback +makeMouseCallback ic button state = + mouseSinks (getSinks ic) ! button $ case state of + Press -> True + Release -> False
src/FRP/Spice/Input/Keyboard.hs view
@@ -1,40 +1,40 @@-{-|- The keyboard specific section of input.--}-module FRP.Spice.Input.Keyboard where------------------------- Global Imports ---import qualified Data.Traversable as T-import Data.Map.Strict hiding (keys, map)-import Graphics.UI.GLFW as GLFW-import FRP.Elerea.Param-import Control.Monad--------------- Code ----{-|- A list of all possible keys used in the GLFW API.--}-keys :: [Key]-keys = map toEnum [0 .. 318]--{-|- A Map from @'Key'@ to the externals created for every single key.--}-externals :: IO (Map Key (Signal Bool, Bool -> IO ()))-externals = liftM fromList $ mapM (\k -> liftM ((,) k) $ external False) keys--{-|- Creating the @'Signal'@ of a @'Map'@ from @'Key'@ to @'Bool'@ from a @'Map'@- of @'externals'@.--}-signals :: Map Key (Signal Bool, Bool -> IO ()) -> Signal (Map Key Bool)-signals = T.sequence . fmap fst--{-|- Making a @'Map'@ from @'Key'@ to sink from a @'Map'@ of @'externals'@.--}-sinks :: Map Key (Signal Bool, Bool -> IO ()) -> Map Key (Bool -> IO ())-sinks = fmap snd+{-| + The keyboard specific section of input. +-} +module FRP.Spice.Input.Keyboard where + +-------------------- +-- Global Imports -- +import qualified Data.Traversable as T +import Data.Map.Strict hiding (keys, map) +import Graphics.UI.GLFW as GLFW +import FRP.Elerea.Param +import Control.Monad + +---------- +-- Code -- + +{-| + A list of all possible keys used in the GLFW API. +-} +keys :: [Key] +keys = map toEnum [0 .. 318] + +{-| + A Map from @'Key'@ to the externals created for every single key. +-} +externals :: IO (Map Key (Signal Bool, Bool -> IO ())) +externals = liftM fromList $ mapM (\k -> liftM ((,) k) $ external False) keys + +{-| + Creating the @'Signal'@ of a @'Map'@ from @'Key'@ to @'Bool'@ from a @'Map'@ + of @'externals'@. +-} +signals :: Map Key (Signal Bool, Bool -> IO ()) -> Signal (Map Key Bool) +signals = T.sequence . fmap fst + +{-| + Making a @'Map'@ from @'Key'@ to sink from a @'Map'@ of @'externals'@. +-} +sinks :: Map Key (Signal Bool, Bool -> IO ()) -> Map Key (Bool -> IO ()) +sinks = fmap snd
src/FRP/Spice/Input/Mouse.hs view
@@ -1,50 +1,50 @@-{-|- The mouse (button) specific section of input.--}-module FRP.Spice.Input.Mouse where------------------------- Global Imports ---import qualified Data.Traversable as T (sequence)-import Data.Map.Strict hiding (keys, map)-import Graphics.UI.GLFW as GLFW-import FRP.Elerea.Param-import Control.Monad--------------- Code ------ Adding an instance of Ord to MouseButton so it can be used with the map-instance Ord MouseButton where- mb1 <= mb2 = fromEnum mb1 < fromEnum mb2--{-|- A list of all mouse buttons available via the GLFW api.--}-buttons :: [MouseButton]-buttons = [ ButtonLeft- , ButtonRight- , ButtonMiddle- ]- ++- map ButtonNo [0 .. 7]--{-|- A map from @'MouseButton'@ to externals created for every button.--}-externals :: IO (Map MouseButton (Signal Bool, Bool -> IO ()))-externals = liftM fromList $ mapM (\b -> liftM ((,) b) $ external False) buttons--{-|- Creating the @'Signal'@ of a @'Map'@ from @'MouseButton'@ to @'Bool'@ from a- @'Map'@ of @'externals'@.--}-signals :: Map MouseButton (Signal Bool, Bool -> IO ()) -> Signal (Map MouseButton Bool)-signals = T.sequence . fmap fst--{-|- Making a @'Map'@ from @'MouseButton'@ to sink from a @'Map'@ of- @'externals'@.--}-sinks :: Map MouseButton (Signal Bool, Bool -> IO ()) -> Map MouseButton (Bool -> IO ())-sinks = fmap snd+{-| + The mouse (button) specific section of input. +-} +module FRP.Spice.Input.Mouse where + +-------------------- +-- Global Imports -- +import qualified Data.Traversable as T (sequence) +import Data.Map.Strict hiding (keys, map) +import Graphics.UI.GLFW as GLFW +import FRP.Elerea.Param +import Control.Monad + +---------- +-- Code -- + +-- Adding an instance of Ord to MouseButton so it can be used with the map +instance Ord MouseButton where + mb1 <= mb2 = fromEnum mb1 < fromEnum mb2 + +{-| + A list of all mouse buttons available via the GLFW api. +-} +buttons :: [MouseButton] +buttons = [ ButtonLeft + , ButtonRight + , ButtonMiddle + ] + ++ + map ButtonNo [0 .. 7] + +{-| + A map from @'MouseButton'@ to externals created for every button. +-} +externals :: IO (Map MouseButton (Signal Bool, Bool -> IO ())) +externals = liftM fromList $ mapM (\b -> liftM ((,) b) $ external False) buttons + +{-| + Creating the @'Signal'@ of a @'Map'@ from @'MouseButton'@ to @'Bool'@ from a + @'Map'@ of @'externals'@. +-} +signals :: Map MouseButton (Signal Bool, Bool -> IO ()) -> Signal (Map MouseButton Bool) +signals = T.sequence . fmap fst + +{-| + Making a @'Map'@ from @'MouseButton'@ to sink from a @'Map'@ of + @'externals'@. +-} +sinks :: Map MouseButton (Signal Bool, Bool -> IO ()) -> Map MouseButton (Bool -> IO ()) +sinks = fmap snd
src/FRP/Spice/Input/MousePosition.hs view
@@ -1,31 +1,31 @@-{-|- The mouse (position) specific section of input.--}-module FRP.Spice.Input.MousePosition where------------------------- Global Imports ---import FRP.Elerea.Param-import Data.Default------------------------ Local Imports ---import FRP.Spice.Math.Vector--{-|- Creating the external for the mouse position.--}-externals :: IO (Signal (Vector Float), Vector Float -> IO ())-externals = external def--{-|- Getting the signal from the mouse position external.--}-signals :: (Signal (Vector Float), Vector Float -> IO ()) -> Signal (Vector Float)-signals = fst--{-|- Getting the sink from the mouse position external.--}-sinks :: (Signal (Vector Float), Vector Float -> IO ()) -> Vector Float -> IO ()-sinks = snd+{-| + The mouse (position) specific section of input. +-} +module FRP.Spice.Input.MousePosition where + +-------------------- +-- Global Imports -- +import FRP.Elerea.Param +import Data.Default + +------------------- +-- Local Imports -- +import FRP.Spice.Math.Vector + +{-| + Creating the external for the mouse position. +-} +externals :: IO (Signal (Vector Float), Vector Float -> IO ()) +externals = external def + +{-| + Getting the signal from the mouse position external. +-} +signals :: (Signal (Vector Float), Vector Float -> IO ()) -> Signal (Vector Float) +signals = fst + +{-| + Getting the sink from the mouse position external. +-} +sinks :: (Signal (Vector Float), Vector Float -> IO ()) -> Vector Float -> IO () +sinks = snd
src/FRP/Spice/Math.hs view
@@ -1,9 +1,9 @@-{-|- This module re-exports a number of modules having to do with math.--}-module FRP.Spice.Math ( module FRP.Spice.Math.Vector- ) where---------------------------- Rexported Imports ---import FRP.Spice.Math.Vector+{-| + This module re-exports a number of modules having to do with math. +-} +module FRP.Spice.Math ( module FRP.Spice.Math.Vector + ) where + +----------------------- +-- Rexported Imports -- +import FRP.Spice.Math.Vector
src/FRP/Spice/Math/Vector.hs view
@@ -1,50 +1,50 @@-{-|- This module provides a @'Vector'@ datatype as described below.--}-module FRP.Spice.Math.Vector where------------------------- Global Imports ---import Data.Default-import Data.Monoid--------------- Code ----{-|- A datatype that houses two values of a given type. It is provided with a- @'Num'@ instance so that, when used with number types it can function- similarly (though not exactly) to a mathematical vector.--}-data Vector a = Vector a a--{-|- The default for the @'Vector'@.--}-instance Default a => Default (Vector a) where- def = Vector def def--{-|- Displaying the @'Vector'@.--}-instance Show a => Show (Vector a) where- show (Vector x y) = mconcat ["Vector ", show x, " ", show y]--{-|- Performs operations on the matching fields of the other @'Vector'@.--}-instance Num a => Num (Vector a) where- (Vector x1 y1) + (Vector x2 y2) = Vector (x1 + x2) (y1 + y2)- (Vector x1 y1) * (Vector x2 y2) = Vector (x1 * x2) (y1 * y2)-- fromInteger n = Vector (fromInteger n) (fromInteger n)-- abs (Vector x y) = Vector (abs x) (abs y)- signum (Vector x y) = Vector (signum x) (signum y)- negate (Vector x y) = Vector (negate x) (negate y)--{-|- Maps over both values in the @'Vector'@--}-instance Functor Vector where- fmap fn (Vector x y) = Vector (fn x) (fn y)+{-| + This module provides a @'Vector'@ datatype as described below. +-} +module FRP.Spice.Math.Vector where + +-------------------- +-- Global Imports -- +import Data.Default +import Data.Monoid + +---------- +-- Code -- + +{-| + A datatype that houses two values of a given type. It is provided with a + @'Num'@ instance so that, when used with number types it can function + similarly (though not exactly) to a mathematical vector. +-} +data Vector a = Vector a a + +{-| + The default for the @'Vector'@. +-} +instance Default a => Default (Vector a) where + def = Vector def def + +{-| + Displaying the @'Vector'@. +-} +instance Show a => Show (Vector a) where + show (Vector x y) = mconcat ["Vector ", show x, " ", show y] + +{-| + Performs operations on the matching fields of the other @'Vector'@. +-} +instance Num a => Num (Vector a) where + (Vector x1 y1) + (Vector x2 y2) = Vector (x1 + x2) (y1 + y2) + (Vector x1 y1) * (Vector x2 y2) = Vector (x1 * x2) (y1 * y2) + + fromInteger n = Vector (fromInteger n) (fromInteger n) + + abs (Vector x y) = Vector (abs x) (abs y) + signum (Vector x y) = Vector (signum x) (signum y) + negate (Vector x y) = Vector (negate x) (negate y) + +{-| + Maps over both values in the @'Vector'@ +-} +instance Functor Vector where + fmap fn (Vector x y) = Vector (fn x) (fn y)
+ src/test/Main.hs view
@@ -0,0 +1,78 @@+module Main where + +------------- +-- Imports -- +import FRP.Spice.Graphics +import FRP.Spice.Math +import FRP.Spice + +import Data.Default + +---------- +-- Code -- + +-- Some directions +up, down, left, right :: Vector Float +up = Vector ( 0) ( 1) +down = Vector ( 0) (-1) +left = Vector (-1) ( 0) +right = Vector ( 1) ( 0) + +-- The speed in which pos should move +speedCfg :: Float +speedCfg = 0.5 + +-- The size rendered squares should take +size :: Float +size = 0.05 + +-- Applying the speed to a vector +speed :: DeltaTime -> Vector Float -> Vector Float +speed dt vec = vec * Vector (speedCfg * dt) (speedCfg * dt) + +-- A basic game type +data BasicGame = BasicGame { pos :: Vector Float + , mousePos :: Vector Float + } + +-- Updating the game +updatePos :: DeltaTime -> Input -> BasicGame -> BasicGame +updatePos dt input game = + move dt game [ (keyboard input ! CharKey 'W', speed dt up ) + , (keyboard input ! CharKey 'S', speed dt down ) + , (keyboard input ! CharKey 'A', speed dt left ) + , (keyboard input ! CharKey 'D', speed dt right) + ] + where move :: DeltaTime -> BasicGame -> [(Bool, Vector Float)] -> BasicGame + move dt game [] = game + move dt game@(BasicGame pos _) ((True , d):xs) = move dt (game { pos = pos + d }) xs + move dt game ((False, _):xs) = move dt game xs + +-- Updating the mouse position +updateMousePosition :: Input -> BasicGame -> BasicGame +updateMousePosition input game@(BasicGame _ mousePos) = + game { mousePos = mousePosition input } + +updateGame :: DeltaTime -> Input -> BasicGame -> BasicGame +updateGame dt input game = updateMousePosition input $ updatePos dt input game + +-- Rendering the game +renderGame :: BasicGame -> Scene +renderGame (BasicGame pos mousePos) = do + bindColor $ color3i 0 0 255 + renderRectangle ((mousePos * Vector 0 1) - (Vector 1 0)) $ Vector 2 2 + + bindColor $ color3i 255 0 0 + renderSquare pos size + + bindColor $ color3i 0 255 0 + renderSquare mousePos size + +-- Giving BasicGame a Game instance. +instance Game BasicGame where + update dt input game = updateGame dt input game + render game = renderGame game + +-- Starting the game +main :: IO () +main = startEngine defaultWindowConfig $ BasicGame def def