GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

粘菌 迷路 9種 — 最短経路を自ら見つけるキャンバス

粘菌 迷路シミュレーションとは、迷路の絵に道を塗る作業ではなく、点が匂いを嗅いで曲がる規則の反復だけで最短経路がひとりでに浮かぶ計算です。ライブラリ・WebGLなしで、画面を這う点の一つ一つが前・左・右の匂いを嗅ぎ濃い方へ曲がる規則を繰り返すだけで、迷路の最短経路を自ら見つける粘菌ネットワークが育ちます。

デモ

自動再生 · タイルを押すとその項目へ · すべて1つのzip

センサー角度ひとつでサンゴから血管まで分けた九つの段階

並び順の基準は派手さではなくトレイル場への一手がどこに加わるかです。01の基本3センサー操舵は場を何も足さない原形で、02が障害物と餌の匂いを場に混ぜ、03は餌を6つに増やし、04はクリックした座標を寿命つきの符号付きアンカーとして足します。05は場そのものを種ごとに2枚に分けて縄張り競争を起こし、06は積み上げる値を最後に踏まれたステップ番号に置き換え、07はセンサー角度と距離を毎フレーム動かし、08は育ちきった場を静止画として固め、09は黒い舞台に照明を焼いて締めくくります。粘菌 シミュレーションで検索してすぐ試せる9盤は、中身がどれも同じ3点センサーを土台に、01〜05・07〜09は拡散・蒸発の2行を足し、06だけは蒸発の代わりに「最後に踏まれたステップ番号」で古さを読む構成です。群れの規則で形が決まるボイドの9盤や縞が自ら育つ反応拡散の9盤と同じく、複雑な網は1つの単純な規則の繰り返しから生まれます。

3センサー操舵という1つの軸が9盤を貫く

01から09まで、すべての盤で各エージェントは毎ステップ、前方・左(+SENSOR_ANGLE)・右(-SENSOR_ANGLE)の3点の濃度だけを読み、最も濃い方へTURNだけ曲がってSTEPだけ進みます。迷路も鉄道網も縄張りも、この読み取りの対象に何を足すかの違いだけで生まれています。

項目別コード

項目ごとに、成長を左右する定数の名前をそのまま示し、実際に動いているデモの関数を丸ごと引用します。切り詰めた断片ではなく動いているコードそのものなので、この画面から直接自分のページへ貼って動かせます。

01基本の3センサーエージェント — 前・左・右の匂いを嗅いで曲がる粘菌一粒

各エージェントが前方3点のフェロモン濃度を読み、最も濃い方へ曲がって一歩進み、着いたマスにトレイルを積むだけのひと層です。1300体が中央の円形の種から出発して血管のようなネットワークを育て、場は上下左右がつながるトーラスなので境界で途切れません。24フレーム中23フレームが動き続け、累積面積は51.385%まで育ちます。

基本の3センサーエージェント — 前・左・右の匂いを嗅いで曲がる粘菌一粒
新しいタブで開く

01 · 基本の3センサーエージェント — 前・左・右の匂いを嗅いで曲がる粘菌一粒 — 計測 累積面積 51.385% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

直進・左折・右折の分岐だけで操舵が決まる

3点の濃度を比較して同じifの連鎖に流すだけです。正面が最も濃ければ直進、左が濃ければ+TURN、右が濃ければ-TURN、同点なら乱数でどちらかに振ります。最後の(rand() - 0.5) * 0.14が向きを揺らして、一度決まった道に閉じ込められるのを防ぎます。

  function stepAgents() {
    for (var i = 0; i < AGENTS; i++) {
      var x = ax[i], y = ay[i], h = ah[i];
      var sf = sample(x + Math.cos(h) * SENSOR_DIST, y + Math.sin(h) * SENSOR_DIST);
      var sl = sample(x + Math.cos(h + SENSOR_ANGLE) * SENSOR_DIST, y + Math.sin(h + SENSOR_ANGLE) * SENSOR_DIST);
      var sr = sample(x + Math.cos(h - SENSOR_ANGLE) * SENSOR_DIST, y + Math.sin(h - SENSOR_ANGLE) * SENSOR_DIST);
      if (sf >= sl && sf >= sr) {
        // 直進 — 最も濃いのが正面
      } else if (sl > sr) {
        h += TURN;
      } else if (sr > sl) {
        h -= TURN;
      } else {
        h += (rand() < 0.5 ? 1 : -1) * TURN;
      }
      h += (rand() - 0.5) * 0.14;
      var nx = x + Math.cos(h) * STEP, ny = y + Math.sin(h) * STEP;
      nx = ((nx % fw) + fw) % fw; ny = ((ny % fh) + fh) % fh;
      ax[i] = nx; ay[i] = ny; ah[i] = h;
      var ii = idx(Math.round(nx), Math.round(ny));
      trail[ii] = Math.min(CAP, trail[ii] + DEPOSIT);
    }
    stepCount++;
  }

