PCRBにLLMのChatGPTのコード生成でロジック作成（1日目）

Python Code Robot Battle で “勝てるロボット” を育てる

# 目次

1.	はじめに
2.	PCRB とは
3.	検証環境
4.	STEP 1 : 初期ロジックの生成
5.	STEP 2 : トラップ無限設置バグを修正
6.	STEP 3 : 遠距離戦での劣勢を解消
7.	STEP 4 : 近接戦での劣勢を解消…失敗
8.	結果と考察
9.	まとめ & 学び

# はじめに 

- 目的：
    - Python Code Robot Battle（以下 PCRB）に登録できる “戦えるロジックファイル” を ChatGPT のみで 作り上げられるかを検証する。
- スタンス：
    - “最適解” を探すというより “試行錯誤のログ” を残す。

本記事では、
	1.	ChatGPT-4o へ段階的にプロンプトを投げる
	2.	生成コードを PCRB 上で即テスト
	3.	ログを見て原因分析 → 追加プロンプト
を繰り返した軌跡をまとめる。

# PCRB とは

> python_code_robot_battle は、ロボットの行動ロジックを作成し、仮想バトルを行うためのアプリケーションです。Streamlit を利用して操作します。

•デモ: https://python-code-robot-battle.streamlit.app/
•ソース: https://github.com/ai-study-room-notat/python_code_robot_battle

# 検証環境 

|項目|内容|
|---|---|
|LLM|	ChatGPT-4o (2025/06 時点)|
|PCRB|	GitHub Release v0.1.0-beta|
|実装IDE| 	VS Code|
|評価ページ|	Robot Battle Page / Drawer Page|

アップロードしたプロジェクトファイル（robot.py, main.py, controller.py, actions.py）を ChatGPT へ連携し、ロジック関数 robot_logic のみを生成してもらう形式を採用した。

# STEP 1 : 初期ロジックの生成 

## 投げたプロンプト

````text
アップロードしたファイルは、ロボットの行動ロジックを作成し、バトルを行うためのアプリケーションのスクリプトです。
バトルに勝利できるロジックを作成して下さい。
ロジックは次のような関数の形式で作成します。

def robot_logic(robot, game_info, memos):
    # スタミナが少ない場合は休み、それ以外は敵に近づいて攻撃
    enemy_position = game_info['enemy_position']
    memo = {}
    if robot.sp < 20:
        return "rest", memo
    elif abs(robot.position[0] - enemy_position[0]) + abs(robot.position[1] - enemy_position[1]) == 1:
        return "attack", memo
    else:
        if robot.position[0] < enemy_position[0]:
            return "right", memo
        elif robot.position[0] > enemy_position[0]:
            return "left", memo
        elif robot.position[1] < enemy_position[1]:
            return "down", memo
        else:
            return "up", memo
````

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575176/88adbb6d-7acb-4b07-b057-7b3c5019f82d.png)

## 生成されたコード

概要
•	SP<20 なら rest
•	隣接なら attack
•	2 マス離れで SP>=15 なら ranged_attack
•	SP>=25 で 4 方向いずれかにトラップ
•	それ以外は敵へ接近

<details><summary>生成されたコード全文</summary>

