简化一些 Haskell 代码

3
所以我正在为一个类似跳棋的游戏编写minimax实现,以帮助我更好地学习Haskell。我遇到麻烦的函数需要接受游戏状态列表,并生成立即后继游戏状态列表。就像跳棋一样,如果有可以跳的棋子,玩家必须跳。如果有多个选择,玩家可以自行选择。
对于大部分情况,这在列表单子上工作得很好:循环所有输入的游戏状态,循环所有可能被跳的棋子,循环该棋子的所有跳跃。此列表单子将所有列表都漂亮地展平成最后的简单状态列表。
问题在于,如果没有找到给定游戏状态的任何跳跃,则需要返回当前游戏状态而不是空列表。下面的代码是我想出来的最佳方法,但它看起来对我来说真的很丑陋。有什么建议可以使其更清晰?
eHex :: Coord -> Coord -- Returns the coordinates immediately to the east on the board
nwHex :: Coord -> Coord -- Returns the coordinates immediately to the northwest on the board

generateJumpsIter :: [ZertzState] -> [ZertzState]
generateJumpsIter states = do
    ws <- states
    case children ws of
      [] -> return ws
      n@_ -> n
  where 
    children ws@(ZertzState s1 s2 b p) = do
      (c, color)  <- occupiedCoords ws
      (start, end) <- [(eHex, wHex), (wHex, eHex), (swHex, neHex),
                       (neHex, swHex), (nwHex, seHex), (seHex, nwHex)]
      if (hexOccupied b $ start c) && (hexOpen b $ end c)
        then case p of
          1 -> return $ ZertzState (scoreMarble s1 color) s2
                                   (jumpMarble (start c) c (end c) b) p
          (-1) -> return $ ZertzState s1 (scoreMarble s2 color)
                                      (jumpMarble (start c) c (end c) b) p
        else []

编辑:为*Hex函数提供示例类型签名。

2个回答

3
技术相关内容翻译如下:

技巧在于,如果给定游戏状态没有找到任何跳跃,我需要返回当前的游戏状态,而不是空列表。

为什么?我已经多次编写了极小化最大算法,但我无法想象出这种函数的用途。您是否可以使用类型为

nextStates :: [ZertzState] -> [Maybe [ZertzState]]

或者

nextStates :: [ZertzState] -> [[ZertzState]]

然而,如果你真的想返回“下一个状态列表,或者如果该列表为空,则返回原始状态”,那么你需要的类型是

nextStates :: [ZertzState] -> [Either ZertzState [ZertzState]]

你可以轻松地将其扁平化。

至于如何实现,我建议定义一个类型为 helper function 的函数。

[ZertzState] -> [(ZertzState, [ZertzState])]

并且你可以映射。
(\(start, succs) -> if null succs then Left start else Right succs)

除结果之外,还有各种其他事情。

正如弗雷德·布鲁克斯所说(引用),一旦你正确地理解了类型,代码几乎可以自己编写。


这只是生成可能移动集合的代码的一部分,它是minimax输入的一部分。具体来说,它是与生成可用跳跃相关的部分。 - Resistor
规则类似于跳棋:如果有可跳的棋子,必须跳。如果有多个可选项,则玩家可以选择哪一个跳。允许连续跳(即在跳棋中的双跳),并且同样适用必须跳的规则。 - Resistor
我一直在将其视为状态的递归生成树。我从包含初始状态的列表开始。从那里,我生成可到达的状态列表,这些状态最多相隔一个跳跃。从中,我生成了最多相隔两个跳跃的状态列表,等等。我重复此过程,直到列表停止更改。只有当“死胡同”状态保留在列表中而不进入空列表时,才有效。 - Resistor
@电阻器:这是Haskell!为什么你不懒惰地生成无限的移动树呢?所有你需要的信息都在John Hughes的论文为什么函数式编程很重要中。 - Norman Ramsey

1

不要滥用单子符号来表示列表,这样做没有任何意义。此外,您可以以同样的方式使用列表推导式:

do x <- [1..3]
   y <- [2..5]      <=>  [ x + y | x <- [1..3], y <- [2..5] ]
   return x + y

现在是针对“简化”部分的内容

listOfHex :: [(Coord -> Coord,Coord -> Coord)]
listOfHex = [ (eHex, wHex), (wHex, eHex), (swHex, neHex)
            , (neHex, swHex), (nwHex, seHex), (seHex, nwHex)]

generateJumpsIter :: [ZertzState] -> [ZertzState]
generateJumpsIter states =
    [if null ws then ws else children ws | ws <- states]
  where -- I named it foo because I don t know what it do....
    foo True   1  = ZertzState (scoreMarble s1 color) s2
                               (jumpMarble (start c) c (end c) b) p
    foo True (-1) = ZertzState s1 (scoreMarble s2 color)
                               (jumpMarble (start c) c (end c) b) p
    foo False  _  = []
    foo _ _ = error "Bleh"

    children ws@(ZertzState s1 s2 b p) =
      [ foo (valid c hex) p | (c, _)  <- occupiedCoords ws, hex <- listOfHex ]
        where valid c (start, end) =
                 (hexOccupied b $ start c) && (hexOpen b $ end c)

let 在列表推导式中的使用让我有点困扰,但由于我没有所有的代码,不知道是否有其他方法。如果您能更深入地修改,我建议您使用更多的组合器(例如 map、foldr、foldl' 等),因为根据我的经验它们确实可以减少代码量。

请注意,此代码未经测试,可能无法编译。


第一个列表推导式不能只是 [if null (children ws) then ws else children ws | ws <- states] 吗?我期望编译器会消除函数调用的重复。 - Nathan Shively-Sanders
listOfHex 的类型为 listOfHex :: [Coord -> Coord],棋盘上的每个六边形都由一个坐标(仅为 Int 对的同义词)进行标识。例如,函数 nwHex 返回输入六边形向西北方向的坐标。listOfHex 包含这些函数对的列表,用于确定是否能够进行跳跃。 - Resistor
listOfHex :: [(Coord -> Coord,Coord -> Coord)],它是一个由一对函数组成的列表。 - Raoul Supercopter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接