Claude 3.7 Sonnetのコーディング能力を試す 10


以前の投稿でClaude 3.7 Sonnetのコーディング能力を試した.

今回はClaudeの発想力を試してみた.

すると,かなり工夫を凝らしたシミュレーターを作成してくれた.特にClaudeはパラメータを調整できるようにしてくれる傾向がある.

プロンプトは下記.

Claude 3.7 Sonnetじゃないと作れないような,高度で有意義な人工生命プログラムのシミュレーターをPythonで書いて.確実に処理できる出力の長さでOK.
Reactで動的に処理できるようにして. 
エラーが出ないように自己点検して.
import React, { useState, useEffect, useRef, useCallback } from 'react';
import _ from 'lodash';

// 人工生命シミュレーションのメインロジック
const simulateLife = (currentGrid, rules) => {
  const { rows, cols } = rules.dimensions;
  const newGrid = Array(rows).fill().map(() => Array(cols).fill(null));
  
  // 各セルの新しい状態を計算
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      const cell = currentGrid[i][j];
      const neighbors = getNeighbors(currentGrid, i, j, rules);
      
      // 新しいセルの状態を計算
      newGrid[i][j] = calculateNewCellState(cell, neighbors, rules);
    }
  }
  
  return newGrid;
};

// あるセルの周囲のセルを取得
const getNeighbors = (grid, row, col, rules) => {
  const { rows, cols } = rules.dimensions;
  const neighbors = [];
  
  // ムーア近傍(8方向)を使用
  for (let i = -1; i <= 1; i++) {
    for (let j = -1; j <= 1; j++) {
      if (i === 0 && j === 0) continue; // 自分自身はスキップ
      
      const r = (row + i + rows) % rows;  // トーラス状にラップ
      const c = (col + j + cols) % cols;  // トーラス状にラップ
      
      neighbors.push(grid[r][c]);
    }
  }
  
  return neighbors;
};

// 新しいセルの状態を計算する関数
const calculateNewCellState = (cell, neighbors, rules) => {
  if (!cell) {
    // 空のセルの場合、新しく生命が誕生する可能性を計算
    const livingNeighbors = neighbors.filter(n => n && n.alive).length;
    
    if (livingNeighbors === rules.birth) {
      // 新しい生命の誕生
      return createNewLife(rules);
    }
    return null;
  } else {
    // 既に生命がある場合
    const livingNeighbors = neighbors.filter(n => n && n.alive).length;
    
    // 生存条件を満たすかどうか
    if (rules.survival.includes(livingNeighbors)) {
      // エネルギーとDNAの更新
      return {
        ...cell,
        energy: Math.min(cell.energy + rules.energyGain, rules.maxEnergy),
        age: cell.age + 1,
        // わずかなDNA変異の可能性
        dna: mutate(cell.dna, rules.mutationRate)
      };
    } else {
      // 死亡の場合
      if (cell.energy > 0) {
        return {
          ...cell,
          alive: false,
          energy: cell.energy - 1
        };
      }
      return null; // 完全に消滅
    }
  }
};

// 新しい生命を作成
const createNewLife = (rules) => {
  // DNAはランダムに生成(各特性の初期化)
  const dna = {
    color: [
      Math.floor(Math.random() * 256),
      Math.floor(Math.random() * 256),
      Math.floor(Math.random() * 256)
    ],
    metabolism: Math.random() * 0.5 + 0.5, // 0.5〜1.0 の間
    reproduction: Math.random(), // 0〜1 の間
    lifespan: Math.floor(Math.random() * 100) + 50, // 50〜150 の間
    aggression: Math.random() // 0〜1 の間
  };
  
  return {
    alive: true,
    energy: Math.floor(Math.random() * rules.maxEnergy / 2) + rules.maxEnergy / 2,
    age: 0,
    dna
  };
};

// DNA変異処理
const mutate = (dna, rate) => {
  // 確率に基づいて突然変異を発生させる
  if (Math.random() > rate) return dna;
  
  // 深いコピーを作成
  const newDna = _.cloneDeep(dna);
  
  // 突然変異させる特性をランダムに選択
  const keys = Object.keys(dna);
  const keyToMutate = keys[Math.floor(Math.random() * keys.length)];
  
  if (keyToMutate === 'color') {
    // 色の突然変異
    const colorIndex = Math.floor(Math.random() * 3);
    newDna.color[colorIndex] = Math.max(0, Math.min(255, 
      newDna.color[colorIndex] + Math.floor(Math.random() * 40) - 20));
  } else if (typeof dna[keyToMutate] === 'number') {
    // 数値特性の突然変異
    const mutationAmount = (Math.random() * 0.2) - 0.1; // -0.1〜0.1 の変異
    
    if (keyToMutate === 'lifespan') {
      newDna[keyToMutate] = Math.max(10, Math.min(200, 
        Math.round(newDna[keyToMutate] * (1 + mutationAmount))));
    } else {
      newDna[keyToMutate] = Math.max(0, Math.min(1, 
        newDna[keyToMutate] + mutationAmount));
    }
  }
  
  return newDna;
};

