作为编码挑战的一部分,我必须实施地下城地图。
我已经使用Data.Map
作为设计选择进行设计,因为不需要打印地图,有时我不得不更新地图图块,例如当一个障碍被摧毁时。
type Dungeon = Map Pos Tile
type Pos = (Int,Int) -- cartesian coordinates
data Tile = Wall | Destroyable | ...
但是,如果我必须打印它,那么我将不得不使用类似的东西
elaboratePrint . sort $ fromList dungeon
其中elaboratePrint
处理换行符并从tileset中生成漂亮的unicode符号。
我考虑的另一个选择是嵌套列表
type Dungeon = [[Tile]]
这样做的缺点是,很难更新这种数据结构中的单个元素。但是,然后打印将是一个简单的衬垫unlines . map show
。
我考虑的另一个结构是Array
,但由于我不习惯对hackage文档进行简短的一瞥 - 我只发现了一个对索引进行操作的map函数和一个对元素进行操作的函数,除非是愿意使用可变数组更新一个元素乍一看并不容易。打印阵列也不清楚如何快速轻松地完成这项工作。
所以现在我的问题 - 是否有更好的数据结构来表示具有易于打印和轻松更新单个元素的属性的地下城地图。
答案 0 :(得分:4)
Array
怎么样? Haskell有真正的二维数组。
import Data.Array.IArray -- Immutable Arrays
现在Array
被Ix a => a
编入索引。幸运的是,有一个实例(Ix a, Ix b) => Ix (a, b)
。所以我们可以
type Dungeon = Array (Integer, Integer) Tile
现在用几个函数中的任何一个构造其中一个函数,最简单的就是
array :: Ix i => (i, i) -> [(i, a)] -> Array i a
所以对你来说,
startDungeon = array ( (0, 0), (100, 100) )
[ ( (x, y), Empty ) | x <- [0..100], y <- [0..100]]
只需将100
和Empty
替换为适当的值。
如果速度成为一个问题,那么使用MArray和ST
是一个简单的解决方法。我建议不要切换,除非速度实际上是一个真正的问题。
解决漂亮的印刷问题
import Data.List
import Data.Function
pretty :: Array (Integer, Integer) Tile -> String
pretty = unlines . map show . groupBy ((==) `on` snd.fst) . assoc
然后可以将map show
设置为您想要将[Tile]
格式化为一行。如果你确定你真的希望以一种非常有效的方式打印它们(可能是控制台游戏),你应该看一个合适的漂亮的打印库,比如this一个。
答案 1 :(得分:3)
首先 - 像Data.Map
这样的树喜欢和列表仍然是函数式语言的自然数据结构。如果你只需要矩形地图,那么Map
在结构方面有点过分,但[[Tile]]
实际上可能非常好。对于随机访问和更新都有O(√n)
,这不是太糟糕。
特别是,比2D数组(O(n)
)的纯功能更新更好!因此,如果您需要非常好的性能,则无法使用可变数组。虽然游戏本质上与IO和状态有关,但这并不一定是坏事。正如jozefg所指出的,{em> 对Data.Array
的好处是能够将元组用作Ix
索引,因此我会使用MArray
。
阵列打印很容易。你可能只需要整个地图的矩形部分,所以我只是用一个简单的列表理解来提取这样的切片
[ [ arrayMap ! (x,y) | x<-[21..38] ] | y<-[37..47] ]
您已经知道如何打印列表。