Kiro

AWS発のAIエージェントIDE「Kiro」を使用した仕様駆動開発を触ってみた。その➁

前回はこちら

https://ex-ture.com/blog/2025/07/18/aws%e7%99%ba%e3%81%aeai%e3%82%a8%e3%83%bc%e3%82%b8%e3%82%a7%e3%83%b3%e3%83%88ide%e3%80%8ckiro%e3%80%8d%e3%82%92%e4%bd%bf%e7%94%a8%e3%81%97%e3%81%9f%e4%bb%95%e6%a7%98%e9%a7%86%e5%8b%95%e9%96%8b

続きから

前回作成されたTASKをよりシンプルな形にするように依頼をかけてTASKを書き直してもらいました。

# 実装計画

- [x] 1. 基本的なHTMLとCanvasの設定
  - index.htmlファイルを作成(Canvas要素とゲームタイトル)
  - 基本的なCSSスタイリングを追加(黒背景、中央配置)
  - JavaScriptファイルの基本構造を作成
  - _要件: 4.2, 5.1_

- [ ] 2. ゲームループとプレイヤーの実装
  - Gameクラスを実装(初期化、ゲームループ、レンダリング)
  - Playerクラスを実装(位置、描画、左右移動)
  - 矢印キーでの左右移動機能を実装
  - requestAnimationFrameを使用したゲームループを実装
  - _要件: 1.1, 4.3_

- [ ] 3. 弾丸システムの 実装
  - Bulletクラスを実装(位置、上向き移動、描画)
  - スペースキーでの弾丸発射機能を実装
  - 複数弾丸の管理システムを実装(配列での管理)
  - 画面外に出た弾丸の削除処理を実装
  - _要件: 1.2_

- [ ] 4. 敵システムの実装
  - Enemyクラスを実装(位置、下向き移動、描画)
  - 敵の定期的な出現システムを実装(画面上部からランダム位置)
  - 敵の配列管理システムを実装
  - 画面下部に到達した敵の削除処理を実装
  - _要件: 3.1, 3.2, 3.3_

- [ ] 5. 衝突判定システムの実装
  - 矩形衝突判定関数を実装
  - 弾丸と敵の衝突判定を実装(敵の破壊処理)
  - プレイヤーと敵の衝突判定を実装(ゲームオーバー処理)
  - 衝突時のオブジェクト削除処理を実装
  - _要件: 1.3, 1.4_

- [ ] 6. スコアシステムとゲームオーバーの実装
  - スコア管理機能を実装(敵撃破時に10ポイント加算)
  - 画面上のスコア表示機能を実装
  - ゲームオーバー画面の実装(最終スコア表示)
  - スペースキーでのゲーム再開機能を実装
  - _要件: 2.1, 2.2, 2.3, 5.2, 5.3_

- [ ] 7. Docker環境の設定
  - Dockerfileを作成(Nginxベースイメージ使用)
  - nginx.confファイルを作成(ポート8080設定)
  - 静的ファイル配信設定を実装
  - Docker buildとrunのテストを実行
  - _要件: 6.1, 6.2, 6.3, 6.4_

- [ ] 8. 統合テストと最終調整
  - 全機能の動作確認テストを実行
  - Docker環境での動作確認を実行
  - ゲームバランスの調整(敵の出現頻度、移動速度)
  - コードの最適化とクリーンアップ
  - _要件: 4.3_

さっさとキャラクターを動かすところまでやりたいのでどんどん実行していきます。

5まで実装したところ(再度サーバーの実行状態でフリーズしてしまったので手動で切った)

ちゃんと動くし、弾も打てるようになりました。

敵に当たるとライフが減って0になるとゲームオーバーになるところまで実装できています。

テストコードを見てみるとこのような感じで出来ていました。

作成された基本のクラスの機能について網羅的にテストしつつ、モック環境の構築や、仕様で定義されている部分やゲームバランスまでカバーされているのが驚きです。

// 統合テスト - 全機能の動作確認
console.log('=== 統合テスト開始 ===');

// テスト結果を記録する配列
const testResults = [];

function addTestResult(testName, passed, message = '') {
    testResults.push({
        name: testName,
        passed: passed,
        message: message
    });
    console.log(`${passed ? '✓' : '✗'} ${testName}${message ? ': ' + message : ''}`);
}

// 1. 基本クラスの動作確認
console.log('\n--- 基本クラステスト ---');

