Pokédexプロンプト + Windsurf + Claude 3.7 Sonnet でポケモン図鑑を作ってみた

AIツール

はじめに

AI技術の進化とともに、開発の方法も劇的に変わりつつあります。今回は最新のAIモデル「Claude 3.7 Sonnet」とWindsurfというAIエディタを使って、ポケモン図鑑(Pokédex)アプリを開発した体験を共有します。コードを書く手間を大幅に減らしながら、機能的で魅力的なアプリケーションを短時間で作成できることを示します。

Windsurfとは?

Windsurfは単なるAIコード補完ツールではなく、「AIフロー」という新しいパラダイムを実現した最初の「エージェンティックIDE」です。従来のAI利用パラダイムには大きく分けて2つありました:

  1. コパイロット: 開発者との協力を重視し、人間が常にループ内にいる。ただし、短いスコープのタスクに限定される。
  2. エージェント: AIが独立して反復作業を行い、より大きなタスクを完了できる。ただし、協力的な側面が失われる。

Windsurfはこの両方の長所を組み合わせています。AIが協力的かつ独立的であることを可能にし、開発者とAIが真に「マインドメルド(心を一つにする)」できるプラットフォームです。魔法のように感じられるAIとの協働体験を提供します。

今回のプロジェクト

今回は、Windsurfエディタ上でClaude 3.7 Sonnetを利用して、以下の要件を持つポケモン図鑑アプリを開発しました:

  • オープンソースのポケモンスプライトを使用
  • 第2世代までのポケモンを表示
  • アニメーション付きスプライトの使用
  • クリックでポケモン詳細表示機能
  • 古典的なポケデックスのようなインターフェース
  • 日本語のUI

使用したプロンプトと実際のやり取り

初期プロンプト

最初のプロンプトは非常にシンプルでしたが、要件を明確に伝えることを意識しました:

I want an html app that does the following: 
- Reference open source pokemon sprites
- Create a pokedex interface
- Make each pokemon viewable
- List each pokemon (up to 2nd generation)
- Click any list item to view pokemon
- Use the animated sprites
- Center the pokemon in the middle of the view
- Make the interface like an oldschool pokedex
- UI in japanease

Claudeの思考プロセス

このプロンプトに対し、Claudeは日本語で思考プロセスを共有してくれました:

この要求は、ポケモン図鑑(ポケデックス)のようなHTMLアプリを作成するものです。第2世代までのポケモンをリスト表示し、
アニメーション付きスプライトを使用して、クリックすると詳細が見られるインターフェースを作成する必要があります。
また、UIは日本語で、古いスタイルのポケデックスのようなデザインにする必要があります。

まず、プロジェクトの構造を考えてみます:
1. HTML/CSS/JSファイル
2. ポケモンのデータ(可能であればJSONで)
3. 必要な画像・アセット

最初に、ファイル構造を確認して、必要なファイルを作成していきましょう。

開発プロセスの詳細

1. プロジェクト構造の作成

> mkdir -p assets/images
> mkdir -p css js

Claudeはまず基本的なディレクトリ構造を作成し、次に必要なファイルを順番に作成していきました。

2. ファイルの作成

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ポケモン図鑑</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <div class="pokedex">
        <div class="pokedex-top">
            <div class="camera-lens"></div>
            <div class="small-light red"></div>
            <div class="small-light yellow"></div>
            <div class="small-light green"></div>
        </div>
        <div class="pokedex-screen">
            <div class="screen-header">
                <input type="text" id="search-box" placeholder="名前またはIDで検索">
            </div>
            <div class="screen-container">
                <div class="pokemon-list" id="pokemon-list">
                    <!-- ポケモンリストはJSで動的に生成 -->
                </div>
                <div class="pokemon-view" id="pokemon-view">
                    <!-- 選択されたポケモンの詳細表示エリア -->
                    <div class="pokemon-details">
                        <h2 id="pokemon-name">ポケモンを選択してください</h2>
                        <div id="pokemon-sprite"></div>
                        <div id="pokemon-info"></div>
                    </div>
                </div>
            </div>
        </div>
        <div class="pokedex-buttons">
            <div class="dpad">
                <div class="dpad-up"></div>
                <div class="dpad-right"></div>
                <div class="dpad-down"></div>
                <div class="dpad-left"></div>
                <div class="dpad-center"></div>
            </div>
            <div class="red-button"></div>
            <div class="blue-button"></div>
        </div>
    </div>
    <script src="js/pokedex.js"></script>
</body>
</html>

style.css(一部抜粋)

:root {
    --pokedex-red: #FF0000;
    --pokedex-dark-red: #CC0000;
    --screen-green: #3ED67F;
    --screen-dark-green: #2CA86A;
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: 'Helvetica', 'Arial', sans-serif;
}