02迷路の最短経路 — イグノーベル賞実験をキャンバスで再現

13×7の迷路を深さ優先探索で掘り、中央の行の左右端に餌を2つ置くと、壁はセンサー値-1000で塞がれ、行き止まりのトレイルから先に蒸発で枯れて最短の通路だけが太く残ります。本物の粘菌が2008年にイグノーベル認知科学賞で認められた迷路実験と同じ理屈です。24フレーム中23フレームが動き、累積面積は43.166%、フレーム最大面積は2.37%と小さいのは13×7の迷路のマス自体が狭いためです。

迷路の最短経路 — イグノーベル賞実験をキャンバスで再現
新しいタブで開く

02 · 迷路の最短経路 — イグノーベル賞実験をキャンバスで再現 — 計測 累積面積 43.166% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDatarequestAnimationFrame

壁マスが-1000を返すことが迷路成立の条件

sense()が障害物マスで大きな負の値を返すので、3センサーは壁の方を決して濃いと読みません。餌はexp(-距離²/幅)でにじむ匂い場になってセンサー値に加わり、入口と出口だけが広く強い引力になります。

  function buildFood() {
    food = new Float32Array(fw * fh);
    var pts = [foodA, foodB];
    for (var y = 0; y < fh; y++) {
      for (var x = 0; x < fw; x++) {
        var best = 0;
        for (var p = 0; p < pts.length; p++) {
          var dx = x - pts[p][0], dy = y - pts[p][1];
          var d2 = dx * dx + dy * dy;
          var v = Math.exp(-d2 / (fw * 1.1));
          if (v > best) best = v;
        }
        food[y * fw + x] = best;
      }
    }
  }

  function sense(x, y) {
    var i = clampIdx(x, y);
    if (obstacle[i]) return -1000;
    return trail[i] + food[i] * FOOD_W * CAP;
  }

03複数餌ネットワーク — 鉄道網のように最適な接続線を描く

餌6つを放射状にまき、センサー値に餌の匂いを足すと、粘菌がすべての点を結ぶ太い幹線と細い支線を自ら区別して、放射状の輸送網を描きます。蒸発を01より遅くしたDECAY 0.038のおかげで支線がすぐ枯れず、太さの差が画面に残ります。24フレーム中23フレームが動き、累積面積は36.237%です。

複数餌ネットワーク — 鉄道網のように最適な接続線を描く
新しいタブで開く

03 · 複数餌ネットワーク — 鉄道網のように最適な接続線を描く — 計測 累積面積 36.237% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDatarequestAnimationFrame

餌6つの匂い場を1枚の配列に焼く

餌の座標を決め、全マスから各餌への距離二乗にexpを掛けた値の最大値を匂いとして敷き詰めます。このfood配列がtrailに加算されることで、複数の目標の間を最短で結ぶ枝の選別が始まります。

  function buildFoods() {
    foods = [];
    var n = 6;
    var cx = fw / 2, cy = fh / 2, r = Math.min(fw, fh) * 0.42;
    for (var i = 0; i < n; i++) {
      var a = (i / n) * Math.PI * 2 + rand() * 0.5;
      var rr = r * (0.7 + rand() * 0.3);
      foods.push([cx + Math.cos(a) * rr, cy + Math.sin(a) * rr, rand() * Math.PI * 2]);
    }
    food = new Float32Array(fw * fh);
    for (var y = 0; y < fh; y++) {
      for (var x = 0; x < fw; x++) {
        var best = 0;
        for (var p = 0; p < foods.length; p++) {
          var dx = x - foods[p][0], dy = y - foods[p][1];
          var d2b = dx * dx + dy * dy;
          var v = Math.exp(-d2b / (fw * 0.9));
          if (v > best) best = v;
        }
        food[y * fw + x] = best;
      }
    }
  }

