|
| 1 | +{-# LANGUAGE Arrows #-} |
| 2 | +{-# LANGUAGE FlexibleInstances #-} |
| 3 | +{-# LANGUAGE MultiParamTypeClasses #-} |
| 4 | +import Control.Applicative |
| 5 | +import Control.Arrow |
| 6 | +import Control.Monad |
| 7 | +import Control.Extra.Monad |
| 8 | +import Data.IORef |
| 9 | +import FRP.Yampa as Yampa |
| 10 | +import FRP.Yampa.Extensions as Yampa |
| 11 | +import FRP.Yampa.Backends.SDL as Yampa |
| 12 | +import FRP.Yampa.Reactimator as Yampa |
| 13 | +import Graphics.UI.SDL as SDL |
| 14 | +import Graphics.UI.SDL.Extensions as SDL |
| 15 | +import System.Random |
| 16 | + |
| 17 | +main :: IO () |
| 18 | +main = do |
| 19 | + |
| 20 | + -- Sources of randomness, used for the top and bottom bars |
| 21 | + stdGen1 <- newStdGen |
| 22 | + stdGen2 <- getStdGen |
| 23 | + |
| 24 | + -- Input sensing, time sensing, rendering |
| 25 | + (producer, initSample) <- initializeSg :: IO (SDLSignal SDLInput, Controller) |
| 26 | + consumer <- initializeSi :: IO MySDLRenderer |
| 27 | + |
| 28 | + -- Reactimation |
| 29 | + alwaysReactimate (return initSample) |
| 30 | + (pollSg producer) |
| 31 | + (pushSi consumer) |
| 32 | + (game stdGen1 stdGen2) |
| 33 | + |
| 34 | +-- * Game |
| 35 | + |
| 36 | +game :: StdGen -> StdGen -> SF Controller (Int, ([Int], [Int])) |
| 37 | +game stdGen1 stdGen2 = proc (c) -> do |
| 38 | + -- (identity &&& allBars) >>> (bird &&& arr snd) |
| 39 | + sBars <- allBars -< () |
| 40 | + sBird <- bird -< (c, sBars) |
| 41 | + returnA -< (sBird, sBars) |
| 42 | + where -- Top bars move slower, bottom bars move faster |
| 43 | + allBars = bars stdGen1 &&& timeTransform (*3) (bars stdGen2) |
| 44 | + |
| 45 | +-- Restart every time the bird hits one of the bars |
| 46 | +bird :: SF (Controller, ([Int],[Int])) Int |
| 47 | +bird = switch |
| 48 | + ((birdWithinBounds *** identity) >>> (arr fst &&& birdHitBars)) |
| 49 | + (\_ -> bird) |
| 50 | + where birdHitBars = arr birdOnBars >>> edge |
| 51 | + birdOnBars (playerY, (barsTop, barsBottom)) = |
| 52 | + any (> playerY) selectedTops || any (< playerY) selectedBottoms |
| 53 | + where bottomBarTops = map (\x -> height - x - playerHeight) barsBottom |
| 54 | + selectedTops = take playerWidth $ drop playerX barsTop |
| 55 | + selectedBottoms = take playerWidth $ drop playerX bottomBarTops |
| 56 | + |
| 57 | +-- Restart every time the bird hits the boundaires |
| 58 | +birdWithinBounds :: SF Controller Int |
| 59 | +birdWithinBounds = switch |
| 60 | + (aliveBird >>> (identity &&& hitBounds)) |
| 61 | + (\_ -> birdWithinBounds) -- Restart from middle of the screen |
| 62 | + where hitBounds = arr outOfBounds >>> edge |
| 63 | + outOfBounds = ((> height - playerHeight) &&& (< 0)) >>> uncurry (||) |
| 64 | + |
| 65 | +-- Just a falling/rising bird |
| 66 | +aliveBird :: SF Controller Int |
| 67 | +aliveBird = aliveBird0 (height / 2 :: Double) |
| 68 | + |
| 69 | +aliveBird0 :: Double -> SF Controller Int |
| 70 | +aliveBird0 p0 = proc (c) -> do |
| 71 | + -- Acceleration up/down depending on input |
| 72 | + let acc = if controllerClick c then -stdAcc else stdAcc |
| 73 | + -- Otherwise, obey basic physics laws |
| 74 | + v <- integral -< acc |
| 75 | + p <- (p0 +) ^<< integral -< v |
| 76 | + returnA -< round p |
| 77 | + |
| 78 | +bars :: RandomGen g => g -> SF a [Int] |
| 79 | +bars g = barSides >>> hold 0 >>> sampleWindow' 0 width 0.1 >>> hold [] |
| 80 | + where -- Bunch of events carrying random altitudes (at regular times) |
| 81 | + barSides :: SF a (Yampa.Event Int) |
| 82 | + barSides = (barsUp `rMergeSF` barsDown) |
| 83 | + |
| 84 | + -- Bunch of events, carrying random values (bar heights) |
| 85 | + barsUp :: SF a (Yampa.Event Int) |
| 86 | + barsUp = (occasionalEvent &&& justNoise) >>> arr (uncurry tag) |
| 87 | + |
| 88 | + -- Bunch of events, carrying zero, at regular times, with a bar width delay |
| 89 | + barsDown :: SF a (Yampa.Event Int) |
| 90 | + barsDown = barDownSides >>> Yampa.delay barWidth noEvent |
| 91 | + where barDownSides :: SF a (Yampa.Event Int) |
| 92 | + barDownSides = repeatedly barSep 0 |
| 93 | + |
| 94 | + -- Source of random bar sizes |
| 95 | + justNoise :: SF a Int |
| 96 | + justNoise = noiseR (0, maxBarSize) g |
| 97 | + |
| 98 | + -- Regular separations |
| 99 | + occasionalEvent :: SF a (Yampa.Event ()) |
| 100 | + occasionalEvent = repeatedly barSep () |
| 101 | + |
| 102 | +-- * App-specific SDL-Yampa bridge |
| 103 | + |
| 104 | +data SDLInput = SDLInput (IORef Controller) |
| 105 | + |
| 106 | +-- | Controller info at any given point. |
| 107 | +data Controller = Controller |
| 108 | + { controllerClick :: Bool } |
| 109 | + |
| 110 | +instance Source SDLInput Controller IO where |
| 111 | + |
| 112 | + initializeSo = |
| 113 | + SDLInput <$> newIORef (Controller { controllerClick = False }) |
| 114 | + |
| 115 | + pollSo (SDLInput cref) = do |
| 116 | + c <- readIORef cref |
| 117 | + c' <- sdlGetController c |
| 118 | + writeIORef cref c' |
| 119 | + return c' |
| 120 | + |
| 121 | +-- We need a non-blocking controller-polling function. |
| 122 | +-- TODO: Check http://gameprogrammer.com/fastevents/fastevents1.html |
| 123 | +sdlGetController :: Controller -> IO Controller |
| 124 | +sdlGetController info = |
| 125 | + foldWhileM info pollEvent (not.isEmptyEvent) ((return .) . handleEvent) |
| 126 | + |
| 127 | +-- | Handles one event only and returns the updated controller. |
| 128 | +handleEvent :: Controller -> SDL.Event -> Controller |
| 129 | +handleEvent c e = |
| 130 | + case e of |
| 131 | + MouseButtonDown _ _ ButtonLeft -> c { controllerClick = True } |
| 132 | + MouseButtonUp _ _ ButtonLeft -> c { controllerClick = False} |
| 133 | + KeyDown (Keysym { symKey = SDLK_SPACE }) -> c { controllerClick = True } |
| 134 | + KeyUp (Keysym { symKey = SDLK_SPACE }) -> c { controllerClick = False } |
| 135 | + _ -> c |
| 136 | + |
| 137 | +-- ** SDL renderer as Yampa output consumer (sink) |
| 138 | +data MySDLRenderer = MySDLRenderer |
| 139 | + |
| 140 | +instance Sink MySDLRenderer (Int, ([Int],[Int])) IO where |
| 141 | + |
| 142 | + -- Initialize renderer |
| 143 | + initializeSi = do |
| 144 | + SDL.init [InitVideo] |
| 145 | + |
| 146 | + _screen <- setVideoMode width height bpp [SWSurface] |
| 147 | + |
| 148 | + setCaption "Test" "" |
| 149 | + |
| 150 | + enableUnicode True |
| 151 | + |
| 152 | + return MySDLRenderer |
| 153 | + |
| 154 | + -- Rendering each frame |
| 155 | + pushSi MySDLRenderer (playerY, (ceilingHeights,floorHeights)) = do |
| 156 | + screen <- getVideoSurface |
| 157 | + |
| 158 | + let format = surfaceGetPixelFormat screen |
| 159 | + |
| 160 | + -- Background |
| 161 | + green <- mapRGB format 0 0xFF 0 |
| 162 | + _ <- fillRect screen Nothing green |
| 163 | + |
| 164 | + -- Paint bars |
| 165 | + red <- mapRGB format 0xFF 0 0 |
| 166 | + let paintTopBar (x,h) = fillRect screen (Just (Rect x (height - h) 1 h)) red |
| 167 | + paintBotBar (x,h) = fillRect screen (Just (Rect x 0 1 h)) red |
| 168 | + mapM_ paintBotBar (zip [0 .. width-1] ceilingHeights) |
| 169 | + mapM_ paintTopBar (zip [0 .. width-1] floorHeights) |
| 170 | + |
| 171 | + -- Paint player |
| 172 | + blue <- mapRGB format 0 0 0xFF |
| 173 | + _ <- fillRect screen (Just $ Rect playerX playerY playerWidth playerHeight) blue |
| 174 | + |
| 175 | + SDL.flip screen |
| 176 | + |
| 177 | +-- * Game settings |
| 178 | +-- Configuration parameters |
| 179 | +width :: Num a => a |
| 180 | +width = 640 |
| 181 | + |
| 182 | +height :: Num a => a |
| 183 | +height = 480 |
| 184 | + |
| 185 | +bpp :: Num a => a |
| 186 | +bpp = 16 |
| 187 | + |
| 188 | +-- Settings |
| 189 | +maxBarSize :: Num a => a |
| 190 | +maxBarSize = 200 |
| 191 | + |
| 192 | +-- barSep :: Num a => a |
| 193 | +barSep = 20 |
| 194 | + |
| 195 | +barWidth :: Num a => a |
| 196 | +barWidth = 5 |
| 197 | + |
| 198 | +playerHeight :: Num a => a |
| 199 | +playerHeight = 32 |
| 200 | + |
| 201 | +playerWidth :: Num a => a |
| 202 | +playerWidth = 32 |
| 203 | + |
| 204 | +playerX :: Num a => a |
| 205 | +playerX = 300 |
| 206 | + |
| 207 | +stdAcc :: Double |
| 208 | +stdAcc = 9.8 |
0 commit comments