🤖

ライトに Vibe Coding 挑戦 with Amazon Q Developer for CLI

に公開

はじめに

moritalous さんのこちらの企画に参加し、10 本目:小学生向けのタイピング練習を投稿しました。
https://zenn.dev/moritalous/scraps/d0f5f857058420

この時は、目的に近いアプリができるまで 2 ターンかかりましたが、結果を考えるとプロンプト次第では一発で作成できる可能性を感じています。そこで、実行ログや作成されたアプリのソースコードの記録も兼ねて、再度挑戦してみようと思います。

出来上がったもの

まずは結論ですが、こんな感じのアプリを一発の指示で作成することができました。

Let's Vibe Coding

ターミナルで Amazon Q Developer for CLI を起動する

ターミナルを開き、q chatコマンドを実行することで、Chat 機能の利用を開始できます。
通常の振る舞いとしては、対話の中で安全のための確認ステップが挟まりますが、今回はより Vibe 感を発揮したかったので、--accept-allオプションをつけています。

% q chat --accept-all
# 省略形
% q chat -a

アプリを作成してもらう

10 本目:小学生向けのタイピング練習の経験を踏まえて、今回のプロンプトはこちらです。

小学生低学年向けの、タイピング練習アプリを作ってください。ゲーム性があるといいです。ブラインドタッチができるようになってほしいです。
-   表示された日本語の問題に対して、ローマ字をタイプする。
-   問題は日本語で意味のある単語(例:りんご)や文章(例:今日は天気がいい)にする。
-   ゲームは途中で終了して、リトライできるようにする。

具体的な指示がない場合、「小学生低学年」というエッセンスをよしなに解釈して、ひらがな入力が前提となったり、ローマ字 1 文字ずつ入力となったりするので、箇条書きの部分を追加しています。
個人的には「ブラインドタッチができるようになってほしい」と、抽象的にでもざっくり目標を与えることは重要と感じています。

プロンプトを実行すると、以下のような感じで、バリバリ作成してくれます。
繰り返しですが、--accept-allオプションをつけているため、何も確認ステップは挟まずに、完成まで一気通貫で実行されます。

小学生低学年向けのタイピング練習アプリを作成しましょう。ブラインドタッチの習得を目的とし、ゲーム性を持たせたアプリを作ります。

HTML、CSS、JavaScriptを使って、シンプルで楽しいタイピングゲームを作成します。

まず、HTMLファイルを作成しましょう:


🛠️  Using tool: fs_write (trusted)
 ⋮
 ● Path: /workspace/zenn-docs/typing-game.html

Creating: /workspace/zenn-docs/typing-game.html

 ⋮
 ● Completed in 0.2s


「たのしいタイピングゲーム」を作成しました!以下の特徴があります:

## 主な機能
• 日本語の問題をローマ字でタイプする形式
• 4つのレベル構成で徐々に難しくなる
  • レベル1: 簡単な単語(動物など)
  • レベル2: 少し長い単語(果物など)
  • レベル3: 短い挨拶フレーズ
  • レベル4: 簡単な文章
• ゲーム性を高める要素
  • 60秒の制限時間
  • スコアシステム(レベルが上がるとポイントも増加)
  • レベルアップ機能(5問正解ごとにレベルアップ)
  • レベルアップでボーナス時間とポイント獲得
  • 進捗バーでレベルアップまでの状況を視覚化
• 画面上のキーボード表示(押したキーが光る)
• いつでもリトライ可能

## 使い方
1. /workspace/zenn-docs/typing-game.html をブラウザで開きます
2. 「スタート」ボタンを押してゲーム開始
3. 表示された日本語の単語をローマ字で入力
4. 正解するとポイント獲得、次の問題へ進む
5. 時間切れになるとゲーム終了、最終スコアが表示される
6. 「もういちどあそぶ」ボタンでリスタート可能

小学生低学年でも楽しめるよう、カラフルなデザインと分かりやすいインターフェースにしました。また、ブラインドタッチの練習になるよう、画面上のキーボードで押すべきキーが分かるようになって
います。

このゲームを通じて、楽しみながらローマ字入力とブラインドタッチの基礎を身につけることができます。

出来上がったソースコードも記念に貼っておきます。

