Claude 4 Sonnetのコーディング能力を試す 6
Claude 4 が話題を呼んでいる.今回はClaude 3.7との比較で想像力を試してみた.
プロンプトは下記で,Claude 4の出力に任せてみた.
すると,動作は不完全なものの非常に興味深い提案をしてきた.
Claude 4.0 Sonnetじゃないと作れないような,高度で有意義な人工生命プログラムのシミュレーターをPythonで書いて.確実に処理できる出力の長さでOK.
Reactで動的に処理できるようにして.
エラーが出ないように自己点検して.
一切の遠慮は不要です!!最高品質を追い求めて.
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { Play, Pause, RotateCcw, Settings, Activity, Zap, Heart, Eye } from 'lucide-react';
// 生命体クラス - 高度な遺伝子システム
class LifeForm {
constructor(x, y, genome = null) {
this.x = x;
this.y = y;
this.age = 0;
this.energy = 100;
this.maxEnergy = 150;
this.health = 100;
this.reproductionCooldown = 0;
this.generation = genome ? genome.generation + 1 : 0;
// 高度な遺伝子システム
this.genome = genome ? this.mutateGenome(genome) : this.randomGenome();
// 神経ネットワーク風の意思決定システム
this.memory = new Array(5).fill(0);
this.lastActions = [];
this.socialBonds = new Map();
// 視覚システム
this.vision = {
range: this.genome.visionRange,
angle: this.genome.visionAngle,
direction: Math.random() * Math.PI * 2
};
this.state = 'exploring'; // exploring, feeding, reproducing, fleeing, social
}
randomGenome() {
return {
speed: 0.5 + Math.random() * 2,
size: 3 + Math.random() * 7,
energyEfficiency: 0.7 + Math.random() * 0.6,
reproductionThreshold: 80 + Math.random() * 40,
aggressionLevel: Math.random(),
socialTendency: Math.random(),
adaptability: Math.random(),
visionRange: 30 + Math.random() * 70,
visionAngle: Math.PI / 3 + Math.random() * Math.PI / 3,
metabolicRate: 0.8 + Math.random() * 0.4,
immuneSystem: 0.5 + Math.random() * 0.5,
intelligence: Math.random(),
generation: 0,
colorR: Math.floor(Math.random() * 256),
colorG: Math.floor(Math.random() * 256),
colorB: Math.floor(Math.random() * 256)
};
}
mutateGenome(parentGenome) {
const mutationRate = 0.1;
const mutationStrength = 0.2;
const newGenome = { ...parentGenome };
newGenome.generation = parentGenome.generation + 1;
Object.keys(newGenome).forEach(key => {
if (key === 'generation') return;
if (Math.random() < mutationRate) {
if (typeof newGenome[key] === 'number') {
const change = (Math.random() - 0.5) * mutationStrength;
newGenome[key] *= (1 + change);
// 色の値は0-255に制限
if (key.includes('color')) {
newGenome[key] = Math.max(0, Math.min(255, Math.floor(newGenome[key])));
}
}
}
});
return newGenome;
}
perceiveEnvironment(world) {
const perception = {
nearbyLifeForms: [],
nearbyFood: [],
threats: [],
environmentalFactors: {
temperature: world.temperature,
toxicity: world.toxicity,
resourceDensity: world.resourceDensity
}
};
// 視覚範囲内の検索
world.lifeForms.forEach(other => {
if (other === this) return;
const dx = other.x - this.x;
const dy = other.y - this.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= this.vision.range) {
const angle = Math.atan2(dy, dx);
const relativeAngle = Math.abs(angle - this.vision.direction);
if (relativeAngle <= this.vision.angle / 2) {
perception.nearbyLifeForms.push({
lifeForm: other,
distance,
angle,
threat: this.assessThreat(other)
});
}
}
});
// 食物の検知
world.food.forEach(food => {
const dx = food.x - this.x;
const dy = food.y - this.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= this.vision.range) {
perception.nearbyFood.push({ food, distance });
}
});
return perception;
}
assessThreat(other) {
const sizeDiff = other.genome.size - this.genome.size;
const aggressionDiff = other.genome.aggressionLevel - this.genome.aggressionLevel;
return sizeDiff * 0.5 + aggressionDiff * 0.3;
}
makeDecision(perception) {
// 高度な意思決定システム
const weights = {
hunger: this.energy < 60 ? 1.5 : 0.3,
reproduction: this.energy > this.genome.reproductionThreshold ? 1.2 : 0,
social: this.genome.socialTendency,
survival: this.health < 50 ? 2.0 : 0.5,
exploration: this.genome.intelligence * 0.5
};
let bestAction = 'explore';
let bestScore = 0;
// 食物探索
if (perception.nearbyFood.length > 0) {
const score = weights.hunger * 2;
if (score > bestScore) {
bestAction = 'feed';
bestScore = score;
}
}
// 繁殖行動
const reproductionCandidates = perception.nearbyLifeForms.filter(
p => p.threat < 0 && p.lifeForm.energy > 50
);
if (reproductionCandidates.length > 0 && this.reproductionCooldown === 0) {
const score = weights.reproduction;
if (score > bestScore) {
bestAction = 'reproduce';
bestScore = score;
}
}
// 脅威回避
const threats = perception.nearbyLifeForms.filter(p => p.threat > 0.5);
if (threats.length > 0) {
const score = weights.survival * 1.5;
if (score > bestScore) {
bestAction = 'flee';
bestScore = score;
}
}
// 社会的行動
const socialCandidates = perception.nearbyLifeForms.filter(
p => Math.abs(p.threat) < 0.3
);
if (socialCandidates.length > 0) {
const score = weights.social;
if (score > bestScore) {
bestAction = 'social';
bestScore = score;
}
}
return bestAction;
}
update(world) {
this.age++;
this.reproductionCooldown = Math.max(0, this.reproductionCooldown - 1);
// 環境ダメージ
if (world.toxicity > 0.5) {
this.health -= (world.toxicity - 0.5) * 10 * (1 - this.genome.immuneSystem);
}
// 代謝コスト
this.energy -= this.genome.metabolicRate * (this.genome.size / 5);
const perception = this.perceiveEnvironment(world);
const action = this.makeDecision(perception);
this.executeAction(action, perception, world);
// 死亡判定
if (this.energy <= 0 || this.health <= 0 || this.age > 1000) {
return false;
}
// 境界チェック
this.x = Math.max(this.genome.size, Math.min(world.width - this.genome.size, this.x));
this.y = Math.max(this.genome.size, Math.min(world.height - this.genome.size, this.y));
return true;
}
executeAction(action, perception, world) {
this.state = action;
switch (action) {
case 'feed':
const nearestFood = perception.nearbyFood.reduce((nearest, current) =>
!nearest || current.distance < nearest.distance ? current : nearest, null);
if (nearestFood) {
this.moveToward(nearestFood.food.x, nearestFood.food.y);
if (nearestFood.distance < this.genome.size) {
this.energy += 30 * this.genome.energyEfficiency;
this.energy = Math.min(this.maxEnergy, this.energy);
world.removeFood(nearestFood.food);
}
}
break;
case 'reproduce':
const partner = perception.nearbyLifeForms.find(p =>
p.threat < 0 && p.lifeForm.energy > 50 && p.distance < 20);
if (partner && this.energy > this.genome.reproductionThreshold) {
this.reproduce(partner.lifeForm, world);
}
break;
case 'flee':
const threat = perception.nearbyLifeForms.find(p => p.threat > 0.5);
if (threat) {
this.moveAway(threat.lifeForm.x, threat.lifeForm.y);
}
break;
case 'social':
const socialTarget = perception.nearbyLifeForms.find(p => Math.abs(p.threat) < 0.3);
if (socialTarget) {
this.moveToward(socialTarget.lifeForm.x, socialTarget.lifeForm.y);
if (socialTarget.distance < 15) {
this.energy += 2; // 社会的利益
socialTarget.lifeForm.energy += 2;
}
}
break;
default: // explore
this.explore();
}
// 視線方向の更新
this.vision.direction += (Math.random() - 0.5) * 0.2;
}
moveToward(targetX, targetY) {
const dx = targetX - this.x;
const dy = targetY - this.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
this.x += (dx / distance) * this.genome.speed;
this.y += (dy / distance) * this.genome.speed;
}
}
moveAway(targetX, targetY) {
const dx = this.x - targetX;
const dy = this.y - targetY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
this.x += (dx / distance) * this.genome.speed * 1.5;
this.y += (dy / distance) * this.genome.speed * 1.5;
}
}
explore() {
this.x += (Math.random() - 0.5) * this.genome.speed;
this.y += (Math.random() - 0.5) * this.genome.speed;
}
reproduce(partner, world) {
if (this.reproductionCooldown > 0) return;
const childX = (this.x + partner.x) / 2 + (Math.random() - 0.5) * 20;
const childY = (this.y + partner.y) / 2 + (Math.random() - 0.5) * 20;
// 遺伝子の交叉
const childGenome = this.crossoverGenome(this.genome, partner.genome);
const child = new LifeForm(childX, childY, childGenome);
world.addLifeForm(child);
this.energy -= 30;
partner.energy -= 30;
this.reproductionCooldown = 50;
partner.reproductionCooldown = 50;
}
crossoverGenome(genome1, genome2) {
const childGenome = {};
Object.keys(genome1).forEach(key => {
if (key === 'generation') {
childGenome[key] = Math.max(genome1[key], genome2[key]) + 1;
} else {
// ランダムに親の遺伝子を選択
childGenome[key] = Math.random() < 0.5 ? genome1[key] : genome2[key];
}
});
return this.mutateGenome(childGenome);
}
}
// 世界環境クラス
class World {
constructor(width, height) {
this.width = width;
this.height = height;
this.lifeForms = [];
this.food = [];
this.temperature = 0.5;
this.toxicity = 0.1;
this.resourceDensity = 0.7;
this.time = 0;
this.statistics = {
totalBorn: 0,
totalDied: 0,
generations: new Map()
};
}
addLifeForm(lifeForm) {
this.lifeForms.push(lifeForm);
this.statistics.totalBorn++;
const gen = lifeForm.generation;
this.statistics.generations.set(gen, (this.statistics.generations.get(gen) || 0) + 1);
}
removeLifeForm(lifeForm) {
const index = this.lifeForms.indexOf(lifeForm);
if (index > -1) {
this.lifeForms.splice(index, 1);
this.statistics.totalDied++;
}
}
addFood(x, y) {
this.food.push({ x, y, energy: 25 });
}
removeFood(food) {
const index = this.food.indexOf(food);
if (index > -1) {
this.food.splice(index, 1);
}
}
update() {
this.time++;
// 環境変化
this.temperature += (Math.random() - 0.5) * 0.01;
this.temperature = Math.max(0, Math.min(1, this.temperature));
this.toxicity += (Math.random() - 0.5) * 0.005;
this.toxicity = Math.max(0, Math.min(1, this.toxicity));
// 生命体の更新
this.lifeForms = this.lifeForms.filter(lifeForm => lifeForm.update(this));
// 食物の生成
if (Math.random() < this.resourceDensity * 0.1) {
this.addFood(
Math.random() * this.width,
Math.random() * this.height
);
}
// 食物の腐敗
this.food = this.food.filter(() => Math.random() > 0.01);
// 災害イベント
if (Math.random() < 0.001) {
this.catastrophicEvent();
}
}
catastrophicEvent() {
this.toxicity += 0.3;
// 一部の生命体にダメージ
this.lifeForms.forEach(lifeForm => {
if (Math.random() < 0.3) {
lifeForm.health -= 20;
}
});
}
getStatistics() {
const avgGeneration = this.lifeForms.reduce((sum, lf) => sum + lf.generation, 0) / this.lifeForms.length || 0;
const avgEnergy = this.lifeForms.reduce((sum, lf) => sum + lf.energy, 0) / this.lifeForms.length || 0;
const avgSize = this.lifeForms.reduce((sum, lf) => sum + lf.genome.size, 0) / this.lifeForms.length || 0;
return {
population: this.lifeForms.length,
foodCount: this.food.length,
avgGeneration: avgGeneration.toFixed(1),
avgEnergy: avgEnergy.toFixed(1),
avgSize: avgSize.toFixed(1),
temperature: (this.temperature * 100).toFixed(1),
toxicity: (this.toxicity * 100).toFixed(1),
time: this.time
};
}
}
// メインコンポーネント
const ArtificialLifeSimulator = () => {
const canvasRef = useRef(null);
const worldRef = useRef(null);
const animationRef = useRef(null);
const [isRunning, setIsRunning] = useState(false);
const [statistics, setStatistics] = useState({});
const [selectedLifeForm, setSelectedLifeForm] = useState(null);
const [showSettings, setShowSettings] = useState(false);
// 初期化
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
worldRef.current = new World(canvas.width, canvas.height);
// 初期生命体を生成
for (let i = 0; i < 20; i++) {
const lifeForm = new LifeForm(
Math.random() * canvas.width,
Math.random() * canvas.height
);
worldRef.current.addLifeForm(lifeForm);
}
// 初期食物を生成
for (let i = 0; i < 50; i++) {
worldRef.current.addFood(
Math.random() * canvas.width,
Math.random() * canvas.height
);
}
setStatistics(worldRef.current.getStatistics());
}, []);
// 描画関数
const draw = useCallback(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
const world = worldRef.current;
if (!ctx || !world) return;
// 背景
const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
gradient.addColorStop(0, `hsl(220, 20%, ${5 + world.temperature * 10}%)`);
gradient.addColorStop(1, `hsl(220, 30%, ${10 + world.temperature * 15}%)`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 毒性オーバーレイ
if (world.toxicity > 0.3) {
ctx.fillStyle = `rgba(255, 0, 0, ${(world.toxicity - 0.3) * 0.1})`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// 食物の描画
world.food.forEach(food => {
ctx.fillStyle = '#4ade80';
ctx.beginPath();
ctx.arc(food.x, food.y, 3, 0, Math.PI * 2);
ctx.fill();
// 食物の光効果
const foodGradient = ctx.createRadialGradient(food.x, food.y, 0, food.x, food.y, 8);
foodGradient.addColorStop(0, 'rgba(74, 222, 128, 0.3)');
foodGradient.addColorStop(1, 'rgba(74, 222, 128, 0)');
ctx.fillStyle = foodGradient;
ctx.beginPath();
ctx.arc(food.x, food.y, 8, 0, Math.PI * 2);
ctx.fill();
});
// 生命体の描画
world.lifeForms.forEach(lifeForm => {
const { x, y, genome, state, energy, health, vision, generation } = lifeForm;
// 選択された生命体のハイライト
if (selectedLifeForm === lifeForm) {
ctx.strokeStyle = '#fbbf24';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(x, y, genome.size + 5, 0, Math.PI * 2);
ctx.stroke();
}
// 視野の描画
if (selectedLifeForm === lifeForm) {
ctx.strokeStyle = 'rgba(251, 191, 36, 0.3)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.arc(x, y, vision.range,
vision.direction - vision.angle / 2,
vision.direction + vision.angle / 2);
ctx.lineTo(x, y);
ctx.stroke();
}
// 生命体本体
const bodyGradient = ctx.createRadialGradient(x, y, 0, x, y, genome.size);
const alpha = Math.max(0.7, health / 100);
bodyGradient.addColorStop(0, `rgba(${genome.colorR}, ${genome.colorG}, ${genome.colorB}, ${alpha})`);
bodyGradient.addColorStop(1, `rgba(${genome.colorR * 0.7}, ${genome.colorG * 0.7}, ${genome.colorB * 0.7}, ${alpha * 0.5})`);
ctx.fillStyle = bodyGradient;
ctx.beginPath();
ctx.arc(x, y, genome.size, 0, Math.PI * 2);
ctx.fill();
// 状態に応じた効果
switch (state) {
case 'feeding':
ctx.strokeStyle = '#10b981';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(x, y, genome.size + 2, 0, Math.PI * 2);
ctx.stroke();
break;
case 'reproducing':
ctx.strokeStyle = '#ec4899';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(x, y, genome.size + 3, 0, Math.PI * 2);
ctx.stroke();
break;
case 'fleeing':
ctx.strokeStyle = '#ef4444';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(x, y, genome.size + 1, 0, Math.PI * 2);
ctx.stroke();
break;
}
// エネルギーバー
if (selectedLifeForm === lifeForm) {
const barWidth = 30;
const barHeight = 4;
const barX = x - barWidth / 2;
const barY = y - genome.size - 15;
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
ctx.fillRect(barX, barY, barWidth, barHeight);
ctx.fillStyle = energy > 60 ? '#10b981' : energy > 30 ? '#f59e0b' : '#ef4444';
ctx.fillRect(barX, barY, (energy / 100) * barWidth, barHeight);
// 世代表示
ctx.fillStyle = '#ffffff';
ctx.font = '10px monospace';
ctx.textAlign = 'center';
ctx.fillText(`Gen: ${generation}`, x, barY - 5);
}
});
// 統計情報の更新
setStatistics(world.getStatistics());
}, [selectedLifeForm]);
// アニメーションループ
const animate = useCallback(() => {
if (!isRunning) return;
const world = worldRef.current;
if (world) {
world.update();
draw();
}
animationRef.current = requestAnimationFrame(animate);
}, [isRunning, draw]);
useEffect(() => {
if (isRunning) {
animate();
} else {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
}
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}, [isRunning, animate]);
// 初期描画
useEffect(() => {
if (!isRunning) {
draw();
}
}, [draw, isRunning]);
// キャンバスクリック処理
const handleCanvasClick = (e) => {
const canvas = canvasRef.current;
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const world = worldRef.current;
if (!world) return;
// 最も近い生命体を選択
let nearest = null;
let minDistance = Infinity;
world.lifeForms.forEach(lifeForm => {
const dx = lifeForm.x - x;
const dy = lifeForm.y - y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < lifeForm.genome.size + 10 && distance < minDistance) {
nearest = lifeForm;
minDistance = distance;
}
});
setSelectedLifeForm(nearest);
};
const resetSimulation = () => {
const canvas = canvasRef.current;
if (!canvas) return;
worldRef.current = new World(canvas.width, canvas.height);
for (let i = 0; i < 20; i++) {
const lifeForm = new LifeForm(
Math.random() * canvas.width,
Math.random() * canvas.height
);
worldRef.current.addLifeForm(lifeForm);
}
for (let i = 0; i < 50; i++) {
worldRef.current.addFood(
Math.random() * canvas.width,
Math.random() * canvas.height
);
}
setSelectedLifeForm(null);
draw();
};
return (
<div className="w-full h-screen bg-gray-900 text-white p-4">
<div className="flex flex-col h-full">
{/* ヘッダー */}
<div className="flex justify-between items-center mb-4">
<h1 className="text-2xl font-bold bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent">
高度人工生命シミュレーター
</h1>
<div className="flex gap-2">
<button
onClick={() => setIsRunning(!isRunning)}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
>
{isRunning ? <Pause size={16} /> : <Play size={16} />}
{isRunning ? 'Pause' : 'Start'}
</button>
<button
onClick={resetSimulation}
className="flex items-center gap-2 px-4 py-2 bg-gray-600 hover:bg-gray-700 rounded-lg transition-colors"
>
<RotateCcw size={16} />
Reset
</button>
<button
onClick={() => setShowSettings(!showSettings)}
className="flex items-center gap-2 px-4 py-2 bg-purple-600 hover:bg-purple-700 rounded-lg transition-colors"
>
<Settings size={16} />
Stats
</button>
</div>
</div>
<div className="flex flex-1 gap-4">
{/* シミュレーション画面 */}
<div className="flex-1">
<canvas
ref={canvasRef}
width={800}
height={600}
className="border border-gray-700 rounded-lg cursor-crosshair bg-gray-800"
onClick={handleCanvasClick}
/>
</div>
{/* サイドパネル */}
<div className="w-80 space-y-4">
{/* 環境統計 */}
<div className="bg-gray-800 p-4 rounded-lg">
<h3 className="text-lg font-semibold mb-3 flex items-center gap-2">
<Activity size={18} />
環境統計
</h3>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span>個体数:</span>
<span className="text-green-400">{statistics.population}</span>
</div>
<div className="flex justify-between">
<span>食物数:</span>
<span className="text-blue-400">{statistics.foodCount}</span>
</div>
<div className="flex justify-between">
<span>平均世代:</span>
<span className="text-purple-400">{statistics.avgGeneration}</span>
</div>
<div className="flex justify-between">
<span>平均エネルギー:</span>
<span className="text-yellow-400">{statistics.avgEnergy}</span>
</div>
<div className="flex justify-between">
<span>平均サイズ:</span>
<span className="text-cyan-400">{statistics.avgSize}</span>
</div>
<div className="flex justify-between">
<span>温度:</span>
<span className="text-red-400">{statistics.temperature}%</span>
</div>
<div className="flex justify-between">
<span>毒性:</span>
<span className="text-orange-400">{statistics.toxicity}%</span>
</div>
<div className="flex justify-between">
<span>時間:</span>
<span className="text-gray-400">{statistics.time}</span>
</div>
</div>
</div>
{/* 選択された生命体の詳細 */}
{selectedLifeForm && (
<div className="bg-gray-800 p-4 rounded-lg">
<h3 className="text-lg font-semibold mb-3 flex items-center gap-2">
<Eye size={18} />
選択された個体
</h3>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span>世代:</span>
<span className="text-purple-400">{selectedLifeForm.generation}</span>
</div>
<div className="flex justify-between">
<span>年齢:</span>
<span className="text-gray-400">{selectedLifeForm.age}</span>
</div>
<div className="flex justify-between">
<span>エネルギー:</span>
<span className="text-yellow-400">{selectedLifeForm.energy.toFixed(1)}</span>
</div>
<div className="flex justify-between">
<span>健康:</span>
<span className="text-green-400">{selectedLifeForm.health.toFixed(1)}</span>
</div>
<div className="flex justify-between">
<span>状態:</span>
<span className="text-blue-400">{selectedLifeForm.state}</span>
</div>
<div className="flex justify-between">
<span>サイズ:</span>
<span className="text-cyan-400">{selectedLifeForm.genome.size.toFixed(1)}</span>
</div>
<div className="flex justify-between">
<span>速度:</span>
<span className="text-indigo-400">{selectedLifeForm.genome.speed.toFixed(2)}</span>
</div>
<div className="flex justify-between">
<span>知能:</span>
<span className="text-pink-400">{(selectedLifeForm.genome.intelligence * 100).toFixed(0)}%</span>
</div>
<div className="flex justify-between">
<span>社会性:</span>
<span className="text-teal-400">{(selectedLifeForm.genome.socialTendency * 100).toFixed(0)}%</span>
</div>
</div>
</div>
)}
{/* コントロールパネル */}
<div className="bg-gray-800 p-4 rounded-lg">
<h3 className="text-lg font-semibold mb-3 flex items-center gap-2">
<Zap size={18} />
環境操作
</h3>
<div className="space-y-3">
<button
onClick={() => {
const world = worldRef.current;
if (world) {
for (let i = 0; i < 10; i++) {
world.addFood(
Math.random() * 800,
Math.random() * 600
);
}
}
}}
className="w-full px-3 py-2 bg-green-600 hover:bg-green-700 rounded transition-colors text-sm"
>
食物を追加
</button>
<button
onClick={() => {
const world = worldRef.current;
if (world) {
const lifeForm = new LifeForm(
Math.random() * 800,
Math.random() * 600
);
world.addLifeForm(lifeForm);
}
}}
className="w-full px-3 py-2 bg-blue-600 hover:bg-blue-700 rounded transition-colors text-sm"
>
個体を追加
</button>
<button
onClick={() => {
const world = worldRef.current;
if (world) {
world.catastrophicEvent();
}
}}
className="w-full px-3 py-2 bg-red-600 hover:bg-red-700 rounded transition-colors text-sm"
>
災害を発生
</button>
</div>
</div>
{/* 説明 */}
<div className="bg-gray-800 p-4 rounded-lg">
<h3 className="text-lg font-semibold mb-3 flex items-center gap-2">
<Heart size={18} />
使用方法
</h3>
<div className="text-sm text-gray-300 space-y-2">
<p>• 生命体をクリックして詳細を表示</p>
<p>• 緑の点は食物、色付きの円は生命体</p>
<p>• 生命体は進化し、環境に適応します</p>
<p>• 状態により異なる色の輪が表示されます</p>
<p>• 災害や環境変化が進化を促進します</p>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default ArtificialLifeSimulator;
