用于生命游戏的OCaml 2D阵列

时间:2016-12-10 20:31:47

标签: arrays random 2d ocaml conways-game-of-life

我正在努力做康威的练习游戏,因为我之前在C ++中已经完成了这个,但我想知道如何在Ocaml中根据随机分配的spawns给出高度和宽度来生成二维数组给定的人口密度,我试图使用make_matrix。我在网上找到的所有教程都使用图形或其他一些硬编码方式(即rosetta代码),但我想尽量避免这种情况,以便我可以有一些变化。谢谢。

这是我当前的代码,它返回未绑定的值。

print_string "Input width ";
let num = read_int () in
 print_string "Input height";
 let num2 = read_int () in
  myArray = Array.make_matrix num num2 0;

error: Unbound value myArray;

1 个答案:

答案 0 :(得分:1)

您有多种选择:

  1. 大阵列
  2. 阵列
  3. 映射
  4. Bigarrays只能将数字作为其值,但它们可以是任意大小和尺寸。它们还具有用于访问多维数据的良好界面。数组可以包含任何类型的值,您可以创建数组数组来建模多维空间。数组和bigarray都是命令式数据结构,因此代码也是如此。这就是为什么我建议你使用持久性地图,使你的代码纯功能(如果你想在OCaml中练习,最好在其功能子空间中练习)。

    地图示例

    因此,让我们为状态和坐标定义一个类型:

    type state = Dead | Live
    
    type coord = {x : int; y : int}
    

    由于我们将使用地图,因此我们不需要无人居住的状态。未填充的状态只是未映射的。

    现在,我们可以定义电路板数据结构的实现:

    module Board = Map.Make(struct 
        type t = coord
        let compare = compare
      end)
    

    作为使用示例,让我们定义一个名为fold_neighbors的更高阶函数,它将用户提供的函数应用于每个填充的相邻单元格。

    let neighbors {x;y} = [
      x,  y+1;
      x+1,y+1;
      x+1,y;
      x+1,y-1;
      x,  y-1;
      x-1,y-1;
      x-1,y;
      x-1,y+1;
    ] |> List.map (fun (x,y) -> {x;y})
    
    let fold_neighbors board cell ~f ~init =
      neighbors cell |>
      List.fold_left (fun acc n -> 
          try f acc (Board.find n board) 
          with Not_found -> acc)
        init
    

    使用这个通用迭代器函数,我们可以定义像count_live_neighbors这样的专用函数:

    let count_live_neighbors =
      fold_neighbors ~init:0 ~f:(fun count nb -> match nb with
          | Live -> count + 1
          | Dead -> count)
    

    实现假设一个无限的板,如果你想使它有界,那么你需要调整fold_neighbors函数来排除那些在板外面的人。

    数组

    的示例

    另一种选择是使用普通数组。我们可以使用方便的make_matrix函数创建给定大小的二维数组,并用提供的值填充它,例如

     make_matrix 300 400 0 
    

    将创建一个零填充矩阵,包含300行和400列。

    在我们的案例中,我们不想用数字填充矩阵,而是用状态填充矩阵。我们需要一个状态类型,它能够代表3个状态,我们将重用上一个例子中的state类型,但我们也将它包装成option类型,以便一个单元格无论是死的还是现场的都会被表示为Some DeadSome Live,而未填充的只会None,所以我们可以创建一个空板

    Array.make_matrix width height None
    

    为了初始化我们的电路板,我们可以先创建它,然后根据所需的人口密度为随机选择的细胞提供生命。我们将用0到1之间的浮点数来表示密度,例如,密度0.1意味着大约10%的单元将是活的。为了保持更多功能,我们将使用Array.map来转换我们的数组。当然,使用就地修改的显式循环和迭代会更快,更惯用,但仅仅为了实验,我们可以使用更多功能方法:

    let create_board width height density =
      Array.make_matrix width height None |>
      Array.map (Array.map (fun cell ->
          if Random.float 1.0 > density then Some Live else None))
    

    但是,我们可以更好地完成这项工作,如果我们不会在第一手创建空板,而是从初始化的板开始。为此,我们可以使用Array.init函数,它允许为每个单元格提供不同的值,这是更好的create_board函数的示例:

    let create_board width height density =
      Array.init height (fun _ -> 
          Array.init width (fun _ ->
              if Random.float 1.0 > density then Some Live else None))
    

    Array.init函数使用元素的索引调用用户提供的函数。在我们的例子中,生命的概率并不取决于坐标,所以我们可以忽略这个位置,这就是我们使用_指定的原因,我们不会去使用参数。