「ClaudeのGPT-4o超え」って意外と本当かもと驚いた！！！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52151/7d171bcc-02be-03fe-f77f-6c872132a528.png)
**あっという間**に、抽選ルーレットが出来上がってしまった話です。

# サマリー

この記事でやっていることのサマリーです
* やりたいことをプロンプトで伝える
* 変更点をプロンプトで伝える
* バグをプロンプトで伝える
* 微調整をプロンプトで伝える

特に生成されたコードがその場でリアルタイムに実行され、**出来栄えを見ながらプロンプトに指示する**ことができるのは本当に便利でした。

# はじめに

みなさん！話題のClaude使ってますか？Qiita民なら誰よりも早く使ってますよね。
そんな私も『GPT-4oを超えた』とか聞いた時は、「本当かな？」と思い、すぐに試してみて、「やるね、Claude！」と思ったものでした。

が、しかし、今回発表されたClaude 3.5 Sonnet Artifactsには本当に驚いてしまいました。
「何をそんなに驚いているの？」って聞こえてきそうですが、まずは、こちらの動画をご覧ください。

<iframe width="560" height="315" src="https://www.youtube.com/embed/P9-6iyEAnW4?si=H6S8weOTjzG8zsXb" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

実は、これ、Claude 3.5 Sonnet Artifactsに236文字のプロンプトを入力しただけで生成されたアプリなんです。

# 準備

準備といっても、

1. [Claudeのホームページ](https://claude.ai/login)にアクセス
1. Googleアカウントでログイン
1. 表示されたプロンプト入力欄にプロンプトを入力する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52151/2498d5b9-f496-ad9a-8e24-ee8ca2a439d4.png)

これだけです。

# レッツ・トライ！

それでは、早速、私が入力したプロンプトを公開いたします。
コピペもOKです:bangbang:

```shell:プロンプト 1回目
プレゼンをする人を決定するアプリを作成してください。プレゼン者を入力するフィールドもつけてください
```
出力結果は、こちら
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52151/bbcb020c-f75d-57b7-3ed3-ba9f75af3805.png)
プログラムが生成されるだけでなく、生成されたプログラムが実際に実行され、右側に表示されます。
プロンプトを入力してから、生成されたプログラムが動くまでの様子をこの動画でご確認ください。
<iframe width="560" height="315" src="https://www.youtube.com/embed/TRmsTOUZfbo?si=AFgebEOLbRKPXNve" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

なんと、一発で動くプログラムが生成されましたし、そのプログラムがClaude上でリアルタイムに動作し確認まで出来ます (すごい)

でも、なんかちょっと寂しいですね (贅沢)
せっかくなんで、抽選の雰囲気を出すために、ルーレット風に変えてみます。

```shell:プロンプト 2回目
ルーレットを回すことで決まるようなアプリにしてください
```
サクッと修正が入りました。こんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52151/13a64dd8-c22a-5d28-c9eb-d1c5161fa94f.png)
それでは実際に動かしてみます。
<iframe width="560" height="315" src="https://www.youtube.com/embed/gFI66cF74TE?si=MINFLStgkARY9qDH" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

ルーレットが回ったところまでは良かったのですが、結果は、ちょうど境目で止まって、どちらが選ばれたのか、判断が微妙です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52151/8dff4834-bb2a-f679-5779-17695e0a89ab.png)
プログラム的には『宮崎』さんです。

```shell:プロンプト 3回目
ちょうど境目で止まり、プレゼン発表者を決めることができませんけど
```
また、プログラムの再生成が始まりました。
実行してみると、前回と同じように、また境目で止まり、プレゼン発表者を決めることができません。
仕方ないので、もう一度。ちょっとプロンプトを変えてみます。

```shell:プロンプト 4回目
まだ矢印が境目で止まります。境目で止まらないようにしてください
```
またまた、プログラムの再生成が始まりました。
今度は良さそう:interrobang:
境目のところで止まらなくなったのですが、今度は、矢印と選ばれたプレゼン者が違いますね。もう！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52151/7601aee3-8739-bb75-cecc-cea1bffc96f8.png)


また修正プロンプトを入力してみます。

```shell:プロンプト 5回目
矢印が止まった箇所と選ばれたプレゼン者が違いますよ
```
今度は、矢印の先と選ばれたプレゼン者が一致しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52151/88f979b9-f6f5-0eb8-cec6-7f3608256e48.png)
欲を言えば、ルーレット内のフォントが小さくて見づらいですね。

```shell:プロンプト 6回目
入力したプレゼン者のフォントの大きさを2倍にしてください
```
ルーレット内の文字のフォントが大きくなり、見やすいです:bangbang:

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52151/4785a4b9-2008-1ea5-f3ec-60c116b4ea0e.png)
完成したと思いきや、実は数回[ルーレットを回す]をクリックしてみると、いつも同じ人が選ばれてしまいます。
(わたくし、柳ばかりが選ばれます。これはいかん、いかん)

```shell:プロンプト 7回目
いつも同じ人が選ばれてしまいます。回転速度をランダムに変えてください
```
ランダムにプレゼン者が選ばれるようです。ついに完成しました:bangbang::bangbang:
<iframe width="560" height="315" src="https://www.youtube.com/embed/C-c-TbUsp3Y?si=jQQ5RgvAloOYGfFp" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
スゴすぎる:grin:

# 参考: 生成されたソースコード

```node.js
import React, { useState, useEffect, useRef } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';

const PresenterRoulette = () => {
  const [presenters, setPresenters] = useState([]);
  const [newPresenter, setNewPresenter] = useState('');
  const [selectedPresenter, setSelectedPresenter] = useState(null);
  const [isSpinning, setIsSpinning] = useState(false);
  const canvasRef = useRef(null);

  const addPresenter = () => {
    if (newPresenter.trim() !== '') {
      setPresenters([...presenters, newPresenter.trim()]);
      setNewPresenter('');
    }
  };

  const spinRoulette = () => {
    if (presenters.length > 0 && !isSpinning) {
      setIsSpinning(true);
      const minSpins = 5;
      const maxSpins = 10;
      const spins = minSpins + Math.random() * (maxSpins - minSpins);
      const minDuration = 4000;
      const maxDuration = 8000;
      const duration = minDuration + Math.random() * (maxDuration - minDuration);
      let currentRotation = 0;
      const startTime = Date.now();

      const sliceAngle = 360 / presenters.length;
      const additionalRotation = Math.random() * 360; // 完全にランダムな追加回転

      const animate = () => {
        const elapsedTime = Date.now() - startTime;
        const progress = Math.min(elapsedTime / duration, 1);
        const easeOut = 1 - Math.pow(1 - progress, 3);
        
        currentRotation = easeOut * spins * 360 + additionalRotation;
        drawRoulette(currentRotation);

        if (progress < 1) {
          requestAnimationFrame(animate);
        } else {
          setIsSpinning(false);
          selectPresenter(currentRotation);
        }
      };

      animate();
    }
  };

  const selectPresenter = (finalRotation) => {
    const normalizedRotation = finalRotation % 360;
    const sliceAngle = 360 / presenters.length;
    const selectedIndex = (presenters.length - Math.floor(normalizedRotation / sliceAngle) - 1) % presenters.length;
    setSelectedPresenter(presenters[selectedIndex]);
  };

  const drawRoulette = (rotation) => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    const centerX = canvas.width / 2;
    const centerY = canvas.height / 2;
    const radius = Math.min(centerX, centerY) - 10;

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.save();
    ctx.translate(centerX, centerY);
    ctx.rotate((rotation * Math.PI) / 180);

    const sliceAngle = (2 * Math.PI) / presenters.length;
    const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8'];

    presenters.forEach((presenter, index) => {
      const startAngle = index * sliceAngle;
      const endAngle = startAngle + sliceAngle;

      ctx.beginPath();
      ctx.moveTo(0, 0);
      ctx.arc(0, 0, radius, startAngle, endAngle);
      ctx.closePath();
      ctx.fillStyle = colors[index % colors.length];
      ctx.fill();

      ctx.save();
      ctx.rotate(startAngle + sliceAngle / 2);
      ctx.textAlign = 'right';
      ctx.fillStyle = '#000';
      ctx.font = 'bold 28px Arial';
      ctx.fillText(presenter, radius - 30, 0);
      ctx.restore();
    });

    ctx.restore();

    // Draw arrow
    ctx.beginPath();
    ctx.moveTo(centerX + radius, centerY);
    ctx.lineTo(centerX + radius + 20, centerY - 10);
    ctx.lineTo(centerX + radius + 20, centerY + 10);
    ctx.closePath();
    ctx.fillStyle = '#333';
    ctx.fill();
  };

  useEffect(() => {
    if (canvasRef.current) {
      drawRoulette(0);
    }
  }, [presenters]);

  return (
    <div className="p-4 max-w-md mx-auto">
      <h1 className="text-2xl font-bold mb-4">プレゼン者ルーレットアプリ</h1>
      <div className="flex mb-4">
        <Input
          type="text"
          value={newPresenter}
          onChange={(e) => setNewPresenter(e.target.value)}
          placeholder="プレゼン者の名前を入力"
          className="flex-grow mr-2"
        />
        <Button onClick={addPresenter}>追加</Button>
      </div>
      <div className="mb-4">
        <h2 className="text-lg font-semibold mb-2">プレゼン者リスト:</h2>
        <ul className="list-disc pl-5">
          {presenters.map((presenter, index) => (
            <li key={index}>{presenter}</li>
          ))}
        </ul>
      </div>
      <div className="mb-4">
        <canvas ref={canvasRef} width="300" height="300" className="mx-auto"></canvas>
      </div>
      <Button onClick={spinRoulette} className="w-full mb-4" disabled={isSpinning}>
        {isSpinning ? 'ルーレット回転中...' : 'ルーレットを回す'}
      </Button>
      {selectedPresenter && (
        <div className="text-center p-4 bg-blue-100 rounded">
          <h2 className="text-xl font-bold">選ばれたプレゼン者:</h2>
          <p className="text-2xl text-blue-600">{selectedPresenter}</p>
        </div>
      )}
    </div>
  );
};

export default PresenterRoulette;
```
# 終わりに

GPT-4o超えとか、最初は信じられませんでしたが、今回、やりたい事を素直にプロンプトに入力したら、たった10分程度で、こんなアプリがサクッとできてしまいました。
「Claudeが本当にGPT-4oを超えてしまったかもしれない:laughing:」

# 参考

https://claude.ai/login