如何在 Haskell 的 SDL2 绑定中绘制随机放置的矩形?

How to draw randomly-placed rectangles in Haskell's SDL2 bindings?

提问人:TheSinisterStone 提问时间:10/21/2023 最后编辑:K. A. BuhrTheSinisterStone 更新时间:10/22/2023 访问量:71

问:

我正在尝试使用 Haskell 在 SDL2 中绘制随机定位的矩形。IO monad 真的让我发疯,我无法让它工作。我已经准备好了样板 SDL2 代码和 的自定义数据类型,我想做的就是在函数中随机生成矩形,然后在函数中绘制它们。RectangleObjectmainappLoop

我收到错误:

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
   |                                                             ^^^^^^^^^^^^^^^^^^^^^^^^^

我的代码是:

{-# 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)
Haskell SDL 单子

评论

4赞 cafce25 10/21/2023
那么问题出在哪里呢?它不编译 -> 添加错误消息。它的行为是否与您的预期不同 - >添加预期和实际行为。
0赞 K. A. Buhr 10/22/2023
@TheSinisterStone,我添加了您的问题的(主要)错误消息。如果这不是您遇到的问题,您可以编辑问题以更好地解释您的问题,也可以提出一个新问题。

答:

2赞 K. A. Buhr 10/22/2023 #1

您的主要问题是,在此代码中:

let gameState = GameState (V2 10 10) (V2 40 40) (V2 1 1) (createRandomRectangles 10) in
  appLoop renderer gameState

表达式是 类型,但对象中的第四个字段是纯 .createRandomRectangles 10IO [RectangleObject]GameState[RectangleObject]

将其改写为:

rects <- createRandomRectangles 10
let gameState = GameState (V2 10 10) (V2 40 40) (V2 1 1) rects in
  appLoop renderer gameState

您的函数应键入 check。main

您的代码中仍然有几个未定义的变量( 和 ),渲染器中也存在一些逻辑错误(例如,您正在绘制随机矩形,然后通过用黑色矩形绘制它们来擦除它们)。此外,随机矩形非常小,它们只是在左上角显示为小点。qPressedvGameState

经过几次调整后,以下代码似乎有效,甚至看起来“像游戏一样”。祝你好运!

{-# 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)