現在、趣味のプロジェクトとして小さなゲーム プログラム (Skat) を作成しようとしています。Skat は、2 人のプレイヤーが 1 人のプレイヤーと対戦するトリックテイキング ゲームです。プレーヤーにはさまざまな種類 (ローカル プレーヤー、ネットワーク プレーヤー、コンピューターなど) があるため、プレーヤーへのインターフェイスを抽象化したいと考えました。
私の基本的なアイデアは、 typeclass を使用することですPlayer
。これは、プレイヤーがしなければならないことや知っておくべきことすべてを定義します (カードをプレイする、誰がトリックに勝ったかについて通知を受けるなど)。次に、ゲーム全体playSkat :: (Player a, Player b, Player c) => a -> b -> c -> IO ()
がa
、 、b
およびc
さまざまな種類のプレーヤーである関数によって行われます。プレーヤーは、実装で定義された方法で反応する場合があります。ローカル プレイヤーは自分の端末で何らかのメッセージを受信し、ネットワーク プレイヤーはネットワークを介して何らかの情報を送信し、コンピューター プレイヤーは新しい戦略を計算します。
プレイヤーは何らかの IO を実行したい場合があり、プライベートなものを追跡するためにある種の状態を確実に持ちたい場合があるため、ある種のモナドに存在する必要があります。Player
そこで、次のようにクラスを定義することを考えました。
class Player p where
playCard :: [Card] -> p -> IO (Card,p)
notifyFoo :: Event -> p -> IO p
...
このパターンはステート トランスフォーマーに非常に似ているように見えますが、どのように処理すればよいかわかりません。IO の上に追加のモナド変換子として書くとしたら、一日の終わりには 3 つの異なるモナドができました。この抽象化をうまく書くにはどうすればよいでしょうか。
明確にするために、通常の制御フローは次のようになります。
トリックをプレイするとき、最初のプレイヤーがカードをプレイし、次に 2 番目のプレイヤーがプレイし、最後に 3 番目のプレイヤーがカードをプレイします。これを行うには、ロジックplayCard
でプレーヤーごとに関数 trice を実行する必要があります。その後、ロジックは、どのプレイヤーがトリックに勝つかを決定し、勝った情報をすべてのプレイヤーに送信します。