1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PyxelとPymunkでサンプルプログラム その6

Last updated at Posted at 2025-11-07

1. 古いchatGPTのテンプレート

古いchatGPTを使ったAI生成メニューで、PyxelとPymunkを使った物理シミュレーション生成プロンプトを試みているところです。以下、解説してもらった。ありがとうございます。

古いChatGPT(特にGPT-3.5や初期のGPT-4)では、コメント行を自動挿入した際に**インデントの整合性が壊れる(特にupdate()やdraw()内部の空行・コメント混在時)という既知の問題があります。
 この問題はモデル側の整形癖(comment insertion bias)**に起因しており、プロンプトで強くルール指定しても完全には抑制できません。

🔧 仕組みのポイント

問題点 改善方法
コメント自動生成時にインデントずれ 「update()とdraw()にコメント禁止」を明示
コメントと空行が混ざるとズレる 「コメント直後に空行禁止」ルールで固定
古いGPTが自動整形する 「自動修正せず、統一して出力」と指示
出力後に文章混入 「コードブロックのみ出力」と強制

💡補足アドバイス

  • **「コメント絶対禁止」**にすると最も安定しますが、上記ルールなら最低限のコメントを許可しつつも安全です。
  • それでも崩れる場合は、プロンプト冒頭に出力は「コードのみ」。整形後の再修正禁止。

「コメント完全禁止」版プロンプトは、インデント崩れを完全に防ぐ効果がありますが、その副作用として、古いChatGPT(特にGPT-3.5系)は**「コメントを出すことも解説することも許されない」=“応答制約モード”**に入ってしまい、あなたが見たように
「申し訳ありませんが、コメントや解説は提供できません。」
という反応を返すようになります。

⚙️ なぜそうなるか

旧モデル(GPT-3.5や一部のGPT-4)には「安全モード判定」があり、プロンプト内で

コメント禁止/解説禁止/コードのみ

といった強い禁止指定を含むと、「指示に従って会話文を出してはいけない」と自己判断して、
**“プログラムだけを返すモード”**になります。
このため、説明を挟んだり補足を付けたりする応答が遮断されます。

以下、改良版プロンプトを作って試したみたところ、インデントはうまくいくようになりました。

🧠 改良版プロンプトのポイント

旧版との差 目的
「コメント完全禁止」を「一部許可」に変更 モデルが“説明禁止モード”に入らない
「update/drawコメント禁止」を明記 崩れやすい箇所だけを保護
「自動整形を行わず、統一出力」 出力中の内部リフォーマット抑止
「コード外での説明不要」と明記 出力前後の会話制限を解除しつつ整形維持

2. プロンプトの書き方比べ

大量の空行が発生したプロンプト例

「マウス左クリックした位置に長方形を発生させ、回転しながら落下する物理シミュレーションを作成してください。 Rキーでリセット時には、全オブジェクトをspaceから削除します。」

draw()メソッド内に大量の空行が発生。プロンプトが2つの独立した動作(マウスクリック生成 + リセット削除)を含み、かつ文末が途切れているため、AIが処理を分断して空行を挿入した可能性がある?

  1. プロンプトの構造的問題: 「〜させ、」「〜します。」という文末の混在
  2. 動作の独立性: 「発生」と「削除」が別の処理として認識された
  3. AIの整形癖: 処理を区切る際に空行を挿入する傾向

追記(2025-11-12):生成AIのfrequency penaltyを上げると、制約条件を無視して、コメント、タブふえる挙動、コード生成がおかしくなるということがわかりました。デフォルトの0で安定しています。ありがとうございます。

改善されたプロンプト案

案1: 一文で流れをつなぐ

「過去のコードを参照せず、独立したプログラムを生成してください。
マウス左クリックした位置に赤い長方形が発生し、角速度3で回転しながら落下して青い地面に衝突する物理シミュレーションを作成し、Rキーでリセット時には全オブジェクトをspaceから削除して初期状態に戻してください。」

import pyxel
import pymunk

COLOR_RED = 8
COLOR_BLUE = 5

