今更ながらPythonを学習してみました

## はじめに

こんにちは！23日目担当の [@cy-yamaken](https://qiita.com/cy-yamaken)  です。
[CYBIRD Advent Calendar 2024](https://qiita.com/advent-calendar/2024/cybird)の22日目は [@cy-tsuzawa](https://qiita.com/cy-tsuzawa)  さんの「Sign in with Apple のユーザーを別のチームに移行してみる」でした。 
本日の記事ではPythonについて実際に手を動かしながら学習ことについて記載しております。
記事投稿自体が慣れていないため、温かい目で見守っていただければ幸いです。

## きっかけ
きっかけはずっと触ろうと思ったまま放置し続けていたPythonをこの機会に触ってしまおうと考えたためです。
Pythonはここ最近ずっと話題の機械学習を用いたAIの開発からデータ分析やアプリ開発まで多岐に渡るので、今後の自分のキャリアに活かせるのではと思い期待しております。

## 学習内容
学習はしたいけど文章を読んで理解みたいなのはやりたくなく、文法やらお作法的なのを覚えるのは手を動かしたほうが速そうだったので、適当に頭に思い浮かんだ簡易的なブラックジャックを作成することにしました。
必要最小限の文法やら何やらが網羅できるのではと考えたためです。

## Pythonをインストール
使用PCはmacで学習当初Homebrewはインストールされていたのですが、Pythonがインストールされていなかったため、インストールしました。ターミナルで以下のコマンドを入力して実行します。

```bash:bash
brew install python
```

上記コマンドでPythonの最新バージョンがインストールされます。
インストールが完了したら、以下のコマンドでインストールされたPythonのバージョンが確認できれば問題ありません。

```bash:bash
python3 --version
```

```bash:bash
Python 3.9.6
```

## ブラックジャックの要件
プラックジャックなのですが、プレイヤーとディーラーの2人で、コイン等のbetシステムについて今回は実装しておりません。

カードの値は以下のように定義されます

* 数字カード（2～10）はそのままの数値
* J、Q、Kは全て10点
* A（エース）は11または1点として使います
```python:const.py
# ブラックジャックのカードの点数について定義
VALUES = {
    '2': 2, '3': 3, '4': 4, '5': 5, '6': 6,'7': 7, '8': 8, '9': 9, '10': 10, 'J': 10, 'Q': 10, 'K': 10, 'A': 11
}
```

以下の流れでゲーム進行するものとします。

1. プレイヤーは最初に2枚のカードを引く
2. ディーラーも2枚のカードを引き、1枚は裏向きとする
3. プレイヤーはカードを引くか引かないかを選択する(hit or stand)
4. ディーラーはカードを引き、合計が17以上になるまで引き続ける
5. プレイヤーとディーラーのカード合計を比較して、21を越えなければプレイヤーが勝ち

## 必要なライブラリと構成
まず最初に、ゲームの基本的な部分を作るために必要なライブラリと、プログラムの構成について説明します。

* random モジュール：カードのシャッフルやランダムな選択を行うために使用
* const.py ファイル：定数を定義したファイル

```python:const.py
# カードについて定義
# カードのマーク
MARKS = ['♠', '♦', '♣', '♥']

# カードの数字
NUMBERS = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']

# ブラックジャックのカードの点数について定義
VALUES = {
    '2': 2, '3': 3, '4': 4, '5': 5, '6': 6,'7': 7, '8': 8, '9': 9, '10': 10, 'J': 10, 'Q': 10, 'K': 10, 'A': 11
}

# ChatGPTさんに勝った時のメッセージと負けたときのメッセージを考えてもらいました
WIN_MESSAGES = [
    "ラッキー！あなたが運良く勝ちました！",
    "素晴らしい！あなたの勝ちです！",
    "大勝利！あなたが運を味方につけました！",
    "お見事！完璧なプレイでした！",
    "勝利おめでとうございます！あなたは真のブラックジャックの達人です！",
    "運とスキルが合わさった結果ですね！お見事です！",
    "あなたの勝ちです！まさにブラックジャックの王者！",
    "素晴らしいプレイ！これぞ勝者の風格です！",
    "一発逆転！さすがです！",
    "これが本物の運命の勝者！次も頑張ってください！",
    "勝利の女神が微笑んでいますね！あなたの勝ちです！",
    "大勝利！まさに神がかり的なプレイでした！",
    "素晴らしい勝利！これがブラックジャックの魅力です！",
    "最高の瞬間！運も実力のうちです！",
    "祝福します！あなたの決断が勝利を呼びました！",
    "完全勝利！最高の結果です！",
    "お見事！ブラックジャック界の新たな王者誕生です！",
    "すごい！これぞ真のブラックジャックプレイヤーです！",
    "あなたの勝ち！運と戦略が見事にマッチしました！"
]

LOOSER_MESSAGES = [
    "残念！でも次はきっと勝てる！",
    "惜しい！もう少しでした。",
    "ディーラーの勝ちでしたが、次は逆転できるはず！",
    "今回の負けは運が悪かっただけ！次頑張ろう！",
    "ゲームは波があるから次はきっと！",
    "今回は負けてしまいましたが、次はあなたのターンです！"
]

```

構成は以下のとおりです

* Deckクラス：カードのデッキを作成し、カードを引く処理を担当するクラス
* calculate_hand関数：手札の合計点数を計算する関数
* display_result_message関数：勝者や敗者のメッセージをランダムに表示する関数
* play_game関数：ゲームの進行を担当する関数

## カードのデッキを作成するクラス
ブラックジャックのゲームでは、52枚のカードからランダムにカードを引いてプレイします。Deckクラスはそのカードのデッキを作成し、カードを1枚ずつ引くdeal機能を提供するクラスとなっています。

```python:blackjack.py
import random
import const

class Deck:
    def __init__(self):
        self.cards = [(mark, number) for mark in const.MARKS for number in const.NUMBERS]  # カードのデッキを作成    
        random.shuffle(self.cards)

    def deal(self):
        """デッキからカードを1枚引く
        Returns:
            tuple: カードのマークと数字
        """
        return self.cards.pop()
```

Deckクラスでは、カードのデッキをリストとして管理し、初期化時にconst.MARKSとconst.NUMBERSに基づいて52枚のカードを生成しています。カードはシャッフルされ、dealメソッドでカードを1枚引くことができます。

## 手札の合計を計算する関数
calculate_hand関数では、手札のカードの合計点数を計算します。
エース(A)のみ1か11でよしなに対応する必要がありました。

```python:blackjack.py
def calculate_hand(cards):
    """手札の合計点数を計算する
    Args:
        cards (list): 手札
    Returns:
        int: 合計点数
    """
    total = sum(const.VALUES[card[1]] for card in cards)
    num_aces = sum(1 for card in cards if card[1] == 'A')
    while total > 21 and num_aces:
        total -= 10
        num_aces -= 1
    return total
```
この関数では、カードの点数を足し合わせ、その後エースを11点として計算している場合には10点を引いて1点として扱います。

## 勝者や敗者のメッセージをランダムに表示する関数
```python:blackjack.py
import random

def display_result_message(result, message):
    """勝者と敗者のメッセージを確率でレアメッセージにする
    Args:
        result (str): 勝者か敗者か
        message (str): 基本メッセージ
    Returns:
        str: メッセージ
    """
    # 勝者と敗者のメッセージ
    if result == "win":
        message = random.choice(const.WIN_MESSAGES)
    elif result == "lose":
        message = random.choice(const.LOOSER_MESSAGES)
    return message
```
この関数は、result（勝敗）に基づいてランダムにメッセージを選び、結果に応じたメッセージを表示します。
メッセージの内容はconst.pyのWIN_MESSAGESとLOOSER_MESSAGESに記載しています。

## ゲームの進行を担当する関数
```python:blackjack.py
def play_game():
    """ブラックジャックのゲームを開始する"""
    print('ブラックジャックを始めます！')

    deck = Deck()
    player_hand = [deck.deal(), deck.deal()]
    dealer_hand = [deck.deal(), deck.deal()]

    player_total = calculate_hand(player_hand)
    # あなたの手札と合計点を表示
    print(f"\nあなたの手札: {player_hand} 合計: {player_total}")
    print("ディーラーの手札: ", [dealer_hand[0], "??"])

    while True:
        player_total = calculate_hand(player_hand)
        if player_total == 21:
            print("ブラックジャック！あなたの勝ちです！")
            break
        elif player_total > 21:
            print("バーストしてしまいました！")
            print(display_result_message("lose", "あなたの負けです！"))
            break
        action = input("カードを引きますか？(y/n): ")
        if action == 'y':
            player_hand.append(deck.deal())
            player_total = calculate_hand(player_hand)
            print(f"\nあなたの手札: {player_hand} 合計: {player_total}")
        else:
            break
    if player_total <= 21:
        dealer_total = calculate_hand(dealer_hand)
        while calculate_hand(dealer_hand) < 17:
            dealer_hand.append(deck.deal())
        dealer_total = calculate_hand(dealer_hand)
        print(f"ディーラーの手札: {dealer_hand} 合計: {dealer_total}")
        if dealer_total > 21:
            print("ディーラーがバースト！")
            print(display_result_message("win", "あなたの勝ちです！"))
        elif dealer_total == player_total:
            print("引き分け！")
        elif dealer_total > player_total:
            print(display_result_message("lose", "あなたの負けです！"))
        else:
            print(display_result_message("win", "あなたの勝ちです！"))
```
この関数はゲームの進行を管理し、プレイヤーがカードを引く引かないの選択肢を提供する関数になります。また、ディーラーのターンでは、合計点数が17以上になるまでカードを引き続けます。

## 実際の画面
brackjack.pyを格納しているディレクトリ配下で
```bash:bash
python3 blackjack.py
```
を実行すると以下のような結果になります！
```bash:bash
ブラックジャックを始めます！

あなたの手札: [('♠', 'A'), ('♦', '3')] 合計: 14
ディーラーの手札:  [('♦', '9'), '??']
カードを引きますか？(y/n): y

あなたの手札: [('♠', 'A'), ('♦', '3'), ('♥', '5')] 合計: 19
カードを引きますか？(y/n): n
ディーラーの手札: [('♦', '9'), ('♠', '6'), ('♦', 'Q')] 合計: 25
ディーラーがバスト！
素晴らしい勝利！これがブラックジャックの魅力です！
```

## まとめ
本当は私が今まで学んだ言語との違いについて書いたり、何なら擬似コインをベットできるようにしたりと色々やりたいことがあったのですが、時間の関係上一旦ここで区切りをつけたいと思います。
今後は違うライブラリを使用したり、機械学習の方も学んでいきたいなと思っております。
[CYBIRD Advent Calendar 2024](https://qiita.com/advent-calendar/2024/cybird)の24日目は [@cy-seiyan](https://qiita.com/cy-seiyan) さんの「MacでブータブルUSBの作成に挑戦した話」です。
お楽しみに！