Haskell中如何表示文本冒险游戏的关卡场景与方位连接关系
问题背景
我正在开发一款文本冒险游戏作为业余项目,目的是自学更多Haskell相关概念。
目前我遇到了游戏关卡(即“场景”)及其连接关系的表示难题。为简化实现,我将游戏地图设计为简单二维空间,场景通过基本方位(北、东、南、西,即NESW)连接,示例布局如下:
Lake ⇳ Forest ⇳ Home ⬄ Field* *: Starting location
在该布局中,从Lake向南走可到达Forest,反向同理;从Field向西走可到达Home。核心需求是:我不仅需要编码哪些场景之间存在连接,还需要编码连接的方式,此处的连接方式即指对应的方位。
我目前考虑过两种实现方案:将关卡表示为图(Graph),或是使用元组列表,但两种方案都存在明显缺陷。
方案一:使用Graph表示场景
我最初尝试用图结构表示场景及连接关系的实现代码如下:
data Scene = InitialField | Forest | House | Lake -- |`gameScenes` maps scenes to their integer representation, as used in `sceneGraph`. gameScenes :: Map Int Scene gameScenes = let scenes = [ (0, InitialField) , (1, Forest) , (2, House) , (3, Lake) ] in fromList scenes -- |the `sceneGraph` describes the way different scenes of the game connect to each other. sceneGraph :: Graph sceneGraph = let bounds = (0, 3) edges = [ (0, 1) -- link initial field to forest , (0, 2) -- link initial field to house , (1, 3) -- link forest to lake ] in buildG bounds edges
缺陷:该实现的核心局限是图结构无法编码边对应的方位信息。除此之外图结构非常适合表示作为顶点的场景、作为边的连接关系,因此我想了解:是否有方法为图的边添加“元数据”(本例中为方位信息)?
方案二:使用元组列表表示场景
发现上述图结构的局限后,我尝试了另一种方案,使用元组列表表示场景间的连接,代码如下:
data Scene = InitialField | Forest | House | Lake data CardinalDirection = North | East | South | West type LinkedScene = (CardinalDirection, Scene) -- |`linkedScenes`, applied to a `Scene`, returns a list of the connected scenes and their cardinal directions. linkedScenes :: Scene -> [LinkedScene] linkedScenes InitialField = [(North, Forest)] linkedScenes Forest = [(South, InitialField), (West, Lake)] -- The other links are left as an exercise for the reader ;) linkedScenes _ = []
缺陷:该方案确实可以通过传入玩家当前所在Scene调用linkedScenes获取所有连接及对应方位,但存在一个明显的问题:需要手动双向编码场景间的空间连接关系。也就是说,如果已知Forest在InitialField北侧,我需要写两段逻辑:一段是传入Forest返回[(South, InitialField)],另一段反向传入InitialField返回[(North, Forest)]。这种实现极易引入bug:后续新增场景、调整游戏世界布局时,我需要逐一检查所有连接的两端是否匹配。
需求总结
我期望找到满足以下特性的实现方案:
- 支持通过单条数据编码场景间的双向连接关系,不需要像第二种方案一样手动双向编码(这类重复逻辑极易引发bug);
- 支持后续便捷调整游戏/地图布局,维护成本低;
- 可在编译期保证空间一致性,即不会出现非法空间配置,比如玩家从Field向北走到Forest后,向南返回却到达Lake这类错误。不过我不确定不使用依赖类型是否能实现该特性。
希望Haskell社区的开发者可以提供合适的实现思路,感谢!
内容的提问来源于stack exchange,提问作者Niek