class PhysicsSimulation:
    def __init__(self):
        self.width = 640
        self.height = 480
        pyxel.init(self.width, self.height, title="2D Physics Simulation")
        pyxel.mouse(True)
        self.reset()
        pyxel.run(self.update, self.draw)

    def reset(self):
        self.space = pymunk.Space()
        self.space.gravity = (0, -900)
        ground_body = pymunk.Body(body_type=pymunk.Body.STATIC)
        ground_body.position = (self.width / 2, 20)
        ground_shape = pymunk.Poly.create_box(ground_body, (self.width, 40))
        ground_shape.friction = 0.8
        ground_shape.elasticity = 0.4
        self.space.add(ground_body, ground_shape)
        self.ground_shape = ground_shape
        self.rect_bodies = []
        self.rect_shapes = []

    def add_rectangle(self, x, y):
        size = (60, 30)
        mass = 5
        moment = pymunk.moment_for_box(mass, size)
        body = pymunk.Body(mass, moment)
        body.position = (x, y)
        body.angular_velocity = 3.0
        shape = pymunk.Poly.create_box(body, size)
        shape.friction = 0.5
        shape.elasticity = 0.6
        self.space.add(body, shape)
        self.rect_bodies.append(body)
        self.rect_shapes.append(shape)

    def update(self):
        if pyxel.btnp(pyxel.KEY_R):
            for body in self.rect_bodies:
                for shape in body.shapes:
                    if shape in self.space.shapes:
                        self.space.remove(shape)
                if body in self.space.bodies:
                    self.space.remove(body)
            del self.rect_bodies[:]
            del self.rect_shapes[:]
            self.reset()
            return
        if pyxel.btnp(pyxel.MOUSE_BUTTON_LEFT):
            mx, my_pyxel = pyxel.mouse_x, pyxel.mouse_y
            my_pymunk_y = self.height - my_pyxel
            self.add_rectangle(mx, my_pymunk_y)
        self.space.step(1 / 60.0)

    def draw(self):
        pyxel.cls(0)
        
        bbg = self.ground_shape.bb
        pyxel.rect(
            int(bbg.left), int(self.height - bbg.top),
            int(bbg.right - bbg.left), int(bbg.top - bbg.bottom), COLOR_BLUE)

        
        for body, shape in zip(self.rect_bodies, self.rect_shapes):
            verts_world = [body.local_to_world(v) for v in shape.get_vertices()]
            verts_pyxel = [(int(x), int(self.height - y)) for x,y in verts_world]
            v0,v1,v2,v3=verts_pyxel
            pyxel.tri(v0[0],v0[1],v1[0],v1[1],v2[0],v2[1],COLOR_RED)
            pyxel.tri(v2[0],v2[1],v3[0],v3[1],v0[0],v0[1],COLOR_RED)

if __name__ == "__main__":
    PhysicsSimulation()

pyxel-20251107-222753.gif

案2: より詳細な仕様を明記

「過去のコードを参照せず、独立したプログラムを生成してください。
640x480の画面で、青い地面(弾性0.4、摩擦0.8)を配置し、マウス左クリックで赤い長方形(60x30、質量5、弾性0.6、摩擦0.5)をクリック位置に生成し、角速度3で回転しながら落下させ、Rキーでリセット時には生成した全長方形をspaceから削除してください。」

import pyxel
import pymunk

COLOR_RED = 8
COLOR_BLUE = 5

