-
Notifications
You must be signed in to change notification settings - Fork 3
/
Main.hs
317 lines (266 loc) · 8.76 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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
{-# LANGUAGE ForeignFunctionInterface #-}
-- Nario
module Main where
import Graphics.UI.SDL hiding (Event)
import Graphics.UI.SDL.Utilities
import System.IO.Unsafe (unsafeInterleaveIO)
import Control.Concurrent (threadDelay)
--import Control.Exception
import Player
import Field
import Util
import AppUtil
import Const
import Images
import Font
import Event
import Actor
import Actor.AnimBlock
import Actor.Kuribo
import Actor.Nokonoko
import Actor.Kinoko
import Actor.Flower
import Actor.BrokenBlock
import Actor.CoinGet
import Actor.ScoreAdd
import Mixer
import Data.List
import Foreign
import Foreign.C.Types
import Foreign.Ptr
import Foreign.Marshal.Alloc
import Foreign.Marshal.Array
foreign export ccall "start_hs" main :: IO ()
-- Background color
backColor = Pixel 0x5080FF
-- Display command
type Scr = Surface -> Mixer -> IO ()
foreign import ccall unsafe "SDL_GetKeyState" sdlGetKeyState :: Ptr CInt -> IO (Ptr Word8)
getKeyState :: IO [SDLKey]
getKeyState = alloca $ \numkeysPtr -> do
keysPtr <- sdlGetKeyState numkeysPtr
numkeys <- peek numkeysPtr
(map Graphics.UI.SDL.Utilities.toEnum . map fromIntegral . findIndices (== 1)) `fmap` peekArray (fromIntegral numkeys) keysPtr
-- Program etrny point
main :: IO ()
main = do
Graphics.UI.SDL.init [InitVideo]
setCaption wndTitle wndTitle
sur <- setVideoMode screenWidth screenHeight wndBpp [HWSurface, DoubleBuf, AnyFormat]
do
mixer <- createMixer
strm <- delayedStream (1000000 `div` frameRate) fetch
scrs <- process $ map snd $ takeWhile notQuit strm
mapM_ (\scr -> scr sur mixer) scrs
quit
where
-- fetch for environment
fetch = do
quit <- checkSDLEvent
ks <- getKeyState
return (quit, ks)
notQuit = not . fst
-- Delayed stream
-- return result list of action, interval microsec
delayedStream :: Int -> IO a -> IO [a]
delayedStream microsec func = unsafeInterleaveIO $ do
-- threadDelay microsec -- Using this cause serious slow down in Ubuntu (bad precision?)
delay (fromInteger (toInteger (microsec `div` 1000))) -- SDL.Time.delay
x <- func
xs <- delayedStream microsec func
return $ x:xs
-- Process SDL events
-- return True if quit event has come
checkSDLEvent = do
ev <- pollEvent
case ev of
Quit -> return True
KeyDown (Keysym { symKey = ks, symModifiers = km } )
| ks == SDLK_ESCAPE -> return True
| ks == SDLK_F4 && (KeyModLeftAlt `elem` km || KeyModRightAlt `elem` km) -> return True
NoEvent -> return False
_ -> checkSDLEvent
-- State of Game
data GameGame =
GameGame {
pl :: Player,
fld :: Field,
actors :: [ActorWrapper],
time :: Int,
snds :: [SoundType]
}
-- Process whole key input and return display command list
process :: [[SDLKey]] -> IO [Scr]
process kss = do
imgres <- loadImageResource imageTypes
sndres <- loadSoundResource soundTypes
fldmap <- loadField 0
let tmpscrs = doTitle fldmap kss
let scrs = zipWith (common imgres sndres) tmpscrs kss
return $ scrs ++ [final imgres sndres]
where
-- Common Action
common imgres sndres scr ks sur mixer = do
scr imgres sndres sur mixer
if SDLK_s `elem` ks
then saveBMP sur "ss.bmp" >> return ()
else return ()
Graphics.UI.SDL.flip sur
return ()
-- Finalize
final imgres sndres sur mixer = releaseImageResource imgres
-- Title
doTitle :: Field -> [[SDLKey]] -> [ImageResource -> SoundResource -> Scr]
doTitle fldmap kss = loop kss
where
loop :: [[SDLKey]] -> [ImageResource -> SoundResource -> Scr]
loop (ks:kss) = res : left ks kss
res imgres sndres sur mixer = do
fillRect sur Nothing backColor
renderTitle imgres sur
left ks kss
| SDLK_SPACE `elem` ks = doGame fldmap kss
| otherwise = loop kss
-- Scroll event
scrollEvent :: Field -> Int -> (Field, [Event])
scrollEvent fld cx
| cx < length (head fld) = foldl proc (fld, []) $ zip [0..] cols
| otherwise = (fld, [])
where
proc (f, e) (cy, c) =
case event cy c of
Just ev -> (fieldSet f cx cy ' ', ev : e)
Nothing -> (f, e)
cols = map (!! cx) fld
event cy c
| c `elem` "kn" = Just $ EvAddActor $ genActor c
| otherwise = Nothing
where
genActor c = case c of
'k' -> ActorWrapper $ newKuribo cx cy
'n' -> ActorWrapper $ newNokonoko cx cy
-- Collision detection and response
hitcheck :: Player -> [ActorWrapper] -> (Player, [ActorWrapper], [Event])
hitcheck player actors = foldl proc (player, [], []) actors
where
proc (pl, ac, ev) (ActorWrapper a) = case getHitRect a of
Nothing -> nothingHappened
Just rc ->
if not $ ishit plrc rc
then nothingHappened
else (pl', ac', ev')
where
nothingHappened = (pl, ac ++ [ActorWrapper a], ev)
plrc = getPlayerHitRect player
(pl', a', evtmp) = onHit pl a
ac' = case a' of
Just a'' -> ac ++ [a'']
Nothing -> ac
ev' = ev ++ evtmp
-- Game
doGame :: Field -> [[SDLKey]] -> [ImageResource -> SoundResource -> Scr]
doGame fldmap kss = loop (head kss) initialState kss
where
loop :: [SDLKey] -> GameGame -> [[SDLKey]] -> [ImageResource -> SoundResource -> Scr]
loop bef gs (ks:kss) = scr' : left ks kss
where
(scr', gs') = updateProc (keyProc bef ks) gs
isPlayerDead = getPlayerY (pl gs') >= (screenHeight + chrSize * 2) * one
timeOver = time gs' <= 0
left ks kss
| isPlayerDead || timeOver = doGameOver fldmap kss
| otherwise = loop ks gs' kss
-- Update
updateProc :: KeyProc -> GameGame -> (ImageResource -> SoundResource -> Scr, GameGame)
updateProc kp gs = (scr', gs')
where
time' = max 0 (time gs - 1)
(fld', screv') = scrollEvent (fld gs) $ getScrollPos (pl gs) `div` chrSize + 18
(pl', plev) = updatePlayer kp fld' (pl gs)
actors_updates = updateActors (fld gs) (actors gs)
actors' = filterActors $ map fst actors_updates
ev' = concatMap snd actors_updates
(pl'', actors'', ev'') = hitcheck pl' actors'
gstmp = gs { pl = pl'', fld = fld', actors = actors'', time = time' }
allEvent = plev ++ ev' ++ screv' ++ ev''
gs' = procEvent gstmp allEvent
scr' imgres sndres sur mixer = do
mapM_ (\ev -> case ev of
EvSound sndtype -> play sndtype
otherwise -> return ()
) allEvent
renderProc gs' imgres sndres sur mixer
where
play sndtype = do
if False
then
playWav mixer $ lookup sndtype sndres
else do
-- Instead of play wav, print message
--putStrLn $ "play " ++ show sndtype
return ()
initialState = GameGame { pl = newPlayer, fld = fldmap, actors = [], time = 400 * timeBase, snds = [] }
-- Game over
doGameOver fldmap kss = doTitle fldmap kss
-- Process events
procEvent :: GameGame -> [Event] -> GameGame
procEvent gs ev = foldl proc gs ev
where
proc gs (EvHitBlock imgtype cx cy bSuper)
| hardBlock c = gs
| bSuper && breakable c = breakBlock gs cx cy
| c == 'K' = genKinoko
| c == '?' = getCoin
| otherwise = gs'
where
c = fieldRef (fld gs) cx cy
breakable c = c == 'O'
gs' = gs { fld = fld', actors = actors' }
actors' = actors gs ++ [ActorWrapper $ newAnimBlock cx cy $ fieldRef (fld gs) cx cy]
fld' = fieldSet (fld gs) cx cy '*'
breakBlock gs cx cy =
gs {
fld = fieldSet (fld gs) cx cy ' ',
actors = actors gs ++ map ActorWrapper (newBrokenBlock cx cy),
pl = addScore pointBreakBlock $ pl gs
}
genKinoko = gs' { actors = actors gs' ++ [a] }
where a = if not bSuper then ActorWrapper $ newKinoko cx cy else ActorWrapper $ newFlower cx cy
getCoin = gs' { actors = actors gs' ++ [ActorWrapper a], pl = addScore pointGetCoin $ playerGetCoin $ pl gs' }
where a = newCoinGet cx cy
proc gs (EvSetField cx cy c) = gs { fld = fieldSet (fld gs) cx cy c }
proc gs (EvAddActor act) = gs { actors = actors gs ++ [act] }
proc gs (EvScoreAddEfe sx sy pnt) = gs { actors = actors gs ++ [ActorWrapper $ newScoreAdd sx sy pnt] }
proc gs (EvSound sndtype) = gs
-- Render
renderProc :: GameGame -> ImageResource -> SoundResource -> Scr
renderProc gs imgres sndres sur mixer = do
fillRect sur Nothing backColor
let scrx = getScrollPos (pl gs)
renderField sur imgres scrx (fld gs)
renderInfo gs imgres sur
renderActors imgres scrx sur (actors gs)
renderPlayer sur imgres scrx (pl gs)
return ()
-- Render information
renderInfo gs imgres sur = do
puts 3 1 "NARIO"
puts 3 2 $ deciWide 6 '0' $ getPlayerScore (pl gs)
puts 11 2 ("?*" ++ deciWide 2 '0' (getPlayerCoin $ pl gs))
puts 18 1 "WORLD"
puts 19 2 "1-1"
puts 25 1 "TIME"
puts 26 2 $ deciWide 3 '0' $ (time gs + timeBase-1) `div` timeBase
where
puts = fontPut font sur
font = Font (getImageSurface imgres ImgFont) 8 8 16
-- Render title screen
renderTitle imgres sur = do
putimg sur imgres ImgTitle (5*8) (3*8)
-- puts 13 14 "@1985 NINTENDO"
puts 9 17 "> 1 PLAYER GAME"
-- puts 9 19 " 2 PLAYER GAME"
puts 12 22 "TOP- 000000"
where
puts = fontPut font sur
font = Font (getImageSurface imgres ImgFont) 8 8 16