04ポインタの餌・忌避物 — クリックした場所へ育つ、または避ける

クリック(タップ)した座標に符号付きアンカー(+1=餌・-1=塩)を植えると、センサー値に全アンカーの符号×残り寿命×exp(-距離²/30)の合計が加わり、餌には枝が伸び、塩は避けて曲がりながら育ちます。アンカーはANCHOR_LIFE=5.2秒後に消え、ポインター入力のない自動再生では、餌と塩が混ざった5つのアンカーを同じ時刻表で並べたscriptが同じplaceAnchor()を呼んで再現されます。24フレーム中23フレームが動き、累積面積は41.776%です。

ポインタの餌・忌避物 — クリックした場所へ育つ、または避ける
新しいタブで開く

04 · ポインタの餌・忌避物 — クリックした場所へ育つ、または避ける — 計測 累積面積 41.776% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

pointermoveImageDatarequestAnimationFrame

符号付きアンカーの寿命つき場をその場で足す

アンカーは配列に積まれ、anchorField()が現在時刻との差で寿命を計り、距離の二乗でにじんだ値に符号を掛けて足し合わせます。正負が同じ1本の式で引力と斥力の両方が生まれます。

  function placeAnchor(fx, fy, sign) {
    anchors.push({ x: fx, y: fy, sign: sign, born: curT });
    if (anchors.length > MAX_ANCHORS) anchors.shift();
  }

  function anchorField(x, y) {
    var v = 0;
    for (var i = 0; i < anchors.length; i++) {
      var a = anchors[i];
      var age = curT - a.born;
      if (age < 0 || age > ANCHOR_LIFE) continue;
      var life = 1 - age / ANCHOR_LIFE;
      var dx = x - a.x, dy = y - a.y;
      var d2 = dx * dx + dy * dy;
      v += a.sign * life * Math.exp(-d2 / 30);
    }
    return v;
  }

  function sense(x, y) {
    var i = clampIdx(x, y);
    return trail[i] + anchorField(x, y) * ANCHOR_W * CAP;
  }

05多種の縄張り競争 — 色の違う2つの群れが画面を分け合う

種ごとに独立したトレイル場(trailA・trailB)を持ち、センサー値を「自分の場 − 相手の場×REPEL_W」(REPEL_W=1.7)で計算すると、互いのトレイルを障害物のように避け合い、左端と右端の3割の位置から出発した2色の群れが押し合って境界のはっきりした縄張りに画面を分けます。9種中最大の累積面積53.258%・フレーム最大41.897%で、最も広く塗る盤です。

多種の縄張り競争 — 色の違う2つの群れが画面を分け合う
新しいタブで開く

05 · 多種の縄張り競争 — 色の違う2つの群れが画面を分け合う — 計測 累積面積 53.258% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDatarequestAnimationFrame

相手の場を1.7倍して引くだけで縄張りが生まれる

stepSpecies()は自分の配列ownに積み、相手の配列otherを引いて読むだけで、中身は01と同じ3センサー分岐です。2つの種が同じstepAll()で同じ回数進むため、押し合いが対称のまま画面の分割まで進みます。

  function stepSpecies(ax, ay, ah, count, own, other) {
    for (var i = 0; i < count; i++) {
      var x = ax[i], y = ay[i], h = ah[i];
      var sf = senseFrom(own, other, x + Math.cos(h) * SENSOR_DIST, y + Math.sin(h) * SENSOR_DIST);
      var sl = senseFrom(own, other, x + Math.cos(h + SENSOR_ANGLE) * SENSOR_DIST, y + Math.sin(h + SENSOR_ANGLE) * SENSOR_DIST);
      var sr = senseFrom(own, other, x + Math.cos(h - SENSOR_ANGLE) * SENSOR_DIST, y + Math.sin(h - SENSOR_ANGLE) * SENSOR_DIST);
      if (sf >= sl && sf >= sr) {
        // 直進
      } else if (sl > sr) {
        h += TURN;
      } else if (sr > sl) {
        h -= TURN;
      } else {
        h += (rand() < 0.5 ? 1 : -1) * TURN;
      }
      h += (rand() - 0.5) * 0.14;
      var nx = x + Math.cos(h) * STEP, ny = y + Math.sin(h) * STEP;
      nx = ((nx % fw) + fw) % fw; ny = ((ny % fh) + fh) % fh;
      ax[i] = nx; ay[i] = ny; ah[i] = h;
      var ii = idx(Math.round(nx), Math.round(ny));
      own[ii] = Math.min(CAP, own[ii] + DEPOSIT);
    }
  }

