首页 > 解决方案 > 使用 Haskell 中纸牌游戏的数据模型避免不可能的状态

问题描述

我正在尝试在 Haskell 中实现 Jass 纸牌游戏,并希望使游戏状态尽可能明确或避免类型的不可能状态。

标准变体涉及四名球员。彼此相对的玩家组成一个团队。在一场比赛中进行几轮比赛,直到一支球队达到目标分数。在每一轮开始时,每个玩家都会得到 9 张牌,玩家可以选择王牌,并领先第一墩。在一个技巧中,每个玩家都必须打出一张牌。将这些牌与领先的花色和王牌进行比较,拥有最高牌的玩家赢得了这一把戏。技巧的分数被添加到获胜队的分数中。获胜者在下一招中也处于领先地位。在一轮中进行技巧,直到每个玩家没有剩余的牌。wikipedia上有更详细的解释, jassa.at上有德语解释。

我的玩家数据模型看起来像这样

data Player = Player
    { playerID :: PlayerID
    , name :: String
    , cards :: Set Card
    , sendMessage :: Message -> IO ()
    , receiveAction :: IO Action
    }

这样我就可以为命令行或网络播放器使用不同的发送和接收功能。

  1. 我希望将游戏表示为具有纯更新功能的状态机,但是在大多数状态下,只有一个玩家的一个动作是有效的,所以我认为在更新功能中很大一部分只是处理无效输入.

我不知道代表游戏状态这些部分的最佳方式是什么。

  1. 玩家们。

    • 我的第一个想法是使用一个简单的列表,并且每次都轮换这个列表,这样当前的玩家就永远是主角。这很容易做到,但我也认为很容易出错,比如如果列表为空,或者只有一个玩家等等......
    • 为玩家使用一个数组,为当前玩家使用索引。这样做的好处是要找到下一个玩家,我只需要增加索引即可。在某个地方我必须使用 mod 来循环阵列,但这并不是真正的问题。我还尝试使用 XDataKinds 来获得类型级别的数组大小,因此 nextPlayer 函数可以处理 mod。这也有一个优势,对于球队我可以只使用偶数和奇数的球员指数。但是有了这个,我必须从 playerID 或索引存储一个额外的 Map 到玩家的卡片。所以现在数组和地图可能会不同步。
  2. 技巧
    我不确定我是否应该存储一张已打牌的列表以及谁打过它们,或者只是记录领先、最高牌、获胜者和打出的牌的价值,并在 writer monad 中跟踪所有打出的牌。

  3. 回合和技巧
    这对两者来说都是一样的,我应该存储一个包含所有已玩回合和技巧的列表还是只存储当前回合和技巧并保存前几轮/技巧的积分总和

标签: haskellfunctional-programming

解决方案


让类型检查器证明你的程序是完美的很容易。然而,这将成为永无止境的追求。一天中真的没有时间花在教计算机如何确定你所确定的东西是确定的,对每个保证人来说都是肯定的。

我所做的是从某种解决问题的方法开始,然后在进行过程中了解痛点是什么。我实际上容易犯哪些错误?在你的情况下,我会以某种我能想到的方式来实现游戏(或部分),然后从那个经验中我会知道如何做得更好。

cycle :: [a] -> [a]获取一个列表并永远重复它。您可以为您的玩家做到这一点,并永远占据榜首。

对于非空列表,有Data.List.NonEmpty.

仅构造有效游戏状态的一种方法是定义抽象数据类型。无需导出类型的数据构造函数,您只需导出可以构造类型的自己定义的函数。这样,您可以进行任何(运行时)检查或修复。

另一个工具是单元测试。将命题编码为类型是很困难的,尤其是在 Haskell 中,因此绝大多数都不会。相反,您可以使用基于属性的单元测试来恢复一点保证。


推荐阅读