コードを書かずにゲームを作る by Amazon Q CLI

## はじめに
Tシャツ目当てでつくります。（2025/6/20まで）

https://aws.amazon.com/jp/blogs/news/build-games-with-amazon-q-cli-and-score-a-t-shirt/
## 前準備
WindowsPCでWSLを利用してダウンロードしました。
手順はこちらを参考にしています。

https://community.aws/content/2v5PptEEYT2y0lRmZbFQtECA66M/the-essential-guide-to-installing-amazon-q-developer-cli-on-windows?trk=e07eca93-fa2f-4351-b567-f293b83eb635&sc_channel=el
## つくってみた
claude-3.7-sonnetくんと会話していきます。
今回はシューティングゲームとタイピングゲームをかけ合わせたゲームをつくってもらいました。
```
🤖 You are chatting with claude-3.7-sonnet
> シューティングゲームとタイピングゲームをかけ合わせたゲームをつくりたいです。
自機が移動して敵の文字に合わせたキーを押したら弾がでて敵を倒せるゲームです。
敵は徐々に自機側に近づいてきて倒せずに自機を超えたらゲームオーバー、
一定数 敵を倒したらゲームクリアです。そんなゲームをつくって。
```
これだけで作成をはじめてくれます。
```
タイピングとシューティングを組み合わせたゲームを作りたいとのことですね。
Pythonと、ゲーム開発に便利なPygameライブラリを使って実装してみましょう。

Pygameのインストールに問題がありますが、コードを作成して、後でPygameをインス
トールして実行できるようにしましょう。以下にタイピングシューティングゲームの
コードを作成します：（長いので省略）
```
Pygame等のインストールもしていませんでしたが、会話して許可するだけでインストールまでしてくれました。

一度の作成では上手く遊べない部分がありますが、その点を伝えると修正してくれます。
今回は5回の修正をしてもらいました。
```
ひらがなの文字化け問題を解決しました！以下の変更を行いました：

1. 日本語フォントをインストール：

   sudo apt-get install fonts-ipafont fonts-ipaexfont


2. ゲームコードを修正して、日本語対応のIPAexフォントを使用するようにしました
：

python
   # フォント設定
   try:
       # IPAフォントを使用（日本語対応）
       font = pygame.font.Font('/usr/share/fonts/opentype/ipaexfont-gothic/ipaexg.ttf', 36)
       large_font = pygame.font.Font('/usr/share/fonts/opentype/ipaexfont-gothic/ipaexg.ttf', 72)
   except:
       # フォールバック：システムデフォルトフォント
       font = pygame.font.SysFont(None, 36)
       large_font = pygame.font.SysFont(None, 72)



これで、ゲーム内のひらがなが正しく表示されるようになりました。IPAexフォントは
日本語表示に最適化されたフォントで、ゲーム内のひらがなをきれいに表示します。
```
```
ゲームを修正しました！以下の変更を行いました：

## 変更点

1. プレイヤーの移動速度アップ
   • player_speed を 5 から 15 に増加させ、より素早く移動できるようにしました

2. 弾の発射方法の変更
   • 弾は敵に向かって飛ぶのではなく、真上に直線で飛ぶようにしました
   • 敵の真下にいる場合のみ文字入力が有効になるようにしました

3. 位置合わせの判定
   • 敵の真下にいるかどうかを判定する hit_range を追加（50ピクセル以内なら有
効）
   • 敵の真下にいる場合、照準線（黄色い線）が表示されるようにして位置合わせを
わかりやすくしました

4. 当たり判定の修正
   • 弾が真上に飛び、敵に当たった場合のみ敵を倒せるようにしました
   • 入力が完了している敵のみ倒せるようにしました
```
```
問題を修正しました！以下の変更を行いました：

## 主な変更点

1. 敵の出現位置を制限
   • プレイヤーの移動可能範囲を明示的に定義（PLAYER_MIN_X と PLAYER_MAX_X）
   • 敵の出現位置をプレイヤーの移動可能範囲内に制限
   • これにより、プレイヤーが必ず敵の真下に移動できるようになりました

2. 照準の視覚的フィードバックを強化
   • 敵の真下に位置している場合、黄色い照準線に加えて、プレイヤーの上に照準円
を表示
   • これにより、敵と自機の位置が合っているかどうかがより分かりやすくなりまし
た

3. 弾の発射方向を修正
   • 弾が自機の真上ではなく、敵のX座標に向かって発射されるように修正
   • これにより、照準が合っている敵に確実に弾が当たるようになりました

4. 当たり判定の安定性向上
   • 弾の削除処理を安全に行うよう修正（念のためのチェックを追加）
```
```
問題を解決するために、以下の重要な改善を行いました：

## 主な変更点

1. プレイヤーの移動と敵の出現位置を同期
   • プレイヤーの移動距離を大幅に増加（15 → 75）
   • 敵の出現位置をプレイヤーの移動単位に合わせて離散化
   • プレイヤーが移動すると、自動的に最も近い敵の出現位置に合わせるように調整

2. 敵の出現頻度を減少
   • 敵の出現間隔を2.0秒から3.5秒に延長
   • これにより画面上の敵の数が減少し、プレイヤーが対応しやすくなります

3. 移動システムの改善
   • 方向キーを押すと、プレイヤーは必ず敵が出現する位置のいずれかに移動
   • これにより、「敵の真下に移動できない」問題を完全に解消
```
```
ゲームを完全に修正しました！以下の改善を行いました：。
## 主な変更点

1. 移動位置を5箇所に固定
   • 画面を5等分して、プレイヤーが移動できる位置を5箇所に固定
   • 左右キーで隣の位置に移動できるように実装

2. 敵の重なり防止
   • 敵が生成される際に、既に上部に敵がいる位置を避けるロジックを追加
   • 利用可能な位置がない場合のみランダムに選択

3. 敵の出現頻度調整
   • 敵の出現間隔を3.5秒に設定（レベルに応じて徐々に短くなる）
   • 初期敵は3箇所のみに配置して、ゲーム開始時の難易度を下げる

4. 移動システムの改善
   • 方向キーを押すと、現在位置から隣の位置に移動
   • 位置が見つからない場合は最も近い位置に自動調整

5. ゲーム初期化の改善
   • init_game() 関数を追加して、ゲーム開始時とリスタート時に一貫した初期化を
行う
```

