#はじめに
「プログラミング入門者からの卒業試験は『ブラックジャック』を開発すべし」という記事を見て、勉強中のGo言語で試してみました。
※本業は今時珍しいコボラーです。
#1 カードの扱い
元の記事、Haskellで実装した方、Kotlinで実装した方などとは変えています。
・カードは0~51で表す
・13で割った商をカードの種類に割り当て
・13で割った余り+1を数字に割り当てました。
※4で割っても同等です。商がカードの数字、余りがカードの種類になるだけです。
こうすることでカード自体を1次元で割り当て可能です。
またネックになりやすい重複がない等の確認がしやすい他、処理も簡単になると考えました。
また0~51(以降カード番号)のカード番号からカードの種類、数字は容易に求めることができます。
なお、商とカード種類は
・0:ハート
・1:ダイヤ
・2:スペード
・3:グラブ
に割り当てました(根拠はありません。適当です。)
※カードの種類、数字を持つ構造体を定義した場合、確かに4種類の種類で13までの数値の中から選ぶことができます。ただし、「重複がなく52枚存在すること」を満たす方法が全く思いつきませんでした。カードの番号であれば扱いやすいし、0~51の連番さえ振ってしまえば自動的に重複がなくなります。
ここまでの実装は以下の通り。
/* カードの数字を取得 */
func getNumber(cardNo uint8) uint8 {
ret := cardNo%13 + 1
return ret
}
/* カードのマークを取得 */
// 0:ハート
// 1:ダイヤ
// 2:スペード
// 3:グラブ
func getMarkNo(cardNo uint8) uint8 {
ret := cardNo / 13
return ret
}
/* カードのマーク(名称)を取得 */
func getMarkName(markNo uint8) string {
var ret string = ""
switch markNo {
case 0:
ret = "ハート"
case 1:
ret = "ダイヤ"
case 2:
ret = "スペード"
case 3:
ret = "クラブ"
}
return ret
}
#2 手札の点数確認
エースを1、11どちらでもできるようにしないといけません。
実装はいったん11として加算し、21を超えた場合に最大エースの枚数分まで10を引くループで実現しました。
/* 手札から得点を取得 */
func getPoint(tefuda []uint8) uint8 {
var cntAce uint8 = 0
var sumP uint8 = 0
for i := 0; i < len(tefuda); i++ {
switch {
case getNumber(tefuda[i]) == 1:
sumP += 11
cntAce++
case getNumber(tefuda[i]) >= 2 && getNumber(tefuda[i]) <= 10:
sumP += getNumber(tefuda[i])
case getNumber(tefuda[i]) >= 11:
sumP += 10
}
}
// エース独自処理。いったん11とカウントしておき、
// 最大でエースの枚数分10を引けるようにする
for i := 0; i < int(cntAce); i++ {
if sumP > 21 {
sumP = sumP - 10
}
}
return sumP
}
#3 山積みカードの作成
やり方をさんざん考えて(カード番号、ランダム数)のスライスを作り
そのあとランダム数でソートしカード番号をランダムに並べました。
ランダム数は適当に10000程度としました。このくらいあればまず重複しないだろうと。
Sortは・・・はまりました。
sort.Sortで実行しようとして何度やってもエラー・・・
結局sort.Sliceという方法があると知り試したらできました。
Sortができたらそれを1次元のスライスに渡して山積みの完成です。
// カードをシャッフルさせるために使用
// 独自の構造体
type Card struct {
idx uint8
intRandum int32
}
type allCard []Card
/* カードをシャッフルした山積みを取得 */
func shufleCard() []uint8 {
wkyama := make([]Card, 52)
var i uint8
retyama := make([]uint8, 52)
// idxには連番を(0~51)を設定する
// これをカードと見立てる。
// intRandumにはランダムの数字を設定する。
rand.Seed(time.Now().UnixNano())
for i = 0; i < 52; i++ {
wkyama[i].idx = i
wkyama[i].intRandum = rand.Int31n(10000)
}
//ソート
sort.Slice(wkyama, func(i, j int) bool { return wkyama[i].intRandum < wkyama[j].intRandum })
for j := 0; j < 52; j++ {
// fmt.Printf("idx-%d val-%d\n", j, wkyama[j].idx)
retyama[j] = wkyama[j].idx
}
return retyama
}
#4 カードをめくる処理
カードをめくる処理は山積みからめくった場合、スライスを削除するのではなくめくられた箇所のカード番号に99を入れる方法としました。
(例)
前 | 後 | |
---|---|---|
山積み | (13,12,44,・・・) | (99,12,44,・・・) |
手札 | (05,40) | (05,40,13) |
実際のソースはこちら
/* 山積みからカードを1枚取得し手札に加える */
func getCard(tefuda []uint8, yama []uint8) ([]uint8, []uint8) {
// 引いたあとは99としておく。
for i := range yama {
if yama[i] == 99 {
continue
} else {
tefuda = append(tefuda, yama[i])
yama[i] = 99
break
}
}
return tefuda, yama
}
#5 その他
全体のソースはこちら
#6 感想
ソートでかなり時間がかかりました。
非に少しづつだったにせよ全体で20時間くらいかかったかもしれません。
最低でも1/3はソートです。
カードの扱いは多分他の人のやり方より楽だと思っています。
#7 参考
・「プログラミング入門者からの卒業試験は『ブラックジャック』を開発すべし」
・初心者卒業試験のブラックジャック作成に挑戦してみました(Haskell編)
・Kotlinでブラックジャック作ってみた
・ランダム数の取得
・スライスソート