实现Conway的《人生游戏》的功能编程方式是什么?


12

我最近实施的乐趣Conway的生命游戏在Javascript(实际上CoffeeScript的,但同样的事情)。由于javascript可以用作功能性语言,因此我一直试图保持这种状态。我对结果不满意。我是一个相当不错的面向对象程序员,而我的解决方案充满了同样的古老。这么长的问题很简短:这样做的(伪代码)功能样式是什么?

这是我的尝试的伪代码:

class Node
  update: (board) ->
    get number_of_alive_neighbors from board
    get this_is_alive from board
    if this_is_alive and number_of_alive_neighbors < 2 then die
    if this_is_alive and number_of_alive_neighbors > 3 then die
    if not this_is_alive and number_of_alive_neighbors == 3 then alive

class NodeLocations
  at: (x, y) -> return node value at x,y
  of: (node) -> return x,y of node

class Board
  getNeighbors: (node) -> 
   use node_locations to check 8 neighbors 
   around node and return count

nodes = for 1..100 new Node
state = new NodeState(nodes)
locations = new NodeLocations(nodes)
board = new Board(locations, state)

executeRound:
  state = clone state
  accumulated_changes = for n in nodes n.update(board)
  apply accumulated_changes to state
  board = new Board(locations, state)


@Oded令人沮丧。我认识基本概念,但仅能勉强接受
George Mauer

我也想不通...我只是将其发布为一个示例,以说明一种专门语言的主人可以做什么。说它是一个灵感为我们大家:)
奥德

@GeorgeMauer“实际上是cofferscript但同样的事情”,这是悲伤的一天
Raynos

Answers:


11

好吧,有两个想法。我不是FP方面的专家,但是...

很明显,我们应该有一个Board代表游戏状态的类型。实现的基础应该是evolve类型的函数evolve :: Board -> Board; 这意味着它Board通过将游戏规则应用于产生了一个Board

我们应该如何实施evolve?A Board应该是的nxm矩阵Cell。我们可以实现一个cellEvolve类型cellEvolve :: Cell -> [Cell] -> Cell为a的函数,给定a Cell及其相邻元素Cells Cell在下一次迭代中计算状态。

我们还应该实现从a getCellNeighbors提取Cells邻居的功能Board。我不确定这种方法的签名。取决于您的实现方式CellBoard例如getCellNeighbors :: Board -> CoordElem -> CoordElem -> [Cell],给定一个Board和两个坐标(CoordElem这是用于索引的位置的类型Board),它会为您提供可变长度的邻居列表(并非Board中的所有单元都有相同数量的邻居-每个角落有3个邻居,边界5个,其他所有人8个)。

evolve因此,可以通过组合cellEvolvegetCellNeighbors针对板中的所有单元来实现,再次的确切实现将取决于您如何实现BoardCell,但这应该类似于“对于当前板中的所有单元,获取它们的邻居并使用它们来计算新板的相应单元”,这应该可以通过使用“板的单元功能图”在整个板上通用应用这些功能来完成。

其他想法:

  • 您应该真正实现,cellEvolve以便将GameRules编码游戏规则的类型作为参数- (State,[(State,NumberOfNeighbors)],State)例如表示给定状态的元组列表和每个状态中的邻居数,该状态应该是下一次迭代中的状态。cellEvolve的签名可以是cellEvolve :: GameRules -> Cell -> [Cell] -> Cell

  • 从逻辑上讲,这会使您evolve :: Board -> Board转向evolve :: GameRules -> Board -> Board,这样您可以evolve在不同的情况下使用不变的东西GameRules,但是您可以更进一步地使其成为cellEvolve可插拔的GameRules

  • getCellNeighbors您一起玩还可以使的拓扑结构变得evolve通用- Board您可以将getCellNeighbors其围绕任一板的边缘,3d板等包裹。


9

如果您正在编写Life的功能性编程版本,则您应该自己了解Gosper算法。它利用函数式编程的思想,在一面数万亿平方的板上实现每秒数万亿的生成。我知道这听起来不可能,但完全有可能。我在C#中有一个不错的实现,可以轻松处理一侧的2 ^ 64正方形方形板。

诀窍是要利用生命板在时间和空间上的巨大自相似性。通过记住董事会大部分内容的未来状态,您可以立即快速推进大量内容。

多年来,我一直在意向Gosper算法的初学者入门博客,但是我从来没有时间。如果最终这样做,我将在此处发布链接。

请注意,您要查找用于生命计算的Gosper算法,而不是用于计算超几何和的Gosper算法。


看起来很有趣-尽管仍在等待该链接...;)
jk。


3

您可能希望查看RosettaCode上的实现以获取启发。

例如,有功能的Haskell和OCaml版本通过在前一匝上应用功能来创建新板,而图形OCaml版本使用两个阵列并交替更新以提高速度。

一些实现将电路板更新功能分解为用于计数邻居,应用寿命规则和在电路板上迭代的功能。这些似乎是用于功能设计的有用组件。尝试仅修改电路板,将其他所有功能保持纯功能。


1

这是Clojure中的简短纯功能版本。一切归功于克里斯托弗·格兰德(Christophe Grand),后者在他的博客文章:康威(Conway)的人生游戏中发表了这一观点

(defn neighbours [[x y]]
  (for [dx [-1 0 1] 
        dy (if (zero? dx) [-1 1] [-1 0 1])]
    [(+ dx x) (+ dy y)]))

(defn step [cells]
  (set (for [[loc n] (frequencies (mapcat neighbours cells))
             :when (or (= n 3) (and (= n 2) (cells loc)))]
         loc)))

然后可以通过将“ step”功能重复应用于一组单元格来玩游戏,例如:

(step #{[1 0] [1 1] [1 2]})
=> #{[2 1] [1 1] [0 1]}

聪明之处在于(mapcat邻居单元)部分-这样做是为每个活动单元创建一个八个邻居的列表,并将它们全部串联在一起。然后,每个单元格出现在此列表中的次数可以用(频率....)计数,最后具有正确频率计数的单元格可以直达下一代。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.