body {
    background-color: #f0f0f0;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    padding: 20px;
}

.pokedex {
    width: 100%;
    max-width: 800px;
    background-color: var(--pokedex-red);
    border-radius: 15px;
    padding: 20px;
    box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
}

/* ポケデックスの上部(カメラやライト) */
.pokedex-top {
    display: flex;
    align-items: center;
    margin-bottom: 20px;
}

.camera-lens {
    width: 80px;
    height: 80px;
    background: radial-gradient(circle, #fff, #88A3FF);
    border-radius: 50%;
    border: 10px solid white;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.5) inset;
}

/* 他のスタイル定義は省略 */

pokedex.js(一部抜粋)

// ポケモン図鑑アプリケーション
document.addEventListener('DOMContentLoaded', () => {
    const pokemonList = document.getElementById('pokemon-list');
    const pokemonView = document.getElementById('pokemon-view');
    const pokemonName = document.getElementById('pokemon-name');
    const pokemonSprite = document.getElementById('pokemon-sprite');
    const pokemonInfo = document.getElementById('pokemon-info');
    const searchBox = document.getElementById('search-box');

    // タイプの日本語変換マップ
    const typeTranslations = {
        'normal': 'ノーマル',
        'fire': 'ほのお',
        'water': 'みず',
        'electric': 'でんき',
        'grass': 'くさ',
        'ice': 'こおり',
        'fighting': 'かくとう',
        'poison': 'どく',
        'ground': 'じめん',
        'flying': 'ひこう',
        'psychic': 'エスパー',
        'bug': 'むし',
        'rock': 'いわ',
        'ghost': 'ゴースト',
        'dragon': 'ドラゴン',
        'dark': 'あく',
        'steel': 'はがね',
        'fairy': 'フェアリー'
    };

    // 第二世代までのポケモン数(251匹)
    const POKEMON_COUNT = 251;

    // ポケモンデータのキャッシュ
    const pokemonCache = {};

    // すべてのポケモンを取得して表示
    async function fetchAllPokemon() {
        for (let i = 1; i <= POKEMON_COUNT; i++) {
            await fetchPokemonBasic(i);
        }
    }

    // 基本的なポケモン情報を取得
    async function fetchPokemonBasic(id) {
        try {
            // すでにキャッシュにあるかチェック
            if (pokemonCache[id]) {
                renderPokemonListItem(pokemonCache[id]);
                return;
            }

            const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
            const data = await response.json();

            // 日本語名を取得
            const speciesResponse = await fetch(data.species.url);
            const speciesData = await speciesResponse.json();

            // 日本語の名前を検索
            const japaneseName = speciesData.names.find(name => name.language.name === 'ja')?.name || data.name;

            // ポケモンデータを整形
            const pokemon = {
                id: data.id,
                name: japaneseName,
                types: data.types.map(type => type.type.name),
                sprite: data.sprites.versions['generation-v']['black-white'].animated.front_default || data.sprites.front_default,
                height: data.height / 10, // メートル単位に変換
                weight: data.weight / 10, // キログラム単位に変換
                abilities: data.abilities.map(ability => ability.ability.name),
                stats: data.stats.map(stat => ({
                    name: stat.stat.name,
                    value: stat.base_stat
                }))
            };

            // キャッシュに保存
            pokemonCache[id] = pokemon;

            // リストアイテムをレンダリング
            renderPokemonListItem(pokemon);

        } catch (error) {
            console.error(`ポケモンID ${id} の取得中にエラーが発生しました:`, error);
        }
    }

    // 続きのコードは省略

3. 実行と動作確認

> python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
127.0.0.1 - - [27/Feb/2025 05:54:50] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [27/Feb/2025 05:54:51] "GET /css/style.css HTTP/1.1" 200 -
127.0.0.1 - - [27/Feb/2025 05:54:51] "GET /js/pokedex.js HTTP/1.1" 200 -

デプロイプロセスの詳細

1. GitHubリポジトリの作成と初期設定

まず、Gitリポジトリを初期化し、コードをコミットしました:

> git init
Initialized empty Git repository in /home/maki/prj/pokedex-html-3-7/.git/

> git add .

> git commit -m "🎮 feat: ポケモン図鑑アプリの初回リリース"
[master (root-commit) 34263e7] 🎮 feat: ポケモン図鑑アプリの初回リリース
 6 files changed, 732 insertions(+)
 create mode 100644 CONTRIBUTING.md
 create mode 100644 README.md
 create mode 100644 assets/header.svg
 create mode 100644 css/style.css
 create mode 100644 index.html
 create mode 100644 js/pokedex.js

> git branch -M main

次に、GitHubリポジトリを作成:

> gh repo create pokemon-nostalgic-pokedex-jp --public --description "懐かしの雰囲気を再現した第二世代までのポケモン図鑑アプリ(日本語UI)" --source=. --push
✓ Created repository Sunwood-ai-labs/pokemon-nostalgic-pokedex-jp on GitHub
  https://github.com/Sunwood-ai-labs/pokemon-nostalgic-pokedex-jp
✓ Added remote https://github.com/Sunwood-ai-labs/pokemon-nostalgic-pokedex-jp.git
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 16 threads
Compressing objects: 100% (8/8), done.
Writing objects: 100% (11/11), 8.27 KiB | 8.27 MiB/s, done.
Total 11 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To https://github.com/Sunwood-ai-labs/pokemon-nostalgic-pokedex-jp.git
 * [new branch]      HEAD -> main
branch 'main' set up to track 'origin/main'.
✓ Pushed commits to https://github.com/Sunwood-ai-labs/pokemon-nostalgic-pokedex-jp.git

GitHub Pagesを有効化:

> gh api --method POST repos/Sunwood-ai-labs/pokemon-nostalgic-pokedex-jp/pages --input - << EOF
{
  "source": {
    "branch": "main",
    "path": "/"
  }
}
EOF

{
  "url": "https://api.github.com/repos/Sunwood-ai-labs/pokemon-nostalgic-pokedex-jp/pages",
  "status": null,
  "cname": null,
  "custom_404": false,
  "html_url": "https://sunwood-ai-labs.github.io/pokemon-nostalgic-pokedex-jp/",
  "build_type": "legacy",
  "source": {
    "branch": "main",
    "path": "/"
  },
  "public": true,
  "protected_domain_state": null,
  "pending_domain_unverified_at": null,
  "https_enforced": true
}

2. Hugging Faceへのデプロイ準備

まず、.gitignoreファイルを作成して仮想環境を除外:

> cat .gitignore
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
ENV/
env/
env.bak/
venv.bak/

# 環境変数
.env

# その他
.DS_Store
node_modules/

次に、Hugging Face用のデプロイスクリプトを作成:

# deploy_to_hf.py
import os
import argparse
from huggingface_hub import HfApi, create_repo
from dotenv import load_dotenv
import glob

# .envファイルから環境変数を読み込む
load_dotenv()

# 環境変数からトークンを取得
hf_token = os.getenv("HF_TOKEN")

# オーナー名
OWNER_NAME = "MakiAi"
REPO_NAME = "pokemon-nostalgic-pokedex"

def create_hf_repo():
    """Hugging Faceにリポジトリを作成"""
    try:
        api = HfApi()
        repo_url = create_repo(
            repo_id=f"{OWNER_NAME}/{REPO_NAME}",
            token=hf_token,
            repo_type="space",
            space_sdk="static",
            exist_ok=True
        )
        print(f"リポジトリ {OWNER_NAME}/{REPO_NAME} が作成されました(または既に存在します)。")
        return repo_url
    except Exception as e:
        print(f"リポジトリの作成中にエラーが発生しました: {e}")
        return None

def upload_files():
    """ファイルをHugging Faceにアップロード"""
    try:
        api = HfApi()

        # README-HF.mdをREADME.mdとして使用
        if os.path.exists("README-HF.md"):
            api.upload_file(
                path_or_fileobj="README-HF.md",
                path_in_repo="README.md",
                repo_id=f"{OWNER_NAME}/{REPO_NAME}",
                token=hf_token
            )

        # 他のすべてのファイルをアップロード(.gitとvenvを除く)
        files_to_upload = []

        # ディレクトリとファイルを取得
        for path in glob.glob("**", recursive=True):
            # 隠しファイル、venv、.gitを除外
            if (not path.startswith(".git") and 
                not path.startswith("venv") and 
                not path.startswith(".") and
                not path == "README-HF.md" and
                not path == "deploy_to_hf.py" and
                os.path.isfile(path)):
                files_to_upload.append(path)

        # アップロード
        api.upload_files(
            path_or_fileobj=files_to_upload,
            repo_id=f"{OWNER_NAME}/{REPO_NAME}",
            token=hf_token
        )

        print(f"ファイルが {OWNER_NAME}/{REPO_NAME} にアップロードされました。")
        return True
    except Exception as e:
        print(f"ファイルのアップロード中にエラーが発生しました: {e}")
        return False

def main():
    """メイン関数"""
    if not hf_token:
        print("環境変数 HF_TOKEN が設定されていません。")
        return False

    # リポジトリの作成
    repo_url = create_hf_repo()
    if not repo_url:
        return False

    # ファイルのアップロード
    success = upload_files()
    if success:
        print("デプロイが完了しました!")
        print(f"Hugging Face Space URL: https://huggingface.co/spaces/{OWNER_NAME}/{REPO_NAME}")
        return True

    return False

if __name__ == "__main__":
    main()

3. Hugging Faceへのデプロイ実行

> . venv/bin/activate && python3 deploy_to_hf.py
リポジトリ MakiAi/pokemon-nostalgic-pokedex が作成されました(または既に存在します)。
ファイルが MakiAi/pokemon-nostalgic-pokedex にアップロードされました。
デプロイが完了しました!
Hugging Face Space URL: https://huggingface.co/spaces/MakiAi/pokemon-nostalgic-pokedex

4. メタデータの追加

Hugging Face用のREADMEの先頭にYAMLフロントマターを追加:

---
title: Pokedex Html
emoji: 🏢
colorFrom: purple
colorTo: purple
sdk: static
pinned: false
---

問題解決の様子

1. GitHubリポジトリ名の変更

最初のリポジトリ名が気に入らなかったようで、より魅力的な名前に変更:

ユーザー: もっと別のいいリポジトリの名前にして

Claude: もっと魅力的なリポジトリ名に変更します。以下のような名前はいかがでしょうか:

> gh repo create pokemon-nostalgic-pokedex-jp --public --description "懐かしの雰囲気を再現した第二世代までのポケモン図鑑アプリ(日本語UI)" --source=. --push

2. Hugging Faceデプロイの認証問題

トークン入力方法の改善:

ユーザー: .env からトークンは取得するようにして
また、オーナー名はMakiAiです

Claude: deploy_to_hf.pyスクリプトを修正して、.envファイルからトークンを読み取り、オーナー名を"MakiAi"に変更します:

[スクリプトの修正内容]

完成したアプリの機能

完成したポケモン図鑑アプリには以下の機能が実装されています:

  • 第2世代までの全251匹のポケモン表示
  • 日本語インターフェース(ポケモン名やタイプなど)
  • アニメーション付きスプライト表示
  • 古典的ポケデックスデザイン(赤いボディに緑色の画面など)
  • 検索機能(ポケモン名や図鑑番号での検索)
  • レスポンシブデザイン

実装の特徴

  • PokeAPI活用: RESTful APIを使ってポケモンデータを取得
  • データキャッシング: パフォーマンス向上のためにデータをメモリにキャッシュ
  • 日本語変換: 英語データを日本語に変換する仕組み
  • クライアントサイド実装: バックエンドやデータベース不要で完全にブラウザで動作

Claudeとのコラボレーションの特徴

Claudeとの協働で特に印象的だった点:

  1. 日本語での思考共有:
この要求は、ポケモン図鑑(ポケデックス)のようなHTMLアプリを作成するものです。第2世代までのポケモンをリスト表示し、
アニメーション付きスプライトを使用して、クリックすると詳細が見られるインターフェースを作成する必要があります。
  1. 段階的な実装プロセス:
ポケモン図鑑アプリの開発を始めます
まず、プロジェクトの構造を確認し、必要なファイルを作成していきましょう。
  1. 問題解決能力:
エラーが発生しました: 'HfApi' object has no attribute 'login'
HuggingFaceのログイン部分にエラーがあるようです。APIトークンを使用する方法に修正します。
  1. 完了後の詳細な報告:
🚀 デプロイ完了のお知らせ
ポケモン図鑑アプリケーションのGitHubとHugging Faceへのデプロイが正常に完了しました!

📋 デプロイ情報
📦 GitHubリポジトリ
URL: https://github.com/Sunwood-ai-labs/pokemon-nostalgic-pokedex-jp
公開ページ: https://sunwood-ai-labs.github.io/pokemon-nostalgic-pokedex-jp/

まとめ

Windsurf + Claude 3.7 Sonnetの組み合わせは、開発プロセスを劇的に効率化しました。単一のプロンプトから始めて、完全に機能するWebアプリケーションを作成し、複数のプラットフォームにデプロイすることができました。

このプロジェクトの経験から得られた主な利点:

  1. 開発速度の向上: 通常なら数日かかる作業が数時間で完了
  2. コード品質の維持: AIが生成するコードは整理され、ベストプラクティスに従っている
  3. 学習機会: AIの思考プロセスを見ることで新しい知識や技術を学べる
  4. 自律的な問題解決: デプロイ時の問題などを自動的に特定し解決
  5. 言語の柔軟性: 日本語での指示でも完璧に対応

これからのソフトウェア開発は、AIとの協働がますます重要になるでしょう。Windsurfのような「AIフロー」を実現するツールは、開発者の生産性を大幅に向上させる可能性を秘めています。


関連リンク:

コメント

タイトルとURLをコピーしました