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.

コメントを残す

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