Geminiを使ってプロンプトを検討
AIエージェントにゲームを開発させるにあたって最初期のビデオゲームの模倣がいいと思うんだけど、ミサイルコマンドをubuntu上で実行するpython3のスクリプトとしたらどんなプロンプトにするのがいいだろうか
- 作成したプロンプト
# Role: Python Game Developer
# Objective:
あなたはプロのPythonゲーム開発者です。レトロアーケードゲームの名作「ミサイルコマンド」を模倣したデスクトップゲームを、Python 3とPygameライブラリを使って開発してください。
# Environment:
- OS: Ubuntu
- Language: Python 3
- Library: Pygame
# Core Specifications:
以下の仕様に厳密に従って、単一のPythonスクリプトファイルとしてコードを生成してください。
## 1. ゲームウィンドウ
- ウィンドウサイズ: 幅800ピクセル x 高さ600ピクセル
- 背景色: 黒 (`#000000`)
- 地面: 画面下部に緑色の長方形を描画する。
## 2. プレイヤーの砲台 (Bases)
- 画面下部の地面の上に、3つの砲台を均等な間隔で配置する。
- 各砲台は三角形または四角形で表現する。
- 各砲台には弾数(例: 10発)の制限があり、弾がなくなるとその砲台からは発射できなくなる。
## 3. 都市 (Cities)
- 3つの砲台の間に、合計6つの都市を配置する。
- 都市は小さな四角形で表現する。
- 都市は健在な状態(例: 青色)と破壊された状態(例: 灰色)を持つ。
## 4. 照準 (Crosshair)
- マウスカーソルの位置に、十字のマーク(クロスヘア)を描画する。マウスの動きに追従させること。
## 5. 敵ミサイル (Enemy Missiles)
- 画面の上部からランダムなX座標で出現する。
- 各敵ミサイルは、地上にある6つの都市または3つの砲台の中から、ランダムに1つを目標として設定する。
- 細い線として軌跡を描きながら、目標に向かって一定速度で落下する。
- 時間経過やステージクリアに応じて、落下速度と出現頻度を少しずつ上昇させる。
## 6. 迎撃ミサイル (Player Missiles)
- プレイヤーがマウスの左ボタンをクリックすると発射される。
- クリックされた照準の位置に最も近い、弾が残っている砲台から自動的に選択されて発射される。
- 砲台から照準の位置に向かって、敵ミサイルより速い速度で飛んでいく。
- 目標地点(クリックされた位置)に到達すると爆発する。
## 7. 爆発 (Explosions)
- 迎撃ミサイルが目標地点に到達すると、円形の爆発エフェクトを生成する。
- 爆発エフェクトは、生成されてから一定時間(例: 1秒)かけて徐々に大きくなり、その後消滅する。
- この爆発エフェクトの円の中に敵ミサイルが入ると、その敵ミサイルは破壊される。
## 8. ゲームロジック
- **スコア:** 敵ミサイルを1つ破壊するごとにスコアが加算される(例: 25点)。
- **ゲームオーバー:** 6つの都市がすべて破壊された時点でゲームオーバーとする。
- **ゲームオーバー表示:** ゲームオーバー時には、画面中央に "GAME OVER" と表示し、最終スコアを表示する。
- **リスタート:** ゲームオーバー画面で特定のキー(例: 'R'キー)を押すと、ゲームが初期状態に戻り、再スタートできるようにする。
# Code Requirements:
- 全てのロジックを単一の `missile_command.py` というファイルに記述してください。
- オブジェクト指向プログラミングの原則に従い、以下のクラスを使用してください:
- `Game`: ゲーム全体の管理(ゲームループ、イベント処理、状態管理など)
- `Base`: プレイヤーの砲台
- `City`: 都市
- `EnemyMissile`: 敵ミサイル
- `PlayerMissile`: 迎撃ミサイル
- `Explosion`: 爆発エフェクト
- 色、画面サイズ、速度などの設定値は、コードの先頭で定数としてまとめて定義してください。
- コードには、各クラスや関数の役割を説明する適切なコメントを加えてください。
# Execution Instructions:
- 最後に、このスクリプトを実行するために必要なPygameのインストールコマンドと、実行コマンドをコメントとして記載してください。
amazon q developper に開発依頼
- 出来上がったソース
missile_command.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Missile Command Game (Version 2)
A recreation of the classic arcade game using Python and Pygame.
Added wave-based gameplay with increasing difficulty.
Installation:
pip install pygame
Run:
python missile_command.py
"""
import pygame
import sys
import random
import math
from typing import List, Tuple, Optional
# Initialize pygame
pygame.init()
# Constants
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
FPS = 60
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
GRAY = (128, 128, 128)
ORANGE = (255, 165, 0)
CYAN = (0, 255, 255)
PURPLE = (128, 0, 128)
# Game settings
GROUND_HEIGHT = 30
BASE_WIDTH = 30
BASE_HEIGHT = 20
CITY_WIDTH = 20
CITY_HEIGHT = 15
MISSILE_SPEED = 5
ENEMY_MISSILE_SPEED_INITIAL = 0.5
ENEMY_MISSILE_SPAWN_RATE_INITIAL = 2500 # milliseconds
ENEMY_MISSILE_SIZE = 5
EXPLOSION_MAX_RADIUS = 30
EXPLOSION_GROWTH_RATE = 0.5
EXPLOSION_DURATION = 1000 # milliseconds
MISSILES_PER_BASE = 10
SCORE_PER_HIT = 25
MISSILES_PER_WAVE = 20 # 各ウェーブの隕石数
SPEED_INCREASE_PER_WAVE = 0.1 # ウェーブごとの速度増加
class Base:
"""Player's missile base that can fire interceptor missiles."""
def __init__(self, x: int, y: int):
self.x = x
self.y = y
self.width = BASE_WIDTH
self.height = BASE_HEIGHT
self.missiles_left = MISSILES_PER_BASE
self.destroyed = False
def draw(self, screen):
"""Draw the base on the screen."""
if not self.destroyed:
# Draw a triangle for the base
pygame.draw.polygon(screen, GREEN, [
(self.x - self.width // 2, self.y),
(self.x + self.width // 2, self.y),
(self.x, self.y - self.height)
])
# Draw missile count
font = pygame.font.SysFont(None, 20)
text = font.render(str(self.missiles_left), True, WHITE)
screen.blit(text, (self.x - 5, self.y + 5))
def fire_missile(self) -> bool:
"""Attempt to fire a missile from this base."""
if self.missiles_left > 0 and not self.destroyed:
self.missiles_left -= 1
return True
return False
def refill_missiles(self):
"""Refill missiles at the end of a wave."""
if not self.destroyed:
self.missiles_left = MISSILES_PER_BASE
class City:
"""A city that needs to be protected from enemy missiles."""
def __init__(self, x: int, y: int):
self.x = x
self.y = y
self.width = CITY_WIDTH
self.height = CITY_HEIGHT
self.destroyed = False
def draw(self, screen):
"""Draw the city on the screen."""
color = GRAY if self.destroyed else BLUE
pygame.draw.rect(screen, color,
(self.x - self.width // 2,
self.y - self.height,
self.width,
self.height))
class EnemyMissile:
"""Enemy missile that targets cities or bases."""
def __init__(self, target_x: int, target_y: int, speed: float):
# Start from a random position at the top of the screen
self.start_x = random.randint(0, SCREEN_WIDTH)
self.start_y = 0
self.x = self.start_x
self.y = self.start_y
self.target_x = target_x
self.target_y = target_y
self.speed = speed
self.destroyed = False
# Calculate direction vector
dx = self.target_x - self.start_x
dy = self.target_y - self.start_y
distance = math.sqrt(dx * dx + dy * dy)
self.dx = dx / distance * self.speed
self.dy = dy / distance * self.speed
# Trail points
self.trail = [(self.x, self.y)]
def update(self):
"""Update the missile position."""
if not self.destroyed:
self.x += self.dx
self.y += self.dy
self.trail.append((self.x, self.y))
# Keep only the last 10 positions for the trail
if len(self.trail) > 10:
self.trail.pop(0)
def draw(self, screen):
"""Draw the missile and its trail on the screen."""
if not self.destroyed:
# Draw the trail
if len(self.trail) > 1:
pygame.draw.lines(screen, RED, False, self.trail, 2)
# Draw the missile head (隕石を大きくする)
pygame.draw.circle(screen, ORANGE, (int(self.x), int(self.y)), ENEMY_MISSILE_SIZE)
# 隕石の内側を描画して立体感を出す
pygame.draw.circle(screen, RED, (int(self.x), int(self.y)), ENEMY_MISSILE_SIZE - 2)
def has_reached_target(self) -> bool:
"""Check if the missile has reached its target."""
return (abs(self.x - self.target_x) < ENEMY_MISSILE_SIZE + 3 and
abs(self.y - self.target_y) < ENEMY_MISSILE_SIZE + 3)
class PlayerMissile:
"""Player's interceptor missile."""
def __init__(self, start_x: int, start_y: int, target_x: int, target_y: int):
self.start_x = start_x
self.start_y = start_y
self.x = start_x
self.y = start_y
self.target_x = target_x
self.target_y = target_y
self.speed = MISSILE_SPEED
self.exploded = False
# Calculate direction vector
dx = self.target_x - self.start_x
dy = self.target_y - self.start_y
distance = math.sqrt(dx * dx + dy * dy)
self.dx = dx / distance * self.speed
self.dy = dy / distance * self.speed
# Trail points
self.trail = [(self.x, self.y)]
def update(self) -> bool:
"""Update the missile position. Returns True if reached target."""
if not self.exploded:
self.x += self.dx
self.y += self.dy
self.trail.append((self.x, self.y))
# Keep only the last 10 positions for the trail
if len(self.trail) > 10:
self.trail.pop(0)
# Check if reached target
if (abs(self.x - self.target_x) < 5 and
abs(self.y - self.target_y) < 5):
self.exploded = True
return True
return False
def draw(self, screen):
"""Draw the missile and its trail on the screen."""
if not self.exploded:
# Draw the trail
if len(self.trail) > 1:
pygame.draw.lines(screen, YELLOW, False, self.trail, 1)
# Draw the missile head
pygame.draw.circle(screen, YELLOW, (int(self.x), int(self.y)), 2)
class Explosion:
"""Explosion effect created when a player missile reaches its target."""
def __init__(self, x: int, y: int):
self.x = x
self.y = y
self.radius = 0
self.max_radius = EXPLOSION_MAX_RADIUS + 10
self.growth_rate = EXPLOSION_GROWTH_RATE
self.duration = EXPLOSION_DURATION + 200
self.start_time = pygame.time.get_ticks()
self.finished = False
def update(self):
"""Update the explosion state."""
current_time = pygame.time.get_ticks()
elapsed = current_time - self.start_time
if elapsed < self.duration:
# Grow the explosion
progress = elapsed / self.duration
self.radius = self.max_radius * min(progress * 2, 1)
if progress > 0.5:
# Start shrinking after half the duration
self.radius = self.max_radius * (2 - progress * 2)
else:
self.finished = True
def draw(self, screen):
"""Draw the explosion on the screen."""
if not self.finished:
# 爆発をより目立たせるために、外側と内側の2重の円を描画
pygame.draw.circle(screen, YELLOW, (int(self.x), int(self.y)), int(self.radius), 2)
pygame.draw.circle(screen, ORANGE, (int(self.x), int(self.y)), int(self.radius * 0.7), 1)
def check_collision(self, missile_x: float, missile_y: float) -> bool:
"""Check if a missile is within the explosion radius."""
if self.finished:
return False
distance = math.sqrt((missile_x - self.x) ** 2 + (missile_y - self.y) ** 2)
return distance <= self.radius
class Game:
"""Main game class that manages the game state and logic."""
def __init__(self):
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Missile Command - Version 2")
self.clock = pygame.time.Clock()
self.font = pygame.font.SysFont(None, 36)
self.small_font = pygame.font.SysFont(None, 24)
self.reset_game()
def reset_game(self):
"""Reset the game to its initial state."""
self.game_over = False
self.score = 0
self.wave = 1
self.enemy_missile_speed = ENEMY_MISSILE_SPEED_INITIAL
self.enemy_missile_spawn_rate = ENEMY_MISSILE_SPAWN_RATE_INITIAL
self.last_enemy_spawn_time = pygame.time.get_ticks()
# Wave system
self.missiles_in_wave = 0
self.missiles_destroyed = 0
self.wave_complete = False
self.wave_message_time = 0
self.show_wave_message = False
# Create bases
ground_y = SCREEN_HEIGHT - GROUND_HEIGHT
base_positions = [SCREEN_WIDTH // 4, SCREEN_WIDTH // 2, 3 * SCREEN_WIDTH // 4]
self.bases = [Base(x, ground_y) for x in base_positions]
# Create cities
city_positions = [
SCREEN_WIDTH // 8,
SCREEN_WIDTH // 8 * 3,
SCREEN_WIDTH // 8 * 5,
SCREEN_WIDTH // 8 * 7
]
self.cities = [City(x, ground_y) for x in city_positions]
# Add two more cities between bases
self.cities.append(City(SCREEN_WIDTH // 4 + SCREEN_WIDTH // 8, ground_y))
self.cities.append(City(3 * SCREEN_WIDTH // 4 - SCREEN_WIDTH // 8, ground_y))
# Game objects
self.enemy_missiles = []
self.player_missiles = []
self.explosions = []
# 初期状態でミサイルを発射しないための遅延
self.initial_delay = pygame.time.get_ticks() + 1500
def start_new_wave(self):
"""Start a new wave with increased difficulty."""
self.wave += 1
self.enemy_missile_speed += SPEED_INCREASE_PER_WAVE
self.missiles_in_wave = 0
self.missiles_destroyed = 0
self.wave_complete = False
# Refill missiles for all bases
for base in self.bases:
base.refill_missiles()
# Show wave message
self.show_wave_message = True
self.wave_message_time = pygame.time.get_ticks()
def spawn_enemy_missile(self):
"""Spawn a new enemy missile targeting a random city or base."""
current_time = pygame.time.get_ticks()
# 初期遅延が終わるまでミサイルを発射しない
if hasattr(self, 'initial_delay') and current_time < self.initial_delay:
return
# ウェーブメッセージ表示中は敵ミサイルを発射しない
if self.show_wave_message:
if current_time - self.wave_message_time > 3000: # 3秒間表示
self.show_wave_message = False
return
# ウェーブ完了時は新しいミサイルを発射しない
if self.wave_complete:
# すべての敵ミサイルが破壊されたか画面外に出たら次のウェーブを開始
if len(self.enemy_missiles) == 0:
self.start_new_wave()
return
# ウェーブの最大ミサイル数に達したら、それ以上発射しない
if self.missiles_in_wave >= MISSILES_PER_WAVE:
self.wave_complete = True
return
if current_time - self.last_enemy_spawn_time > self.enemy_missile_spawn_rate:
self.last_enemy_spawn_time = current_time
# Choose a random target (city or base)
targets = []
for city in self.cities:
if not city.destroyed:
targets.append((city.x, city.y))
for base in self.bases:
if not base.destroyed:
targets.append((base.x, base.y))
if targets:
target_x, target_y = random.choice(targets)
self.enemy_missiles.append(
EnemyMissile(target_x, target_y, self.enemy_missile_speed)
)
self.missiles_in_wave += 1
def fire_player_missile(self, target_x: int, target_y: int):
"""Fire a player missile from the nearest available base."""
# ウェーブメッセージ表示中は発射できない
if self.show_wave_message:
return
# Find the closest base with missiles left
available_bases = [base for base in self.bases
if base.missiles_left > 0 and not base.destroyed]
if available_bases:
# Sort bases by distance to target
available_bases.sort(key=lambda base:
abs(base.x - target_x))
closest_base = available_bases[0]
if closest_base.fire_missile():
self.player_missiles.append(
PlayerMissile(closest_base.x, closest_base.y - BASE_HEIGHT,
target_x, target_y)
)
def check_collisions(self):
"""Check for collisions between explosions and enemy missiles."""
# Check if enemy missiles hit cities or bases
for missile in self.enemy_missiles[:]:
if missile.has_reached_target():
# Check if it hit a city
for city in self.cities:
if (not city.destroyed and
abs(missile.x - city.x) < CITY_WIDTH // 2 and
abs(missile.y - city.y) < CITY_HEIGHT):
city.destroyed = True
break
# Check if it hit a base
for base in self.bases:
if (not base.destroyed and
abs(missile.x - base.x) < BASE_WIDTH // 2 and
abs(missile.y - base.y) < BASE_HEIGHT):
base.destroyed = True
break
missile.destroyed = True
self.enemy_missiles.remove(missile)
# Check if explosions hit enemy missiles
for explosion in self.explosions:
for missile in self.enemy_missiles[:]:
if not missile.destroyed and explosion.check_collision(missile.x, missile.y):
missile.destroyed = True
self.enemy_missiles.remove(missile)
self.score += SCORE_PER_HIT
self.missiles_destroyed += 1
# Check if all cities are destroyed
if all(city.destroyed for city in self.cities):
self.game_over = True
def update(self):
"""Update the game state."""
if not self.game_over:
# Spawn enemy missiles
self.spawn_enemy_missile()
# Update enemy missiles
for missile in self.enemy_missiles[:]:
missile.update()
# 画面外に出たミサイルを削除
if missile.y > SCREEN_HEIGHT or missile.x < 0 or missile.x > SCREEN_WIDTH:
self.enemy_missiles.remove(missile)
# Update player missiles
for missile in self.player_missiles[:]:
if missile.update():
# Missile reached target, create explosion
self.explosions.append(Explosion(missile.target_x, missile.target_y))
self.player_missiles.remove(missile)
# Update explosions
for explosion in self.explosions[:]:
explosion.update()
if explosion.finished:
self.explosions.remove(explosion)
# Check collisions
self.check_collisions()
def draw(self):
"""Draw the game state to the screen."""
# Clear the screen
self.screen.fill(BLACK)
# Draw ground
pygame.draw.rect(self.screen, GREEN,
(0, SCREEN_HEIGHT - GROUND_HEIGHT,
SCREEN_WIDTH, GROUND_HEIGHT))
# Draw bases
for base in self.bases:
base.draw(self.screen)
# Draw cities
for city in self.cities:
city.draw(self.screen)
# Draw enemy missiles
for missile in self.enemy_missiles:
missile.draw(self.screen)
# Draw player missiles
for missile in self.player_missiles:
missile.draw(self.screen)
# Draw explosions
for explosion in self.explosions:
explosion.draw(self.screen)
# Draw crosshair at mouse position
mouse_x, mouse_y = pygame.mouse.get_pos()
# クロスヘアを大きく、目立つようにする
pygame.draw.line(self.screen, WHITE, (mouse_x - 15, mouse_y), (mouse_x + 15, mouse_y), 2)
pygame.draw.line(self.screen, WHITE, (mouse_x, mouse_y - 15), (mouse_x, mouse_y + 15), 2)
pygame.draw.circle(self.screen, WHITE, (mouse_x, mouse_y), 3, 1)
# Draw score
score_text = self.small_font.render(f"Score: {self.score}", True, WHITE)
self.screen.blit(score_text, (10, 10))
# Draw wave information
wave_text = self.small_font.render(f"Wave: {self.wave}", True, WHITE)
self.screen.blit(wave_text, (10, 40))
# Draw missiles remaining in wave
if not self.wave_complete:
missiles_text = self.small_font.render(
f"Missiles: {self.missiles_destroyed}/{MISSILES_PER_WAVE}", True, WHITE)
else:
missiles_text = self.small_font.render(
f"Wave Complete! {self.missiles_destroyed}/{MISSILES_PER_WAVE}", True, CYAN)
self.screen.blit(missiles_text, (10, 70))
# Draw wave message
if self.show_wave_message:
# Semi-transparent overlay
overlay = pygame.Surface((SCREEN_WIDTH, 100), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 180))
self.screen.blit(overlay, (0, SCREEN_HEIGHT // 2 - 50))
# Wave message
wave_msg = self.font.render(f"WAVE {self.wave}", True, CYAN)
msg_rect = wave_msg.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 20))
self.screen.blit(wave_msg, msg_rect)
# Speed message
speed_msg = self.small_font.render(
f"Enemy Speed: {self.enemy_missile_speed:.1f}", True, YELLOW)
speed_rect = speed_msg.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 20))
self.screen.blit(speed_msg, speed_rect)
# Draw game over screen
if self.game_over:
# Semi-transparent overlay
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 128))
self.screen.blit(overlay, (0, 0))
# Game over text
game_over_text = self.font.render("GAME OVER", True, RED)
text_rect = game_over_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 70))
self.screen.blit(game_over_text, text_rect)
# Final score
final_score_text = self.font.render(f"Final Score: {self.score}", True, WHITE)
score_rect = final_score_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 20))
self.screen.blit(final_score_text, score_rect)
# Wave reached
wave_text = self.font.render(f"Waves Completed: {self.wave - 1}", True, CYAN)
wave_rect = wave_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 30))
self.screen.blit(wave_text, wave_rect)
# Restart instructions
restart_text = self.small_font.render("Press 'R' to restart", True, WHITE)
restart_rect = restart_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 80))
self.screen.blit(restart_text, restart_rect)
# Update the display
pygame.display.flip()
def run(self):
"""Main game loop."""
running = True
while running:
# Handle events
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1 and not self.game_over: # Left mouse button
self.fire_player_missile(event.pos[0], event.pos[1])
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_r and self.game_over:
self.reset_game()
elif event.key == pygame.K_ESCAPE:
running = False
# Update game state
self.update()
# Draw everything
self.draw()
# Cap the frame rate
self.clock.tick(FPS)
pygame.quit()
sys.exit()
if __name__ == "__main__":
game = Game()
game.run()
実行方法
pygameをインストール
pip install pygame
コマンドラインでゲームを実行
python3 missile_command.py
感想
- cluade codeやchatgpt codexを普段使っていますが、amazon q developperは初めてで操作に慣れるか不安でしたが、claude codeに近い感覚で使えました。
- Bluetoothが直前で故障し、スピーカが使えなくなったためPC + Quest3 + VirtualDesktopで参加しましたが、意外と普通に作業できました