WindsurfでWebアプリを作ってみる
前書き
こんにちは!
オアシステクノロジーズの山本です。
AIを使って簡単なWebアプリをNext.jsで作ってみたいなと思い、Windsurfを使ってみました。
ただ、使ってみた結果としては「うーん微妙」というのが正直な感想です。
これにはいろいろ理由があるのですが、VSCodeの拡張機能としてWindsurfを使ったのが大きいと考えています。
IDEもWindsurfを使うべきでした。。。
特にワークスペースの実装状態の把握させるのが大変で、同じところの実装をぐるぐる回る、
チャットを一度閉じて作業を再開すると、前回までの状態が認識できていないようでした。
皆さんが実施される際には、IDEはWindsurfを使うことをお勧めします。
Windsurfについてのもろもろ
細かい話はいろいろありますが、今回Windsurfを使う理由は「ファイルを一括で修正可能」なことです。
Webアプリを作るとき、ファイルを一つ一つ修正していくのは面倒ですし、影響箇所を見極めることは一定のスキルが必要となります。
Windsurfを使うことで一括で修正できるのであれば、開発が楽になるのではと思い、使ってみることにしました。
こちらのサイトを参考にさせていただきました。今回はVSCodeを使用します、またnpm等のインストールは済んでいる前提で進めます。
作ってみるWebアプリについて
今回は、AIを使って簡単なWebアプリを作ってみたいと思います。
具体的には、Next.jsを使ったブラックジャックのWebアプリをです。
ブラックジャックは、プレイヤーとディーラーが対戦するカードゲームで、21を超えないように手札の合計を競います。
ルールは簡単で、プレイヤーは2枚のカードを引き、さらに1枚引くかどうかを選択します。
ディーラーは、17以上の合計になるまでカードを引き続けます。
プレイヤーが21を超えた場合は負け、ディーラーが21を超えた場合はプレイヤーの勝ちとなります。
Webアプリの作成
早速、Windsurfに丸投げしてみます!特に細かい設定を行わず
「Next.jsでブラックジャックのWebアプリを作ってください。」と投げてみます。
得られた、結果は以下の通りです。ステップ毎に実際に実行していきます。
Next.jsでブラックジャックのWebアプリを作る
ブラックジャックのWebアプリを作るために、以下のステップに従ってください。
ステップ1: Next.jsプロジェクトを作成する
以下のコマンドを実行して、Next.jsプロジェクトを作成します。
bash
CopyInsert in Terminal
npx create-next-app blackjack-app
ステップ2: pagesディレクトリを作成する
pagesディレクトリを作成し、そこにindex.jsファイルを作成します。
bash
CopyInsert
mkdir pages
touch pages/index.js
ステップ3: ブラックジャックのゲームロジックを作成する
pages/index.jsファイルに以下のコードを追加します。
jsx
CopyInsert
import Head from 'next/head';
function Blackjack() {
const [deck, setDeck] = useState([]);
const [playerHand, setPlayerHand] = useState([]);
const [dealerHand, setDealerHand] = useState([]);
const [result, setResult] = useState('');
useEffect(() => {
const createDeck = () => {
const suits = ['Hearts', 'Diamonds', 'Clubs', 'Spades'];
const ranks = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'];
const deck = [];
for (const suit of suits) {
for (const rank of ranks) {
deck.push({ suit, rank });
}
}
return deck;
};
setDeck(createDeck());
}, []);
const dealCard = () => {
const card = deck.pop();
setPlayerHand([...playerHand, card]);
setDealerHand([...dealerHand, deck.pop()]);
};
const calculateScore = (hand) => {
let score = 0;
for (const card of hand) {
if (card.rank === 'A') {
score += 11;
} else if (card.rank === 'J' || card.rank === 'Q' || card.rank === 'K') {
score += 10;
} else {
score += parseInt(card.rank);
}
}
return score;
};
const checkResult = () => {
const playerScore = calculateScore(playerHand);
const dealerScore = calculateScore(dealerHand);
if (playerScore > 21) {
setResult('Player busts! Dealer wins!');
} else if (dealerScore > 21) {
setResult('Dealer busts! Player wins!');
} else if (playerScore > dealerScore) {
setResult('Player wins!');
} else if (playerScore < dealerScore) {
setResult('Dealer wins!');
} else {
setResult('Push!');
}
};
const handleHit = () => {
dealCard();
checkResult();
};
const handleStand = () => {
checkResult();
};
return (
<div>
<Head>
<title>ブラックジャック</title>
</Head>
<h1>ブラックジャック</h1>
<p>Player's hand: {playerHand.map((card) => `${card.rank} of ${card.suit}`).join(', ')}</p>
<p>Dealer's hand: {dealerHand.map((card) => `${card.rank} of ${card.suit}`).join(', ')}</p>
<p>Result: {result}</p>
<button onClick={handleHit}>Hit</button>
<button onClick={handleStand}>Stand</button>
</div>
);
}
export default Blackjack;
ステップ4: アプリを起動する
以下のコマンドを実行して、アプリを起動します。
bash
CopyInsert in Terminal
npm run dev
ブラウザでhttp://localhost:3000にアクセスすると、ブラックジャックのWebアプリが表示されます。
Note: このコードは、ブラックジャックの基本的なルールに基づいて作成されています。より複雑なルールや機能を追加する場合は、コードを修正する必要があります。
ステップ1: Next.jsプロジェクトを作成する
「npx create-next-app blackjack-app」のコマンドを実行しろとのこと。
これを実行すると、Next.jsのプロジェクトが作成されます。
ただ、細かい設定については記載がありません。TypeScriptを使うか、AppRouterを使うか、CSSフレームワークを使うかなど
このあたりはも決めさせてしまいたい。。。
npxコマンド実行後、問い合わせ内容をもとに、以下のように、Windsurfに投げると決めてくれたので、その内容で進めます。
「Would you like your code inside a src/
directory? と問われています、決めて」
最終的には以下となりました。
? Would you like to customize the import alias
> npx create-next-app blackjack-app
Need to install the following packages:
create-next-app@15.3.1
Ok to proceed? (y) y
√ Would you like to use TypeScript? ... No / Yes
√ Would you like to use ESLint? ... No / Yes
√ Would you like to use Tailwind CSS? ... No / Yes
√ Would you like your code inside a `src/` directory? ... No / Yes
√ Would you like to use App Router? (recommended) ... No / Yes
√ Would you like to use Turbopack for `next dev`? ... No / Yes
√ Would you like to customize the import alias (`@/*` by default)? ... No / Yes
Creating a new Next.js app in C:\Users\yamamoto\blackjack-app.
pagesディレクトリを作成する)
ステップ2: コンポーネントの作成(最初のWindsurfからの指示ではpagesディレクトリを作成するとのことです。しかし、先ほどのプロジェクト作成時の
設定を考慮し、再度確認を行います。
「Next.jsでブラックジャックのWebアプリを作ってください。」
と投げてみます。
得られた結果は変わり、コンポーネントを作成するようにとのこと。提示されたファイルを作成していきます。
ステップ2: コンポーネントの作成
componentsディレクトリを作成し、以下のコンポーネントを作成します。
Card.js: カードを表示するコンポーネント
Deck.js: デッキを管理するコンポーネント
Game.js: ゲームのロジックを管理するコンポーネント
Hand.js: プレイヤーの手札を表示するコンポーネント
import React from 'react';
interface CardProps {
suit: string;
rank: string;
reveal: boolean;
}
const Card: React.FC<CardProps> = ({ suit, rank, reveal }) => {
return (
<div>
<span>{rank}</span>
<span>{suit}</span>
</div>
);
};
export default Card;
import React, { useState } from 'react';
import Card from './Card';
interface DeckProps {
// なし
}
interface Card {
suit: string;
rank: string;
value: number;
}
const Deck: React.FC<DeckProps> = () => {
const [cards, setCards] = useState<Card[]>([]);
const [deck, setDeck] = useState<Card[]>([]);
const createDeck = () => {
const suits = ['Hearts', 'Diamonds', 'Clubs', 'Spades'];
const ranks = ['Ace', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King'];
const newDeck: Card[] = [];
for (const suit of suits) {
for (const rank of ranks) {
newDeck.push({ suit, rank, value: getValue(rank) });
}
}
setDeck(newDeck);
};
const shuffleDeck = () => {
const shuffledDeck = [...deck];
for (let i = shuffledDeck.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffledDeck[i], shuffledDeck[j]] = [shuffledDeck[j], shuffledDeck[i]];
}
setDeck(shuffledDeck);
};
const dealCard = () => {
if (deck.length > 0) {
const topCard = deck[0];
setCards([...cards, topCard]);
setDeck(deck.slice(1));
}
};
const getValue = (rank: string): number => {
switch (rank) {
case 'Ace':
return 11;
case 'Jack':
case 'Queen':
case 'King':
return 10;
default:
return parseInt(rank, 10);
}
};
return (
<div>
<button onClick={createDeck}>Create Deck</button>
<button onClick={shuffleDeck}>Shuffle Deck</button>
<button onClick={dealCard}>Deal Card</button>
<ul>
{cards.map((card, index) => (
<li key={index}>
<Card suit={card.suit} rank={card.rank} reveal={true} />
</li>
))}
</ul>
</div>
);
};
export default Deck;
import React, { useState } from 'react';
import Deck from './Deck';
import Hand from './Hand';
interface GameProps {
// なし
}
interface Card {
suit: string;
rank: string;
value: number;
}
const Game: React.FC<GameProps> = () => {
const [playerHand, setPlayerHand] = useState<Card[]>([]);
const [dealerHand, setDealerHand] = useState<Card[]>([]);
const [gameOver, setGameOver] = useState(false);
const startGame = () => {
setPlayerHand([]);
setDealerHand([]);
setGameOver(false);
};
const hit = () => {
if (playerHand.length < 5) {
const newCard = { suit: 'Hearts', rank: 'Ace', value: 11 };
setPlayerHand([...playerHand, newCard]);
}
};
const stand = () => {
setGameOver(true);
};
return (
<div>
<Deck />
<Hand hand={playerHand} isDealer={false} reveal={true} />
<Hand hand={dealerHand} isDealer={true} reveal={false} />
<button onClick={startGame}>Start Game</button>
<button onClick={hit}>Hit</button>
<button onClick={stand}>Stand</button>
{gameOver && <p>Game Over!</p>}
</div>
);
};
export default Game;
import React from 'react';
import Card from './Card';
interface HandProps {
hand: { suit: string; rank: string; value: number }[];
isDealer: boolean;
reveal: boolean;
}
const Hand: React.FC<HandProps> = ({ hand, isDealer, reveal }) => {
const handValue = hand.reduce((acc, card) => acc + card.value, 0);
return (
<div>
<h2>
{isDealer ? 'Dealer' : 'Player'}'s Hand
</h2>
<ul>
{hand.map((card, index) => (
<li key={index}>
<Card suit={card.suit} rank={card.rank} reveal={reveal} />
</li>
))}
</ul>
<p>
Hand Value: {handValue}
</p>
</div>
);
};
export default Hand;
ブラックジャックのゲームロジックを作成する
ステップ3: App.tsx Index.tsx の追加 当初の流れと変わっているので、次は何をすればよいか確認するとApp.tsxとIndex.tsxを作成するようにとのこと。
import React from 'react';
import Game from './components/Game';
const App: React.FC = () => {
return (
<div>
<Game />
</div>
);
};
export default App;
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
const root = createRoot(document.getElementById('root')!);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
ステップ4: アプリを起動する
はい、これで動作する!とはなりませんでした。
いろいろとエラーが出てきます。。。解決させようと、プロンプトを入れ続けるのですが、
始めに書いたとおり、実装状態の把握させるのが大変で、同じところの実装をぐるぐる回り解決しない。。。
無料プランなので、モデルもBaseModelを使用しており、これも原因かもしれません。。。
結局、ChatGpt(o4 mini high)を使い一から実装し直しました。。。
実装し直すと一発で解決しました。
まとめ
AIコーディング凄い!という記事を書こうと思ったのですが、変な落とし穴にはまってしまったという、なんとも締まらない記事になってしまいました。
しかし、AIコーディングの進化は凄い勢いで進んでいます。
めげずに、引き続き挑戦していきたいと思います。
参考
Discussion