// 初期グリッドの生成
const generateInitialGrid = (rules) => {
  const { rows, cols } = rules.dimensions;
  const grid = Array(rows).fill().map(() => Array(cols).fill(null));
  
  // ランダムに初期生命を配置
  const initialPopulation = Math.floor(rows * cols * rules.initialPopulationRatio);
  
  for (let i = 0; i < initialPopulation; i++) {
    const row = Math.floor(Math.random() * rows);
    const col = Math.floor(Math.random() * cols);
    
    // 既に占有されていない場合のみ配置
    if (!grid[row][col]) {
      grid[row][col] = createNewLife(rules);
    }
  }
  
  return grid;
};

// 統計情報の計算
const calculateStats = (grid) => {
  let population = 0;
  let totalEnergy = 0;
  let totalAge = 0;
  let species = new Map(); // 種の多様性(DNAの類似性に基づく)
  
  // グリッドをスキャンして統計を計算
  grid.forEach(row => {
    row.forEach(cell => {
      if (cell && cell.alive) {
        population++;
        totalEnergy += cell.energy;
        totalAge += cell.age;
        
        // DNAを文字列化して種を識別
        const dnaSignature = cell.dna.color.join(',') + 
          Math.round(cell.dna.metabolism * 10) +
          Math.round(cell.dna.reproduction * 10);
        
        if (species.has(dnaSignature)) {
          species.set(dnaSignature, species.get(dnaSignature) + 1);
        } else {
          species.set(dnaSignature, 1);
        }
      }
    });
  });
  
  return {
    population,
    averageEnergy: population > 0 ? totalEnergy / population : 0,
    averageAge: population > 0 ? totalAge / population : 0,
    diversity: species.size,
    speciesDistribution: Array.from(species.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, 5) // トップ5の種
  };
};