class PhysicsSimulation:
    def __init__(self):
        self.width = 640
        self.height = 480
        pyxel.init(self.width, self.height, title="2D Physics Simulation")
        pyxel.mouse(True)
        self.reset()
        pyxel.run(self.update, self.draw)

    def reset(self):
        self.space = pymunk.Space()
        self.space.gravity = (0, -900)
        ground_body = pymunk.Body(body_type=pymunk.Body.STATIC)
        ground_body.position = (self.width / 2, 20)
        ground_shape = pymunk.Poly.create_box(ground_body, (self.width, 40))
        ground_shape.friction = 0.8
        ground_shape.elasticity = 0.4
        self.space.add(ground_body, ground_shape)
        self.ground_shape = ground_shape
        self.rect_bodies = []
        self.rect_shapes = []

    def add_rectangle(self, pos):
        size = (60, 30)
        mass = 5
        moment = pymunk.moment_for_box(mass, size)
        body = pymunk.Body(mass, moment)
        body.position = pos[0], self.height - pos[1]
        body.angular_velocity = 3.0
        shape = pymunk.Poly.create_box(body, size)
        shape.friction = 0.5
        shape.elasticity = 0.6
        self.space.add(body, shape)
        self.rect_bodies.append(body)
        self.rect_shapes.append(shape)

    def update(self):
        if pyxel.btnp(pyxel.KEY_R):
            for body in self.rect_bodies:
                for shape in body.shapes:
                    if shape in self.space.shapes:
                        self.space.remove(shape)
                if body in self.space.bodies:
                    self.space.remove(body)
            self.rect_bodies.clear()
            self.rect_shapes.clear()
            return
        if pyxel.btnp(pyxel.MOUSE_BUTTON_LEFT):
            x, y = pyxel.mouse_x, pyxel.mouse_y
            if y < self.height - 40:
                self.add_rectangle((x,y))
        self.space.step(1 / 60.0)

    def draw(self):
        pyxel.cls(0)
        
        bbg = self.ground_shape.bb
        pyxel.rect(
            int(bbg.left), int(self.height - bbg.top),
            int(bbg.right - bbg.left), int(bbg.top - bbg.bottom), COLOR_BLUE)

        
        for body, shape in zip(self.rect_bodies, self.rect_shapes):
            verts_world = [body.local_to_world(v) for v in shape.get_vertices()]
            verts_pyxel = [(int(x), int(self.height - y)) for x,y in verts_world]
            v0,v1,v2,v3=verts_pyxel
            pyxel.tri(v0[0],v0[1],v1[0],v1[1],v2[0],v2[1],COLOR_RED)
            pyxel.tri(v2[0],v2[1],v3[0],v3[1],v0[0],v0[1],COLOR_RED)

if __name__ == "__main__":
    PhysicsSimulation()

pyxel-20251107-222952.gif

案3: テンプレート形式(最も安定)

「過去のコードを参照せず、独立したプログラムを生成してください。

条件:

  • 640x480画面
  • 青い地面(色=5、弾性0.4、摩擦0.8)
  • 赤い長方形(色=8、サイズ60x30、質量5、弾性0.6、摩擦0.5、角速度3)
  • マウス左クリックでクリック位置に長方形生成
  • Rキーでリセット時に全オブジェクトをspaceから削除
  • マウスカーソル表示
import pyxel
import pymunk

COLOR_RED = 8
COLOR_BLUE = 5

