Claude Opus4.6 でコーディングを試す.
以前の投稿で, Claude Opus 4.5でのコーディング能力を試すためにWeasel Program (Dawkins, 1986) を組んだ. 今回は, さらにその最新版であるClaude Opus 4.6でコーディング能力を試してみたい.
プロンプトと出力結果は以下の通りである.
Weasel Program を可能な限りリッチな表現を用いたコードで書いて.
その過程をアニメーションとして出力できるようにして.
import { useState, useEffect, useRef, useCallback } from "react";
const TARGET = "METHINKS IT IS LIKE A WEASEL";
const CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ";
const MUTATION_RATE = 0.04;
const OFFSPRING_COUNT = 100;
function randomChar() {
return CHARSET[Math.floor(Math.random() * CHARSET.length)];
}
function randomString(len) {
return Array.from({ length: len }, () => randomChar()).join("");
}
function fitness(str) {
let score = 0;
for (let i = 0; i < str.length; i++) {
if (str[i] === TARGET[i]) score++;
}
return score;
}
function mutate(parent, rate) {
return parent
.split("")
.map((ch) => (Math.random() < rate ? randomChar() : ch))
.join("");
}
function evolveStep(parent) {
let bestChild = parent;
let bestFit = fitness(parent);
for (let i = 0; i < OFFSPRING_COUNT; i++) {
const child = mutate(parent, MUTATION_RATE);
const f = fitness(child);
if (f >= bestFit) {
bestFit = f;
bestChild = child;
}
}
return { best: bestChild, fitness: bestFit };
}
// DNA helix decorative component
function DNAHelix({ height = 200 }) {
const steps = 20;
return (
<svg width="60" height={height} viewBox={`0 0 60 ${height}`} style={{ opacity: 0.15 }}>
{Array.from({ length: steps }).map((_, i) => {
const y = (i / steps) * height;
const x1 = 30 + Math.sin((i / steps) * Math.PI * 4) * 20;
const x2 = 30 - Math.sin((i / steps) * Math.PI * 4) * 20;
return (
<g key={i}>
<circle cx={x1} cy={y} r="3" fill="#00ffc8" />
<circle cx={x2} cy={y} r="3" fill="#ff6b9d" />
{i % 2 === 0 && (
<line x1={x1} y1={y} x2={x2} y2={y} stroke="#ffffff" strokeWidth="0.5" opacity="0.3" />
)}
</g>
);
})}
</svg>
);
}
export default function WeaselProgram() {
const [state, setState] = useState("idle"); // idle | running | complete
const [current, setCurrent] = useState("");
const [generation, setGeneration] = useState(0);
const [currentFitness, setCurrentFitness] = useState(0);
const [history, setHistory] = useState([]);
const [speed, setSpeed] = useState(60);
const [charStates, setCharStates] = useState([]);
const [particles, setParticles] = useState([]);
const intervalRef = useRef(null);
const parentRef = useRef("");
const genRef = useRef(0);
const historyRef = useRef([]);
const particleId = useRef(0);
const spawnParticles = useCallback((indices) => {
const newParticles = indices.map((idx) => ({
id: particleId.current++,
x: idx * 26 + 13,
born: Date.now(),
}));
setParticles((prev) => [...prev.slice(-30), ...newParticles]);
}, []);
const start = useCallback(() => {
const initial = randomString(TARGET.length);
parentRef.current = initial;
genRef.current = 0;
historyRef.current = [{ gen: 0, str: initial, fitness: fitness(initial) }];
setCurrent(initial);
setGeneration(0);
setCurrentFitness(fitness(initial));
setHistory(historyRef.current);
setState("running");
setParticles([]);
setCharStates(
initial.split("").map((ch, i) => ({
char: ch,
matched: ch === TARGET[i],
justMatched: false,
}))
);
}, []);
useEffect(() => {
if (state !== "running") {
if (intervalRef.current) clearInterval(intervalRef.current);
return;
}
intervalRef.current = setInterval(() => {
const prev = parentRef.current;
const { best, fitness: fit } = evolveStep(prev);
const newlyMatched = [];
const chars = best.split("").map((ch, i) => {
const wasMatched = prev[i] === TARGET[i];
const isMatched = ch === TARGET[i];
if (isMatched && !wasMatched) newlyMatched.push(i);
return { char: ch, matched: isMatched, justMatched: isMatched && !wasMatched };
});
if (newlyMatched.length > 0) spawnParticles(newlyMatched);
parentRef.current = best;
genRef.current += 1;
if (genRef.current % 1 === 0 || fit === TARGET.length) {
historyRef.current = [
...historyRef.current,
{ gen: genRef.current, str: best, fitness: fit },
];
}
setCurrent(best);
setGeneration(genRef.current);
setCurrentFitness(fit);
setHistory([...historyRef.current]);
setCharStates(chars);
if (fit === TARGET.length) {
clearInterval(intervalRef.current);
setState("complete");
}
}, Math.max(10, 200 - speed * 2));
return () => clearInterval(intervalRef.current);
}, [state, speed, spawnParticles]);
// Clean old particles
useEffect(() => {
const timer = setInterval(() => {
setParticles((prev) => prev.filter((p) => Date.now() - p.born < 1500));
}, 500);
return () => clearInterval(timer);
}, []);
const progressPct = (currentFitness / TARGET.length) * 100;
const fitnessData = history.map((h) => ({
gen: h.gen,
pct: (h.fitness / TARGET.length) * 100,
}));
return (
<div style={{
minHeight: "100vh",
background: "linear-gradient(160deg, #0a0a0f 0%, #0d1117 30%, #0f0a1a 60%, #0a0a0f 100%)",
color: "#e0e0e0",
fontFamily: "'JetBrains Mono', 'Fira Code', 'Source Code Pro', monospace",
overflow: "hidden",
position: "relative",
}}>
{/* Animated background grid */}
<div style={{
position: "fixed", inset: 0, zIndex: 0, opacity: 0.03,
backgroundImage: `
linear-gradient(rgba(0,255,200,0.3) 1px, transparent 1px),
linear-gradient(90deg, rgba(0,255,200,0.3) 1px, transparent 1px)
`,
backgroundSize: "40px 40px",
}} />
{/* Floating DNA helices */}
<div style={{ position: "fixed", top: 40, left: 20, zIndex: 0 }}>
<DNAHelix height={600} />
</div>
<div style={{ position: "fixed", top: 100, right: 30, zIndex: 0 }}>
<DNAHelix height={500} />
</div>
<div style={{ position: "relative", zIndex: 1, maxWidth: 900, margin: "0 auto", padding: "40px 20px" }}>
{/* Header */}
<div style={{ textAlign: "center", marginBottom: 48 }}>
<div style={{
fontSize: 11, letterSpacing: 6, color: "#00ffc8", marginBottom: 12,
textTransform: "uppercase", fontWeight: 600,
}}>
Richard Dawkins' Evolutionary Algorithm
</div>
<h1 style={{
fontSize: "clamp(28px, 5vw, 52px)", fontWeight: 800, margin: 0,
background: "linear-gradient(135deg, #00ffc8 0%, #7b68ee 40%, #ff6b9d 80%, #ffd700 100%)",
WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent",
letterSpacing: -1, lineHeight: 1.1,
}}>
Weasel Program
</h1>
<p style={{ color: "#666", fontSize: 13, marginTop: 12, maxWidth: 500, margin: "12px auto 0", lineHeight: 1.6 }}>
Cumulative selection demonstrates how non-random selection of random variation can produce complex results — far faster than pure chance.
</p>
</div>
{/* Target display */}
<div style={{
textAlign: "center", marginBottom: 32, padding: "16px 24px",
background: "rgba(0,255,200,0.03)", borderRadius: 12,
border: "1px solid rgba(0,255,200,0.08)",
}}>
<div style={{ fontSize: 10, letterSpacing: 4, color: "#555", marginBottom: 8, textTransform: "uppercase" }}>
Target Phenotype
</div>
<div style={{ fontSize: "clamp(14px, 2.5vw, 22px)", letterSpacing: 4, color: "#00ffc8", fontWeight: 700 }}>
{TARGET}
</div>
</div>
{/* Main evolution display */}
<div style={{
background: "rgba(255,255,255,0.02)",
border: "1px solid rgba(255,255,255,0.06)",
borderRadius: 16, padding: "32px 24px", marginBottom: 24,
position: "relative", overflow: "hidden",
backdropFilter: "blur(10px)",
boxShadow: state === "complete"
? "0 0 60px rgba(0,255,200,0.15), inset 0 0 60px rgba(0,255,200,0.03)"
: "0 4px 40px rgba(0,0,0,0.3)",
transition: "box-shadow 0.8s ease",
}}>
{/* Particle effects */}
{particles.map((p) => {
const age = (Date.now() - p.born) / 1500;
return (
<div key={p.id} style={{
position: "absolute",
left: `calc(50% - ${(TARGET.length * 26) / 2}px + ${p.x}px)`,
top: "50%",
width: 4, height: 4, borderRadius: "50%",
background: "#00ffc8",
opacity: Math.max(0, 1 - age),
transform: `translateY(${-age * 60}px) scale(${1 + age * 2})`,
boxShadow: "0 0 12px #00ffc8",
pointerEvents: "none",
transition: "none",
}} />
);
})}
<div style={{ fontSize: 10, letterSpacing: 3, color: "#555", marginBottom: 16, textTransform: "uppercase", textAlign: "center" }}>
Current Genome — Generation {generation}
</div>
{/* Character display */}
<div style={{
display: "flex", justifyContent: "center", flexWrap: "wrap",
gap: 4, marginBottom: 24,
}}>
{(charStates.length > 0 ? charStates : TARGET.split("").map((ch) => ({ char: " ", matched: false, justMatched: false }))).map((cs, i) => (
<div key={i} style={{
width: 26, height: 36,
display: "flex", alignItems: "center", justifyContent: "center",
fontSize: 16, fontWeight: 700,
borderRadius: 4,
background: cs.matched
? "rgba(0,255,200,0.15)"
: "rgba(255,255,255,0.03)",
border: cs.matched
? "1px solid rgba(0,255,200,0.4)"
: "1px solid rgba(255,255,255,0.06)",
color: cs.matched ? "#00ffc8" : "#666",
transition: "all 0.3s ease",
animation: cs.justMatched ? "pop 0.4s ease" : "none",
boxShadow: cs.justMatched ? "0 0 20px rgba(0,255,200,0.5)" : "none",
}}>
{cs.char}
</div>
))}
</div>
{/* Progress bar */}
<div style={{ marginBottom: 16 }}>
<div style={{
display: "flex", justifyContent: "space-between", fontSize: 11, color: "#555", marginBottom: 6,
}}>
<span>Fitness</span>
<span>{currentFitness}/{TARGET.length} ({progressPct.toFixed(1)}%)</span>
</div>
<div style={{
height: 6, borderRadius: 3,
background: "rgba(255,255,255,0.05)",
overflow: "hidden",
}}>
<div style={{
height: "100%", borderRadius: 3,
width: `${progressPct}%`,
background: progressPct === 100
? "linear-gradient(90deg, #00ffc8, #7b68ee, #ff6b9d)"
: "linear-gradient(90deg, #00ffc8, #7b68ee)",
transition: "width 0.3s ease",
boxShadow: "0 0 12px rgba(0,255,200,0.3)",
}} />
</div>
</div>
{/* Stats row */}
<div style={{ display: "flex", justifyContent: "center", gap: 32, fontSize: 12 }}>
{[
{ label: "Generation", value: generation },
{ label: "Mutation Rate", value: `${(MUTATION_RATE * 100).toFixed(0)}%` },
{ label: "Offspring/Gen", value: OFFSPRING_COUNT },
].map(({ label, value }) => (
<div key={label} style={{ textAlign: "center" }}>
<div style={{ color: "#444", fontSize: 10, letterSpacing: 2, textTransform: "uppercase", marginBottom: 4 }}>
{label}
</div>
<div style={{ color: "#aaa", fontWeight: 700, fontSize: 18 }}>{value}</div>
</div>
))}
</div>
</div>
{/* Fitness graph */}
{fitnessData.length > 1 && (
<div style={{
background: "rgba(255,255,255,0.02)",
border: "1px solid rgba(255,255,255,0.06)",
borderRadius: 16, padding: "24px", marginBottom: 24,
}}>
<div style={{ fontSize: 10, letterSpacing: 3, color: "#555", marginBottom: 16, textTransform: "uppercase" }}>
Fitness Over Generations
</div>
<svg width="100%" height="120" viewBox={`0 0 ${Math.max(fitnessData.length * 3, 200)} 120`} preserveAspectRatio="none" style={{ display: "block" }}>
<defs>
<linearGradient id="graphGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#00ffc8" stopOpacity="0.3" />
<stop offset="100%" stopColor="#00ffc8" stopOpacity="0" />
</linearGradient>
</defs>
{/* Area fill */}
<path
d={
`M0,120 ` +
fitnessData.map((d, i) => {
const x = (i / Math.max(fitnessData.length - 1, 1)) * Math.max(fitnessData.length * 3, 200);
const y = 120 - (d.pct / 100) * 110;
return `L${x},${y}`;
}).join(" ") +
` L${Math.max(fitnessData.length * 3, 200)},120 Z`
}
fill="url(#graphGrad)"
/>
{/* Line */}
<path
d={fitnessData.map((d, i) => {
const x = (i / Math.max(fitnessData.length - 1, 1)) * Math.max(fitnessData.length * 3, 200);
const y = 120 - (d.pct / 100) * 110;
return `${i === 0 ? "M" : "L"}${x},${y}`;
}).join(" ")}
fill="none" stroke="#00ffc8" strokeWidth="2"
/>
</svg>
</div>
)}
{/* Evolution log */}
{history.length > 0 && (
<div style={{
background: "rgba(255,255,255,0.02)",
border: "1px solid rgba(255,255,255,0.06)",
borderRadius: 16, padding: "24px", marginBottom: 24,
maxHeight: 280, overflowY: "auto",
}}>
<div style={{ fontSize: 10, letterSpacing: 3, color: "#555", marginBottom: 12, textTransform: "uppercase" }}>
Evolution Log
</div>
<div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
{history.slice(-40).map((h, idx) => (
<div key={idx} style={{
display: "flex", gap: 16, alignItems: "center",
fontSize: 12, padding: "4px 8px", borderRadius: 4,
background: h.fitness === TARGET.length ? "rgba(0,255,200,0.05)" : "transparent",
}}>
<span style={{ color: "#444", minWidth: 60, fontSize: 10 }}>
Gen {String(h.gen).padStart(4, "0")}
</span>
<span style={{ letterSpacing: 2, flex: 1 }}>
{h.str.split("").map((ch, ci) => (
<span key={ci} style={{
color: ch === TARGET[ci] ? "#00ffc8" : "#3a3a4a",
}}>
{ch}
</span>
))}
</span>
<span style={{ color: "#555", fontSize: 10, minWidth: 40, textAlign: "right" }}>
{((h.fitness / TARGET.length) * 100).toFixed(0)}%
</span>
</div>
))}
</div>
</div>
)}
{/* Controls */}
<div style={{
display: "flex", gap: 12, justifyContent: "center", alignItems: "center", flexWrap: "wrap",
}}>
<button
onClick={start}
style={{
padding: "12px 32px", fontSize: 13, fontWeight: 700,
fontFamily: "inherit", letterSpacing: 2, textTransform: "uppercase",
border: "none", borderRadius: 8, cursor: "pointer",
background: state === "complete"
? "linear-gradient(135deg, #ffd700, #ff6b9d)"
: "linear-gradient(135deg, #00ffc8, #7b68ee)",
color: "#0a0a0f",
boxShadow: "0 4px 20px rgba(0,255,200,0.2)",
transition: "all 0.3s ease",
}}
>
{state === "idle" ? "▶ Begin Evolution" : state === "running" ? "⟳ Restart" : "✦ Evolve Again"}
</button>
{state === "running" && (
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<span style={{ fontSize: 10, color: "#555", letterSpacing: 2, textTransform: "uppercase" }}>Speed</span>
<input
type="range" min="10" max="100" value={speed}
onChange={(e) => setSpeed(Number(e.target.value))}
style={{ width: 100, accentColor: "#00ffc8" }}
/>
</div>
)}
</div>
{/* Completion celebration */}
{state === "complete" && (
<div style={{
textAlign: "center", marginTop: 32, padding: 24,
animation: "fadeIn 0.6s ease",
}}>
<div style={{
fontSize: 32, marginBottom: 8,
background: "linear-gradient(135deg, #ffd700, #ff6b9d, #00ffc8)",
WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent",
fontWeight: 800,
}}>
Evolution Complete
</div>
<p style={{ color: "#555", fontSize: 13 }}>
Target reached in <strong style={{ color: "#00ffc8" }}>{generation}</strong> generations.
Pure chance would require ~27<sup>{TARGET.length}</sup> attempts.
</p>
</div>
)}
</div>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700;800&display=swap');
@keyframes pop {
0% { transform: scale(1); }
50% { transform: scale(1.3); }
100% { transform: scale(1); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(0,255,200,0.2); border-radius: 2px; }
* { box-sizing: border-box; }
`}</style>
</div>
);
}
参考文献
- Dawkins, R. (1996). The blind watchmaker: Why the evidence of evolution reveals a universe without design. WW Norton & Company.