Just $ A sandbox

プログラミングと計算機科学とかわいさ

Haskellで大富豪を作ろう (1)トランプを用意しカードを配ろう

まえがき

Haskellでなんか動くものを作ろうと思った.
規模と知名度等を考えて大富豪あたりが妥当なところかと思ったので, 今回はCUIの大富豪を作ろうということにした*1.

コード自体は完成しているので, 何回かに記事を分けて説明をつけて投稿していくつもり.

なおとにかく動くものを作りたいってことしか考えてなかったので完成したコードはあんまり綺麗じゃない模様.

第一回 トランプを用意しカードを配ろう

まずはトランプを用意しよう. 今回は4種類A~Kまでの通常のカードとジョーカー2枚を使用する.
これをまずはdata Cardとして定義する.

ついでに, 大富豪ではカードの強さ比較をすることが多いのでOrdのインスタンスにする.
このとき大富豪式に数字の強さを(3が弱く2が強くJokerが最強になるように)定義しておくと便利かもしれないということでそうした.

data Suit = Spade | Club | Diamond | Heart deriving (Eq, Enum, Show)
data Number = N3 | N4 | N5 | N6 | N7 | N8 | N9 | N10 | N11 | N12 | N13 | N1 | N2 deriving (Eq, Ord, Enum)
data JokerNumber = J1 | J2 deriving (Eq, Ord, Enum, Show)
data Card = Card Suit Number | Joker JokerNumber deriving (Eq, Show)

instance Ord Card where
  Joker j <= Joker j' = j <= j'
  Card _ _ <= Joker _ = True
  Joker _ <= Card _ _ = False
  Card _ k <= Card _ k' = k <= k'

カードが用意できたら人数分配ろう.
今回は, n人参加する場合, 一人あたり54/n枚配り, 余ったカードはゲームに使用しないことにした.

deal :: Int -> IO [[Card]]
deal n = fmap (take n . chunksOf (length allCards `div` n)) $ shuffleM allCards

実行例

Let's play 大富豪!
[[Card Spade 3,Card Club K,Card Diamond A,Card Club J,Card Club 4,Card Heart 4,Card Heart A,Card Club Q,Card Spade A,Card Spade Q,Card Heart J,Card Spade 6,Card Heart K],[Card Club 9,Card Club 7,Card Club 2,Card Diamond 3,Card Club 10,Card Heart 9,Card Club 3,Card Spade 9,Card Club 5,Card Diamond 7,Card Diamond 8,Card Spade 4,Card Spade 8],[Card Heart 10,Card Diamond J,Joker J2,Card Heart 3,Joker J1,Card Club 6,Card Spade 7,Card Heart Q,Card Spade 10,Card Heart 5,Card Spade 5,Card Diamond 9,Card Diamond Q],[Card Club 8,Card Heart 8,Card Spade K,Card Heart 2,Card Diamond 10,Card Diamond 5,Card Heart 6,Card Diamond 4,Card Club A,Card Spade 2,Card Diamond 2,Card Heart 7,Card Spade J]]

使用したパッケージ

以下をインストールするとページ末尾のコードが動く.
なお関係ないけどインストールにはcabalじゃなくてstackとかを使うと便利.

ソースコード

module Main where
 
import Data.List
import Data.List.Split (chunksOf)
import System.Random.Shuffle (shuffleM)
 
-- (1) トランプを用意しカードを配ろう
 
data Suit = Spade | Club | Diamond | Heart deriving (Eq, Enum, Show)
data Number = N3 | N4 | N5 | N6 | N7 | N8 | N9 | N10 | N11 | N12 | N13 | N1 | N2 deriving (Eq, Ord, Enum)
data JokerNumber = J1 | J2 deriving (Eq, Ord, Enum, Show)
data Card = Card Suit Number | Joker JokerNumber deriving (Eq, Show)
 
allCards :: [Card]
allCards = [Card s n | s <- [Spade .. Heart], n <- [N3 .. N2]] ++ [Joker j | j <- [J1,J2]]
 
instance Show Number where
  show N1 = "A"
  show N11 = "J"
  show N12 = "Q"
  show N13 = "K"
  show k | k <= N13 = show $ fromEnum k + 3
         | otherwise = show $ fromEnum k - 10
 
-- 大富豪式順序
instance Ord Card where
  Joker j <= Joker j' = j <= j'
  Card _ _ <= Joker _ = True
  Joker _ <= Card _ _ = False
  Card _ k <= Card _ k' = k <= k'
 
deal :: Int -> IO [[Card]]
deal n = fmap (take n . chunksOf (length allCards `div` n)) $ shuffleM allCards
 
main = do
  putStrLn "Let's play 大富豪!"
  print =<< deal 4

*1:完成してから思ったのはこれは少し失敗だった. 入力値のエラーチェック等が激しく面倒なので多少無理してもブラウザで動くものにすればよかった