如何在文本冒险游戏(Haskell)中表示和跟踪可变状态

时间:2016-03-13 17:46:33

标签: haskell functional-programming

这个问题是关于如何设计应用程序和布局数据,以表示冒险游戏中的可变状态,其中有玩家可以在的位置,以及可以在这些位置或玩家库存中的项目他们捡起来之后。

newtype ItemName     = ItemName Text
newtype LocationName = LocationName Text
newtype Description  = Description Text
new type ItemWeight  = ItemWeight Int

data Location = Location LocationName Description [Item]
data Item     = Item ItemName [ItemName] Description ItemWeight
data Player   = Player PlayerPosition [Item]

data PlayerPosition = ...

每个Location拥有Item列表可能没有意义,因为这是可变的,其他一切都是不可变的。同样适用于Player

在这样的游戏中,核心逻辑属于一个名为。

的函数
playMove :: Move -> GameState -> ([MoveResult], GameState)

MoveMoveResult函数的位置

data Move                              data MoveResult
  = MoveTo Direction                     = EnteredLocation Location
  | PickUpItem ItemName                  | NoLocationAt Direction
  | UseItemWithItem ItemName ItemName    | PickedUpItem Item
  | Examine ItemName                     | UsedItemWithItem Item
  | Quit                                 | CantUseItems Item Item
                                         | NoSuchItem Item
                                         | Description Item
                                         | InventoryFull ItemWeight Item
                                         | GameWon
                                         | ...

将用户输入解析为Move值,并输出MoveResult,这是IO monad中发生的事情。方向只是North | South | East | West

的总和类型

其中大多数无疑会涉及GameState捕获

  1. 世界各地的玩家位置
  2. 他们的库存内容
  3. 物品的位置。物品可能位于某个位置,玩家的库存中,也可能处于不稳定状态。尚未通过组合项目或在组合它们时将其删除来创建物品中的物品。
  4. 我的问题是

    1. 什么是表示GameState的良好数据结构。这样的结构应该封装玩家的位置和库存内容,以及可以在世界中找到物品的地方。它应该促进API之类的
      • moveTo :: GameState -> Direction -> ([MoveResult], GameState)其中MoveResultEnteredLocationNoLocationAt。这又需要像locationAt :: Location -> Direction -> Maybe Location
      • 这样的功能
      • pickUp :: GameState -> ItemName -> ([MoveResult], GameState)其中MoveResultPickedUpItemNoSuchItem。在这种情况下,如何将ItemNameItem
      • 匹配
      • useItemWithItem :: GameState -> (ItemName, ItemName) -> ([MoveResult], GameState) MoveResult如果有新项目,则为PickedUpItem,否则为CantUserItems。这又需要像combineItems :: Item -> Item -> Maybe Item这样的函数来查看是否可以合并项目。
    2. 什么是表示PlayerPosition
    3. 的良好数据结构
    4. 我应该使用繁重的Location类型还是引入一些LocationId来表示PlayerPosition以及可能的项目位置
    5. 对于通过Location边缘将原始Location到目标Direction的图表映射,要使用的最佳数据结构是什么。我在想Data.Map (Location, Direction)Location
    6. 我知道拉链,以及playMove适合State monad的事实。我的问题是如何构造GameState以捕获此问题的可变和不可变部分。

      编辑:答案

      我确实从这个富有成效的discussion on Reddit中找到了答案。如果其他人发现了这个问题,那个讨论中的/u/achadoseperdidos指出我的问题的答案是由chapter 11 of Learn PureScript by Example提供的,它实际上描述了如何使用RWS monads编写一个冒险游戏 - 完全是答案我在寻找。

1 个答案:

答案 0 :(得分:0)

在这种规模下,您无需担心尺寸或效率。只需使用如下记录:

import qualified Data.Map as M

data Location = Location
  { locationname :: String
  , description :: String
  } deriving Ord

data GameStep = GameStep
  { itemsWorld :: M.Map Location [Item]
  , inventory :: [Item]
  , position :: Location
  }