```python
def robot_logic(robot, game_info, memos):
    from random import choice

    memo = {}
    enemy_pos = game_info.get("enemy_position")
    sp = robot.sp
    position = robot.position
    traps = game_info.get("enemy_traps", [])
    adjacent = lambda a, b: abs(a[0] - b[0]) + abs(a[1] - b[1]) == 1

    # スタミナが少なければ休む
    if sp < 20:
        return "rest", memo

    # 敵位置が不明な場合：スキャンまたはカモフラージュ
    if enemy_pos is None:
        if sp >= 10:
            return "scan", memo
        return "rest", memo

    # 罠の上にいるなら移動
    if position in traps:
        directions = ["up", "down", "left", "right"]
        safe_moves = [d for d in directions if d not in [f"trap_{d}" for d in directions]]
        return choice(safe_moves), memo

    # 敵が隣接していたら攻撃
    if adjacent(position, enemy_pos):
        return "attack", memo

    # 遠距離攻撃ができる距離なら
    if abs(position[0] - enemy_pos[0]) + abs(position[1] - enemy_pos[1]) == 2:
        if sp >= 15:
            return "ranged_attack", memo

    # 罠を設置（SPが十分なとき）
    if sp >= 25:
        for direction in ["trap_up", "trap_down", "trap_left", "trap_right"]:
            return direction, memo

    # 敵に近づく（基本ロジック）
    if position[0] < enemy_pos[0]:
        return "right", memo
    elif position[0] > enemy_pos[0]:
        return "left", memo
    elif position[1] < enemy_pos[1]:
        return "down", memo
    else:
        return "up", memo
```

</details>

## バトル結果

生成されたロジックをスクリプトに保存してアプリにアップロードします。
今回は、`chatgpt_logic.py`という名前にしました。

アプリの「Robot Battle Page」で性能を確かめます。

|試合数	|勝利数|	勝率|
|---|---|---|
|26	|21	|80.8 %|

敗北ロボット
・robot_01_rest_only (プレイヤー:先攻)
・robot_04_defensive (プレイヤー:先攻)
・robot_07_basic_bot (プレイヤー:先攻)
・robot_07_basic_bot (プレイヤー:後攻)
・robot_09_trapster (プレイヤー:先攻)

# STEP 2 : トラップ無限設置バグを修正 

## 問題

休憩の行動しかしない robot_01_rest_only (プレイヤー:先攻) に敗北しているのが気になるので、ここに勝てるようにしたい。

ログファイルを ChatGPT に投げて改善を考えても良かったが、ひとまず対戦の様子を可視化して確認する。

「Robot Battle Page」の結果からログをダウンロードして、
「Drawer Page」にアップロードして確認する。

確認したところ、自機は自分の上のマスにトラップをずっと置き続け、その後動かないため時間切れで負けとなっていた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575176/26458968-3d5d-4485-9e52-d18c39982c6b.png)

## 修正プロンプト

```text
ロジックを動作させたところ相手が全く動かない場合に、自機のロボットはずっと自分の上のマスにトラップを置き続ける動作になってしまっていました。
トラップを置こうとした場所にすでに自分のトラップが置いてある場合は、置かないようにロジックを修正して下さい。
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575176/def1f898-5527-42e0-898c-bf753cc04982.png)

## 生成コードのポイント

•	robot.trap.traps で自分の罠座標を取得
•	未設置マスのみ trap_* を返す

<details><summary>生成されたコード全文</summary>

```python
def robot_logic(robot, game_info, memos):
    from random import choice

    memo = {}
    enemy_pos = game_info.get("enemy_position")
    sp = robot.sp
    position = robot.position
    traps = game_info.get("enemy_traps", [])
    my_traps = robot.trap.traps  # 自分の罠

    def is_adjacent(a, b):
        return abs(a[0] - b[0]) + abs(a[1] - b[1]) == 1

    # スタミナが少なければ休む
    if sp < 20:
        return "rest", memo

    # 敵位置が不明な場合：スキャンまたは休息
    if enemy_pos is None:
        if sp >= 10:
            return "scan", memo
        return "rest", memo

    # 罠の上にいるなら回避
    if position in traps:
        directions = ["up", "down", "left", "right"]
        return choice(directions), memo

    # 敵が隣接していたら攻撃
    if is_adjacent(position, enemy_pos):
        return "attack", memo

    # 遠距離攻撃可能なら実行
    if abs(position[0] - enemy_pos[0]) + abs(position[1] - enemy_pos[1]) == 2:
        if sp >= 15:
            return "ranged_attack", memo

    # 罠を設置（SPが十分、かつ未設置の方向があれば）
    if sp >= 25:
        direction_offsets = {
            "trap_up":    (0, -1),
            "trap_down":  (0, 1),
            "trap_left":  (-1, 0),
            "trap_right": (1, 0),
        }

        for direction, (dx, dy) in direction_offsets.items():
            trap_pos = (position[0] + dx, position[1] + dy)
            if trap_pos not in my_traps:
                return direction, memo

    # 敵に近づく（基本移動）
    if position[0] < enemy_pos[0]:
        return "right", memo
    elif position[0] > enemy_pos[0]:
        return "left", memo
    elif position[1] < enemy_pos[1]:
        return "down", memo
    else:
        return "up", memo
