[Python] mathの関数を使ったバトルゲームを作ってみる

仕事で math の関数を使い、以下の記事を書きました。
[[Python] math : sqrt / pow / ceil / floor / fabs](https://qiita.com/yam_dev/items/4db71b30c04b9cf2eab7)
[[Python] math : factorial / log / gcd / degrees / radians](https://qiita.com/yam_dev/items/b33c7b1244b8695de1ad)
[[Python] math : isqrt / prod / comb / perm / fsum](https://qiita.com/yam_dev/items/a68b250d9f2b7f624e67)

せっかくここまで書いたので、これらの関数を使って、バトルゲームを作ることに。（全部は使わないけど）

# 要件
- プレイヤーがモンスターと戦う
- プレイヤーは数学を駆使して攻撃をする
- モンスターは`sin`を使って攻撃をしてくる
- どんな数字がダメージの計算に使われるかは、ランダムである
- HPが先に 0 になった方が負け

## ゲームの概要

プレイヤーが数学を武器にしてモンスターと戦うバトルゲーム。
プレイヤーは4種類の数学的攻撃を選択し、モンスターにダメージを与える。モンスターも反撃してくる。先に相手のHPを0にした方が勝ち。

## ゲームの流れ

1. **初期化:** プレイヤーとモンスターのHPが設定される
2. **プレイヤーのターン:**
    - プレイヤーの現在のHPとモンスターのHPが表示される
    - 4種類の攻撃（平方根、階乗、組み合わせ、べき乗）から一つを選択する
    - 選択した攻撃に基づいてダメージが計算され、モンスターのHPが減少
3. **モンスターのターン:**
    - モンスターが`sin`関数に基づいた攻撃を行い、プレイヤーにダメージを与える
4. **勝敗判定:** プレイヤーまたはモンスターのHPが0以下になると、ゲームが終了

## 各攻撃の詳細

- **平方根攻撃 (sqrt):**
ランダムな数値の平方根を計算し、その値がダメージになる

- **階乗攻撃 (factorial):**
ランダムな小さい整数の階乗を計算し、その値がダメージになる

- **組み合わせ攻撃 (comb):**
ランダムな`n`と`k`を選び、`nCk` (n 個から k 個を選ぶ組み合わせの数) を計算

- **べき乗攻撃 (pow):**
ランダムな底と指数を選び、べき乗を計算

- **モンスターの攻撃:**
ランダムな角度（度数法）と防御値を選び、`sin(角度) * 防御値`を計算

# コード
```python:main.py
import math
import random
import time

def math_battle():
    """数学バトルゲーム"""

    player_hp = 50
    monster_hp = 70

    print("\n数学バトル開始！")
    time.sleep(1)

    while player_hp > 0 and monster_hp > 0:
        print(f"\n--- プレイヤーのターン ---")
        print(f"プレイヤーHP: {player_hp}")
        print(f"モンスターHP: {monster_hp}")
        time.sleep(1)

        # プレイヤーの攻撃選択
        print("\n攻撃を選択してください:")
        print("1: 平方根攻撃 (sqrt)")
        print("2: 階乗攻撃 (factorial)")
        print("3: 組み合わせ攻撃 (comb)")
        print("4: べき乗攻撃 (pow)")

        while True:
            try:
                choice = int(input("> "))
                if 1 <= choice <= 4:
                    break
                else:
                    print("無効な入力です。1〜4の数字を入力してください。")
                    time.sleep(1)
            except ValueError:
                print("無効な入力です。数字を入力してください。")
                time.sleep(1)


        # 攻撃の実行
        if choice == 1:  # 平方根攻撃
            base = random.randint(100, 400)
            damage = int(math.sqrt(base))
            print(f"平方根攻撃！√{base} = {damage} のダメージ！")
            time.sleep(1)
            print("豆知識：平方根は、2乗すると元の数になる値のこと。古代バビロニアの時代から研究されているらしい。。")
            time.sleep(1)

        elif choice == 2:  # 階乗攻撃
            num = random.randint(3, 5)
            damage = math.factorial(num)
            print(f"階乗攻撃！{num}! = {damage} のダメージ！")
            time.sleep(1)
            print("豆知識：階乗は、1からその数までのすべての整数を掛け合わせたもの。順列や組み合わせの計算でよく使われるとか。。")
            time.sleep(1)

        elif choice == 3:  # 組み合わせ攻撃
            n = random.randint(5, 10)
            k = random.randint(1, min(n, 4))  # kはnを超えないようにする
            damage = math.comb(n, k)
            print(f"組み合わせ攻撃！comb({n}, {k}) = {damage} のダメージ！")
            time.sleep(1)
            print(f"豆知識：{n}個の中から{k}個を選ぶ組み合わせの数は、nCk = n! / (k! * (n-k)!) で計算できる。確率や統計で重要らしい。。")
            time.sleep(1)


        elif choice == 4:  # べき乗攻撃
            base = random.randint(2, 4)
            exponent = random.randint(2, 3)
            damage = int(math.pow(base, exponent))
            print(f"べき乗攻撃！{base}^{exponent} = {damage} のダメージ！")
            time.sleep(1)
            print("豆知識：べき乗は、同じ数を繰り返し掛け合わせる演算。指数法則を理解すると、複雑な計算も簡単にできると言われている。。")
            time.sleep(1)


        monster_hp -= damage

        # モンスターの攻撃
        if monster_hp > 0:
            defense_value = random.randint(10, 30)
            degrees = random.randint(0, 90)
            radians = math.radians(degrees)
            monster_damage = int(math.sin(radians) * defense_value)
            print(f"\nモンスターの攻撃！sin({degrees}°) * {defense_value} = {monster_damage} のダメージ！")
            time.sleep(1)
            player_hp -= monster_damage

    if player_hp <= 0:
        print("\n敗北…")
    else:
        print("\n勝利！")


if __name__ == "__main__":
    math_battle()
```

# 実行結果
```bash
$ python main.py
数学バトル開始！

--- プレイヤーのターン ---
プレイヤーHP: 50
モンスターHP: 70

攻撃を選択してください:
1: 平方根攻撃 (sqrt)
2: 階乗攻撃 (factorial)
3: 組み合わせ攻撃 (comb)
4: べき乗攻撃 (pow)
> 1
平方根攻撃！√317 = 17 のダメージ！
豆知識：平方根は、2乗すると元の数になる値のこと。古代バビロニアの時代から研究されているらしい。。

モンスターの攻撃！sin(23°) * 18 = 7 のダメージ！
```

```bash
--- プレイヤーのターン ---
プレイヤーHP: 43
モンスターHP: 53

攻撃を選択してください:
1: 平方根攻撃 (sqrt)
2: 階乗攻撃 (factorial)
3: 組み合わせ攻撃 (comb)
4: べき乗攻撃 (pow)
> 2
階乗攻撃！4! = 24 のダメージ！
豆知識：階乗は、1からその数までのすべての整数を掛け合わせたもの。順列や組み合わせの計算でよく使われるとか。。

モンスターの攻撃！sin(67°) * 29 = 26 のダメージ！
```

```bash
--- プレイヤーのターン ---
プレイヤーHP: 17
モンスターHP: 29

攻撃を選択してください:
1: 平方根攻撃 (sqrt)
2: 階乗攻撃 (factorial)
3: 組み合わせ攻撃 (comb)
4: べき乗攻撃 (pow)
> 3
組み合わせ攻撃！comb(7, 2) = 21 のダメージ！
豆知識：7個の中から2個を選ぶ組み合わせの数は、nCk = n! / (k! * (n-k)!) で計算できる。確率や統計で重要らしい。。

モンスターの攻撃！sin(14°) * 23 = 5 のダメージ！
```

```bash
--- プレイヤーのターン ---
プレイヤーHP: 12
モンスターHP: 8

攻撃を選択してください:
1: 平方根攻撃 (sqrt)
2: 階乗攻撃 (factorial)
3: 組み合わせ攻撃 (comb)
4: べき乗攻撃 (pow)
> 4
べき乗攻撃！3^3 = 27 のダメージ！
豆知識：べき乗は、同じ数を繰り返し掛け合わせる演算。指数法則を理解すると、複雑な計算も簡単にできると言われている。。

勝利！
```

無事に勝利しました！

# まとめ
今回は、mathの関数を使って、バトルゲームを作ってみました。
こうやって数学の勉強をするのも楽しいかもしれませんね。

また何か思いついたら、作ってみます:grinning: