I'm trying to draw randomly-positioned rectangles in SDL2 using Haskell. The IO monad is really driving me insane and I can't get it to work. I have the boilerplate SDL2 code ready to go and a custom datatype for the RectangleObject
, and all I want to do is randomly generate the rectangles in the main
function, then draw them inside the appLoop
function.
I get the error:
BadRandomRectangles.hs:17:61-85: error:
• Couldn't match expected type: [RectangleObject]
with actual type: IO [RectangleObject]
• In the fourth argument of ‘GameState’, namely
‘(createRandomRectangles 10)’
In the expression:
GameState
(V2 10 10) (V2 40 40) (V2 1 1) (createRandomRectangles 10)
In an equation for ‘gameState’:
gameState
= GameState
(V2 10 10) (V2 40 40) (V2 1 1) (createRandomRectangles 10)
|
17 | let gameState = GameState (V2 10 10) (V2 40 40) (V2 1 1) (createRandomRectangles 10) in
| ^^^^^^^^^^^^^^^^^^^^^^^^^
My code is:
{-# LANGUAGE OverloadedStrings #-}
import SDL
import System.Random
import Control.Monad (unless, replicateM)
import Foreign.C.Types (CInt)
data RectangleObject = RectangleObject { rectPosn :: V2 CInt, rectDim :: V2 CInt };
data GameState = GameState { posn :: V2 CInt, dim :: V2 CInt, vel :: V2 CInt, objects :: [RectangleObject] };
main :: IO ()
main = do
initializeAll
window <- createWindow "Rectangles" (defaultWindow { windowInitialSize = V2 640 480 })
renderer <- createRenderer window (-1) defaultRenderer
let gameState = GameState (V2 10 10) (V2 40 40) (V2 1 1) (createRandomRectangles 10) in
appLoop renderer gameState
destroyRenderer renderer
destroyWindow window
quit
appLoop :: Renderer -> GameState -> IO ()
appLoop renderer gameState = do
events <- pollEvents
clear renderer
rendererDrawColor renderer $= V4 0 0 0 255
-- Get the RectangleObjects from the game state and draw them
let objs = objects gameState
mapM_ (\(RectangleObject posn dim) -> fillRect renderer (Just $ Rectangle (P $ posn) dim)) objs
-- Fill a rectangle of size 1280x720 with black color
fillRect renderer (Just $ Rectangle (P $ V2 0 0) (V2 1280 720))
rendererDrawColor renderer $= V4 255 0 0 255
fillRect renderer (Just $ Rectangle (P $ posn gameState) (dim gameState))
present renderer
SDL.delay 16
unless qPressed (appLoop renderer (updateGameState vGameState))
updateGameState :: GameState -> GameState
updateGameState (GameState posn dim vel objs) =
GameState (posn + vel) dim vel objs
createRandomRectangles :: Int -> IO [RectangleObject]
createRandomRectangles n = replicateM n generateRandomRectangle
generateRandomRectangle :: IO RectangleObject
generateRandomRectangle = do
x <- randomRIO (1, 10)
y <- randomRIO (1, 10)
w <- randomRIO (1, 10)
h <- randomRIO (1, 10)
return $ RectangleObject (V2 x y) (V2 w h)
Your main problem is that, in this code:
let gameState = GameState (V2 10 10) (V2 40 40) (V2 1 1) (createRandomRectangles 10) in
appLoop renderer gameState
the expression createRandomRectangles 10
is of type IO [RectangleObject]
, but the fourth field in your GameState
object is a pure [RectangleObject]
.
Rewrite this as:
rects <- createRandomRectangles 10
let gameState = GameState (V2 10 10) (V2 40 40) (V2 1 1) rects in
appLoop renderer gameState
and your main
function should type check.
There were still a couple of undefined variables in your code (qPressed
and vGameState
) and some logic errors in your renderer (e.g., you were drawing the random rectangles and then erasing them by drawing over them with a black rectangle). Also, the random rectangles were so small, they just showed up as little blips in the top left corner.
After making a couple adjustments, the following code seemed to work and even looked "game-like". Good luck!
{-# LANGUAGE OverloadedStrings #-}
import SDL
import System.Random
import Control.Monad (unless, replicateM)
import Foreign.C.Types (CInt)
data RectangleObject = RectangleObject { rectPosn :: V2 CInt, rectDim :: V2 CInt };
data GameState = GameState { posn :: V2 CInt, dim :: V2 CInt, vel :: V2 CInt, objects :: [RectangleObject] };
main :: IO ()
main = do
initializeAll
window <- createWindow "Rectangles" (defaultWindow { windowInitialSize = V2 640 480 })
renderer <- createRenderer window (-1) defaultRenderer
rects <- createRandomRectangles 10
let gameState = GameState (V2 10 10) (V2 40 40) (V2 1 1) rects in
appLoop renderer gameState
destroyRenderer renderer
destroyWindow window
quit
appLoop :: Renderer -> GameState -> IO ()
appLoop renderer gameState = do
events <- pollEvents
clear renderer
-- Fill a rectangle of size 1280x720 with black color
rendererDrawColor renderer $= V4 0 0 0 255
fillRect renderer (Just $ Rectangle (P $ V2 0 0) (V2 1280 720))
-- Get the RectangleObjects from the game state and draw them in green
let objs = objects gameState
rendererDrawColor renderer $= V4 255 255 0 255
mapM_ (\(RectangleObject posn dim) -> fillRect renderer (Just $ Rectangle (P $ posn) dim)) objs
-- Draw the main game rectangle in red
rendererDrawColor renderer $= V4 255 0 0 255
fillRect renderer (Just $ Rectangle (P $ posn gameState) (dim gameState))
present renderer
SDL.delay 16
-- unless qPressed (appLoop renderer (updateGameState vGameState))
appLoop renderer (updateGameState gameState)
updateGameState :: GameState -> GameState
updateGameState (GameState posn dim vel objs) =
GameState (posn + vel) dim vel objs
createRandomRectangles :: Int -> IO [RectangleObject]
createRandomRectangles n = replicateM n generateRandomRectangle
generateRandomRectangle :: IO RectangleObject
generateRandomRectangle = do
let scale = 500
x <- randomRIO (1, scale)
y <- randomRIO (1, scale)
w <- randomRIO (1, scale)
h <- randomRIO (1, scale)
return $ RectangleObject (V2 x y) (V2 w h)