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;