06トレイルの年齢彩色 — 最近通った道ほど明るく光るネオン残像

トレイル値を積む代わりに、各マスに「最後に踏まれたステップ番号」(lastVisit)だけを記録し、年齢で色を読むと、踏まれたばかりのマスだけがネオンシアンに明るく、古いマスは藍色に沈みます。蒸発を色で表すのではなく、年齢フィールドそのものがセンサーの値になる構成です。餌5つも混ざり、9種中最高の強度平均76.0で光ります。

トレイルの年齢彩色 — 最近通った道ほど明るく光るネオン残像
新しいタブで開く

06 · トレイルの年齢彩色 — 最近通った道ほど明るく光るネオン残像 — 計測 累積面積 35.206% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

年齢フィードがそのままセンサーの値になる

lastVisitに現在のステップ番号だけを書き込み、DECAY_STEPS=46ステップ以内の若さを0〜1に正規化してageKとし、これに4を掛けた値をセンサー値として返します。新しいマスほど濃いと読まれるので、操舵と彩色が同じ1本の配列から生まれます。

  function sense(x, y) {
    var i = clampIdx(x, y);
    // 年齢フィールドを検知にもそのまま使う: 最近踏まれたセル = 濃いトレイル
    var lv = lastVisit[i];
    var ageK = lv < 0 ? 0 : Math.max(0, 1 - (stepCount - lv) / DECAY_STEPS);
    return ageK * 4 + food[i] * FOOD_W;
  }

07センサー角度モーフ — 角度1つでサンゴから血管へ形が変わる

センサー開き角(10〜90度、既定29度)とセンサー距離(4〜30、既定15)の2つのライブスライダーがそのまま毎フレームの操舵計算に入り、同じ規則が密なサンゴ状からまばらな血管状、格子状の都市網まで描き分けます。自動デモは両方の値を正弦波で絶え間なく変化させ、24フレーム中23フレームが動き続けます。

センサー角度モーフ — 角度1つでサンゴから血管へ形が変わる
新しいタブで開く

07 · センサー角度モーフ — 角度1つでサンゴから血管へ形が変わる — 計測 累積面積 49.385% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDatarequestAnimationFrame

2本の正弦波が角度と距離を刻々と動かす

autoParams()が角度29度を中心に±28度、距離15を中心に±9で毎秒揺らし、範囲からはみ出た値は切り詰めます。人がスライダーを触ると自動モーフは止まり、その値で同じ計算が走ります。

  function autoParams(t) {
    var ang = 29 + 28 * Math.sin(t * 0.42) + 8 * Math.sin(t * 0.13);
    var dist = 15 + 9 * Math.sin(t * 0.31 + 1.2);
    if (ang < 10) ang = 10; if (ang > 90) ang = 90;
    if (dist < 4) dist = 4; if (dist > 30) dist = 30;
    sensorAngleDeg = Math.round(ang);
    sensorDist = Math.round(dist);
  }

08有機的テクスチャパネル — 1フレームを切り出しカード背景に敷く

GROW_STEPS=130ステップだけネットワークを育て、育ちきった場を静止画として固め、薄い不透明度で敷きます。キャプチャの境界ごとにFADE=0.7秒のクロスフェードで新しいテクスチャへ滑らかに切り替わるので、静止画を並べただけでも画面が死にません。累積面積34.819%・強度平均15.5は9種中最も低い数字ですが、わざと薄いテクスチャとして敷く項目の性格上妥当です。

有機的テクスチャパネル — 1フレームを切り出しカード背景に敷く
新しいタブで開く

08 · 有機的テクスチャパネル — 1フレームを切り出しカード背景に敷く — 計測 累積面積 34.819% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDatarequestAnimationFrame

育ちきった場を丸ごとスナップショットへ固める

capture()は場の配列を2つのスナップショットの古い方と入れ替え、現在のtrailを新しいスナップに写してから場をゼロに戻し、種の再配置から次の成長を始めます。レンダリングはこの2枚のスナップをmix係数で混ぜるので、切り替わりが瞬間ではなく0.7秒のフェードになります。

  function capture() {
    // 今のフィールドを丸ごと固めて新しい質感にし、フィールドは空にして次の成長を始める。
    var tmp = snapOld;
    snapOld = snapNew;
    snapNew = tmp;
    if (!snapNew) snapNew = new Float32Array(fw * fh);
    snapNew.set(trail);
    captureCount++;
    if (nOut) nOut.textContent = String(captureCount);
    for (var i = 0; i < trail.length; i++) trail[i] = 0;
    stepCount = 0;
    initAgents();
  }

