仮想計算機構

IT業界と無縁な派遣社員のブログ

CanvasによるLangton's Loops

はじめに

aidairyさんの記事↓に刺激されて自分でも作ってみたくなったのでラングトンのループを再現してみます。
ラングトンの自己複製オートマトン - 人工知能に関する断創録

aidairyさんの記事ではPython/PyGameを使用しているのですが、Webで見られる方が楽かなーということで、今回はHTML5/Canvasでプログラムを作成しました。プログラムについてはaidiaryさんのものと下記のブログを参考にしています。
JavaScriptとcanvasでアニメーションを作る

プログラム

作成したプログラムは以下の通りです。

<html>
    <body>
        <script type="text/javascript" src="langton_rule.js"></script>
        <script type="text/javascript" src="langtons-loops.js"></script>
    </body>
</html>

GitHub - GollyGang/ruletablerepository: Previously at https://code.google.com/p/ruletablerepository
↑のサイトにはラングトンのループの遷移規則があります。
遷移規則をコピペして以下のように文字列にしておきます。

const langton_rule_str = `000000
000012
000020
...
702525
702720`;


メインのプログラムが以下になります。ピュアなJavaScriptだとPythonのようにタプルがないのでちょっと苦し紛れの実装となりました。

const WIDTH = 400;
const HEIGHT = 400;

const canvas = document.createElement('canvas');
canvas.width = WIDTH;
canvas.height = HEIGHT;

const context = canvas.getContext('2d');

document.body.appendChild(canvas);

const CELL_SIZE = 4;
const N_ROW = Math.round(HEIGHT / CELL_SIZE);
const N_COL = Math.round(WIDTH / CELL_SIZE);

const langtons_loops = [
    [0,2,2,2,2,2,2,2,2,0,0,0,0,0,0],
    [2,1,7,0,1,4,0,1,4,2,0,0,0,0,0],
    [2,0,2,2,2,2,2,2,0,2,0,0,0,0,0],
    [2,7,2,0,0,0,0,2,1,2,0,0,0,0,0],
    [2,1,2,0,0,0,0,2,1,2,0,0,0,0,0],
    [2,0,2,0,0,0,0,2,1,2,0,0,0,0,0],
    [2,7,2,0,0,0,0,2,1,2,0,0,0,0,0],
    [2,1,2,2,2,2,2,2,1,2,2,2,2,2,0],
    [2,0,7,1,0,7,1,0,7,1,1,1,1,1,2],
    [0,2,2,2,2,2,2,2,2,2,2,2,2,2,0]];

const state2color = {"0":"black", "1":"blue", "2":"red", "3":"green",
                     "4":"yellow", "5":"magenta", "6":"white", "7":"cyan"};

class Langton{
    constructor(){
        this.init_field();
        this.load_rule();
    }
    init_field(){
        // fieldの初期化
        this.field = new Array(N_ROW);
        for(let i=0;i<N_ROW;i++){
            this.field[i] = new Array(N_COL).fill(0);
        }
        // Langtons Loopを配置する
        let offset_y = Math.round((N_ROW - langtons_loops.length) / 2);
        let offset_x = Math.round((N_COL - langtons_loops[0].length) / 2);
        for(let y=0;y<langtons_loops.length;y++){
            for(let x=0;x<langtons_loops[y].length;x++){
                this.field[y + offset_y][x + offset_x - 10] = langtons_loops[y][x];
            }
        }
    }
    load_rule(){
       this.rules = {};
       let l = langton_rule_str.split("\n");
       for(let i=0;i<l.length;i++){
           let neighbor = l[i].slice(0,5);
           let next_c = l[i].charAt(5);
           // rotate4
           let [c,n,e,s,w] = Array.from(neighbor);
           this.rules[c+n+e+s+w] = next_c;
           this.rules[c+w+n+e+s] = next_c;
           this.rules[c+s+w+n+e] = next_c;
           this.rules[c+e+s+w+n] = next_c;
       }
    }
    update(){
        let next_field = new Array(N_ROW);
        for(let i=0;i<N_ROW;i++){
            next_field[i] = new Array(N_COL).fill(0);
        }
        for(let y=1;y<N_ROW-1;y++){
            for(let x=1;x<N_COL-1;x++){
                let c = "" + this.field[y][x];
                let n = "" + this.field[y - 1][x];
                let e = "" + this.field[y][x + 1];
                let s = "" + this.field[y + 1][x];
                let w = "" + this.field[y][x - 1];
                
                if((c+n+e+s+w) in this.rules){
                    next_field[y][x] = Number(this.rules[c+n+e+s+w]);
                }else{
                    next_field[y][x] = this.field[y][x];
                }
            }
        }
        this.field = next_field;
    }
    render(ctx){
        for(let y=0;y<N_ROW;y++){
            for(let x=0;x<N_COL;x++){
                ctx.beginPath();
                ctx.fillStyle = state2color[this.field[y][x]];
                ctx.rect(x*CELL_SIZE,y*CELL_SIZE,CELL_SIZE,CELL_SIZE);
                ctx.fill();
            }
        }
    }
}

var langton = new Langton();

function loop(timestamp) {
  context.clearRect(0, 0, WIDTH, HEIGHT);
  langton.render(context);
  langton.update();
  window.requestAnimationFrame((ts) => loop(ts));
}

window.requestAnimationFrame((ts) => loop(ts));

結果

実行の様子を動画にしてみました。
動画作成の都合で WIDTH=630, HEIGHT=340,CELL_SIZE=4 として実行しています。

頑張って増えてるの可愛い。