Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What is going on with this article?

More than 3 years have passed since last update.

@aimof

ブラックジャックをGoで実装してみた。

プログラミング入門者からの卒業試験は『ブラックジャック』を開発すべし」という気合の入った記事が投稿されていたので実際にやってみた。

元記事ではC#を使うことを想定していたようだが、普段使っているGoを選択した。

ソースコード

github.com/aimof/blackjack

ぱっとやってみた感じそれっぽく動いています。

元記事内縦長のかわいいAA以降は読んでいません(より正確に言うならルールすらちゃんと読んでなかった)。
2〜3時間のやっつけ仕事でテストも最低限なので、ちょっと見せびらかすには物足りない…。

ポイント

指摘されていたポイントを詰めていきます。

カードの管理

0~51の整数で管理しました。

type card struct {
    id int
}

func (c card) suit() string {
    switch c.id % 4 {
    case 0:
        return "C"
    case 1:
        return "D"
    case 2:
        return "H"
    default:
        return "S"
    }
}

// Aの場合1を返す
func (c card) strength() int {
    if c.id >= 40 {
        return 10
    } else {
        return c.id/4 + 1
    }
}

func (c card) number() string {
    switch c.id / 4 {
    case 0:
        return "A"
    case 9:
        return "T"
    case 10:
        return "J"
    case 11:
        return "Q"
    case 12:
        return "K"
    default:
        return strconv.Itoa(c.id/4 + 1)
    }
}

func (c card) toString() string {
    var s string
    s += c.suit()
    s += c.number()
}

それぞれ、

  • suitメソッド:スート(CDHS)を返す
  • strengthメソッド:強さ(Aのときは1)を返す
  • numberメソッド:気持ちの悪い名前をしているが、表示用のカード名を返す(ATJQKはアルファベット)
  • toStringメソッド:スートとランクをくっつけた文字列を返す

山札について

type deck struct {
    // if true, the card is drawn.
    cards [52]bool
}

func newDeck() *deck {
    return &deck{}
}

func (d *deck) draw() int {
    var drawnN int
    for {
        n := rand.Intn(52)
        if d.cards[n] {
            continue
        } else {
            d.cards[n] = true
            drawnN = n
            break
        }
    }
    return drawnN
}

こんな感じで配列での実装です。
カードドローの実装は、デッキにないものをひこうとした場合には再抽選という原始的な方法。
ここは実装を変えたい。

ちなみに、デックはグローバル変数にしてある。
この規模ならわかりやすいかと思ったが果たしてどうなのだろうか?

Aは1か11か問題

type cards []card

func (c cards) reveal() (sumExceptAce, countAce int) {
    for _, card := range c {
        if card.strength() == 1 {
            countAce++
        } else {
            sumExceptAce += card.strength()
        }
    }
    return sumExceptAce, countAce
}

func (c cards) strength() int {
    var sum = 0
    sumExceptAce, countAce := c.reveal()
    switch countAce {
    case 0:
        sum = sumExceptAce
    case 1:
        if sumExceptAce <= 10 {
            sum = sumExceptAce + 11
        } else {
            sum = sumExceptAce + 1
        }
    default:
        if sumExceptAce+countAce > 11 {
            sum = sumExceptAce + countAce
        } else {
            sum = sumExceptAce + countAce + 10
        }
    }
    if sum > 21 {
        return 0
    }
    return sum
}

ちょっと複雑になってしまいました。エース以外の和とエースの枚数で計算しています。

playerとdealerの実装について

type dealer struct {
    cards cards
}

func (d *dealer) play() {
    for d.cards.strength() < 17 && d.cards.strength() > 0 {
        d.cards = append(d.cards, card{id: usedDeck.draw()})
    }
}

type player struct {
    cards cards
}

func (p *player) play() {
    p.cards = append(p.cards, card{id: usedDeck.draw()})
}

元記事ではいろいろと議論が起こっているようだが、プレイヤーは手札を持ってプレイだけしていればいいと思う。

まとめ

結構楽しかった。工夫のしどころが結構多かったです。

ご意見お待ちしています。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
2
Help us understand the problem. What is going on with this article?