## 完成！
最終的にできたのがこちら。
まったくコードを書かずに、小学生がパソコンの授業中に遊ぶくらいのゲームができました！

![Videotogif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4107789/cdc720a8-eaac-475b-9ded-0e5eed320d60.gif)
## おわりに
この記事執筆も含めてトータルで3時間ほどでした。
Tシャツ欲しい方も欲しくない方も遊んでみてはいかがでしょうか。

## コード
```python:typing_shooter.py
import pygame
import sys
import random
import time
from pygame.locals import *

# 初期化
pygame.init()

# 画面設定
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('タイピングシューター')

# 色の定義
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)

# フォント設定
try:
    # IPAフォントを使用（日本語対応）
    font = pygame.font.Font('/usr/share/fonts/opentype/ipaexfont-gothic/ipaexg.ttf', 36)
    large_font = pygame.font.Font('/usr/share/fonts/opentype/ipaexfont-gothic/ipaexg.ttf', 72)
except:
    # フォールバック：システムデフォルトフォント
    font = pygame.font.SysFont(None, 36)
    large_font = pygame.font.SysFont(None, 72)

# ゲーム変数
player_speed = 30  # 移動速度を調整（5箇所に移動できるように）
bullet_speed = 15
enemy_speed = 1
enemy_spawn_interval = 3.5  # 敵の出現間隔（秒）- 出現頻度を下げる
hit_range = 50  # 敵と自機の位置が合っていると判断する範囲
target_score = 30  # クリアに必要なスコア

# プレイヤーの移動可能範囲
PLAYER_MIN_X = 50
PLAYER_MAX_X = WIDTH - 50

# 敵の文字リスト（ひらがな）
characters = [
    'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ',
    'さ', 'し', 'す', 'せ', 'そ', 'た', 'ち', 'つ', 'て', 'と',
    'な', 'に', 'ぬ', 'ね', 'の', 'は', 'ひ', 'ふ', 'へ', 'ほ',
    'ま', 'み', 'む', 'め', 'も', 'や', 'ゆ', 'よ',
    'ら', 'り', 'る', 'れ', 'ろ', 'わ', 'を', 'ん'
]

# キーマッピング（ひらがなとキーの対応）
key_mapping = {
    'あ': 'a', 'い': 'i', 'う': 'u', 'え': 'e', 'お': 'o',
    'か': 'ka', 'き': 'ki', 'く': 'ku', 'け': 'ke', 'こ': 'ko',
    'さ': 'sa', 'し': 'si', 'す': 'su', 'せ': 'se', 'そ': 'so',
    'た': 'ta', 'ち': 'ti', 'つ': 'tu', 'て': 'te', 'と': 'to',
    'な': 'na', 'に': 'ni', 'ぬ': 'nu', 'ね': 'ne', 'の': 'no',
    'は': 'ha', 'ひ': 'hi', 'ふ': 'hu', 'へ': 'he', 'ほ': 'ho',
    'ま': 'ma', 'み': 'mi', 'む': 'mu', 'め': 'me', 'も': 'mo',
    'や': 'ya', 'ゆ': 'yu', 'よ': 'yo',
    'ら': 'ra', 'り': 'ri', 'る': 'ru', 'れ': 're', 'ろ': 'ro',
    'わ': 'wa', 'を': 'wo', 'ん': 'nn'
}

# 敵の出現位置を5箇所に固定
def get_enemy_spawn_positions():
    # 画面を5等分する
    positions = []
    segment_width = (PLAYER_MAX_X - PLAYER_MIN_X) / 4  # 4分割で5箇所
    for i in range(5):
        positions.append(PLAYER_MIN_X + segment_width * i)
    return positions

# 敵の出現位置リスト
enemy_positions = get_enemy_spawn_positions()

# 敵を生成する関数
def create_enemy():
    char = random.choice(characters)
    
    # 既存の敵の位置を避けて新しい位置を選ぶ
    available_positions = enemy_positions.copy()
    for enemy in enemies:
        if enemy['y'] < 100:  # 上部にある敵のみ考慮
            if enemy['x'] in available_positions:
                available_positions.remove(enemy['x'])
    
    # 利用可能な位置がなければランダムに選ぶ
    if available_positions:
        x = random.choice(available_positions)
    else:
        x = random.choice(enemy_positions)
    
    return {'x': x, 'y': 0, 'char': char, 'width': 40, 'height': 40, 'typing': key_mapping[char], 'typed': '', 'color': RED}

# ゲーム開始時に初期化
def init_game():
    global player_x, enemies, score, level, game_over, game_clear, enemy_speed, bullets, last_enemy_time
    player_x = enemy_positions[2]  # 中央の位置に配置
    score = 0
    level = 1
    enemies = []
    bullets = []
    game_over = False
    game_clear = False
    enemy_speed = 1
    last_enemy_time = time.time()
    
    # 各位置に1つずつ敵を配置（重ならないように）
    for i, pos in enumerate(enemy_positions[:3]):  # 最初は3箇所だけに配置
        char = random.choice(characters)
        enemies.append({
            'x': pos, 
            'y': 0, 
            'char': char, 
            'width': 40, 
            'height': 40, 
            'typing': key_mapping[char], 
            'typed': '', 
            'color': RED
        })

# ゲームを初期化
player_x = 0  # 仮の初期値
enemies = []
bullets = []
score = 0
level = 1
game_over = False
game_clear = False
init_game()

# ゲームループ
clock = pygame.time.Clock()
last_enemy_time = time.time()

while True:
    # イベント処理
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
        
        # キー入力処理
        if event.type == KEYDOWN:
            if game_over or game_clear:
                if event.key == K_RETURN:
                    # リスタート
                    init_game()
            else:
                # 左右移動
                if event.key == K_LEFT:
                    # 現在位置のインデックスを見つける
                    current_index = -1
                    for i, pos in enumerate(enemy_positions):
                        if abs(player_x - pos) < 10:  # 現在位置に近い場合
                            current_index = i
                            break
                    
                    # 左に移動（インデックスを1つ減らす）
                    if current_index > 0:
                        player_x = enemy_positions[current_index - 1]
                    elif current_index == -1:  # 位置が見つからない場合
                        player_x -= player_speed * 5
                        closest_pos = min(enemy_positions, key=lambda pos: abs(pos - player_x))
                        player_x = closest_pos
                
                elif event.key == K_RIGHT:
                    # 現在位置のインデックスを見つける
                    current_index = -1
                    for i, pos in enumerate(enemy_positions):
                        if abs(player_x - pos) < 10:  # 現在位置に近い場合
                            current_index = i
                            break
                    
                    # 右に移動（インデックスを1つ増やす）
                    if current_index >= 0 and current_index < len(enemy_positions) - 1:
                        player_x = enemy_positions[current_index + 1]
                    elif current_index == -1:  # 位置が見つからない場合
                        player_x += player_speed * 5
                        closest_pos = min(enemy_positions, key=lambda pos: abs(pos - player_x))
                        player_x = closest_pos
                
                # 文字入力処理
                if event.unicode:
                    key_pressed = event.unicode.lower()
                    for enemy in enemies:
                        # 敵の真下にいるかチェック
                        if abs(enemy['x'] - player_x) <= hit_range:
                            next_char = enemy['typing'][len(enemy['typed'])] if len(enemy['typed']) < len(enemy['typing']) else None
                            if next_char == key_pressed:
                                enemy['typed'] += key_pressed
                                enemy['color'] = YELLOW  # 入力中は黄色に
                                
                                # 文字列が完全に入力されたら弾を発射
                                if enemy['typed'] == enemy['typing']:
                                    bullets.append({
                                        'x': player_x,
                                        'y': player_y,
                                        'target_x': enemy['x'],  # 敵のX座標に向けて発射
                                        'target_y': 0
                                    })
                                    enemy['color'] = GREEN  # 入力完了は緑に
    
    if not game_over and not game_clear:
        # プレイヤーの位置制限
        player_x = max(PLAYER_MIN_X, min(player_x, PLAYER_MAX_X))
        
        # 敵の移動
        for enemy in enemies[:]:
            enemy['y'] += enemy_speed
            
            # 敵が画面下に到達したらゲームオーバー
            if enemy['y'] > HEIGHT - 50:
                game_over = True
        
        # 弾の移動と当たり判定
        for bullet in bullets[:]:
            # 弾を真上に移動
            bullet['y'] -= bullet_speed
            
            # 弾が画面上部に到達したら削除
            if bullet['y'] < 0:
                bullets.remove(bullet)
                continue
            
            # 当たり判定
            for enemy in enemies[:]:
                if (abs(bullet['x'] - enemy['x']) < 20 and 
                    abs(bullet['y'] - enemy['y']) < 20 and 
                    enemy['typed'] == enemy['typing']):  # 入力が完了している敵のみ倒せる
                    enemies.remove(enemy)
                    if bullet in bullets:  # 念のためチェック
                        bullets.remove(bullet)
                    score += 1
                    if score >= target_score:
                        game_clear = True
                    break
        
        # 新しい敵の生成（時間ベース）
        current_time = time.time()
        if current_time - last_enemy_time > enemy_spawn_interval / level:  # レベルが上がるほど敵の出現頻度が上がる
            enemies.append(create_enemy())
            last_enemy_time = current_time
        
        # レベルアップ（10点ごと）
        if score > 0 and score % 10 == 0 and level < score // 10 + 1:
            level = score // 10 + 1
            enemy_speed = min(1 + level * 0.2, 4)  # 最大速度を制限
    
    # 描画
    screen.fill(BLACK)
    
    # プレイヤーの描画
    player_y = HEIGHT - 100  # プレイヤーのY座標を固定
    pygame.draw.polygon(screen, BLUE, [
        (player_x, player_y),
        (player_x - 20, player_y + 30),
        (player_x + 20, player_y + 30)
    ])
    
    # 敵の描画
    for enemy in enemies:
        pygame.draw.rect(screen, enemy['color'], (enemy['x'] - enemy['width']//2, enemy['y'], enemy['width'], enemy['height']))
        char_text = font.render(enemy['char'], True, WHITE)
        screen.blit(char_text, (enemy['x'] - char_text.get_width()//2, enemy['y'] + 5))
        
        # 入力中の文字を表示
        if enemy['typed']:
            typed_text = font.render(enemy['typed'], True, WHITE)
            screen.blit(typed_text, (enemy['x'] - typed_text.get_width()//2, enemy['y'] + 40))
        
        # 敵の真下に照準線を表示（プレイヤーが敵の下にいるかどうかの目安）
        if abs(enemy['x'] - player_x) <= hit_range:
            pygame.draw.line(screen, YELLOW, (enemy['x'], enemy['y'] + enemy['height']), (enemy['x'], player_y), 2)
            # 照準が合っていることを示す円を描画
            pygame.draw.circle(screen, YELLOW, (player_x, player_y - 20), 5, 1)
    
    # 弾の描画
    for bullet in bullets:
        pygame.draw.circle(screen, GREEN, (int(bullet['x']), int(bullet['y'])), 5)
    
    # スコアとレベルの表示
    score_text = font.render(f'Score: {score}/{target_score}  Level: {level}', True, WHITE)
    screen.blit(score_text, (10, 10))
    
    # ゲームオーバー表示
    if game_over:
        over_text = large_font.render('GAME OVER', True, RED)
        restart_text = font.render('Press ENTER to restart', True, WHITE)
        screen.blit(over_text, (WIDTH//2 - over_text.get_width()//2, HEIGHT//2 - over_text.get_height()//2))
        screen.blit(restart_text, (WIDTH//2 - restart_text.get_width()//2, HEIGHT//2 + 50))
    
    # ゲームクリア表示
    if game_clear:
        clear_text = large_font.render('GAME CLEAR!', True, GREEN)
        restart_text = font.render('Press ENTER to restart', True, WHITE)
        screen.blit(clear_text, (WIDTH//2 - clear_text.get_width()//2, HEIGHT//2 - clear_text.get_height()//2))
        screen.blit(restart_text, (WIDTH//2 - restart_text.get_width()//2, HEIGHT//2 + 50))
    
    pygame.display.flip()
    clock.tick(60)

```