Data structures OCaml中的游戏数据结构

Data structures OCaml中的游戏数据结构,data-structures,functional-programming,ocaml,2d-games,Data Structures,Functional Programming,Ocaml,2d Games,我目前正在制作一个类似计算机的物流系统(如minecraft mod applied energestics)的游戏/模拟 游戏的主要部分是二维方块网格 所有块都具有相同的属性,如位置 但是应该有不同类型的块,比如: 物品容器 输入和输出总线 等等 在命令式面向对象语言(如Java)中,我将通过以下方式实现这一点: 主块类 具有位置等公共属性 然后有子类 从block类继承的 这些子类将实现不同块类型的不同属性 在ocaml中,我有点不知所措 我可以创建继承的对象,但这与Java

我目前正在制作一个类似计算机的物流系统(如minecraft mod applied energestics)的游戏/模拟

游戏的主要部分是二维方块网格

所有块都具有相同的属性,如位置

但是应该有不同类型的块,比如:

  • 物品容器
  • 输入和输出总线
  • 等等
在命令式面向对象语言(如Java)中,我将通过以下方式实现这一点:

  • 主块类
    • 具有位置等公共属性
  • 然后有子类
    • 从block类继承的
    • 这些子类将实现不同块类型的不同属性
ocaml
中,我有点不知所措

我可以创建继承的对象,但这与Java中的工作方式不同

例如:

  • 我不能将不同子类的对象放在一个列表中
我还想通过将数据与逻辑分离,以不同的方式处理数据结构。我不会向对象添加方法。我试着用记录代替对象

我不知道如何实现不同的块类型

我尝试使用如下自定义数据类型:

type blockType = Container | Input | Output | Air
type block = {blockType :blockType; pos :int * int}
let rotateBlock block =
  match block.blockType with
  | Input -> {block with entity = nextDirection block.entity}
  | Output -> {block with entity = nextDirection block.entity}
  |  _ -> block
我努力添加单个附加属性。我试图向块记录类型添加一个实体字段,该字段将保存其他属性:

type entity = Container of inventory | OutputEntity of facing | InputEntity of facing | NoEntity
(其中库存和饰面也是自定义类型)

这个解决方案感觉不太合适

我的一个问题是,我想对输入和输出类型的块执行一些逻辑操作。我必须重复这样的代码:

type blockType = Container | Input | Output | Air
type block = {blockType :blockType; pos :int * int}
let rotateBlock block =
  match block.blockType with
  | Input -> {block with entity = nextDirection block.entity}
  | Output -> {block with entity = nextDirection block.entity}
  |  _ -> block
这对于两种类型来说并不是那么糟糕,但我计划添加更多,因此在可伸缩性方面这是一个很大的负面影响

这种结构的另一个关键点是它有点不一致。我使用记录中的一个字段在块级别实现不同的类型,在实体级别实现多个构造函数。我这样做是为了能够使用
block.pos
轻松访问每个块的位置,而不是使用模式匹配

我对这个解决方案不是很满意

请求

我希望有人能为我指出关于数据结构的正确方向。

听起来很有趣

我不能将不同子类的对象放在一个列表中

实际上你可以。假设有许多不同的块对象 所有人都有一个“衰变”的方法。你可以有一个功能“给我 它可以把所有的积木都列在一个清单上 然后,您可以按一定的时间间隔在列表上迭代并应用 每个块上的衰减方法。这些都是很好的类型和容易 与OCaml的对象系统有关。你不能做的是拿出一个 从这个列表中可以看出,事实上,这也是 一个气闸,我现在想把它当作一个成熟的气闸, 而不是腐烂的

每个类型只能有240个so变体。如果你打算再吃一点 块,一个获得额外空间的简单方法是 对你的区块进行分类,并使用例如
固体岩石|液体熔岩
而不是岩石熔岩