ソースコード (typing-game.html)
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>たのしいタイピングゲーム</title>
        <style>
            body {
                font-family: "Arial Rounded MT Bold", "Arial", sans-serif;
                background-color: #f0f8ff;
                text-align: center;
                margin: 0;
                padding: 20px;
            }
            .container {
                max-width: 800px;
                margin: 0 auto;
                background-color: white;
                border-radius: 15px;
                padding: 20px;
                box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
            }
            h1 {
                color: #ff6b6b;
                font-size: 2.5em;
                margin-bottom: 10px;
            }
            .game-area {
                margin: 20px 0;
                padding: 15px;
                border-radius: 10px;
                background-color: #f9f9f9;
            }
            .question {
                font-size: 2em;
                margin: 20px 0;
                color: #333;
            }
            .romaji {
                font-size: 1.5em;
                color: #666;
                margin-bottom: 20px;
            }
            .input-area {
                margin: 20px 0;
            }
            input[type="text"] {
                padding: 10px 15px;
                font-size: 1.5em;
                border: 3px solid #4dabf7;
                border-radius: 10px;
                width: 80%;
                max-width: 500px;
                text-align: center;
            }
            .buttons {
                margin: 20px 0;
            }
            button {
                background-color: #4dabf7;
                color: white;
                border: none;
                padding: 10px 20px;
                font-size: 1.2em;
                border-radius: 10px;
                cursor: pointer;
                margin: 0 10px;
                transition: background-color 0.3s;
            }
            button:hover {
                background-color: #339af0;
            }
            .score-area {
                display: flex;
                justify-content: space-around;
                margin: 20px 0;
            }
            .score-box {
                background-color: #e9ecef;
                padding: 10px 20px;
                border-radius: 10px;
                min-width: 120px;
            }
            .score-label {
                font-size: 1em;
                color: #495057;
            }
            .score-value {
                font-size: 1.8em;
                color: #212529;
                font-weight: bold;
            }
            .result {
                font-size: 1.5em;
                margin: 10px 0;
                height: 30px;
            }
            .correct {
                color: #40c057;
            }
            .incorrect {
                color: #fa5252;
            }
            .keyboard {
                margin-top: 20px;
                display: flex;
                flex-direction: column;
                align-items: center;
            }
            .keyboard-row {
                display: flex;
                margin: 3px 0;
            }
            .key {
                width: 40px;
                height: 40px;
                margin: 2px;
                display: flex;
                align-items: center;
                justify-content: center;
                background-color: #e9ecef;
                border-radius: 5px;
                font-weight: bold;
                box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1);
            }
            .key.active {
                background-color: #ffd43b;
                transform: translateY(2px);
                box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);
            }
            .game-over {
                font-size: 2em;
                color: #ff6b6b;
                margin: 20px 0;
            }
            .hidden {
                display: none;
            }
            .progress-container {
                width: 100%;
                background-color: #e9ecef;
                border-radius: 10px;
                margin: 20px 0;
            }
            .progress-bar {
                height: 20px;
                background-color: #4dabf7;
                border-radius: 10px;
                width: 0%;
                transition: width 0.3s;
            }
            .level-indicator {
                margin: 10px 0;
                font-size: 1.2em;
                color: #495057;
            }
            .character-highlight {
                color: #ff6b6b;
                font-weight: bold;
            }
        </style>
    </head>
    <body>
        <div class="container">
            <h1>たのしいタイピングゲーム</h1>

            <div id="start-screen">
                <p>ひらがなの言葉をローマ字でタイプしよう!</p>
                <button id="start-button">スタート</button>
            </div>

            <div id="game-screen" class="hidden">
                <div class="level-indicator">
                    レベル: <span id="level">1</span>
                </div>

                <div class="progress-container">
                    <div id="progress-bar" class="progress-bar"></div>
                </div>

                <div class="score-area">
                    <div class="score-box">
                        <div class="score-label">スコア</div>
                        <div id="score" class="score-value">0</div>
                    </div>
                    <div class="score-box">
                        <div class="score-label">のこり時間</div>
                        <div id="timer" class="score-value">60</div>
                    </div>
                </div>

                <div class="game-area">
                    <div class="question" id="question">りんご</div>
                    <div class="romaji" id="romaji">ringo</div>
                    <div class="input-area">
                        <input
                            type="text"
                            id="user-input"
                            placeholder="ここにタイプしてね"
                            autocomplete="off"
                        />
                    </div>
                    <div class="result" id="result"></div>
                </div>

                <div class="keyboard">
                    <div class="keyboard-row">
                        <div class="key" data-key="q">Q</div>
                        <div class="key" data-key="w">W</div>
                        <div class="key" data-key="e">E</div>
                        <div class="key" data-key="r">R</div>
                        <div class="key" data-key="t">T</div>
                        <div class="key" data-key="y">Y</div>
                        <div class="key" data-key="u">U</div>
                        <div class="key" data-key="i">I</div>
                        <div class="key" data-key="o">O</div>
                        <div class="key" data-key="p">P</div>
                    </div>
                    <div class="keyboard-row">
                        <div class="key" data-key="a">A</div>
                        <div class="key" data-key="s">S</div>
                        <div class="key" data-key="d">D</div>
                        <div class="key" data-key="f">F</div>
                        <div class="key" data-key="g">G</div>
                        <div class="key" data-key="h">H</div>
                        <div class="key" data-key="j">J</div>
                        <div class="key" data-key="k">K</div>
                        <div class="key" data-key="l">L</div>
                    </div>
                    <div class="keyboard-row">
                        <div class="key" data-key="z">Z</div>
                        <div class="key" data-key="x">X</div>
                        <div class="key" data-key="c">C</div>
                        <div class="key" data-key="v">V</div>
                        <div class="key" data-key="b">B</div>
                        <div class="key" data-key="n">N</div>
                        <div class="key" data-key="m">M</div>
                    </div>
                </div>

                <div class="buttons">
                    <button id="retry-button">やりなおす</button>
                </div>
            </div>

            <div id="game-over-screen" class="hidden">
                <div class="game-over">ゲームオーバー</div>
                <div class="score-box">
                    <div class="score-label">さいしゅうスコア</div>
                    <div id="final-score" class="score-value">0</div>
                </div>
                <button id="play-again-button">もういちどあそぶ</button>
            </div>
        </div>

        <script>
            // 問題のリスト(日本語とローマ字のペア)
            const wordList = [
                // レベル1: 簡単な単語
                { japanese: "りんご", romaji: "ringo" },
                { japanese: "ねこ", romaji: "neko" },
                { japanese: "いぬ", romaji: "inu" },
                { japanese: "うさぎ", romaji: "usagi" },
                { japanese: "ぞう", romaji: "zou" },
                { japanese: "くま", romaji: "kuma" },
                { japanese: "さる", romaji: "saru" },
                { japanese: "とり", romaji: "tori" },
                { japanese: "きりん", romaji: "kirin" },
                { japanese: "ぱんだ", romaji: "panda" },

                // レベル2: 少し長い単語
                { japanese: "ばなな", romaji: "banana" },
                { japanese: "みかん", romaji: "mikan" },
                { japanese: "いちご", romaji: "ichigo" },
                { japanese: "ぶどう", romaji: "budou" },
                { japanese: "めろん", romaji: "meron" },
                { japanese: "もも", romaji: "momo" },
                { japanese: "なし", romaji: "nashi" },
                { japanese: "すいか", romaji: "suika" },
                { japanese: "れもん", romaji: "remon" },
                { japanese: "きうい", romaji: "kiui" },

                // レベル3: 短い文
                { japanese: "おはよう", romaji: "ohayou" },
                { japanese: "こんにちは", romaji: "konnichiwa" },
                { japanese: "さようなら", romaji: "sayounara" },
                { japanese: "ありがとう", romaji: "arigatou" },
                { japanese: "がんばって", romaji: "ganbatte" },
                { japanese: "おやすみ", romaji: "oyasumi" },
                { japanese: "いただきます", romaji: "itadakimasu" },
                { japanese: "ごちそうさま", romaji: "gochisousama" },
                { japanese: "すみません", romaji: "sumimasen" },
                { japanese: "またあした", romaji: "mataashita" },

                // レベル4: 簡単な文章
                { japanese: "あおいそら", romaji: "aoisora" },
                { japanese: "たのしいゲーム", romaji: "tanoshiige-mu" },
                { japanese: "おいしいたべもの", romaji: "oishiitabemono" },
                { japanese: "あかいはな", romaji: "akaihana" },
                { japanese: "しろいくも", romaji: "shiroikumo" },
                { japanese: "あついなつ", romaji: "atsuinatsu" },
                { japanese: "さむいふゆ", romaji: "samuifuyu" },
                { japanese: "たかいやま", romaji: "takaiyama" },
                { japanese: "ふかいうみ", romaji: "fukaiumi" },
                { japanese: "ながいかわ", romaji: "nagaikawa" },
            ];

            // ゲーム変数
            let currentWord;
            let score = 0;
            let timer = 60;
            let gameInterval;
            let level = 1;
            let wordsPerLevel = 5;
            let wordsCompleted = 0;

            // DOM要素
            const startScreen = document.getElementById("start-screen");
            const gameScreen = document.getElementById("game-screen");
            const gameOverScreen = document.getElementById("game-over-screen");
            const questionElement = document.getElementById("question");
            const romajiElement = document.getElementById("romaji");
            const userInput = document.getElementById("user-input");
            const resultElement = document.getElementById("result");
            const scoreElement = document.getElementById("score");
            const timerElement = document.getElementById("timer");
            const finalScoreElement = document.getElementById("final-score");
            const levelElement = document.getElementById("level");
            const progressBar = document.getElementById("progress-bar");
            const startButton = document.getElementById("start-button");
            const retryButton = document.getElementById("retry-button");
            const playAgainButton =
                document.getElementById("play-again-button");
            const keys = document.querySelectorAll(".key");

            // キーボードイベント
            document.addEventListener("keydown", function (event) {
                const key = event.key.toLowerCase();
                const keyElement = document.querySelector(
                    `.key[data-key="${key}"]`
                );
                if (keyElement) {
                    keyElement.classList.add("active");
                }
            });

            document.addEventListener("keyup", function (event) {
                const key = event.key.toLowerCase();
                const keyElement = document.querySelector(
                    `.key[data-key="${key}"]`
                );
                if (keyElement) {
                    keyElement.classList.remove("active");
                }
            });

            // ゲーム開始
            startButton.addEventListener("click", startGame);
            retryButton.addEventListener("click", resetGame);
            playAgainButton.addEventListener("click", resetGame);

            // ユーザー入力のチェック
            userInput.addEventListener("input", checkInput);

            // ゲーム開始関数
            function startGame() {
                startScreen.classList.add("hidden");
                gameScreen.classList.remove("hidden");
                gameOverScreen.classList.add("hidden");

                resetGame();
                nextWord();

                // タイマー開始
                gameInterval = setInterval(updateTimer, 1000);

                // 入力フィールドにフォーカス
                userInput.focus();
            }

            // タイマー更新
            function updateTimer() {
                timer--;
                timerElement.textContent = timer;

                if (timer <= 0) {
                    endGame();
                }
            }

            // 次の単語を表示
            function nextWord() {
                // レベルに応じた単語を選択
                const levelWords = wordList.slice((level - 1) * 10, level * 10);
                currentWord =
                    levelWords[Math.floor(Math.random() * levelWords.length)];

                questionElement.textContent = currentWord.japanese;
                romajiElement.textContent = currentWord.romaji;
                userInput.value = "";
                resultElement.textContent = "";
                userInput.focus();
            }

            // 入力チェック
            function checkInput() {
                const input = userInput.value.toLowerCase();
                const correct = currentWord.romaji;

                if (input === correct) {
                    resultElement.textContent = "せいかい!";
                    resultElement.className = "result correct";
                    score += 10 * level; // レベルに応じたスコア加算
                    scoreElement.textContent = score;

                    wordsCompleted++;
                    updateProgressBar();

                    // レベルアップチェック
                    if (wordsCompleted >= wordsPerLevel) {
                        levelUp();
                    }

                    setTimeout(() => {
                        nextWord();
                    }, 500);
                }
            }

            // プログレスバー更新
            function updateProgressBar() {
                const progress = (wordsCompleted / wordsPerLevel) * 100;
                progressBar.style.width = `${progress}%`;
            }

            // レベルアップ
            function levelUp() {
                if (level < 4) {
                    level++;
                    levelElement.textContent = level;
                    wordsCompleted = 0;
                    updateProgressBar();

                    // レベルアップボーナス
                    score += 50;
                    scoreElement.textContent = score;

                    // タイマー追加
                    timer += 15;
                    timerElement.textContent = timer;
                }
            }

            // ゲーム終了
            function endGame() {
                clearInterval(gameInterval);
                gameScreen.classList.add("hidden");
                gameOverScreen.classList.remove("hidden");
                finalScoreElement.textContent = score;
            }

            // ゲームリセット
            function resetGame() {
                score = 0;
                timer = 60;
                level = 1;
                wordsCompleted = 0;

                scoreElement.textContent = score;
                timerElement.textContent = timer;
                levelElement.textContent = level;
                updateProgressBar();

                clearInterval(gameInterval);
                gameInterval = setInterval(updateTimer, 1000);

                nextWord();
                userInput.focus();

                gameScreen.classList.remove("hidden");
                gameOverScreen.classList.add("hidden");
            }
        </script>
    </body>
</html>

さいごに

一発で出来るとは思ってましたが、これを自力で作成しようと思う時間を考えると、それなりに時間がかかり、改めて AI コーディングツールを使うことで開発時間を短縮できると実感しました。
Amazon Q Developer CLI の Chat の機能は、自然言語での対話を通じて便利に利用できる可能性を感じつつも、実際の開発現場でチームとしてプロジェクトとして活用するには、まだまだ自分の知識や経験が足りないため、AI コーディングツールに慣れ、使いこなしていく必要性を感じています。
そのためにも、まずは触って理解を深める必要があるので、今回のような企画はそのきっかけとしてありがたかったですし、まだ経験していない人は、気軽にチャレンジしてみてはいかがでしょうか。

Vibe Coding 100 本達成しますように!自分もまた投稿しようと思います!

ではでは。

GitHubで編集を提案

Discussion