我应该用什么结构来表达棋盘游戏?

时间:2013-10-27 11:14:41

标签: haskell

我有一个working implementation Kalah解算器,这是一个计算游戏第一回合最佳连续动作的应用程序。

我正在重新实现这个应用程序,虽然这次是使用测试套件和(希望)更漂亮的代码,它使用了更有趣的结构,如monoids或monad。

正如你在original code中所看到的那样(或者不是,它是非常复杂的,这就是为什么我要重写它)我已经定义了一个“移动”如下:

  1. 我正在列出Pot作为我的董事会名单,以及我在董事会一侧的起始位置。
  2. 我拿起弹珠直到我到达Pot列表的末尾。
  3. 在“一圈”结束时,我返回改变后的棋盘([Pot]),我手里拿着多少弹珠,还有一个ADT表示我是否应该再去一圈({{{ 1}})。
  4. 问题是我怀疑如果我用一些聪明的数据结构表示我可以作为函数的输入参数传递给具有相同数据的板状态,我就不需要将移动分成几圈。结构作为回报值出现。至少这是我的猜测,我的想法是董事会状态让我想起了我读过的关于幺半群的内容。

    因此,如果我将一个“移动”定义为所有的拾取和弹珠,直到你落入空锅或商店,是否有一些明显的方法来重写代码“移动”作品?

    可以找到重新实现的当前状态here

1 个答案:

答案 0 :(得分:1)

注意:我没有测试过这些。它可能是马车。

我认为你的问题是你需要从两个角度考虑董事会,称他们为“白人”和“黑人”。

data Player = White | Black

otherPlayer :: Player -> Player
otherPlayer White = Black
otherPlayer Black = White

Mancala板是一个圆形结构,暗示模块化的算术。我建议像:

import Data.Vector -- More efficient version of Array

type PotNum = Int  -- Use Int for simple index of pot position.

type Pot = Int   -- Just record number of marbles in the pot.

使用Data.Word8而不是Int可能会获得更紧凑的数据结构,但我不确定。暂时保持简单。

type Board = Vector Pot

然后让isStore成为PotNum和播放器的简单功能

isStore :: Player -> PotNum -> Bool
isStore White 0 = True
isStore Black 7 = True
isStore _ _ = False

你也希望在棋盘上前进,跳过其他玩家的商店......

nextPot :: Player -> PotNum -> PotNum
nextPot White 6 = 8  -- Skip Black's store
nextPot White 13 = 0
nextPot Black 12 = 0 -- Skip White's store
nextPot _ n = n + 1

每位球员控制的彩池列表

playerPots :: Player -> [PotNum]  -- Implementation omitted.

给定彩池中的弹珠数量

marblesIn :: PotNum -> Board -> Int  -- Implementation omitted.

现在你可以写一个移动功能了。我们不会因为非法行动而退货。

move :: Player -> PotNum -> Board -> Maybe Board   -- Implementation omitted.

使用List monad可以使这产生所有可能的移动和产生的板状态

allMoves :: Player -> Board -> [(PotNum, Board)]
allMoves p b1 = do
    n <- playerPots p
    case move p n b1 of
       Nothing -> fail ""   -- List monad has this as []
       Just b2 -> return (n, b2)

所以现在你可以使用Data.Tree.unfold从任何起始位置获取完整的游戏树,它采用移动函数的变体。这略显不雅;我们想知道导致这个位置的举动,但最初的立场没有动作导致它。因此,可能。

unfoldTree函数接受一个函数(下面的代码中的f),该函数获取当前状态并返回当前节点和子节点值列表。当前状态和当前节点都是刚刚移动的玩家的三倍,他们所做的移动以及由此产生的棋盘。因此第一位“f”。 “f”的第二位调用“opponentMoves”函数,该函数转换“allMoves”返回的值以添加正确的数据。

unfoldGame :: Player -> Board -> Tree (Player, Maybe PotNum, Board)
unfoldGame p b = unfoldTree f (p, Nothing, b)
   where 
      f (p1, n1, b1) = ((p1, n1, b1), opponentMoves (otherPlayer p1), b1
      opponentMoves p2 b2 = map (\(n3, b3) -> (p2, Just n3, b3)) $ allMoves p2 b2  

现在你只需要走树。每一片叶子都是游戏的终点,因为没有合法的动作。 unfoldGame函数是懒惰的,所以你只需要内存来保存你正在考虑的游戏状态。