-
Notifications
You must be signed in to change notification settings - Fork 0
/
Main.hs
151 lines (137 loc) · 4.4 KB
/
Main.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Codec.Picture.Types
import Control.Concurrent (threadDelay)
import Control.Monad
( unless,
(<=<),
)
import Data.List (nub)
import qualified Data.Map.Strict as Map
import Data.Maybe
( fromJust,
mapMaybe,
)
import Data.Vector.Generic (thaw)
import Foreign.C.Types (CInt)
import Game
import Graphics.Text.TrueType (loadFontFile)
import SDL
import SDL.Raw.Types (JoystickID)
import Space
( DeltaTime,
Time,
)
import System.FilePath ((</>))
import Visual
( ImageId,
backgroundColorSDL,
)
maxFps = 60
frameInterval :: DeltaTime
frameInterval = round $ 1000000 / maxFps -- microseconds
main :: IO ()
main = do
initialize [InitJoystick, InitVideo]
window <-
createWindow
"twinpin"
-- for screenshots
-- defaultWindow { windowInitialSize = V2 882 496 }
defaultWindow {windowMode = FullscreenDesktop}
renderer <- createRenderer window (-1) defaultRenderer
showWindow window
font <-
either fail return
=<< loadFontFile ("fonts" </> "Aller" </> "Aller_Rg.ttf")
winSize <- get $ windowSize window
preRenderedTextures <-
mapM
(toTexture renderer)
(Map.fromList $ getStaticImages font winSize)
gameLoop renderer preRenderedTextures winSize createGame
mapM_ destroyTexture preRenderedTextures
gameLoop :: Renderer -> Map.Map ImageId Texture -> V2 CInt -> Game -> IO ()
gameLoop renderer preRenderedTextures winSize game = do
updateTime <- currentTime
events <- pollEvents
-- unless (null events) $ print events
-- printFps lastTime updateTime
let updatedGame = updateGame events updateTime game
showFrame renderer preRenderedTextures $ drawGame winSize updatedGame
addedJoysticks <- openJoysticks $ getAddedDevices events
let newGame = assignJoysticks addedJoysticks updatedGame
timeSpent <- currentTime `timeDifference` updateTime
threadDelay $ frameInterval - (timeSpent * 1000)
unless (isFinished newGame) $
gameLoop renderer preRenderedTextures winSize newGame
showFrame ::
Renderer ->
Map.Map String Texture ->
[(Rectangle CInt, Either (Image PixelRGBA8) ImageId)] ->
IO ()
showFrame renderer preRenderedTextures images = do
rendererDrawColor renderer $= backgroundColorSDL
clear renderer
mapM_ (showImage renderer preRenderedTextures) images
present renderer
-- time since game start in milliseconds
currentTime :: IO Time
currentTime = fromIntegral <$> ticks
timeDifference :: IO Time -> Time -> IO DeltaTime
timeDifference current previous = flip (-) previous <$> current
showImage ::
Renderer ->
Map.Map String Texture ->
(Rectangle CInt, Either (Image PixelRGBA8) ImageId) ->
IO ()
showImage renderer preRenderedTextures (destination, generatedOrStatic) =
either
( \image -> do
texture <- toTexture renderer image
drawInWindow texture
destroyTexture texture
)
( \imageId ->
drawInWindow $ fromJust $ Map.lookup imageId preRenderedTextures
)
generatedOrStatic
where
drawInWindow tex =
copyEx renderer tex Nothing (Just destination) 0 Nothing $
V2 False False
-- the texture should be destroyed by the caller
toTexture :: Renderer -> Image PixelRGBA8 -> IO Texture
toTexture renderer image = do
let rawImageData = imageData image
width = fromIntegral $ imageWidth image
height = fromIntegral $ imageHeight image
size = V2 width height
pitch = 4 * width
mutableVector <- thaw rawImageData
surface <- createRGBSurfaceFrom mutableVector size pitch ABGR8888
texture <- createTextureFromSurface renderer surface
freeSurface surface
return texture
getAddedDevices :: [Event] -> [JoystickDevice]
getAddedDevices events = nub $ mapMaybe addedEventToJoystickDevice events
where
addedEventToJoystickDevice (Event _ payload) = case payload of
JoyDeviceEvent (JoyDeviceEventData JoyDeviceAdded deviceId) ->
Just $ JoystickDevice "" (fromIntegral deviceId)
_ -> Nothing
-- TODO: do this in a different thread since openJoystick takes a lot of time
openJoysticks :: [JoystickDevice] -> IO [JoystickID]
openJoysticks = mapM $ getJoystickID <=< openJoystick
printFps :: Time -> Time -> IO ()
printFps prev curr =
let diff = curr - prev
fps = round $ 1000 / fromIntegral diff
in putStrLn $
"time: "
++ show curr
++ " ms\t\tinterval: "
++ show diff
++ " ms\t\tfps: "
++ show fps
++ " Hz"