Skip to content

Commit 6d5083c

Browse files
Raindrops, with new assets
1 parent 4119e29 commit 6d5083c

File tree

9 files changed

+276
-0
lines changed

9 files changed

+276
-0
lines changed

examples/raindrops/Raindrops.hs

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
-- A demo game put together in less than 2 hours to show how easy
2+
-- game programming is in Haskell.
3+
--
4+
-- If you want to use the wiimote, compile the game with the flag
5+
-- -rtsopts
6+
-- and run it with
7+
-- +RTS -V0
8+
-- import Control.Concurrent
9+
import Control.Monad
10+
import Graphics.UI.SDL as SDL
11+
import Graphics.UI.SDL.Image as SDL
12+
import Graphics.UI.SDL.TTF as TTF
13+
import System.CWiid
14+
import System.Random
15+
16+
-- * Game State
17+
data GameState = GameState
18+
{ raindrops :: [(Float, Float)]
19+
, paddle :: (Float, Float)
20+
, lives :: Int
21+
, points :: Int
22+
, lastRaindrop :: Float
23+
, lastFrame :: Float -- Time of last frame
24+
, randomGen :: StdGen
25+
}
26+
deriving Show
27+
28+
level :: GameState -> Int
29+
level gameState = points gameState `div` dropsPerLevel
30+
31+
updatePaddlePos :: GameState -> Float -> GameState
32+
updatePaddlePos gameState newX =
33+
gameState { paddle = (min newX (width - paddleW), height - paddleMargin) }
34+
35+
initialGameState :: StdGen -> GameState
36+
initialGameState gen =
37+
GameState [(width / 2, dropMargin)] (0, height - paddleMargin) maxLives 0 0 0 gen
38+
39+
-- * Runtime environment (resources, devices)
40+
data Env = Env
41+
{ bgImg :: Surface
42+
, dropImg :: Surface
43+
, paddleImg :: Surface
44+
, font :: TTF.Font
45+
, cwiid :: Maybe CWiidWiimote
46+
}
47+
48+
main :: IO ()
49+
main = do
50+
-- Initialise SDL
51+
SDL.init [InitEverything]
52+
ttfOk <- TTF.init
53+
54+
when ttfOk $ do
55+
font <- TTF.tryOpenFont "data/font.ttf" 32
56+
bg <- load "data/background.png"
57+
58+
-- Load with a mask
59+
drop <- load "data/cherry.png"
60+
t <- mapRGB (surfaceGetPixelFormat drop) 0 255 0
61+
setColorKey drop [SrcColorKey, RLEAccel] t
62+
63+
-- Load with a mask
64+
paddle <- load "data/player.png"
65+
t <- mapRGB (surfaceGetPixelFormat paddle) 0 255 0
66+
setColorKey paddle [SrcColorKey, RLEAccel] t
67+
68+
-- Create window, no mouse
69+
SDL.setVideoMode (round width) (round height) 32 [SWSurface]
70+
SDL.setCaption "Raindrops" ""
71+
SDL.showCursor False
72+
73+
-- Create Random number generator
74+
gen <- getStdGen
75+
76+
-- Initialise input devices
77+
wiimote <- initializeWiimote
78+
79+
case font of
80+
Nothing -> return ()
81+
Just ttf -> let env = Env bg drop paddle ttf wiimote
82+
gs = initialGameState gen
83+
in run env gs
84+
85+
-- Game loop
86+
run :: Env -> GameState -> IO ()
87+
run env gameState = do
88+
-- IO: Sense (input)
89+
gameStateP <- calculatePaddlePos env gameState
90+
91+
-- IO: Sense (time)
92+
newTime <- fmap fromIntegral SDL.getTicks
93+
let dt = newTime - lastFrame gameStateP
94+
let gameStateT = gameStateP { lastFrame = newTime }
95+
96+
-- Physics (movement)
97+
let gameStateP = moveForward dt gameStateT
98+
99+
-- Physics (collisions)
100+
let gameStateC = raindropsBottom (raindropsPaddle gameStateP)
101+
102+
-- Logic (new raindrops)
103+
let dtLastRaindrop = newTime - lastRaindrop gameStateC
104+
gameStateN =
105+
if dtLastRaindrop > dropDelay
106+
then let (newX, gen') = randomR (0, round (width - dropW) :: Int)
107+
(randomGen gameStateC)
108+
oldDrops = raindrops gameStateC
109+
in gameStateC { raindrops = (fromIntegral newX, dropMargin) : oldDrops
110+
, lastRaindrop = newTime
111+
, randomGen = gen'
112+
}
113+
else gameStateC
114+
115+
-- Logic (game over)
116+
let gameStateO = if lives gameStateN < 0
117+
then initialGameState (randomGen gameStateN)
118+
else gameStateN
119+
120+
-- IO: Paint
121+
render env gameStateO
122+
123+
-- Loop
124+
run env gameStateO
125+
126+
-- * Physics
127+
moveForward :: Float -> GameState -> GameState
128+
moveForward dt gs = gs { raindrops = movedRaindrops }
129+
where movedRaindrops = map moveRaindrop (raindrops gs)
130+
moveRaindrop (x,y) = (x, y + 0.1 * dt * fromIntegral (level gs + 1))
131+
132+
-- * Collisions
133+
134+
-- ** Collisions with paddle
135+
raindropsPaddle :: GameState -> GameState
136+
raindropsPaddle gs = gs { raindrops = remainingRaindrops
137+
, points = points gs + pts
138+
}
139+
where remainingRaindrops = filter (not.collidesWithPaddle) (raindrops gs)
140+
pts = length (raindrops gs) - length remainingRaindrops
141+
collidesWithPaddle (x,y) = (within x paddleXMin paddleXMax
142+
|| within paddleXMin x (x + dropW))
143+
&& (within y paddleYMin paddleYMax
144+
|| within paddleYMin y (y + dropH))
145+
where paddleXMin = fst (paddle gs)
146+
paddleXMax = fst (paddle gs) + paddleW
147+
paddleYMin = snd (paddle gs)
148+
paddleYMax = snd (paddle gs) + paddleH
149+
within x xMin xMax = x >= xMin && x <= xMax
150+
151+
-- ** Collisions with bottom
152+
raindropsBottom :: GameState -> GameState
153+
raindropsBottom gs = gs { raindrops = remainingRaindrops
154+
, lives = decreasedLives
155+
}
156+
where remainingRaindrops = filter (\(_,y) -> y < height) (raindrops gs)
157+
decreasedLives = lives gs - diffDrops
158+
diffDrops = length (raindrops gs) - length remainingRaindrops
159+
160+
-- * Input sensing
161+
calculatePaddlePos :: Env -> GameState -> IO GameState
162+
calculatePaddlePos env gs = case cwiid env of
163+
Nothing -> calculatePaddlePosSDL env gs
164+
Just wm -> do (x,y) <- senseWiimote wm
165+
return (updatePaddlePos gs x)
166+
167+
-- ** SDL Sensing
168+
calculatePaddlePosSDL :: Env -> GameState -> IO GameState
169+
calculatePaddlePosSDL env gs = do
170+
e <- pollEvent
171+
case e of
172+
NoEvent -> return gs
173+
MouseMotion x y _ _ -> calculatePaddlePosSDL env (updatePaddlePos gs (fromIntegral x))
174+
_ -> calculatePaddlePosSDL env gs
175+
176+
-- * Output drawing
177+
render :: Env -> GameState -> IO ()
178+
render env gameState = do
179+
180+
screen <- getVideoSurface
181+
182+
-- Clear screen
183+
SDL.blitSurface (bgImg env) Nothing
184+
screen (Just (SDL.Rect 0 0 (round width) (round height)))
185+
186+
-- Paint each raindrop
187+
let paintADropAt (x,y) = do
188+
SDL.blitSurface (dropImg env) Nothing
189+
screen (Just (SDL.Rect (round x) (round y) (round dropW) (round dropH)))
190+
191+
mapM_ paintADropAt (raindrops gameState)
192+
193+
-- Paint the paddle
194+
let (x,y) = paddle gameState
195+
SDL.blitSurface (paddleImg env) Nothing
196+
screen (Just (SDL.Rect (round x) (round y) (round paddleW) (round paddleH)))
197+
198+
-- Paint points, lives
199+
let ttf = font env
200+
message <- TTF.renderTextSolid
201+
ttf
202+
("Level " ++ show (level gameState)
203+
++ " / Lives " ++ show (lives gameState))
204+
(SDL.Color 128 128 128)
205+
let w1 = SDL.surfaceGetWidth message
206+
h1 = SDL.surfaceGetHeight message
207+
SDL.blitSurface message Nothing
208+
screen (Just (SDL.Rect 10 10 w1 h1))
209+
210+
-- Present
211+
SDL.flip screen
212+
213+
-- * Game constants
214+
paddleW, paddleH :: Float
215+
paddleW = 126
216+
paddleH = 31
217+
218+
paddleMargin :: Float
219+
paddleMargin = 60
220+
221+
width, height :: Float
222+
width = 800
223+
height = 480
224+
225+
dropW, dropH :: Float
226+
dropW = 70
227+
dropH = 70
228+
229+
dropMargin :: Float
230+
dropMargin = 10
231+
232+
dropsPerLevel :: Int
233+
dropsPerLevel = 20
234+
235+
dropDelay :: Float
236+
dropDelay = 500
237+
238+
maxLives :: Int
239+
maxLives = 10
240+
241+
-- * Wiimote sensing
242+
243+
-- | Initializes the wiimote, optionally returning the sensing function. It
244+
-- returns Nothing if the Wiimote cannot be detected. Users should have a BT
245+
-- device and press 1+2 to connect to it. A message is shown on stdout.
246+
initializeWiimote :: IO (Maybe CWiidWiimote)
247+
initializeWiimote = do
248+
putStrLn "Initializing WiiMote. Please press 1+2 to connect."
249+
wm <- cwiidOpen
250+
case wm of
251+
Nothing -> return ()
252+
Just wm' -> void $ cwiidSetRptMode wm' 15 -- Enable button reception, acc and IR
253+
return wm
254+
255+
senseWiimote :: CWiidWiimote -> IO (Float, Float)
256+
senseWiimote wmdev = do
257+
irs <- cwiidGetIR wmdev
258+
259+
-- Obtain positions of leds 1 and 2 (with a normal wii bar, those
260+
-- will be the ones we use).
261+
let led1 = irs!!0
262+
led2 = irs!!1
263+
264+
-- Calculate mid point between sensor bar leds
265+
let posX = ((cwiidIRSrcPosX led1) + (cwiidIRSrcPosX led2)) `div` 2
266+
posY = ((cwiidIRSrcPosY led1) + (cwiidIRSrcPosY led2)) `div` 2
267+
268+
-- Calculate proportional coordinates
269+
let propX = fromIntegral (1024 - posX) / 1024.0
270+
propY = fromIntegral (max 0 (posY - 384)) / 384.0
271+
272+
-- Calculate game area coordinates
273+
let finX = width * propX
274+
finY = height * propY
275+
276+
return (finX, finY)
12.7 KB
Loading

examples/raindrops/data/bg.png

747 KB
Loading

examples/raindrops/data/cherry.png

2.73 KB
Loading

examples/raindrops/data/drop.png

828 Bytes
Loading

examples/raindrops/data/font.ttf

33.3 KB
Binary file not shown.

examples/raindrops/data/font2.ttf

55.5 KB
Binary file not shown.

examples/raindrops/data/paddle.png

1.06 KB
Loading

examples/raindrops/data/player.png

2.15 KB
Loading

0 commit comments

Comments
 (0)