用90行Haskell代码实现2048游戏

上个星期赖斯大学的MOOC 计算的规则 公开课在 Coursera 上开讲啦. 从***周的材料来看,看起来它有了他们之前的课程 Python中的交互式编程介绍 所有优良的东西: 演示文稿做的很不错,也有大量的支持可用, 而布置的作业也很有趣. ***个作业就是编写 2048 游戏的逻辑.

鉴于其设计中的根本性缺陷,我并不认为2048特别的有趣. 首先,你并不能在某个地方取得游戏的胜利. 其次,最有希望的游戏策略使得其玩起来相当的繁琐,而且***的乐趣并不是自己的游戏技能而是随机数生成器制造的幸运连胜. 就我个人而言,更愿意选择那种有时被称为“理论***”的游戏, 比如,游戏的一个属性使得玩它的人能够取得一个确定的胜利. 而2048的游戏结果却没有吸引到我,不过我也明白为什么会有人喜欢让瓷砖四处滑动起来.

为游戏的逻辑编写代码是相当直接的。归因于使用Python作为教学语言的计算原则课程, 对于在我的最初版本中的一个错误是由于python发生了改变,我不会感到奇怪. 我想着用Haskell写这个东西可能会更有趣, 随后就着手开始用这个语言编写了2048的一个完整实现, 包括 I/O 处理. 整个代码可以在 我的git账号 上找到. 最终结果证明,更加完整的Haskell方案所需要的代码比使用Python的程序逻辑要少几行.

作为说明,如果你到这个页面来只是为了找寻计算规则这门课程的Python作业的解决方案,那你就是在浪费时间. Haskell的实现和Python的实现很不同,使用的编程语言构造也不能在Python上用. 换言之,如果你正纠结这个作业,Haskell的源代码将不会对你有所帮助.

在这篇文章中,我仅想着重强调游戏逻辑的核心部分,因为它很好地显示了函数式编程的力量。首先,我定义一个数据类型,用于展示网格中的数字移动的方向,还有一个用于存放整数列表的列表的类型同义词,用来提高类型特征的可读性。从函数‘move’的命名可以明显看出函数的作用;再下一步,将输入作为一个网格的数字和移动方向,并产生新的网格。

 
 
  1. data Move = Up | Down | Left | Right  
  2. type Grid = [[Int]] 

2048这个游戏是在一个4x4的棋盘上进行的。开始位置在我的实现中是固定的:

 
 
  1. start :: Grid  
  2. start = [[0, 0, 0, 0],  
  3.          [0, 0, 0, 0],  
  4.          [0, 0, 0, 2],  
  5.          [0, 0, 0, 2]]  

棋盘上可以在4个方向上对数字进行移动,意味着所有的数字的移动都会向着一个指定的方向,如果是2个数字,移动相同的方向,以彼此相临而告终,则他们合并到一起。举例来说,在如下所示的起始位置,移动方向为‘Up’,结果棋盘变成了下面所示:

 
 
  1. [[0, 0, 0, 4],  
  2.  [0, 0, 0, 0],  
  3.  [0, 0, 0, 0],  
  4.  [0, 0, 0, 0]]  

如果网格中的起始位置移动方向为向右,则不会有任何变化。如果网格变化了,则一个新的数字会在任何空的格子中产生,这个数字可能是2或者4.

我们看这种方法,问题在于其如何更有效的建模。在网格中的任何行列,都可被理解为一个列表。行和列表之间的关系是简单明了的。列将不得不提取、 修改,或虽然再,插入。或者他们不需要?

我写了一个函数来合并一行或一列,表示为一个列表。首先,所有的0要被移动,然后该列表将被处理,合并相邻元素,如果它们包含相同的数字,接着如果必要的话,为结果中填充0.

 
 
  1. merge :: [Int] -> [Int]  
  2. merge xs = merged ++ padding  
  3.     where padding          = replicate (length xs - length merged) 0 
  4.           merged           = combine $ filter (/= 0) xs  
  5.           combine (x:y:xs) | x == y    = x * 2 : combine xs  
  6.                            | otherwise = x     : combine (y:xs)   
  7.           combine x        = x  

当棋盘中的移动方心为左时,这个合并函数可以立刻被应用。其他方向的移动,然而,需要进行一些考虑,如果希望代码保持简洁。向右移动网格是通过采取反转它之前将它提交给函数merge的每一行完成的,然后再次反转结果:

 
 
  1. move :: Grid -> Move -> Grid  
  2. move grid Left  = map merge grid  
  3. move grid Right = map (reverse . merge . reverse) grid  
  4. move grid Up    = transpose $ move (transpose grid) Left  
  5. move grid Down  = transpose $ move (transpose grid) Right  

对于网格向上或者向下移动,如果你想提取出一列,对其应用合并函数,然后产生新的网格进行列的插入,这是极其痛苦的。相反,虽然一点点的线性代数知识,却导致一个更优雅的解决方案。如果你不能立即明确如何移调导致所期望的结果,请看看下面的插图。

 
 
  1. input       transpose   move        transpose  
  2.  
  3. 0 0         0 2         2 0         2 2 
  4. 2 2         0 2         2 0         0 0 
  5.  
  6.  
  7. 2 2         2 0         0 2         0 0   
  8. 0 0         2 0         0 2         2 2 

我Haskell的实现使用终端作为输出。它不像Gabriele Cirulli版本的JavaScript前端一样令人印象深刻,但它是可维护的,如下两个屏幕截图展示:

总体来讲,我对于这个原型还是很满意的。当然有几个可能的改进。一个分数跟踪器的添加将是微不足道的,虽然一个 GUI 将是一个更加耗时的努力。如果有立即响应键盘输入的程序,我会觉得这个很有趣。当前,每个通过 WASD的输入 需要点击回车键进行确认。如果只按一个键将触发程序执行的下一步,那么游戏玩法会加快很多。在研究这一问题时,我没有找到任何快速的解决办法。尽管Haskell库NCurses包含键盘事件。我可能会深入探究一下,如果我用ASCII 图形进行编程使之成为一个“独立”游戏。

如果你觉得这篇文章有趣,请随意看看我的 2048的 Haskell 实现的源代码。

英文原文:Implementing the game 2048 in less than 90 lines of Haskell

译文出自:http://www.oschina.net/translate/2048-in-90-lines-haskell

本文标题:用90行Haskell代码实现2048游戏
分享URL:http://www.shufengxianlan.com/qtweb/news1/387551.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联