```

</details>

## 結果

`chatgpt_logic.py`を上書きして「Robot Battle Page」で実行

ランタイムエラー
原因：robot_logic 内にローカル関数 is_adjacent を定義しており、PCRB が関数定義を禁止。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575176/fce3d8c2-66b5-454e-8df1-1d9d0222fbd4.png)

## 再修正

調べたところ関数の生成が許可されていないようなので、ChatGPTにお願いして修正してもらう

プロンプトはこちら

```text
robot_logic 内部で関数の定義が許可されていないようです。
関数 is_adjacent がなくても動作するように修正して下さい。
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575176/10ca302b-153d-4ad4-9ad4-b78f4c4bb11c.png)

## 提案されたロジック

<details><summary>生成されたコード全文</summary>

```python
def robot_logic(robot, game_info, memos):
    from random import choice

    memo = {}
    enemy_pos = game_info.get("enemy_position")
    sp = robot.sp
    position = robot.position
    traps = game_info.get("enemy_traps", [])
    my_traps = robot.trap.traps  # 自分の罠

    # スタミナが少なければ休む
    if sp < 20:
        return "rest", memo

    # 敵位置が不明な場合：スキャンまたは休息
    if enemy_pos is None:
        if sp >= 10:
            return "scan", memo
        return "rest", memo

    # 罠の上にいるなら回避
    if position in traps:
        return choice(["up", "down", "left", "right"]), memo

    # 敵が隣接していたら攻撃
    if abs(position[0] - enemy_pos[0]) + abs(position[1] - enemy_pos[1]) == 1:
        return "attack", memo

    # 遠距離攻撃可能なら実行
    if abs(position[0] - enemy_pos[0]) + abs(position[1] - enemy_pos[1]) == 2:
        if sp >= 15:
            return "ranged_attack", memo

    # 罠を設置（未設置マスがあれば）
    if sp >= 25:
        if (position[0], max(0, position[1] - 1)) not in my_traps:
            return "trap_up", memo
        if (position[0], position[1] + 1) not in my_traps:
            return "trap_down", memo
        if (max(0, position[0] - 1), position[1]) not in my_traps:
            return "trap_left", memo
        if (position[0] + 1, position[1]) not in my_traps:
            return "trap_right", memo

    # 敵に近づく
    if position[0] < enemy_pos[0]:
        return "right", memo
    elif position[0] > enemy_pos[0]:
        return "left", memo
    elif position[1] < enemy_pos[1]:
        return "down", memo
    else:
        return "up", memo
```

</details>

## 結果

`chatgpt_logic.py`を上書きして「Robot Battle Page」で実行

結果は、勝利数: 21 勝 (試合数: 26 戦)
初期のロジックと勝率自体は変化なし

アップロードしたロジックが敗北した対戦相手が次の5つ
・robot_05_adaptive_strategist (プレイヤー:先攻)	
・robot_05_adaptive_strategist (プレイヤー:後攻)	
・robot_07_basic_bot (プレイヤー:先攻)	
・robot_10_energy_thief (プレイヤー:先攻)	
・robot_13_strategic_scanner (プレイヤー:先攻)	

まずは目標とした「robot_01_rest_only (プレイヤー:先攻)」への勝利は達成したが、
新しく負ける相手が登場したという結果だった。

