LoginSignup
1
4

Pythonでブラックジャック作ってみた(その1)

Last updated at Posted at 2023-06-22

数年前にPythonの独学を始めてから、コーリー・アルソフ著『独学プログラマー Python言語の基本から仕事のやり方まで』という本を、ときどき読み返しています。その中にトランプゲーム「戦争」のコードが載っています。以下のような内容です。

こんなコード、勉強もかねて作ってみたい。けれどもっと遊べるゲームがいい。ブラックジャックなんかどうだろう? ポーカーやバカラに比べると比較的ルールも簡単そうだし。

そう思って調べていたら、5年ほど前にQiitaでお題が出ていました。

出題者の方はC#を想定されていたようですが、Python勢もチャレンジしたようで、いくつか記事を見つけることができました。

他にもたくさんの方が挑戦していて、見比べてみると面白いです。同じルールの同じゲームで、同じように遊べる。けれど、コードはそれぞれかなり違います。
いろいろと見て、勉強させていただいた後で、私もブラックジャック作りに挑戦してみました。ルールは以下です。

  • 初期カードは52枚。引く際にカードの重複は無いようにする
  • プレイヤーとディーラーの2人対戦。プレイヤーは実行者、ディーラーは自動的に実行
  • 実行開始時、プレイヤーとディーラーはそれぞれ、カードを2枚引く。引いたカードは画面に表示する。ただし、ディーラーの2枚目のカードは分からないようにする
  • その後、先にプレイヤーがカードを引く。プレイヤーが21を超えていたらバーストしてプレイヤーの負け、その時点でゲーム終了
  • プレイヤーは、カードを引くたびに、次のカードを引くか選択できる
  • プレイヤーが引き終えたら、その後ディーラーは、自分の手札が17以上になるまで引き続ける
  • プレイヤーとディーラーが引き終えたら勝負。より21に近い方の勝ち
  • JとQとKは10として扱う
  • Aはとりあえず「1」としてだけ扱う。「11」にはしない
  • ダブルダウンなし、スプリットなし、サレンダーなし、その他特殊そうなルールなし
blackjack.py

from random import shuffle
import sys