class PhysicsSimulation:
    def __init__(self):
        self.width = 640
        self.height = 480
        pyxel.init(self.width, self.height, title="2D Physics Simulation")
        pyxel.mouse(True)
        self.reset()
        pyxel.run(self.update, self.draw)

    def reset(self):
        if hasattr(self, "space"):
            for body in self.space.bodies[:]:
                if body.body_type != pymunk.Body.STATIC:
                    for shape in body.shapes[:]:
                        self.space.remove(shape)
                    self.space.remove(body)
            for shape in self.ground_shapes[:]:
                self.space.remove(shape)
            for body in [self.ground_body]:
                if body is not None:
                    self.space.remove(body)
        self.space = pymunk.Space()
        self.space.gravity = (0, -900)

        ground_y = 20
        ground_height = 40
        ground_width = self.width
        ground_body = pymunk.Body(body_type=pymunk.Body.STATIC)
        ground_body.position = (self.width / 2, ground_y)
        ground_shape = pymunk.Poly.create_box(ground_body, (ground_width, ground_height))
        ground_shape.friction = 0.8
        ground_shape.elasticity = 0.4
        self.space.add(ground_body, ground_shape)
        self.ground_body = ground_body
        self.ground_shapes = [ground_shape]

        self.rect_bodies = []
        self.rect_shapes = []

    def add_rectangle(self, pos):
        size = (60, 30)
        mass = 5
        moment = pymunk.moment_for_box(mass, size)
        body = pymunk.Body(mass, moment)
        x, y_pyxel = pos
        y_pymunk = self.height - y_pyxel
        body.position = (x, y_pymunk)
        body.angular_velocity = 3.0

        shape = pymunk.Poly.create_box(body, size)
        shape.friction=0.5
        shape.elasticity=0.6

        self.space.add(body, shape)

        self.rect_bodies.append(body)
        self.rect_shapes.append(shape)

    def update(self):
            if pyxel.btnp(pyxel.KEY_R):
                for body in list(self.rect_bodies):
                    for shape in list(body.shapes):
                        if shape in self.space.shapes:
                            try:
                                self.space.remove(shape)
                            except Exception:
                                pass
                    if body in self.space.bodies:
                        try:
                            self.space.remove(body)
                        except Exception:
                            pass
                del self.rect_bodies[:]
                del self.rect_shapes[:]
            if pyxel.btnp(pyxel.MOUSE_BUTTON_LEFT):
                mx=pyxel.mouse_x
                my=pyxel.mouse_y
                if 0<=mx<self.width and 0<=my<self.height:
                    self.add_rectangle((mx,my))
            for i,b in enumerate(list(self.rect_bodies)):
                if b.position.y < -100 or b.position.x < -100 or b.position.x >self.width+100 or b.position.y >self.height+100:
                    shapes_to_remove=list(b.shapes)[:]
                    for s in shapes_to_remove:
                        try:self.space.remove(s)
                        except Exception:pass
                    try:self.space.remove(b)
                    except Exception:pass
                    del_index=None
                    try:del_index=self.rect_bodies.index(b)
                    except Exception:pass
                    if del_index is not None and del_index<len(self.rect_shapes):
                        del self.rect_bodies[del_index]
                        del self.rect_shapes[del_index]
            dt=1/60.0 
            steps=10 
            sub_dt=dt/steps 
            for _ in range(steps): 
                try:self.space.step(sub_dt) 
                except Exception:pass 

    def draw(self):
            pyxel.cls(0)

            bb=self.ground_shapes[0].bb

            pyxel.rect(
                int(bb.left),
                int(self.height - bb.top),
                int(bb.right - bb.left),
                int(bb.top - bb.bottom),
                COLOR_BLUE,
            )

            for i,(body,shape) in enumerate(zip(self.rect_bodies,self.rect_shapes)):
                verts=[body.local_to_world(v)for v in shape.get_vertices()]
                verts_pyxel=[(int(x),int(self.height-y))for x,y in verts]
                v0,v1,v2,v3=verts_pyxel

                pyxel.tri(v0[0],v0[1],v1[0],v1[1],v2[0],v2[1],COLOR_RED)

                pyxel.tri(v2[0],v2[1],v3[0],v3[1],v0[0],v0[1],COLOR_RED)

if __name__=="__main__":
    PhysicsSimulation()

pyxel-20251107-223151.gif

3.プロンプト設計ガイド(AIが解釈する参考規則)

  • 文を短く切らず、「〜し、〜して…」のように1つの流れにまとめる

  • 「〜を作成します。」のような独立文を連続させない(整形崩れの原因)

  • 「マウスで発生 → 落下 → リセット → 地面追加」のように時系列に沿って記述する

  • 主目的を最初に書き、補足的要素は「さらに」「加えて」などで接続

  • 「過去のコードを参照しないでください」を明記すると安定する

  • 既存コードを変更・追加するような指示(例:〜を付加してください)は避け、**『過去のコードを参照せず新しく生成してください』**と明示すると構造が安定します

  • 位置指定を含むときは、『左下付近からボールを投げ上げる』のように動作主体(ボールなど)を明確にし、数値座標または方位表現(左下・右上など)で書くと誤解を防げます

一歩一歩です。ありとうございます。

1
0
0

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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?