chatGPT でプロ野球シミュレーションゲームは作れるのか

# 最強ソフトといえば 「ベストプレープロ野球」のみ。

そういえば昔ファミコンで神ゲームに遭遇したのは今から四半世紀前。1988年。
この世の中には [ベストプレープロ野球](https://ja.wikipedia.org/wiki/%E3%83%99%E3%82%B9%E3%83%88%E3%83%97%E3%83%AC%E3%83%BC%E3%83%97%E3%83%AD%E9%87%8E%E7%90%83) という至極の製品があります。

現在も [パリティビットさま](http://www.paritybit.co.jp/bp.html) の中にこっそり記載が。

ダービースタリオンシリーズを手掛けた薗部博之さんが開発されているものであり
自由度が鬼高く、一度購入したら選手の名前から属性まで全て設定変更が可能
ちくちく設定を変更するだけで毎年のペナントレースを再現できるという夢の製品。

ターボファイルという外部記憶装置を利用して日本シリーズをしてたのが懐かしいw

## プロ野球選手データを更新し続ける

当初はメモリの都合で１リーグしか利用できなかったこともあり
自分が推している球団選手のパラメタが若干底上げされるものの
週刊ベースボールと選手名鑑を突合しつつ
なんとか毎年選手情報を更新してペナントレース開催してました

そういえば新庄さんがホームラン王に（夢

ベストプレープロ野球スペシャルという製品からは２リーグ進行ができるようになり
あまり熱のない球団情報も毎年更新しないといけない罠に（表現

そこまで詳しくはないので負けそうになった矢先
活躍したのは某同人誌即売会の会場。
全球団の選手情報を冊子にして毎年販売している人がw
そのためだけに年末某所に（略。

ともあれ最近は残存しているWEBページも少なくなってきましたが
こちらとかまだ稼働しているのでお時間があれば
[ベスプレ普及委員会](https://www.eonet.ne.jp/~chinpindo/bp/)

Windows版販売されたときに日程表が固定なことが不満であったので
Delphiで日程表を作成するツールを作成したりしていましたが
それはさておき。
そうです。
話題はchatGPT。これさえあれば Macでもベストプレープロ野球できるのでは。

## ゲームを作れるのかしら。

気がつけば chatGPT を利用すれば何でもできるって先生がいってたので
ひとまず GPT-3.5 と GPT-4 を利用して開発を開始してみます。

・chatGPT / **GPT-3.5** による回答；

Pythonを利用してプロ野球の対戦シミュレーションを行うためには、まずは以下のような準備が必要です。
1. データベースの作成: プロ野球選手の情報を格納するために、データベースを作成する必要があります。例えば、選手名、背番号、打率、投手成績、守備位置などを格納することができます。

2. プログラムの実装: データベースから選手情報を取得し、それをもとに対戦相手や試合状況をシミュレーションします。打席やピッチングのアルゴリズムを考慮し、現実的なプレーが再現できるように実装する必要があります。

3. ユーザーインターフェイスの実装: ゲーム画面やユーザーからの入力を受け取るためのインターフェイスを実装する必要があります。例えば、ユーザーが選手を選択したり、操作したいシミュレーションを選択したりするための画面を作成します。

4. テストとデバッグ: 実装したプログラムをテストし、問題がないか確認します。特に、アルゴリズムやシミュレーションの正確さ、ユーザーインターフェイスの使いやすさなどを確認する必要があります。

以上のように、Pythonを利用したプロ野球の対戦シミュレーションを実装するには、データベースの作成からプログラムの実装、インターフェイスの実装、テストとデバッグまで、多数のタスクが必要です。しかし、Pythonは高い柔軟性を持っており、プログラマが自由に使い方を決められるため、実装にあたっての制限が小さく、開発の幅が広いというメリットがあります。

:::note
GPT-3.5
作れますか？に対して作る元気があればできるよ、の回答。
これならいけそう、という雰囲気なし。
:::

・chatGPT / **GPT-4** による回答；

プロ野球のシミュレーションゲームをPythonで実装するには、いくつかの基本的なコンポーネントを作成する必要があります。以下の手順に従って、ゲームを作成してみましょう。

プレイヤーとチームのクラスを定義する
まず、プレイヤーとチームのクラスを定義しましょう。プレイヤークラスには、名前、ポジション、スキルなどの属性が含まれます。チームクラスには、チーム名とプレイヤーのリストが含まれます。

```python
class Player:
    def __init__(self, name, position, skill):
        self.name = name
        self.position = position
        self.skill = skill

class Team:
    def __init__(self, name, players):
        self.name = name
        self.players = players
```
ゲームのロジックを実装する
ゲームのロジックを実装するために、以下の関数を作成します。

simulate_at_bat()：1対1の対戦をシミュレートし、結果を返す。
simulate_inning()：1イニングの対戦をシミュレートし、得点を返す。
simulate_game()：全イニングの対戦をシミュレートし、最終スコアを返す。

```python
import random

def simulate_at_bat(pitcher, batter):
    # 結果を決定するロジックを実装する
    result = random.choices(['out', 'single', 'double', 'triple', 'home_run'], weights=[0.7, 0.1, 0.1, 0.05, 0.05], k=1)[0]
    return result

def simulate_inning(attack_team, defense_team):
    # イニングのロジックを実装する
    pass

def simulate_game(home_team, away_team):
    # ゲームのロジックを実装する
    pass
```

ゲームの進行と結果表示
simulate_game() 関数を使って、ゲームをシミュレートし、結果を表示します。

```python

if __name__ == "__main__":
    # プレイヤーとチームを作成する
    players_team1 = [Player("Player1", "pitcher", 80), Player("Player2", "catcher", 70), ...]
    players_team2 = [Player("Player3", "pitcher", 85), Player("Player4", "catcher", 65), ...]

    team1 = Team("Team1", players_team1)
    team2 = Team("Team2", players_team2)

    # ゲームをシミュレートして結果を表示する
    result = simulate_game(team1, team2)
    print(f"{team1.name}: {result[team1.name]} vs {team2.name}: {result[team2.name]}")
```
ここで、ゲームの進行と結果表示ができるようになりました。しかし、まだ simulate_inning() と simulate_game() 関数が未実装です。それらの関数を実装しましょう。

イニングとゲームのロジックを実装する
イニングごとのロジックとゲーム全体のロジックを実装しましょう。

```python
def simulate_inning(attack_team, defense_team):
    score = 0
    out_count = 0
    base_state = [0, 0, 0]  # 1st, 2nd, 3rd
    batter_index = 0

    while out_count < 3:
        result = simulate_at_bat(defense_team.players[0], attack_team.players[batter_index % 9])
        if result == 'out':
            out_count += 1
        else:
            # ベースランナーを進めるロジック
            bases_to_advance = {'single': 1, 'double': 2, 'triple': 3, 'home_run': 4}[result]
            for i in range(2, -1, -1):
                if base_state[i] == 1:
                    if i + bases_to_advance >= 3:
                        score += 1
                    else:
                        base_state[i + bases_to_advance] = 1
                    base_state[i] = 0

            # 打者が進むベース
            base_state[bases_to_advance - 1] = 1
            batter_index += 1

    return score

def simulate_game(home_team, away_team):
    home_score = 0
    away_score = 0
    for inning in range(9):
        away_score += simulate_inning(away_team, home_team)
        home_score += simulate_inning(home_team, away_team)

    return {home_team.name: home_score, away_team.name: away_score}
```
これで simulate_inning() と simulate_game() 関数が実装されました。これで、プロ野球の対戦を行うシミュレーションゲームをPythonで実装できました。

注意: この実装は簡単なものであり、実際のプロ野球のルールや戦術などは簡略化されています。また、プレイヤーのスキルやチーム戦術なども考慮されていません。さらにリアルなシミュレーションを実現するには、これらの要素を追加して、詳細なロジックを実装する必要があります。

:::note
GPT-4
ちゃんとソースコードの実例も提案。さすが有料。
:::

## 選手のパラメタを追加してみる

やっぱり名前とポジションだけではなくて
肩、足、眼、実、ス、巧、長とかパラメタがほしくなる今日この頃。お願い。

・chatGPT / **GPT-4** による回答；

選手のクラスを、以下の新しいパラメータに対応するように変更しましょう：
    肩力 (arm_strength)
    足力 (leg_strength)
    選球眼 (plate_discipline)
    実績 (experience)
    スタミナ (stamina)
    巧打 (contact)
    長打 (power)
また、各パラメータはAからE、S、対象外の範囲で設定されるとします。
まず、選手クラスを更新し、新しいパラメータを追加します。

```python
class Player:
    def __init__(self, name, position, arm_strength, leg_strength, plate_discipline, experience, stamina, contact, power):
        self.name = name
        self.position = position
        self.arm_strength = arm_strength
        self.leg_strength = leg_strength
        self.plate_discipline = plate_discipline
        self.experience = experience
        self.stamina = stamina
        self.contact = contact
        self.power = power
```
次に、各パラメータを数値に変換するヘルパー関数を作成します。
```python
def param_to_value(param):
    if param == 'S':
        return 1.0
    elif param == 'A':
        return 0.8
    elif param == 'B':
        return 0.6
    elif param == 'C':
        return 0.4
    elif param == 'D':
        return 0.2
    elif param == 'E':
        return 0.1
    else:  # 対象外
        return 0
```
その後、各パラメータがゲームのロジックに影響するように、simulate_inning() 関数や他の関数を更新します。たとえば、肩力を守備力に影響させる場合、以下のように simulate_inning() 関数を変更できます。
```python
def attempt_steal(player, catcher_arm_strength):
    success_prob = param_to_value(player.leg_strength) * 0.1
    fail_prob = 1 - success_prob

    fielding_factor = (1 - param_to_value(catcher_arm_strength)) * 0.1
    adjusted_success_prob = success_prob + fielding_factor
    adjusted_fail_prob = fail_prob - fielding_factor

    result = random.choices(['success', 'fail'], weights=[adjusted_success_prob, adjusted_fail_prob], k=1)[0]
    return result == 'success'
```
この例では、選手の足力と捕手の肩力を数値に変換し、盗塁の成功確率に影響を与えています。同様に、他のパラメータもゲームのロジックに組み込むことができます。
```python
def simulate_at_bat(pitcher, batter):
    walk_prob = param_to_value(batter.plate_discipline) * 0.05
    strikeout_prob = (1 - param_to_value(batter.plate_discipline)) * 0.1

    # その他の結果を決定するロジックを実装する
    # ...

    result = random.choices(
        ['strikeout', 'walk', 'out', 'single', 'double', 'triple', 'home_run'],
        weights=[strikeout_prob, walk_prob, out_prob, hit_prob * (1 - home_run_prob), 0.1 * hit_prob, 0.05 * hit_prob, home_run_prob],
        k=1
    )[0]
    return result
```
この例では、選球眼を使用して四球および三振の確率を計算し、random.choices()関数のウェイトとして使用しています。

他のパラメータ（実績、スタミナ、巧打、長打）も同様にゲームロジックに組み込むことができます。例えば、実績をチャンスに打撃が打てるかの判定に利用したり、スタミナをシーズン後半に選手のパフォーマンスに影響を与える要素として利用することができます。

最後に、巧打と長打を使用して、ヒットの性質や飛距離に影響を与えることができます。たとえば、巧打が高い選手はシングルヒットが多く、長打が高い選手はホームランやエキストラベースヒットが多いことをシミュレーションすることができます。

## 野手にパラメタつけるなら投手もでしょ。

ですよね。

・chatGPT / **GPT-4** による回答；

投手のクラスに新しいパラメータを追加し、ゲームロジックを更新してこれらの要素を考慮に入れましょう。投手クラスは以下のようになります：

```python
class Pitcher:
    def __init__(self, name, fastball, movement, control, stability, ball_quality, technique, stamina):
        self.name = name
        self.fastball = fastball
        self.movement = movement
        self.control = control
        self.stability = stability
        self.ball_quality = ball_quality
        self.technique = technique
        self.stamina = stamina
```
次に、投手のパラメータをゲームロジックに組み込みます。例えば、simulate_at_bat()関数を以下のように更新できます：
```python
def simulate_at_bat(pitcher, batter):
    strikeout_prob = param_to_value(pitcher.fastball) * 0.05 + param_to_value(pitcher.movement) * 0.05 + param_to_value(pitcher.control) * 0.05
    out_prob = param_to_value(pitcher.technique) * 0.1

    # ピンチの状況を考慮して投手の安定性を調整
    if is_clutch_situation():
        strikeout_prob *= param_to_value(pitcher.stability)
        out_prob *= param_to_value(pitcher.stability)

    # 長打を回避する確率を計算
    no_extra_base_hit_prob = param_to_value(pitcher.ball_quality) * 0.1

    # その他の結果を決定するロジックを実装する
    # ...

    result = random.choices(
        ['strikeout', 'walk', 'out', 'single', 'double', 'triple', 'home_run'],
        weights=[strikeout_prob, walk_prob, out_prob, single_prob, double_prob * no_extra_base_hit_prob, triple_prob * no_extra_base_hit_prob, home_run_prob * (1 - no_extra_base_hit_prob)],
        k=1
    )[0]
    return result
```
この例では、投手の球速、切れ、制球が三振の確率に影響を与えています。また、安定性はピンチの状況での投手のパフォーマンスに影響を与えています。球質が高い投手は、長打を回避する確率が高くなります。技術が高い投手は、内野ゴロなどでアウトになる比率が高くなります。
また、投手のスタミナを考慮して、投手の交代や疲労度に影響を与えるロジックを追加することもできます。たとえば、投手の疲労度を追跡するために新しい属性fatigueを追加し、投球ごとに疲労度を増加させることができます。スタミナが高い投手は、疲労度の増加が遅くなります。疲労度が一定の閾値を超えた場合、投手のパフォーマンスが低下するようにゲームロジックを調整できます。

まず、投手クラスに疲労度属性を追加します。
```python
class Pitcher:
    def __init__(self, name, fastball, movement, control, stability, ball_quality, technique, stamina):
        self.name = name
        self.fastball = fastball
        self.movement = movement
        self.control = control
        self.stability = stability
        self.ball_quality = ball_quality
        self.technique = technique
        self.stamina = stamina
        self.fatigue = 0
```
次に、投球ごとに疲労度を増加させる関数を追加します。
```python
def increase_fatigue(pitcher):
    fatigue_increase = 1 - param_to_value(pitcher.stamina) * 0.1
    pitcher.fatigue += fatigue_increase
```
そして、投手の疲労度がパフォーマンスに影響を与えるように、simulate_at_bat()関数を調整します。
```python
def simulate_at_bat(pitcher, batter):
    # ...
    strikeout_prob *= max(1 - pitcher.fatigue * 0.01, 0)
    out_prob *= max(1 - pitcher.fatigue * 0.01, 0)

    # ...
    result = random.choices(
        # ...
    )[0]

    increase_fatigue(pitcher)

    return result
```

:::note
すごいことになってきた、草
:::

## 結論；　なんだかやる気があれば作れそう、笑

なんとなく GPT-4 の回答をみていると未使用の塗り絵をもらったみたい
自分好みの色を塗っていくことで必要なアプリの開発ができそうです。

分からなければ即答してくれるし。こういうのを利用し
即売会で販売される製品が増殖するのだろうな、と予測。

ちょっと時間が足りないので今日はここまで。そのうち作成してみようかな。

2023/03/19
本家みたいにパラメタを追加してもらったら、こんなソースコードになったｗ

<details><summary>長文なので以下省略w（やばい。ここをクリックください）</summary>

```python
import random
import time

class Pitcher:
    def __init__(self, name, handedness):
        self.name = name
        self.handedness = handedness

class Batter:
    def __init__(self, name, strength, agility, intelligence, wisdom, vitality, condition, handedness):
        self.name = name
        self.strength = strength
        self.agility = agility
        self.intelligence = intelligence
        self.wisdom = wisdom
        self.vitality = vitality
        self.condition = condition
        self.handedness = handedness

def param_to_value(param):
    mapping = {'S': 1.0, 'A': 0.9, 'B': 0.8, 'C': 0.7, 'D': 0.6, 'E': 0.5}
    return mapping.get(param, 0)

def simulate_home_run_derby(pitcher, batter, num_pitches):
    home_runs = 0
    strike_counter = 0  # ストライクカウンターの追加
    for i in range(num_pitches):
        base_prob = (param_to_value(batter.strength) + param_to_value(batter.intelligence) * 0.5) * 0.15
        condition_coefficient = 1.1 if batter.condition == "good" else 0.9
        handedness_coefficient = 1.05 if pitcher.handedness != batter.handedness else 1
        home_run_prob = base_prob * condition_coefficient * handedness_coefficient
        
        intelligence_coefficient = 1 - param_to_value(batter.intelligence) / 10
        vitality_coefficient = param_to_value(batter.vitality) / 10
        speed_coefficient = param_to_value(batter.agility) / 10

        result = random.choices(
            ['home_run', 'strike', 'flyout', 'groundout'],
            weights=[
                home_run_prob,
                (1 - home_run_prob) * intelligence_coefficient,
                (1 - home_run_prob) * vitality_coefficient,
                (1 - home_run_prob) * (1 - intelligence_coefficient - vitality_coefficient) * speed_coefficient
            ],
            k=1
        )[0]

        if result == 'home_run':
            home_runs += 1
            strike_counter = 0  # ホームランが出たらストライクカウンターをリセット
            print(f'Pitch {i+1}: Home Run! (Condition: {batter.condition})')
            batter.condition = "good" if random.random() < 0.4 else batter.condition
        elif result == 'strike':
            strike_counter += 1  # ストライクが出たらストライクカウンターを増やす
            print(f'Pitch {i+1}: Strike. (Condition: {batter.condition})')
            batter.condition = "bad" if random.random() < 0.1 * (1 - param_to_value(batter.vitality) / 10) else batter.condition
            # ストライクが3回続いたら状態をbadにする
            if strike_counter >= 3:
                batter.condition = 'bad'

        elif result == 'flyout':
            strike_counter = 0  # その他の結果が出たらストライクカウンターをリセット
            print(f'Pitch {i+1}: Flyout. (Condition: {batter.condition})')
        elif result == 'groundout':
            strike_counter = 0  # その他の結果が出たらストライクカウンターをリセット
            print(f'Pitch {i+1}: Groundout. (Condition: {batter.condition})')

        home_run_prob += param_to_value(batter.intelligence) * 0.02 * (batter.condition == "bad")  # 知力が高い場合、調子が悪いときのホームラン確率が上がる

        
        time.sleep(1)  # 1秒の待ち時間を設ける

    return home_runs

def choose_batter():
    batters = [
        Batter("Taro", "A", "C", "B", "C", "A", "good", "right"),
        Batter("Jiro", "B", "A", "C", "B", "B", "good", "left"),
        Batter("Saburo", "C", "B", "A", "A", "C", "good", "right"),
    ]
    
    print("選手を選んでください:")
    for i, batter in enumerate(batters):
        print(f"{i + 1}: {batter.name}")
    
    choice = int(input()) - 1
    return batters[choice]

def main():
    pitcher = Pitcher("Pitcher1", "right")
    batter = choose_batter()  # バッターを選択する
    
    num_pitches = 10
    home_runs = simulate_home_run_derby(pitcher, batter, num_pitches) 
    print(f'{batter.name} hit {home_runs} home runs out of {num_pitches} pitches.')


if __name__ == "__main__":
    main()
```
</details>

:::note
やばいｗ

気がつけばホームラン競争ゲームにはなったｗ
:::

```python
(taro) @MacBook-Air src % python baseball.py        
選手を選んでください:
1: Taro
2: Jiro
3: Saburo
1
Pitch 1: Home Run! (Condition: good)
Pitch 2: Flyout. (Condition: good)
Pitch 3: Home Run! (Condition: good)
Pitch 4: Strike. (Condition: good)
Pitch 5: Home Run! (Condition: good)
Pitch 6: Home Run! (Condition: good)
Pitch 7: Strike. (Condition: good)
Pitch 8: Strike. (Condition: good)
Pitch 9: Strike. (Condition: good)
Pitch 10: Home Run! (Condition: bad)
Taro hit 5 home runs out of 10 pitches.

(taro) taro@MacBook-Air src % python baseball.py
選手を選んでください:
1: Taro
2: Jiro
3: Saburo
3
Pitch 1: Strike. (Condition: good)
Pitch 2: Strike. (Condition: good)
Pitch 3: Home Run! (Condition: good)
Pitch 4: Strike. (Condition: good)
Pitch 5: Strike. (Condition: good)
Pitch 6: Strike. (Condition: good)
Pitch 7: Strike. (Condition: bad)
Pitch 8: Strike. (Condition: bad)
Pitch 9: Strike. (Condition: bad)
Pitch 10: Home Run! (Condition: bad)
Saburo hit 2 home runs out of 10 pitches.
```

ストライクが３回続くと気分が落ち込むギミック付きです（そこじゃない