09マクロ発光シーン — 暗い舞台にドキュメンタリー照明の粘菌

createRadialGradientで作った中央照明(LIGHT_RADIUS=0.62)をlightBaseバッファに一度だけ焼いて黒い舞台に敷き、DECAY=0.04の緩やかな蒸発でネットワークを大きく育てます。直近10ステップ以内に踏まれたマスはsinで脈打つ先端色への補間で明るくなり、トレイルの先端が接写のように絶えずうごめきます。24フレーム中23フレームが動き、累積面積は49.253%です。

マクロ発光シーン — 暗い舞台にドキュメンタリー照明の粘菌
新しいタブで開く

09 · マクロ発光シーン — 暗い舞台にドキュメンタリー照明の粘菌 — 計測 累積面積 49.253% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

createRadialGradientImageDatarequestAnimationFrame

照明を1度だけ焼いて色の足し算で脈打たせる

照明グラデーションをオフスクリーンcanvasに描いてFloat32Arrayへ読み込み、以後の毎フレームはこのlightBaseを土台にトレイル値で発光色へ補間し、新しいマスにはsinの位相を掛けた明るさを足します。照明の計算が1回で済むぶん、毎フレームは足し算だけになります。

  function buildLightBase() {
    var off = document.createElement('canvas');
    off.width = fw; off.height = fh;
    var octx = off.getContext('2d');
    var cx = fw / 2, cy = fh / 2, r = Math.min(fw, fh) * LIGHT_RADIUS;
    var g = octx.createRadialGradient(cx, cy, 0, cx, cy, r);
    g.addColorStop(0, 'rgb(' + C_LIGHT[0] + ',' + C_LIGHT[1] + ',' + C_LIGHT[2] + ')');
    g.addColorStop(0.55, 'rgb(' + Math.round((C_LIGHT[0] + C_DARK[0]) / 2) + ',' + Math.round((C_LIGHT[1] + C_DARK[1]) / 2) + ',' + Math.round((C_LIGHT[2] + C_DARK[2]) / 2) + ')');
    g.addColorStop(1, 'rgb(' + C_DARK[0] + ',' + C_DARK[1] + ',' + C_DARK[2] + ')');
    octx.fillStyle = g;
    octx.fillRect(0, 0, fw, fh);
    var d = octx.getImageData(0, 0, fw, fh).data;
    lightBase = new Float32Array(fw * fh * 3);
    for (var i = 0, p = 0; i < d.length; i += 4, p += 3) {
      lightBase[p] = d[i]; lightBase[p + 1] = d[i + 1]; lightBase[p + 2] = d[i + 2];
    }
  }

鉄道網インフォグラフィックとクリック可能なヒーロー — この9枚を実際どこで使うか

交通・物流のデータを説明したいなら03 複数餌ネットワークが一番近道です。粘菌が自力で最適な接続線を見つけていく過程が、鉄道網最適化アルゴリズムを知らなくても目で納得できる絵になります。訪問者に直接クリックしてほしいヒーロー領域には04 ポインタの餌・忌避物を置くと、クリックした場所へネットワークが育つ、または避ける反応がそのまま操作感になります。

ドキュメンタリー風のイントロや科学系サイトの表紙画面には09 マクロ発光シーンが向いています。暗い舞台の上、照明の下で1粒の粘菌が育つ場面がすでに完成しているので、見出しを重ねるだけで済みます。3枚ともファイル1つで完結しているので、追加のライブラリなしで静的セクションにそのまま組み込めます。

粘菌はなぜ最短経路に収束するのか — トレイル強化と蒸発の計算

最短経路が浮かぶ理由は、経路を比べる計算がどこにも無いことです。各エージェントは自分の周囲3点の濃度しか知らず、長い道と短い道の区別も迷路の全体像も見ていません。それでも短い経路が太るのは、拡散と蒸発の2行が毎ステップ全マスに掛かるからです(06はこの2行の代わりに年齢フィールドで同じ減衰を表現します)。

