7

目前,我尝试编写一个小游戏程序(Skat)作为一个爱好项目。Skat 是一个花样游戏,两个玩家对抗一个玩家。由于有不同种类的播放器(本地播放器、网络播放器、计算机等),我想抽象出播放器的接口。

我的基本想法是使用 typeclass Player,它定义了所有类型的事情,玩家必须做和知道的(打牌,得到关于谁赢了把戏的通知等)。然后,整个游戏只是由一个函数playSkat :: (Player a, Player b, Player c) => a -> b -> c -> IO ()where完成ab并且c可能是不同类型的玩家。然后玩家可能会以实现定义的方式做出反应。本地玩家会在他的终端上收到一些消息,网络玩家可能会通过网络发送一些信息,而计算机玩家可能会计算新策略。

因为玩家可能想要做一些 IO 并且肯定想要有某种状态来跟踪私人事物,所以它必须生活在某种 Monad 中。所以我想像这样定义Player类:

class Player p where
  playCard :: [Card] -> p -> IO (Card,p)
  notifyFoo :: Event -> p -> IO p
  ...

这种模式似乎与状态转换器非常相似,但我不知道如何处理它。如果我把它写成 IO 之上的一个额外的 monad-transformer,那么一天结束时我就有了三个不同的 monad。我怎样才能以一种好的方式编写这个抽象?

澄清一下,我需要的是,通常的控制流程应该是这样的:
玩花样时,第一个玩家出牌,然后是第二个,最后是第三个。为此,逻辑需要playCard为每个玩家执行函数 trice。之后,逻辑决定哪位玩家获胜,并将获胜者的信息发送给所有玩家。

4

4 回答 4

6

首先,请记住类型类的主要目的是允许函数重载,即您可以在不同类型中使用单个函数。您实际上并不需要,因此最好使用类似以下的记录类型

data Player = Player { playCard :: [Card] -> IO (Card, Player), ... }


其次,有些玩家需要 IO 而有些不需要的问题可以通过自定义 monad 来解决。我已经为 TicTacToe 游戏编写了相应的示例代码,这是我的操作包的一部分。

于 2011-04-24T13:11:08.913 回答
4

更好的设计是不将 IO 作为任何 Player 类型的一部分。为什么玩家需要做IO?玩家可能需要获取信息并发送信息。制作一个反映这一点的界面。如果/当需要 IO 时,它将由 playSkat 执行。

如果你这样做,你就可以拥有其他不做任何 IO 的 playSkat 版本,并且你也可以更容易地测试你的玩家,因为他们只通过类方法而不是通过 IO 交互。

于 2011-04-24T09:54:42.610 回答
1

这就是我最终设计抽象的方式:

引擎可能想要从其中一个玩家那里得到的所有东西都被编码在一个名为 的大 GADT 中Message,因为我并不总是需要答案。GADT的参数是请求的返回值:

data Message answer where
  ReceiveHand :: [Card] -> Message ()
  RequestBid  :: Message (Maybe Int)
  HoldsBid    :: Int -> Message Bool
  ...

不同种类的玩家被抽象在一个类型类上,它具有一个函数playerMessage,允许引擎向玩家发送消息并请求答案。答案包含在一个 中Either,因此如果无法返回答案(例如,如果函数未实现或网络正在罢工等),玩家可以返回适当的错误。该参数p是播放器存储私有数据和配置的状态记录。播放器在 monad 上被抽象化,m以允许一些播放器使用 IO,而另一些则不需要它:

class Monad m => Player p m | p -> m where
  playerMessage :: Message answer -> p -> m (Either String answer,p)

编辑

我问了另一个问题,因为我对一次又一次地输入上下文不满意,所以我最终更改了代码以具体化 typeclass Player。玩家自己没有状态,但他们可以使用部分应用函数来模拟这一点。有关详细信息,请参阅另一个问题。

于 2011-07-08T21:10:30.777 回答
0

完全没有考虑到这一点,但也许仍然值得考虑。在这里,我注意到您在类型类函数中既有pin 又有pout,我猜这意味着那些 "update" p。一个状态单子不知何故。

class (MonadIO m, MonadState p m) => Player p where
  playCard :: [Card] -> m Card
  notifyFoo :: Event -> m ()

同样,这只是一个自发的想法。我不保证它是明智的(甚至是可编译的)。

于 2011-04-24T09:49:54.550 回答