Claude Opus 4.7でコーディングを試す
以前の投稿でClaude Opus 4.6でのコーディング能力を試すためにWeasel Programを組んだ. 今回は新たに登場したClaude Opus 4.7でコーディング能力を試す.
プロンプトと出力結果は下記である.
Weasel Program を可能な限りリッチな表現を用いたコードで書いて.
その過程をアニメーションとして出力できるようにして.
結果として,Claude Opus 4.6の段階でも非常に素晴らしい内容を出力していたが,4.7の方がビジュアライズの面で様々な工夫を自動的に考え,やってくれるようになったと感じている.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Weasel Program — Evolutionary Simulation</title>
<style>
:root {
--color-background-primary: #ffffff;
--color-background-secondary: #f5f4ee;
--color-background-tertiary: #faf9f5;
--color-text-primary: #141413;
--color-text-secondary: #6b6a65;
--color-text-tertiary: #9c9a93;
--color-border-tertiary: rgba(20, 20, 19, 0.12);
--color-border-secondary: rgba(20, 20, 19, 0.22);
--border-radius-md: 8px;
--border-radius-lg: 12px;
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
--font-mono: "SF Mono", "Monaco", "Menlo", "Consolas", monospace;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background-primary: #1f1e1c;
--color-background-secondary: #2a2927;
--color-background-tertiary: #161513;
--color-text-primary: #f5f4ee;
--color-text-secondary: #a8a69e;
--color-text-tertiary: #6b6a65;
--color-border-tertiary: rgba(245, 244, 238, 0.14);
--color-border-secondary: rgba(245, 244, 238, 0.25);
}
}
* { box-sizing: border-box; }
body {
margin: 0;
padding: 2rem 1rem;
background: var(--color-background-tertiary);
color: var(--color-text-primary);
font-family: var(--font-sans);
line-height: 1.5;
min-height: 100vh;
}
.container {
max-width: 720px;
margin: 0 auto;
}
h1 {
font-size: 22px;
font-weight: 500;
margin: 0 0 0.5rem;
}
.subtitle {
color: var(--color-text-secondary);
font-size: 14px;
margin: 0 0 2rem;
}
input[type="text"] {
padding: 8px 12px;
border: 0.5px solid var(--color-border-tertiary);
border-radius: var(--border-radius-md);
background: var(--color-background-primary);
color: var(--color-text-primary);
font-size: 14px;
height: 36px;
outline: none;
transition: border-color 0.15s;
}
input[type="text"]:hover { border-color: var(--color-border-secondary); }
input[type="text"]:focus { border-color: var(--color-text-primary); }
input[type="range"] {
-webkit-appearance: none;
appearance: none;
height: 4px;
background: var(--color-border-tertiary);
border-radius: 2px;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
background: var(--color-text-primary);
border-radius: 50%;
cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
width: 18px;
height: 18px;
background: var(--color-text-primary);
border-radius: 50%;
cursor: pointer;
border: none;
}
button {
padding: 8px 16px;
border: 0.5px solid var(--color-border-secondary);
border-radius: var(--border-radius-md);
background: transparent;
color: var(--color-text-primary);
font-size: 14px;
font-family: inherit;
font-weight: 500;
cursor: pointer;
height: 36px;
transition: background 0.15s, transform 0.1s;
}
button:hover:not(:disabled) { background: var(--color-background-secondary); }
button:active:not(:disabled) { transform: scale(0.98); }
button:disabled { opacity: 0.4; cursor: not-allowed; }
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>
</head>
<body>
<div class="container">
<h1>Weasel Program</h1>
<p class="subtitle">Dawkinsの進化シミュレーション — 突然変異と選択だけで目標文字列に収束する過程を観察できます。</p>
<div style="padding: 1rem 0; font-family: var(--font-sans);">
<h2 class="sr-only">Weasel program evolutionary simulation with generation-by-generation visualization</h2>
<div style="display: flex; flex-direction: column; gap: 10px; margin-bottom: 1.25rem;">
<div style="display: flex; align-items: center; gap: 10px;">
<label style="font-size: 13px; color: var(--color-text-secondary); min-width: 80px;">Target</label>
<input id="target" type="text" value="METHINKS IT IS LIKE A WEASEL" style="flex: 1; font-family: var(--font-mono); text-transform: uppercase;" maxlength="60" />
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px;">
<div style="display: flex; align-items: center; gap: 8px;">
<label style="font-size: 13px; color: var(--color-text-secondary); min-width: 60px;">Pop</label>
<input id="pop" type="range" min="20" max="300" value="100" step="10" style="flex: 1;" />
<span id="popOut" style="font-size: 13px; font-weight: 500; min-width: 28px; text-align: right;">100</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<label style="font-size: 13px; color: var(--color-text-secondary); min-width: 60px;">Mutate</label>
<input id="mut" type="range" min="1" max="20" value="5" step="1" style="flex: 1;" />
<span id="mutOut" style="font-size: 13px; font-weight: 500; min-width: 36px; text-align: right;">5%</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<label style="font-size: 13px; color: var(--color-text-secondary); min-width: 60px;">Speed</label>
<input id="spd" type="range" min="10" max="500" value="120" step="10" style="flex: 1;" />
<span id="spdOut" style="font-size: 13px; font-weight: 500; min-width: 48px; text-align: right;">120ms</span>
</div>
</div>
<div style="display: flex; gap: 8px;">
<button id="startBtn" style="flex: 1;">Start evolution</button>
<button id="pauseBtn" disabled>Pause</button>
<button id="resetBtn">Reset</button>
</div>
</div>
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 1.25rem;">
<div style="background: var(--color-background-secondary); border-radius: var(--border-radius-md); padding: 12px;">
<div style="font-size: 12px; color: var(--color-text-secondary); margin-bottom: 4px;">Generation</div>
<div id="statGen" style="font-size: 22px; font-weight: 500; font-variant-numeric: tabular-nums;">0</div>
</div>
<div style="background: var(--color-background-secondary); border-radius: var(--border-radius-md); padding: 12px;">
<div style="font-size: 12px; color: var(--color-text-secondary); margin-bottom: 4px;">Best fit</div>
<div id="statFit" style="font-size: 22px; font-weight: 500; font-variant-numeric: tabular-nums;">0 / 0</div>
</div>
<div style="background: var(--color-background-secondary); border-radius: var(--border-radius-md); padding: 12px;">
<div style="font-size: 12px; color: var(--color-text-secondary); margin-bottom: 4px;">Accuracy</div>
<div id="statAcc" style="font-size: 22px; font-weight: 500; font-variant-numeric: tabular-nums;">0%</div>
</div>
<div style="background: var(--color-background-secondary); border-radius: var(--border-radius-md); padding: 12px;">
<div style="font-size: 12px; color: var(--color-text-secondary); margin-bottom: 4px;">Evaluations</div>
<div id="statEval" style="font-size: 22px; font-weight: 500; font-variant-numeric: tabular-nums;">0</div>
</div>
</div>
<div style="background: var(--color-background-primary); border: 0.5px solid var(--color-border-tertiary); border-radius: var(--border-radius-lg); padding: 16px; margin-bottom: 1rem;">
<div style="font-size: 12px; color: var(--color-text-secondary); margin-bottom: 8px; display: flex; justify-content: space-between;">
<span>Fittest individual</span>
<span id="bestLabel">—</span>
</div>
<div id="bestString" style="font-family: var(--font-mono); font-size: 18px; letter-spacing: 2px; line-height: 1.5; word-break: break-all; min-height: 28px;"></div>
<div style="font-size: 12px; color: var(--color-text-secondary); margin: 10px 0 6px;">Target</div>
<div id="targetString" style="font-family: var(--font-mono); font-size: 18px; letter-spacing: 2px; line-height: 1.5; color: var(--color-text-tertiary); word-break: break-all;"></div>
</div>
<div style="background: var(--color-background-primary); border: 0.5px solid var(--color-border-tertiary); border-radius: var(--border-radius-lg); padding: 16px; margin-bottom: 1rem;">
<div style="font-size: 12px; color: var(--color-text-secondary); margin-bottom: 8px;">Population (brightness = fitness)</div>
<canvas id="popCanvas" style="width: 100%; height: 140px; display: block;"></canvas>
</div>
<div style="background: var(--color-background-primary); border: 0.5px solid var(--color-border-tertiary); border-radius: var(--border-radius-lg); padding: 16px;">
<div style="font-size: 12px; color: var(--color-text-secondary); margin-bottom: 8px; display: flex; justify-content: space-between;">
<span>Fitness over generations</span>
<span style="display: inline-flex; gap: 12px; font-size: 11px;">
<span style="display: inline-flex; align-items: center; gap: 4px;"><span style="width: 10px; height: 2px; background: #1D9E75; display: inline-block;"></span>Best</span>
<span style="display: inline-flex; align-items: center; gap: 4px;"><span style="width: 10px; height: 2px; background: #7F77DD; display: inline-block;"></span>Mean</span>
<span style="display: inline-flex; align-items: center; gap: 4px;"><span style="width: 10px; height: 2px; background: #D85A30; display: inline-block;"></span>Worst</span>
</span>
</div>
<canvas id="chartCanvas" style="width: 100%; height: 180px; display: block;"></canvas>
</div>
</div>
</div>
<script>
(function() {
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ";
const $ = (id) => document.getElementById(id);
let target = "METHINKS IT IS LIKE A WEASEL";
let popSize = 100;
let mutRate = 0.05;
let speed = 120;
let running = false;
let paused = false;
let population = [];
let generation = 0;
let evaluations = 0;
let history = [];
let timer = null;
function randChar() {
return ALPHABET[Math.floor(Math.random() * ALPHABET.length)];
}
function randString(len) {
let s = "";
for (let i = 0; i < len; i++) s += randChar();
return s;
}
function fitness(s, t) {
let f = 0;
for (let i = 0; i < t.length; i++) if (s[i] === t[i]) f++;
return f;
}
function mutate(s, rate) {
let out = "";
for (let i = 0; i < s.length; i++) {
out += Math.random() < rate ? randChar() : s[i];
}
return out;
}
function initPop() {
population = [];
for (let i = 0; i < popSize; i++) {
const s = randString(target.length);
population.push({ s, f: fitness(s, target) });
}
evaluations = popSize;
generation = 0;
history = [];
population.sort((a, b) => b.f - a.f);
record();
}
function record() {
const best = population[0].f;
const worst = population[population.length - 1].f;
const mean = population.reduce((a, b) => a + b.f, 0) / population.length;
history.push({ gen: generation, best, worst, mean });
}
function step() {
const parent = population[0];
const newPop = [parent];
for (let i = 1; i < popSize; i++) {
const child = mutate(parent.s, mutRate);
newPop.push({ s: child, f: fitness(child, target) });
evaluations++;
}
newPop.sort((a, b) => b.f - a.f);
population = newPop;
generation++;
record();
}
function renderBest() {
const best = population[0];
const container = $("bestString");
container.innerHTML = "";
for (let i = 0; i < target.length; i++) {
const ch = best.s[i] || " ";
const match = ch === target[i];
const span = document.createElement("span");
span.textContent = ch === " " ? "\u00A0" : ch;
span.style.display = "inline-block";
span.style.minWidth = "12px";
span.style.textAlign = "center";
span.style.padding = "1px 2px";
span.style.borderRadius = "3px";
span.style.transition = "background 0.3s, color 0.3s";
if (match) {
span.style.background = "#9FE1CB";
span.style.color = "#04342C";
span.style.fontWeight = "500";
} else {
span.style.background = "#FAECE7";
span.style.color = "#712B13";
}
container.appendChild(span);
}
const tc = $("targetString");
tc.innerHTML = "";
for (let i = 0; i < target.length; i++) {
const ch = target[i] === " " ? "\u00A0" : target[i];
const span = document.createElement("span");
span.textContent = ch;
span.style.display = "inline-block";
span.style.minWidth = "12px";
span.style.textAlign = "center";
span.style.padding = "1px 2px";
tc.appendChild(span);
}
$("statGen").textContent = generation;
$("statFit").textContent = best.f + " / " + target.length;
$("statAcc").textContent = Math.round((best.f / target.length) * 100) + "%";
$("statEval").textContent = evaluations.toLocaleString();
$("bestLabel").textContent = "gen " + generation;
}
function drawPopulation() {
const canvas = $("popCanvas");
const dpr = window.devicePixelRatio || 1;
const w = canvas.clientWidth;
const h = canvas.clientHeight;
canvas.width = w * dpr;
canvas.height = h * dpr;
const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
ctx.clearRect(0, 0, w, h);
const n = population.length;
const cols = Math.ceil(Math.sqrt(n * (w / h)));
const rows = Math.ceil(n / cols);
const cellW = w / cols;
const cellH = h / rows;
const pad = 1;
for (let i = 0; i < n; i++) {
const r = Math.floor(i / cols);
const c = i % cols;
const x = c * cellW;
const y = r * cellH;
const frac = population[i].f / target.length;
const t = frac;
const r1 = Math.round(225 - (225 - 15) * t);
const g1 = Math.round(245 - (245 - 158) * t);
const b1 = Math.round(238 - (238 - 117) * t);
ctx.fillStyle = "rgb(" + r1 + "," + g1 + "," + b1 + ")";
ctx.fillRect(x + pad, y + pad, cellW - pad * 2, cellH - pad * 2);
}
}
function drawChart() {
const canvas = $("chartCanvas");
const dpr = window.devicePixelRatio || 1;
const w = canvas.clientWidth;
const h = canvas.clientHeight;
canvas.width = w * dpr;
canvas.height = h * dpr;
const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
ctx.clearRect(0, 0, w, h);
const padL = 28, padR = 8, padT = 8, padB = 20;
const plotW = w - padL - padR;
const plotH = h - padT - padB;
const maxF = target.length;
const maxG = Math.max(10, history.length - 1);
ctx.strokeStyle = "rgba(128,128,128,0.15)";
ctx.lineWidth = 0.5;
ctx.font = "10px " + (getComputedStyle(document.body).fontFamily || "sans-serif");
ctx.fillStyle = "rgba(128,128,128,0.7)";
for (let i = 0; i <= 4; i++) {
const y = padT + (plotH * i) / 4;
ctx.beginPath();
ctx.moveTo(padL, y);
ctx.lineTo(w - padR, y);
ctx.stroke();
const val = Math.round(maxF - (maxF * i) / 4);
ctx.textAlign = "right";
ctx.fillText(val, padL - 4, y + 3);
}
ctx.textAlign = "center";
ctx.fillText("0", padL, h - 4);
ctx.fillText(maxG, w - padR, h - 4);
ctx.fillText("gen", (padL + w - padR) / 2, h - 4);
if (history.length < 2) return;
const xAt = (g) => padL + (g / maxG) * plotW;
const yAt = (v) => padT + plotH - (v / maxF) * plotH;
ctx.fillStyle = "rgba(127,119,221,0.12)";
ctx.beginPath();
ctx.moveTo(xAt(history[0].gen), yAt(history[0].worst));
for (let i = 0; i < history.length; i++) ctx.lineTo(xAt(history[i].gen), yAt(history[i].worst));
for (let i = history.length - 1; i >= 0; i--) ctx.lineTo(xAt(history[i].gen), yAt(history[i].best));
ctx.closePath();
ctx.fill();
const drawLine = (key, color, width) => {
ctx.strokeStyle = color;
ctx.lineWidth = width;
ctx.lineJoin = "round";
ctx.beginPath();
for (let i = 0; i < history.length; i++) {
const x = xAt(history[i].gen);
const y = yAt(history[i][key]);
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.stroke();
};
drawLine("worst", "#D85A30", 1.2);
drawLine("mean", "#7F77DD", 1.2);
drawLine("best", "#1D9E75", 2);
}
function renderAll() {
renderBest();
drawPopulation();
drawChart();
}
function loop() {
if (!running || paused) return;
step();
renderAll();
if (population[0].f >= target.length) {
running = false;
$("startBtn").textContent = "Done — restart";
$("startBtn").disabled = false;
$("pauseBtn").disabled = true;
return;
}
timer = setTimeout(loop, speed);
}
function start() {
const input = $("target").value.toUpperCase().replace(/[^A-Z ]/g, "");
if (input.length < 1) return;
target = input;
$("target").value = target;
popSize = parseInt($("pop").value, 10);
mutRate = parseInt($("mut").value, 10) / 100;
speed = parseInt($("spd").value, 10);
initPop();
renderAll();
running = true;
paused = false;
$("startBtn").disabled = true;
$("pauseBtn").disabled = false;
$("pauseBtn").textContent = "Pause";
timer = setTimeout(loop, speed);
}
function togglePause() {
if (!running) return;
paused = !paused;
$("pauseBtn").textContent = paused ? "Resume" : "Pause";
if (!paused) timer = setTimeout(loop, speed);
}
function reset() {
if (timer) clearTimeout(timer);
running = false;
paused = false;
$("startBtn").disabled = false;
$("startBtn").textContent = "Start evolution";
$("pauseBtn").disabled = true;
$("pauseBtn").textContent = "Pause";
target = $("target").value.toUpperCase().replace(/[^A-Z ]/g, "") || "METHINKS IT IS LIKE A WEASEL";
$("target").value = target;
popSize = parseInt($("pop").value, 10);
mutRate = parseInt($("mut").value, 10) / 100;
initPop();
renderAll();
}
$("pop").addEventListener("input", (e) => {
$("popOut").textContent = e.target.value;
});
$("mut").addEventListener("input", (e) => {
$("mutOut").textContent = e.target.value + "%";
mutRate = parseInt(e.target.value, 10) / 100;
});
$("spd").addEventListener("input", (e) => {
$("spdOut").textContent = e.target.value + "ms";
speed = parseInt(e.target.value, 10);
});
$("startBtn").addEventListener("click", start);
$("pauseBtn").addEventListener("click", togglePause);
$("resetBtn").addEventListener("click", reset);
reset();
window.addEventListener("resize", () => {
drawPopulation();
drawChart();
});
})();
</script>
</body>
</html>
参考文献
- Dawkins, R. (1996). The blind watchmaker: Why the evidence of evolution reveals a universe without design. WW Norton & Company.