// GameObject基底クラステスト
try {
    const obj = new GameObject(10, 20, 30, 40);
    addTestResult('GameObject作成', 
        obj.x === 10 && obj.y === 20 && obj.width === 30 && obj.height === 40);
    
    // 衝突判定テスト
    const obj2 = new GameObject(25, 35, 20, 20);
    addTestResult('衝突判定(重複あり)', obj.checkCollision(obj2));
    
    const obj3 = new GameObject(100, 100, 10, 10);
    addTestResult('衝突判定(重複なし)', !obj.checkCollision(obj3));
    
} catch (error) {
    addTestResult('GameObject基本機能', false, error.message);
}

// Playerクラステスト
try {
    const player = new Player(400, 550);
    addTestResult('Player作成', 
        player.x === 400 && player.y === 550 && player.width === 40 && player.height === 30);
    
    // 移動テスト
    player.moveLeft();
    player.update(16); // 16ms = 60fps
    addTestResult('Player左移動', player.x < 400);
    
    // 射撃テスト
    const bullet = player.shoot();
    addTestResult('Player射撃', bullet instanceof Bullet);
    
    // 射撃クールダウンテスト
    const bullet2 = player.shoot();
    addTestResult('射撃クールダウン', bullet2 === null);
    
} catch (error) {
    addTestResult('Playerクラス', false, error.message);
}

// Bulletクラステスト
try {
    const bullet = new Bullet(100, 200);
    const initialY = bullet.y;
    bullet.update(16);
    addTestResult('Bullet上向き移動', bullet.y < initialY);
    
    // 画面外判定テスト
    bullet.y = -50;
    bullet.update(16);
    addTestResult('Bullet画面外削除', !bullet.active);
    
} catch (error) {
    addTestResult('Bulletクラス', false, error.message);
}

// Enemyクラステスト
try {
    const enemy = new Enemy(150, 50);
    const initialY = enemy.y;
    enemy.update(16);
    addTestResult('Enemy下向き移動', enemy.y > initialY);
    addTestResult('Enemyスコア値', enemy.scoreValue === 10);
    
    // 画面外判定テスト
    enemy.y = 650;
    enemy.update(16);
    addTestResult('Enemy画面外削除', !enemy.active);
    
} catch (error) {
    addTestResult('Enemyクラス', false, error.message);
}

// Effectクラステスト
try {
    const explosion = new Effect(200, 300, 'explosion');
    addTestResult('爆発エフェクト作成', explosion.particles.length > 0);
    
    const hitEffect = new Effect(250, 350, 'hit');
    addTestResult('被弾エフェクト作成', hitEffect.particles.length > 0);
    
} catch (error) {
    addTestResult('Effectクラス', false, error.message);
}

// 2. ゲームロジックテスト
console.log('\n--- ゲームロジックテスト ---');

try {
    // 仮想的なゲーム環境を作成
    const mockCanvas = {
        width: 800,
        height: 600,
        getContext: () => ({
            fillStyle: '',
            fillRect: () => {},
            strokeStyle: '',
            strokeRect: () => {},
            beginPath: () => {},
            moveTo: () => {},
            lineTo: () => {},
            closePath: () => {},
            fill: () => {},
            arc: () => {},
            save: () => {},
            restore: () => {},
            translate: () => {},
            rotate: () => {}
        })
    };
    
    // DOM要素のモック
    const mockElement = {
        classList: {
            add: () => {},
            remove: () => {},
            contains: () => false
        },
        textContent: '',
        setAttribute: () => {},
        focus: () => {}
    };
    
    // グローバルオブジェクトのモック
    global.document = {
        getElementById: () => mockElement,
        addEventListener: () => {}
    };
    global.requestAnimationFrame = (callback) => setTimeout(callback, 16);
    
    // ゲーム作成テスト(初期化のみ)
    addTestResult('ゲーム初期化準備', true, 'モック環境準備完了');
    
} catch (error) {
    addTestResult('ゲームロジック', false, error.message);
}

// 3. 衝突判定の詳細テスト
console.log('\n--- 衝突判定詳細テスト ---');

try {
    // 弾丸と敵の衝突
    const bullet = new Bullet(100, 100);
    const enemy = new Enemy(95, 105);
    addTestResult('弾丸-敵衝突判定', bullet.checkCollision(enemy));
    
    // プレイヤーと敵の衝突
    const player = new Player(200, 200);
    const enemy2 = new Enemy(210, 210);
    addTestResult('プレイヤー-敵衝突判定', player.checkCollision(enemy2));
    
    // 非衝突ケース
    const bullet2 = new Bullet(50, 50);
    const enemy3 = new Enemy(200, 200);
    addTestResult('非衝突判定', !bullet2.checkCollision(enemy3));
    
} catch (error) {
    addTestResult('衝突判定詳細', false, error.message);
}

// 4. ゲームバランステスト
console.log('\n--- ゲームバランステスト ---');