# STEP 3 : 遠距離戦での劣勢を解消 

## 問題

先行でも後攻でも負けている「robot_05_adaptive_strategist」に勝てるようにしたいので、また「Robot Battle Page」の結果からログをダウンロードして、「Drawer Page」にアップロードして確認する。

内容を確認すると次が原因でした。
① 相手が止まって遠距離攻撃を打ち始めると、自機も止まって遠距離攻撃を打ち続ける。
② 遠距離攻撃の撃ち合いが始まった段階で、自機が相手よりもHPが低いとそのまま負ける
※ 正確にはSPのバランスもあるがHPの方が影響が大きそう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575176/3a0c9381-0cad-4c9b-941d-b809b5fe0218.png)

## 修正プロンプト

```text
先に依頼したロジックの改善は修正されたことを確認できました。
次が原因で負けているため、出来るだけ現状のロジックを変えずに、次の原因が解決できるようにロジックを修正して下さい。

# 敗因
① 相手が止まって遠距離攻撃を打ち始めると、自機も止まって遠距離攻撃を打ち続ける。
② 遠距離攻撃の撃ち合いが始まった段階で、自機が相手よりもHPが低いとそのまま負ける
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575176/77b73f9c-b929-4485-be0d-ad738b191899.png)

## 生成コード

<details><summary>生成されたコード全文</summary>

```python
def robot_logic(robot, game_info, memos):
    from random import choice

    memo = {}
    enemy_pos = game_info.get("enemy_position")
    sp = robot.sp
    position = robot.position
    traps = game_info.get("enemy_traps", [])
    my_traps = robot.trap.traps  # 自分の罠

    # スタミナが少なければ休む
    if sp < 20:
        return "rest", memo

    # 敵位置が不明な場合：スキャンまたは休息
    if enemy_pos is None:
        if sp >= 10:
            return "scan", memo
        return "rest", memo

    # 罠の上にいるなら回避
    if position in traps:
        return choice(["up", "down", "left", "right"]), memo

    # 敵が隣接していたら攻撃
    if abs(position[0] - enemy_pos[0]) + abs(position[1] - enemy_pos[1]) == 1:
        return "attack", memo

    # 🔽 遠距離撃ち合い → HP劣勢なら回避、そうでなければ攻撃
    if abs(position[0] - enemy_pos[0]) + abs(position[1] - enemy_pos[1]) == 2:
        if robot.hp < game_info.get("enemy_hp", 999):
            return choice(["up", "down", "left", "right"]), memo
        elif sp >= 15:
            return "ranged_attack", memo

    # 罠を設置（未設置マスがあれば）
    if sp >= 25:
        if (position[0], max(0, position[1] - 1)) not in my_traps:
            return "trap_up", memo
        if (position[0], position[1] + 1) not in my_traps:
            return "trap_down", memo
        if (max(0, position[0] - 1), position[1]) not in my_traps:
            return "trap_left", memo
        if (position[0] + 1, position[1]) not in my_traps:
            return "trap_right", memo

    # 敵に近づく
    if position[0] < enemy_pos[0]:
        return "right", memo
    elif position[0] > enemy_pos[0]:
        return "left", memo
    elif position[1] < enemy_pos[1]:
        return "down", memo
    else:
        return "up", memo
```

</details>

## 結果

chatgpt_logic.pyを上書きして「Robot Battle Page」で実行

結果は、勝利数: 21 勝 (試合数: 26 戦)
初期のロジックと勝率自体は変化なし

アップロードしたロジックが敗北した対戦相手の5つは変更なし

# STEP 4 : 近接戦での劣勢を解消…失敗 

## 問題

継続して「robot_05_adaptive_strategist」に負けているので、内容を確認した。

原因を確認したら先ほどの遠距離攻撃の敗因の近距離攻撃版が敗因でした

① 相手が止まって近距離攻撃を打ち始めると、自機も止まって近距離攻撃を打ち続ける。
② 近距離攻撃の撃ち合いが始まった段階で、自機が相手よりもHPが低いとそのまま負ける

## 修正プロンプト

```text
先に依頼したロジックの改善は修正されたことを確認できました。
次が原因で負けているため、出来るだけ現状のロジックを変えずに、次の原因が解決できるようにロジックを修正して下さい。