だれも経路を比べていないのに答えが出る

diffuseEvap()は隣接4マスの平均と自分の値をDIFFUSEの比率で混ぜ、その結果に(1-DECAY)を掛けます。短い経路は同じ数のエージェントが短い周期で踏み返すので補充が蒸発に勝ち、長い回り道や行き止まりは補充が追いつかずに薄れていきます。強化と減衰の釣り合いだけが答えを選ぶので、地図を持たない点の集まりが最短経路に収束します。

迷路では行き止まりから順に枯れる

02の迷路ではこの差が最もはっきり出ます。行き止まりの枝は踏み返す頻度が最も低いので最初に枯れ、入口と出口を結ぶ複数の通路のうち短いものが最後まで濃度を保って太く残ります。03の複数餌でも同じ選別が幹線と支線の太さの差として現れます。

センサー角度と減衰率を変える3つの変種とreduced-motion

名前 変更する値 変わる感触
センサー開き角 07の角度 22.5度 → 45度 ネットワークがより疎に広く広がる
蒸発速度 01のDECAY 0.02 → 0.06 古い枝が早く消え、最適経路だけが残る
エージェント数 03のAGENTS 4,000 → 12,000 ネットワークがより密になり、速く収束する

定数ひとつで変わる3つのつまみ

角度・蒸発・個体数のどれも、書き換えるのはソース先頭の変数ブロックの1行だけです。角度は網の疎密、蒸発は記憶の長さ、個体数は収束の速さという、それぞれ別の性格のつまみです。

動きを減らす設定で残る静止フレーム

9盤すべてにprefers-reduced-motion: reduceの分岐が組み込まれており、ループを回さず固定した1フレームだけを描いて.is-stillクラスを付けます。迷路の02も縄張りの05も静止フレームの選び方は同じで、初期化直後ではなく十分に育った段階の1枚を残すので、動きを止めてもそれぞれ迷路の解けかけと縄張りの境界が見えます。

拡散と蒸発を両方落とすと画面が厚くなるだけの落とし穴

実測で壊れた場面を3つ挙げます。どれも「場の2行を省くと塗り絵に戻る」と同じ根っこです。

拡散と蒸発を両方落とすとただ厚くなるだけ

DIFFUSEとDECAYを0にして積み上げるだけにすると、行き止まりが枯れず画面がただ厚くなり続けて、幹線と支線の区別が死にます。2つの行は常に一緒でなければなりません。

毎フレームのクリアが残光と年齢を消す

毎フレームclearRectで描き直すと、蒸発が勝手に薄める残光も06の年齢情報も消えるので、累積面積30%以上・23フレーム遷移という数値条件が両方壊れました。描画はputImageData1枚に任せます。

壁マスの-1000を漏らすと迷路が迷路でなくなる

02の壁をセンサー計算から漏らすと、点は壁を突き抜けてしまいます。sense()が障害物マスに大きな負の値を返すことが、迷路成立の条件です。

01のDECAYや05のREPEL_Wを自分の値でいじって壊し方を確かめたくなったら、9つのソース一式が入ったzipを開くことになり、解凍のパスワードはhv25u6urです。

粘菌ネットワークを組み込む前によくある質問

粘菌 アルゴリズムはどうやって最短経路を見つけていますか?

経路どうしを比べる処理はどこにもありません。各点は前・左・右の3点のフェロモン濃度だけを読み、濃い方へ曲がり、着いたマスに濃度を積みます。短い経路は踏み返す頻度が高いので補充が蒸発に勝ち、長い経路は負けます。この強化と減衰の釣り合いだけで、地図を持たない点の集まりが結果として最短経路を選びます。

毎回同じ迷路と同じネットワークになりますか?

なります。乱数はすべてmulberry32系でシード固定しており、項目ごとに0x320A01から0x320A09の固定シードを与えています。プレビューを開き直せば同じ迷路の配置から同じ順序で同じネットワークが育ちます。Math.random()に置き換えると毎回違う形に育ち、再現の確認ができなくなります。

ライブラリなしで本当にこの9種は動きますか?

動きます。使っているのはHTMLファイル1つの中のgetContext('2d')とputImageData、requestAnimationFrame、配列の四則演算だけです。WebGLも外部CDNもフォントも読み込みません。zipを展開してindex.htmlを開けば、そのままブラウザで9盤全部が動きます。

もっと読む

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。