try {
    // 敵の移動速度チェック
    const enemy = new Enemy(100, 100);
    addTestResult('敵移動速度', enemy.speed === 100, `速度: ${enemy.speed}px/s`);
    
    // 弾丸の移動速度チェック
    const bullet = new Bullet(100, 100);
    addTestResult('弾丸移動速度', Math.abs(bullet.velocityY) === 400, `速度: ${Math.abs(bullet.velocityY)}px/s`);
    
    // プレイヤーの移動速度チェック
    const player = new Player(100, 100);
    addTestResult('プレイヤー移動速度', player.speed === 5, `速度: ${player.speed}px/frame`);
    
    // 射撃クールダウンチェック
    addTestResult('射撃クールダウン時間', player.shootCooldownTime === 200, `クールダウン: ${player.shootCooldownTime}ms`);
    
} catch (error) {
    addTestResult('ゲームバランス', false, error.message);
}

// 5. パフォーマンステスト
console.log('\n--- パフォーマンステスト ---');

try {
    // 大量オブジェクト作成テスト
    const startTime = performance.now();
    const objects = [];
    
    for (let i = 0; i < 100; i++) {
        objects.push(new Enemy(Math.random() * 800, Math.random() * 600));
        objects.push(new Bullet(Math.random() * 800, Math.random() * 600));
    }
    
    // 全オブジェクトの更新
    for (const obj of objects) {
        obj.update(16);
    }
    
    const endTime = performance.now();
    const duration = endTime - startTime;
    
    addTestResult('大量オブジェクト処理', duration < 50, `処理時間: ${duration.toFixed(2)}ms`);
    
} catch (error) {
    addTestResult('パフォーマンス', false, error.message);
}

// テスト結果の集計
console.log('\n=== テスト結果集計 ===');
const totalTests = testResults.length;
const passedTests = testResults.filter(result => result.passed).length;
const failedTests = totalTests - passedTests;

console.log(`総テスト数: ${totalTests}`);
console.log(`成功: ${passedTests}`);
console.log(`失敗: ${failedTests}`);
console.log(`成功率: ${((passedTests / totalTests) * 100).toFixed(1)}%`);

if (failedTests > 0) {
    console.log('\n--- 失敗したテスト ---');
    testResults.filter(result => !result.passed).forEach(result => {
        console.log(`✗ ${result.name}: ${result.message}`);
    });
}

console.log('\n=== 統合テスト完了 ===');

// テスト結果をエクスポート
if (typeof module !== 'undefined' && module.exports) {
    module.exports = { testResults, totalTests, passedTests, failedTests };
}

それでは、実際に触ってみたフィードバックを反映させていきます。エフェクトが寂しいのと連射機能が欲しいので仕様として追加を依頼してみました。

敵の撃破エフェクトの追加(スクリーンショットを取るのに苦労しました)

連射機能の追加

7番のDockerビルド部分で動かなくなってしまったのですが、概ね依頼通り作成することができました。

HTMLを使用した開発はあまり得意ではないのですが、これだけの指示でそれらしいものが出来るのであれば個人開発などで、どんどん使っていきたい気がしますね。

まとめ

従来のバイブコーディングと比べて仕様の定義⇒設計⇒実装という流れを汲んでいることで処理を分けて実施できる点が非常に便利でした。

ただし、従来のバイブコーディングに追加された課題としてあるのは追加で依頼した際に使用の定義からし直しが発生するため、手戻りが大きいことです。人間が実施するソフトバンク開発にとても近いような問題が起きている気がします。とはいえ、テストの作成・実施もできる点は非常に便利で、仕様定義開発が今後のAIエージェントによるソフトウェア開発でも中心的な役割を果たすかもしれないと思いました。

AWS発のAIエージェントIDE「Kiro」を使用した仕様駆動開発を触ってみた。前のページ

dbt Projects on SnowflakeをTASKを実行してみた次のページ

ピックアップ記事

  1. 最速で理解したい人のためのIT用語集

関連記事

カテゴリ
最近の記事
  1. Adobe WebSDK FPIDでECIDの復元を検証
  2. dbt Projects on Snowflakeで作成した…
  3. Dataformでtype:’increment…
  4. dbt Projects on SnowflakeをTASK…
  5. AWS発のAIエージェントIDE「Kiro」を使用した仕様駆…
  1. Google Apps Script(GAS)

    GASを利用してWebスクレイピングをやってみよう
  2. Google Cloud Platform

    Google Compute EngineのUbuntu VMにスワップ領域を作…
  3. Snowflake

    SnowflakeのHybrid Tableのマニュアルを読み解く
  4. 海外カンファレンス

    Adobe Summit 1日目
  5. Adobe Analytics

    続・SafariのITP2.x対策として別サーバー使ってクッキーを永続化する
PAGE TOP