利用Monad模拟全局变量解决Haskell贪吃蛇特殊苹果计分问题
解决方案:用State Monad管理游戏状态,实现特殊苹果计分
嘿,这个需求我之前做Haskell贪吃蛇项目时也碰到过!咱们可以通过扩展游戏状态+State Monad封装状态操作的方式,优雅解决分数和蛇长度解绑的问题,同时完美适配Gloss的纯函数式游戏循环。
第一步:重构游戏状态类型
首先,咱们需要把原来的游戏状态(只有蛇和普通苹果)扩展,加入特殊苹果的位置列表,以及专门统计吃掉特殊苹果的计数器——这就是你要的“模拟全局变量”的核心:
import Graphics.Gloss import Control.Monad.State -- 坐标类型 type Coord = (Int, Int) -- 扩展后的游戏状态 data World = World { snake :: [Coord] -- 蛇的身体段坐标列表 , regularApples :: [Coord] -- 普通苹果位置 , specialApples :: [Coord] -- 特殊苹果位置 , specialEaten :: Int -- 吃掉的特殊苹果次数(核心统计量) } deriving (Show)
初始化状态时,把specialEaten设为0即可:
-- 初始蛇长度设为3(你可以根据自己的需求调整) initialSnake :: [Coord] initialSnake = [(0,0), (-10,0), (-20,0)] initialWorld :: World initialWorld = World { snake = initialSnake , regularApples = [(100, 50), (-50, 100)] , specialApples = [] , specialEaten = 0 }
第二步:用State Monad封装状态更新逻辑
State Monad天生适合处理这种需要读写状态的场景,咱们把“吃苹果”的逻辑封装成State操作,让代码更模块化、可读性更强:
-- 处理蛇吃苹果的逻辑:检查蛇头是否碰到普通/特殊苹果,更新状态 handleAppleCollision :: State World () handleAppleCollision = do world <- get let snakeHead = head $ snake world -- 分离被吃掉的普通苹果和剩余苹果 (eatenReg, remainingReg) = partition (== snakeHead) (regularApples world) -- 分离被吃掉的特殊苹果和剩余特殊苹果 (eatenSpec, remainingSpec) = partition (== snakeHead) (specialApples world) case (not $ null eatenReg, not $ null eatenSpec) of -- 吃到普通苹果:蛇身加1段,移除该苹果 (True, _) -> put world { snake = snake world ++ [last $ snake world] , regularApples = remainingReg } -- 吃到特殊苹果:蛇身加1段,移除该苹果,特殊计数+1 (_, True) -> put world { snake = snake world ++ [last $ snake world] , specialApples = remainingSpec , specialEaten = specialEaten world + 1 } -- 没吃到苹果,啥也不做 _ -> return ()
第三步:调整计分逻辑
现在分数不再直接绑定蛇长度,而是通过基础分(蛇长度变化)+ 特殊苹果额外分计算:
calculateScore :: World -> Int calculateScore world = let baseScore = length (snake world) - length initialSnake -- 普通苹果带来的分数 specialBonus = specialEaten world -- 特殊苹果额外加的分数(每个+1,因为普通给1分,特殊给2分,差值是1) in baseScore + specialBonus
举个例子:初始蛇长3,吃2个普通苹果(长度+2,基础分2),吃1个特殊苹果(长度+1,基础分+1,特殊计数+1),总分数=3+1=4,正好对应21 +12=4,完全符合需求!
第四步:适配Gloss的游戏循环
Gloss的play函数是纯函数式的,咱们只需要在update函数里运行State操作即可:
-- Gloss的update函数:每帧更新游戏状态 update :: Float -> World -> World update _ = execState handleAppleCollision
然后在渲染函数里显示计算后的分数:
-- 渲染函数:绘制蛇、苹果和分数 render :: World -> Picture render world = pictures [ drawSnake $ snake world , drawRegularApples $ regularApples world , drawSpecialApples $ specialApples world , translate (-190) 190 $ scale 0.1 0.1 $ text $ "Score: " ++ show (calculateScore world) ] -- 以下是辅助绘制函数(你可以根据自己的Gloss绘制逻辑调整) drawSnake :: [Coord] -> Picture drawSnake = pictures . map (\(x,y) -> translate (fromIntegral x) (fromIntegral y) $ color green $ rectangleSolid 10 10) drawRegularApples :: [Coord] -> Picture drawRegularApples = pictures . map (\(x,y) -> translate (fromIntegral x) (fromIntegral y) $ color red $ circleSolid 5) drawSpecialApples :: [Coord] -> Picture drawSpecialApples = pictures . map (\(x,y) -> translate (fromIntegral x) (fromIntegral y) $ color purple $ circleSolid 5)
最后,加上输入处理和启动函数:
-- 处理键盘输入(控制蛇的方向,你可以根据自己的逻辑实现) handleInput :: Event -> World -> World handleInput (EventKey (SpecialKey KeyUp) Down _ _) world = world { snake = adjustSnakeDirection Up (snake world) } handleInput (EventKey (SpecialKey KeyDown) Down _ _) world = world { snake = adjustSnakeDirection Down (snake world) } handleInput (EventKey (SpecialKey KeyLeft) Down _ _) world = world { snake = adjustSnakeDirection Left (snake world) } handleInput (EventKey (SpecialKey KeyRight) Down _ _) world = world { snake = adjustSnakeDirection Right (snake world) } handleInput _ world = world -- 调整蛇方向的辅助函数(示例逻辑,你可以替换成自己的) adjustSnakeDirection :: Direction -> [Coord] -> [Coord] adjustSnakeDirection dir snake = newHead : init snake where (x,y) = head snake newHead = case dir of Up -> (x, y+10) Down -> (x, y-10) Left -> (x-10, y) Right -> (x+10, y) data Direction = Up | Down | Left | Right -- 启动游戏 main :: IO () main = play (InWindow "Haskell Snake" (400, 400) (10, 10)) black 10 initialWorld render handleInput update
额外优化建议
- 如果需要随机生成特殊苹果,可以把随机数种子加入
World状态,用State结合Random模块来生成,避免IO污染纯函数循环。 - 可以给特殊苹果加生命周期,比如一段时间后消失,同样在
update函数里用State操作处理。
内容的提问来源于stack exchange,提问作者Bar
相关产品推荐
相关产品推荐