// 高度な人工生命シミュレーターのメインコンポーネント
const ArtificialLifeSimulator = () => {
  // シミュレーションパラメータを定義
  const [rules, setRules] = useState({
    dimensions: { rows: 50, cols: 50 },
    birth: 3, // 誕生に必要な隣接セル数
    survival: [2, 3], // 生存に必要な隣接セル数
    maxEnergy: 100,
    energyGain: 5,
    mutationRate: 0.05,
    initialPopulationRatio: 0.3
  });
  
  // シミュレーション状態
  const [grid, setGrid] = useState([]);
  const [generation, setGeneration] = useState(0);
  const [stats, setStats] = useState({
    population: 0,
    averageEnergy: 0,
    averageAge: 0,
    diversity: 0,
    speciesDistribution: []
  });
  
  // シミュレーション制御
  const [isRunning, setIsRunning] = useState(false);
  const [speed, setSpeed] = useState(100); // ミリ秒単位
  
  // アニメーションフレームの参照
  const animationRef = useRef(null);
  
  // シミュレーションを初期化
  const initializeSimulation = useCallback(() => {
    const initialGrid = generateInitialGrid(rules);
    setGrid(initialGrid);
    setGeneration(0);
    setStats(calculateStats(initialGrid));
  }, [rules]);
  
  // シミュレーションの実行ステップ
  const runSimulationStep = useCallback(() => {
    setGrid(currentGrid => {
      const newGrid = simulateLife(currentGrid, rules);
      setStats(calculateStats(newGrid));
      setGeneration(gen => gen + 1);
      return newGrid;
    });
  }, [rules]);
  
  // アニメーションループを制御
  useEffect(() => {
    let lastTime = 0;
    
    const animate = (time) => {
      if (time - lastTime > speed) {
        runSimulationStep();
        lastTime = time;
      }
      
      if (isRunning) {
        animationRef.current = requestAnimationFrame(animate);
      }
    };
    
    if (isRunning) {
      animationRef.current = requestAnimationFrame(animate);
    } else if (animationRef.current) {
      cancelAnimationFrame(animationRef.current);
    }
    
    return () => {
      if (animationRef.current) {
        cancelAnimationFrame(animationRef.current);
      }
    };
  }, [isRunning, speed, runSimulationStep]);
  
  // 初期化
  useEffect(() => {
    initializeSimulation();
  }, [initializeSimulation]);
  
  // キャンバスサイズの計算
  const canvasWidth = 600;
  const canvasHeight = 600;
  const cellWidth = canvasWidth / rules.dimensions.cols;
  const cellHeight = canvasHeight / rules.dimensions.rows;
  
  // グリッドの描画
  const renderGrid = () => {
    return (
      <div className="grid-container" style={{ 
        display: 'grid', 
        gridTemplateColumns: `repeat(${rules.dimensions.cols}, ${cellWidth}px)`,
        width: `${canvasWidth}px`,
        height: `${canvasHeight}px`,
        border: '1px solid #ccc'
      }}>
        {grid.map((row, rowIndex) => 
          row.map((cell, colIndex) => (
            <div 
              key={`${rowIndex}-${colIndex}`} 
              className="cell"
              style={{
                width: `${cellWidth}px`,
                height: `${cellHeight}px`,
                backgroundColor: cell && cell.alive 
                  ? `rgba(${cell.dna.color[0]}, ${cell.dna.color[1]}, ${cell.dna.color[2]}, ${cell.energy / rules.maxEnergy})`
                  : 'transparent',
                border: '1px solid #eee'
              }}
              onClick={() => handleCellClick(rowIndex, colIndex)}
            />
          ))
        )}
      </div>
    );
  };
  
  // セルクリック時の処理
  const handleCellClick = (row, col) => {
    setGrid(currentGrid => {
      const newGrid = _.cloneDeep(currentGrid);
      
      // クリックでセルを切り替え
      if (newGrid[row][col] && newGrid[row][col].alive) {
        newGrid[row][col] = null; // 生命を消す
      } else {
        newGrid[row][col] = createNewLife(rules); // 新しい生命を作成
      }
      
      setStats(calculateStats(newGrid));
      return newGrid;
    });
  };
  
  // ルール変更ハンドラ
  const handleRuleChange = (e) => {
    const { name, value } = e.target;
    
    setRules(prevRules => {
      // 特殊な処理が必要なフィールド
      if (name === 'birth' || name === 'survival') {
        return {
          ...prevRules,
          [name]: name === 'birth' 
            ? parseInt(value) 
            : value.split(',').map(v => parseInt(v.trim()))
        };
      } else if (name === 'rows' || name === 'cols') {
        return {
          ...prevRules,
          dimensions: {
            ...prevRules.dimensions,
            [name]: parseInt(value)
          }
        };
      } else if (name === 'mutationRate' || name === 'initialPopulationRatio') {
        return {
          ...prevRules,
          [name]: parseFloat(value)
        };
      } else {
        return {
          ...prevRules,
          [name]: parseInt(value)
        };
      }
    });
  };
  
  // パラメータを変更した後にシミュレーションを再初期化
  const applyRules = () => {
    initializeSimulation();
  };
  
  return (
    <div className="artificial-life-simulator p-4">
      <h1 className="text-2xl font-bold mb-4">高度な人工生命シミュレーター</h1>
      
      <div className="flex flex-row flex-wrap">
        {/* シミュレーション制御パネル */}
        <div className="control-panel mr-4 mb-4">
          <div className="flex mb-4">
            <button 
              className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mr-2"
              onClick={() => setIsRunning(!isRunning)}
            >
              {isRunning ? '停止' : '開始'}
            </button>
            
            <button 
              className="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded mr-2"
              onClick={runSimulationStep}
              disabled={isRunning}
            >
              次のステップ
            </button>
            
            <button 
              className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
              onClick={initializeSimulation}
            >
              リセット
            </button>
          </div>
          
          <div className="mb-4">
            <label className="block text-gray-700 mb-2">
              シミュレーション速度:
              <input 
                type="range" 
                min="10" 
                max="500" 
                value={speed} 
                onChange={(e) => setSpeed(parseInt(e.target.value))} 
                className="w-full"
              />
              {speed}ms
            </label>
          </div>
          
          <div className="rules-panel mb-4 p-4 border rounded">
            <h3 className="font-bold mb-2">シミュレーションパラメータ</h3>
            
            <div className="grid grid-cols-2 gap-2 mb-2">
              <label className="block text-gray-700">
                行数:
                <input 
                  type="number" 
                  name="rows" 
                  value={rules.dimensions.rows} 
                  onChange={handleRuleChange} 
                  min="10" 
                  max="100"
                  className="w-full border rounded p-1"
                />
              </label>
              
              <label className="block text-gray-700">
                列数:
                <input 
                  type="number" 
                  name="cols" 
                  value={rules.dimensions.cols} 
                  onChange={handleRuleChange} 
                  min="10" 
                  max="100"
                  className="w-full border rounded p-1"
                />
              </label>
            </div>
            
            <div className="grid grid-cols-2 gap-2 mb-2">
              <label className="block text-gray-700">
                誕生条件:
                <input 
                  type="number" 
                  name="birth" 
                  value={rules.birth} 
                  onChange={handleRuleChange} 
                  min="1" 
                  max="8"
                  className="w-full border rounded p-1"
                />
              </label>
              
              <label className="block text-gray-700">
                生存条件:
                <input 
                  type="text" 
                  name="survival" 
                  value={rules.survival.join(',')} 
                  onChange={handleRuleChange} 
                  placeholder="2,3"
                  className="w-full border rounded p-1"
                />
              </label>
            </div>
            
            <div className="grid grid-cols-2 gap-2 mb-2">
              <label className="block text-gray-700">
                最大エネルギー:
                <input 
                  type="number" 
                  name="maxEnergy" 
                  value={rules.maxEnergy} 
                  onChange={handleRuleChange} 
                  min="10" 
                  max="1000"
                  className="w-full border rounded p-1"
                />
              </label>
              
              <label className="block text-gray-700">
                エネルギー獲得:
                <input 
                  type="number" 
                  name="energyGain" 
                  value={rules.energyGain} 
                  onChange={handleRuleChange} 
                  min="1" 
                  max="50"
                  className="w-full border rounded p-1"
                />
              </label>
            </div>
            
            <div className="grid grid-cols-2 gap-2 mb-2">
              <label className="block text-gray-700">
                突然変異率:
                <input 
                  type="number" 
                  name="mutationRate" 
                  value={rules.mutationRate} 
                  onChange={handleRuleChange} 
                  min="0" 
                  max="1" 
                  step="0.01"
                  className="w-full border rounded p-1"
                />
              </label>
              
              <label className="block text-gray-700">
                初期人口比率:
                <input 
                  type="number" 
                  name="initialPopulationRatio" 
                  value={rules.initialPopulationRatio} 
                  onChange={handleRuleChange} 
                  min="0.01" 
                  max="0.9" 
                  step="0.01"
                  className="w-full border rounded p-1"
                />
              </label>
            </div>
            
            <button 
              className="bg-green-500 hover:bg-green-700 text-white font-bold py-1 px-4 rounded"
              onClick={applyRules}
            >
              パラメータを適用
            </button>
          </div>
        </div>
        
        {/* グリッド表示 */}
        <div className="grid-view mb-4">
          {grid.length > 0 && renderGrid()}
        </div>
      </div>
      
      {/* 統計情報 */}
      <div className="stats-panel p-4 border rounded">
        <h3 className="font-bold mb-2">統計情報</h3>
        <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
          <div className="stat-box p-2 border rounded">
            <div className="text-lg">世代</div>
            <div className="text-2xl font-bold">{generation}</div>
          </div>
          
          <div className="stat-box p-2 border rounded">
            <div className="text-lg">人口</div>
            <div className="text-2xl font-bold">{stats.population}</div>
          </div>
          
          <div className="stat-box p-2 border rounded">
            <div className="text-lg">平均エネルギー</div>
            <div className="text-2xl font-bold">{stats.averageEnergy.toFixed(1)}</div>
          </div>
          
          <div className="stat-box p-2 border rounded">
            <div className="text-lg">平均年齢</div>
            <div className="text-2xl font-bold">{stats.averageAge.toFixed(1)}</div>
          </div>
          
          <div className="stat-box p-2 border rounded">
            <div className="text-lg">種の多様性</div>
            <div className="text-2xl font-bold">{stats.diversity}</div>
          </div>
        </div>
        
        {/* 主要な種の情報 */}
        {stats.speciesDistribution.length > 0 && (
          <div className="mt-4">
            <h4 className="font-bold">主要な種</h4>
            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 mt-2">
              {stats.speciesDistribution.map((species, index) => (
                <div key={index} className="species-box p-2 border rounded flex items-center">
                  <div 
                    className="species-color mr-2" 
                    style={{ 
                      width: '20px', 
                      height: '20px', 
                      backgroundColor: `rgb(${species[0].split(',').slice(0, 3).join(',')})`,
                      borderRadius: '50%'
                    }}
                  />
                  <div>種 {index + 1}: {species[1]} 個体</div>
                </div>
              ))}
            </div>
          </div>
        )}
      </div>
      
      {/* 説明 */}
      <div className="info-panel mt-4 p-4 border rounded">
        <h3 className="font-bold mb-2">このシミュレーターについて</h3>
        <p>
          このシミュレーターは、各セルが独自のDNA、エネルギー、年齢を持つ高度な人工生命モデルを実装しています。
          生命体は色でDNAの特性を表しており、周囲の条件に応じて誕生、生存、死亡します。
          また、わずかな確率で突然変異が発生し、新しい特性を持つ種が誕生する可能性があります。
        </p>
        <p className="mt-2">
          グリッドのセルをクリックして、手動で生命を追加または削除することができます。
          パラメータを調整して、さまざまな生態系の振る舞いを観察してみてください。
        </p>
      </div>
    </div>
  );
};

export default ArtificialLifeSimulator;

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です