在上周的文章中,我们使用了一个简洁的小算法为我们的游戏生成随机迷宫。 这很酷,但是当我们“完成”迷宫时什么也没发生! 我们将在本周对此进行更改。 完成后,我们将允许游戏继续重新生成新的迷宫! 您可以在该项目的Github存储库的part-2
分支上找到该部分的所有代码!
如果您是Haskell的初学者,希望本系列文章可以帮助您学习简单的方法来做一些有趣的事情! 如果您有点不知所措,请尝试先阅读我们的《 Liftoff系列》!
- 全球游戏果酱:1月20日至22日
- HTML5游戏:现在就是未来
- 在Unity和C#中创建虚拟宠物游戏(跨平台)
- MonoGame教程—第2部分:Jogo e Inicio do Projeto之路
- Smalland:抢先体验游戏透明性的案例
目标
我们这部分的目标非常简单。 我们希望做到这一点,以便当我们到达“终点”位置时,我们会收到“胜利”消息,并可以通过按一个键重新启动游戏。 这样做时,我们会得到一个新的迷宫。 有几个组成部分:
- 到达终点应该改变我们
World
的组成部分。 - 当该组件发生更改时,我们应该显示一条消息而不是迷宫。
- 在此状态下按“ Enter”键,应重新进入新的迷宫游戏。
听起来很简单! 我们走吧!
游戏结果
我们将首先添加一个新类型来表示我们游戏的当前“结果”。 我们将把这个状态添加到我们的World
。 另外,我们将在状态中添加一个随机数生成器。 重新制作迷宫时,我们将需要此:
data GameResult = GameInProgress | GameWon
deriving (Show, Eq)
data World = World
{ playerLocation :: Location
, startLocation :: Location
, endLocation :: Location
, worldBoundaries :: Maze
, worldResult :: GameResult
, worldRandomGenerator :: StdGen
}
我们的生成步骤需要进行一些小调整。 现在,函数本身应返回其最终生成器,作为额外的结果:
generateRandomMaze :: StdGen -> (Int, Int) -> (Maze, StdGen)
generateRandomMaze gen (numRows, numColumns) =
(currentBoundaries finalState, randomGen finalState)
where
...
finalState = execState dfsSearch initialState
然后在我们的main
功能中,我们将新的生成器和游戏结果合并到我们的World
:
main = do
gen <- getStdGen
let (maze, gen') = generateRandomMaze gen (25, 25)
play
windowDisplay
white
20
(World (0, 0) (0, 0) (24, 24) maze GameInProgress gen')
...
现在,让我们修复更新功能,以便在到达最终位置时可以更改游戏结果! 我们将在此处添加防护措施以检查这种情况并相应地进行更新:
updateFunc :: Float -> World -> World
updateFunc _ w
| playerLocation w == endLocation w = w { worldResult = GameWon }
| otherwise = w
我们可以在eventHandler
执行此操作,但让update
函数对其进行处理似乎更惯用了。 如果使用事件处理程序,我们将永远看不到令牌进入最后一个平方。 游戏将直接跳到胜利屏幕。 那有点奇怪。 这里至少有一个很小的差距。
显示胜利!
现在我们的游戏将正确更新。 但是我们必须通过更改显示器的外观来应对这种变化! 这是一个快速修复。 我们将为我们的drawingFunc
添加一个类似的防护:
drawingFunc :: (Float, Float) -> Float -> World -> Picture
drawingFunc (xOffset, yOffset) cellSize world
| worldResult world == GameWon =
Translate (-275) 0 $ Scale 0.12 0.25
(Text "Congratulations! You've won!\
\Press enter to restart with a new maze!")
| otherwise = ...
请注意,这里的Text
是Gloss Picture
构造函数,而不是Data.Text
。 我们还会对其进行缩放和翻译,以使文本显示在屏幕上。 这就是我们需要获得胜利屏幕才能显示出来的全部内容!
重新开始游戏
最后一步是,如果他们按Enter键,我们必须按照流程重新启动游戏! 这涉及到更改inputHandler
以给我们一个全新的World
。 与其他功能一样,我们将添加一个保护装置来处理GameWon
案例:
inputHandler :: Event -> World -> World
inputHandler event w
| worldResult w == GameWon = …
| otherwise = case event of
...
我们要创建一个新的案例部分,说明按“ Enter”键的用户。 这一部分需要做的就是调用generateRandomMaze
并重新初始化世界!
inputHandler event w
| worldResult w == GameWon = case event of
(EventKey (SpecialKey KeyEnter) Down _ _) ->
let (newMaze, gen') = generateRandomMaze
(worldRandomGenerator w) (25, 25)
in World (0, 0) (0, 0) (24, 24) newMaze GameInProgress gen'
_ -> w
至此,我们完成了! 我们可以重新启动游戏,并随意迷惑我们的内心!
结论
重新启动游戏的能力非常棒! 但是,如果我们想使游戏可重玩而不是随机播放,则需要某种方式存储迷宫。 在下一部分中,我们将看一些将迷宫转储为输出格式的代码。 我们还需要一种从此存储的表示中重新加载的方法。 这最终将使我们能够制作出具有保存和加载状态的真实游戏。
为此,您可以阅读我们有关解析的系列文章。 您尤其想熟悉Megaparsec库。 我们将在本系列的第4部分中对此进行介绍!