type block = {blockType :blockType; pos :int * int}
一个区块在你的库存中的位置是什么?职位是什么 一个在世界上被开采出来的区块 现在有点像坐在地上,等着被人接走?为什么不呢? 保留正在使用的数组索引或映射键中的位置 表示世界上各个区块的位置?否则你也 必须考虑块对于同一个位置意味着什么, 或不可能的位置

let rotateBlock block =
  match block.blockType with
  | Input -> {block with entity = nextDirection block.entity}
  | Output -> {block with entity = nextDirection block.entity}
  |  _ -> block
我并没有真正了解这个输入/输出的东西,但在这个 函数你对某种属性感兴趣,比如“有下一个方向” 如果旋转,则指向“面”。为什么不命名该属性并使其与您匹配

type block = {
  id : blockType;
  burnable : bool;
  consumable : bool;
  wearable : bodypart option;  (* None - not wearable *)
  hitpoints : int option;      (* None - not destructible *)
  oriented : direction option; (* None - doesn't have distinct faces *)
}

let rotateBlock block =
  match block.oriented with
  | None -> block
  | Some dir -> {block with oriented = Some (nextDirection dir)}

let burn block =
  match block.burnable, block.hitpoints with
  | false, _ | true, None     -> block
  | true, Some hp when hp > 5 -> { block with hitpoints = Some (hp - 5) }
  | true, Some hp             -> ash

块类型很有趣,因为每种类型都有不同的操作

物品容器, 输入和输出总线, 等等

我的直觉告诉我,您也许可以使用GADT创建块上的操作类型,并为模拟器轻松实现一个计算器

更新 要回答您的评论:

如果您对所有变体都有一个共同的信息,您需要提取它们,您可以想象如下:

type block_info = 
    | Item of specific_item_type ....
    | Bus of specific_bus_type

type block = {position:Vector.t ; information : block_info}

let get_block_position b = b.position

你在努力满足相互竞争的目标。不能同时拥有刚性静态块模型和动态可扩展块类型。所以你需要选择。幸运的是,OCaml为两者提供了解决方案,甚至为两者之间的问题提供了解决方案,但对于中间解决方案来说,它们在这两方面都有点糟糕。让我们试试看

使用ADT的刚性静态层次结构 我们可以使用sum类型来表示对象的静态层次结构。在这种情况下,我们很容易添加新方法,但很难添加新类型的对象。作为基本类型,我们将使用多态记录,该记录通过具体块类型参数化(具体块类型本身可以是多态的,这将允许我们构建层次结构的第三层等等)

其中,
info
是一个额外的特定于混凝土块的有效载荷,即,
block\u info
类型的值。此解决方案允许我们编写接受不同块的多态函数,例如

let distance b1 b2 = 
  sqrt ((float (b1.x - b2.x))**2. + (float (b1.y - b2.y)) **2.)
distance
函数具有类型
'a blk->'b blk->float
,并将计算任意类型的两个块之间的距离

此解决方案有几个缺点:

  • 很难扩展。添加一种新的块是很困难的,你基本上需要事先设计你需要什么块,并且希望你将来不需要添加新的块。看起来您希望需要添加新的块类型,因此此解决方案可能不适合您。然而,我相信
    let distance b1 b2 = 
      sqrt ((float (b1.x - b2.x))**2. + (float (b1.y - b2.y)) **2.)
    
    type block = Block : block_info -> {pos : pos; info : block_info}
    
    type block_info = ..
    type block_info += Air
    
    class block x y = object
       val x = x
       val y = y 
       method x = x
       method y = y 
       method with_x x = {< x = x >}
       method with_y y = {< y = y >}
    end
    
    class input_block facing = object
       inherit block 
       val facing = facing
       method facing = facing
       method with_facing f = {< facing = f >}
    end
    
    type block = int
    type world = {
      map : pos Int.Map.t;
      facing : facing Int.Map.t;
      air : Int.Set.t;
    }
    
    type block = {id : int; eq : int}
    
    type block = 
      | Regular of regular
      | ...  
      | Compose of compose_kind * block * block
    
    type compose_kind = Horizontal | Vertical | Inplace