class Card:

    suits = ['','','','']

    ranks = [None, "A","2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]

    def __init__(self, s, r):
        self.suit = s
        self.rank = r

    def __int__(self):
        r = self.ranks[self.rank]
        if r == "J" or r == "Q" or r == "K":
            r = 10
        elif r == "A":
            r = 1
        return int(r)

    def __str__(self):
        v = self.ranks[self.rank] + " of " + self.suits[self.suit]
        return v

class Deck:
    def __init__(self):
        self.cards = []
        for i in range(1, 14):
            for j in range (4):
                self.cards.append(Card(j, i))
        shuffle(self.cards)

    def draw(self):
        if len(self.cards) == 0:
            return
        return self.cards.pop()

class Player():
    def __init__(self, name):
        self.total = 0
        self.name = name
        self.over = False
        
    def match(self):
        if self.total == 21:
            s = '{} の合計スコア {} '
            print(s.format(self.name,self.total))
            w = 'BLACKJACK! {}の勝ち'
            print(w.format(self.name))
            return True
        elif self.total > 21:
            s = '{} の合計スコア {} '
            print(s.format(self.name,self.total))
            l = 'BUST! {}の負け'
            print(l.format(self.name,self.total))
            return True
        else:
            return False
        
        
class Game():
    def __init__(self):
        self.deck = Deck()
        self.p = Player('あなた')
        self.d = Player('ディーラー')
        
    def draw_msg(self,name,card):
        d = '{} は {} を引いた'
        print(d.format(name,card))

    def score_msg(self,name,score):
        s = '{} の合計スコア {} '
        print(s.format(name,score))

    def hit_stand(self):
        while True:
            hs = input("ヒット or スタンド(h/s):")
            if hs == "h":
                return True
            elif hs == "s":
                return False
            else:
                continue
                
    def game_continue(self):
        while True:
            yn = input('続けますか?(y/n):')
            if yn == "y":
                self.p.total = 0
                self.d.total = 0
                self.play_game()
            elif yn == "n":
                print('ゲームを終了します')
                sys.exit(-1)
            else:
                continue

    def play_game(self):
        
        pc1 = self.deck.draw()
        dc1 = self.deck.draw()
        pc2 = self.deck.draw()
        dc2 = self.deck.draw()
        self.p.total += int(pc1) + int(pc2)
        self.d.total += int(dc1) + int(dc2)

        self.draw_msg(self.p.name, str(pc1))
        self.draw_msg(self.p.name, str(pc2))
        self.draw_msg(self.d.name, str(dc1))
        self.draw_msg(self.d.name, "(伏せ札)")
        
        hs = self.hit_stand()

        while hs:
           pc = self.deck.draw()
           self.p.total += int(pc)
           self.draw_msg(self.p.name, str(pc))
           if self.p.match():
               self.game_continue()
           hs = self.hit_stand()
            
        self.score_msg(self.p.name,self.p.total)
        o = '{} の(伏せ札)は {}'
        print(o.format(self.d.name,str(dc2)))
        
        while self.d.total < 17:
            dc = self.deck.draw()
            self.d.total += int(dc)
            self.draw_msg(self.d.name, str(dc))
            if self.d.match():
                self.game_continue()
                    
        self.score_msg(self.d.name,self.d.total)
                
        if self.d.total < self.p.total:
            print('{}の勝ち'.format(self.p.name))
        elif self.d.total > self.p.total:
            print('{}の負け'.format(self.p.name))
        else:
            print('引き分け')
            
        self.game_continue()



if __name__ == '__main__':
    game = Game()
    game.play_game()


作成の際のポイントは2つ。

ひとつは、Playerクラスの設定です。

出題者の方も、過去のチャレンジャーの方々も、どちらかというとPlayerのクラスをがっつり作り込んでいる印象でした。私はこのPlayerの比重を抑え、シンプルに作りたかったので、名前と得点のパラメータを保持する程度にしました。それから、ディーラー、プレイヤー共通の要素として、得点が21になったら勝ち(BLACKJACK)、22以上は負け(BUST)になるよう設定しました。後はGameクラスでディーラー、プレイヤーそれぞれの役割を付与するだけです。

もうひとつは、クラスの特殊メソッド。

出題者の方も挙げていたのですが、トランプゲームをコードで表現する際、カードデッキの3通りの「意味」をどう扱うかが問題になります。

ブラックジャックを実装する場合、カードの「No」には実は、3つの意味があります。
①トランプの「数値」(1,2,3,4,5,6,7,8,9,10,11,12,13)
②トランプの「表示」(A,2,3,4,5,6,7,8,9,10,J,Q,K)
③ブラックジャックの「点」(1,2,3,4,5,6,7,8,9,10,10,10,10)

この問題に対する答えとして、「独学プログラマー」の「戦争」のコードが参考になりました。「戦争」ではCardクラスで特殊メソッド__lt__,__gt__,__repr__などを使っています。これをブラックジャックでは__int__,__str__にしました。特殊メソッドを内蔵したクラスの変数は、例えばstr()で包めば"Q of ♠️"という文字列を、int()で包めば得点"10"を返してきます。

今さらですが、すごいメソッドですね。コレ。私はこの数年間、全部関数で済ませてました。クラス特殊メソッドはおろか、クラスに無縁で生きてきました。独学ってのは、そういうことです。

ブラックジャック作りを通して、クラスの素晴らしさ、面白さに気づけたことで、私もようやく初心者から一歩踏み出そうとしているのでしょうか…そう考えたいものです。そんな私のためにコードレビューしてくださる親切な方がいたら、コメント欄にてよろしくお願いいたします。

1
4
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4