# 敗因
① 相手が止まって近距離攻撃を打ち始めると、自機も止まって近距離攻撃を打ち続ける。
② 近距離攻撃の撃ち合いが始まった段階で、自機が相手よりもHPが低いとそのまま負ける
```

## 生成コード

<details><summary>生成されたコード全文</summary>

```python
def robot_logic(robot, game_info, memos):
    from random import choice

    memo = {}
    enemy_pos = game_info.get("enemy_position")
    sp = robot.sp
    position = robot.position
    traps = game_info.get("enemy_traps", [])
    my_traps = robot.trap.traps

    if sp < 20:
        return "rest", memo

    if enemy_pos is None:
        if sp >= 10:
            return "scan", memo
        return "rest", memo

    if position in traps:
        return choice(["up", "down", "left", "right"]), memo

    # 🔽 近距離攻撃 → HP 劣勢なら回避
    if abs(position[0] - enemy_pos[0]) + abs(position[1] - enemy_pos[1]) == 1:
        if robot.hp < game_info.get("enemy_hp", 999):
            return choice(["up", "down", "left", "right"]), memo
        return "attack", memo

    # 🔽 遠距離攻撃 → HP 劣勢なら回避
    if abs(position[0] - enemy_pos[0]) + abs(position[1] - enemy_pos[1]) == 2:
        if robot.hp < game_info.get("enemy_hp", 999):
            return choice(["up", "down", "left", "right"]), memo
        elif sp >= 15:
            return "ranged_attack", memo

    if sp >= 25:
        if (position[0], max(0, position[1] - 1)) not in my_traps:
            return "trap_up", memo
        if (position[0], position[1] + 1) not in my_traps:
            return "trap_down", memo
        if (max(0, position[0] - 1), position[1]) not in my_traps:
            return "trap_left", memo
        if (position[0] + 1, position[1]) not in my_traps:
            return "trap_right", memo

    if position[0] < enemy_pos[0]:
        return "right", memo
    elif position[0] > enemy_pos[0]:
        return "left", memo
    elif position[1] < enemy_pos[1]:
        return "down", memo
    else:
        return "up", memo
```

</details>

## 結果

chatgpt_logic.pyを上書きして「Robot Battle Page」で実行

結果は、勝利数: 19 勝 (試合数: 26 戦)

勝率が下がってしまった・・・

内容を確認したが、逃げるダメージを喰らう、逃げるダメージを喰らうを繰り返して負けていた。
一旦ここまででこのアプローチはやめてみる

教訓： 条件分岐を増やせば強くなるわけではない。

# 結果と考察 

|バージョン|	勝率 (26 戦)|	主な改善点|	主な敗北相手|
|---|---|---|---|
|v0 (初期)|	80.8 %|	なし	|01, 04, 07×2, 09|
|v1 (トラップ重複防止)|	80.8 %|	トラップ重複禁止|	05×2, 07, 10, 13|
|v2 (遠距離 HP 判定)|	80.8 %|	遠距離戦で劣勢回避|	同上|
|v3 (近距離 HP 判定)|	73.1 %|	近距離戦で劣勢回避|	敗北数が増加|

# まとめ & 学び

今回は、初期のロジックを生成させて、結果を人が考察→改善を伝える、ChatGPTで改善する、のプロセスを繰り返した。
改善は進んでいったが、勝てるかはまた別の話で、勝率がうまく上がらなかった

# 今後のアイデア

今回は、徐々にロジックを強化していったが、
強化した先が強いとは限らないので、もっとアプローチを模索できるようにしてみたい