F#でブラックジャックを実装しました


はじめに

以下の2記事に触発されて自分でもシンプルなブラックジャックを実装してみました。

プログラミング入門者からの卒業試験は『ブラックジャック』を開発すべし
F# で“卒業試験”ことブラックジャック開発をやってみた

ソースコードはこちら。120行程度です。
https://github.com/msanou/BlackJackFsharp/blob/master/Program.fs

引いたカードの重複チェックについて

既に引いたカードの管理方法についてです。なるべくArrayなどのmutableなコレクションは使わずにやることを目指していたので、引いたカードをdeckとして引き渡しながら管理することにしました。

let rec pickCard hand deck =
    let card = { PlayingCard.Number = rand.Next(1,13); 
                 PlayingCard.Suit = rand.Next(1,4) |> enum<Suits> }
    if isCardExist card deck then pickCard hand deck
    else hand @ [card], deck @ [card]

はじめは山札を模したArrayを用意して……などと考えていましたが、今回のようなゲームだと山札を引き切るケースがまずないのでリストをシャッフルして要素を一つずつ減らしていくより記憶していく方がいいのかもしれない。
あと、かなりタプルを多用しましたが、もう少し冴えたやり方があるようにも思います。

スコアの計算

let calcScore hand =
    let temp = hand |> List.sumBy(fun x -> if x.Number > 10 then 10 else x.Number)
    hand |> List.fold (fun x y ->
        if y.Number = 1 && x <= 11 then x + 10 else x) temp

まず、J,Q,Kの点数を10点として手札の合計を計算します。そして、点数の合計が11を超えない場合に限り、エースのスコアを11として扱う(合計値に10足す)……という形なのですが、「点数の合計が11点以下の時、エースのカードが手札に一枚でも含まれる場合は10を加算する」で良かったですね。わざわざfold関数使わなくてよかった。

let calcScore hand =
    let temp = hand |> List.sumBy(fun x -> if x.Number > 10 then 10 else x.Number)
    if hand |> List.exists (fun x -> x.Number = 1) && temp <= 11 then temp+10 else temp

このように書き換えられました。

おわりに

メッセージの表示処理や入力の受付を記述する場所、判別共用体を手札とかに使ってバーストの判別をそのメンバにやらせるなど、色々他にも工夫の余地はあるように思います。

作って記事にでもしようと考えていましたが、思ったより何も書くことがない。