仮想計算機構

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

Evoloop : 自己解消機能をもった増殖ループ

準備

準備その1:変換規則

変換規則はセルの状態の変化を表す決まりの集合体です。Evoloop の変化規則には下記のような特徴があります。

規則を記述したファイルは以下から入手します。
golly/Evoloop.table at master · jimblandy/golly · GitHub

膨大なルールを特殊な記法で完結に表しているのがわかります。
下記のスクリプトで省略された変換規則を元の状態に復元します。

f = open("Evoloop.table","r")
g = open("output.txt","a+")
lines = f.readlines()
d = dict()
for line in lines:
    if line[0]=="#":
        continue
    elif line[0:3]=="var":
        expr = line.split(" ")[1]
        v, s = expr.split("=")
        l = s[1:-2].split(",")
        d[v] = l
    elif True not in [k in line for k in d.keys()]:
        g.write(line.replace(",",""))
    else:
        rules = [line]
        for k in d.keys():
            l = []
            ids = []
            processed_rules = []
            for i,rule in enumerate(rules):
                srule = rule.split(',')
                if k in srule:
                    processed_rules.append(rule)
                    for v in d[k]:
                        l.append(','.join([v if x==k else x for i,x in enumerate(srule)]))
            for r in processed_rules:
                rules.remove(r)
            rules.extend(l)
        for r in rules:
            g.write(r.replace(",",""))

f.close()
g.close()

色々と雑いプログラムですが、無視してください。
output.txt というファイルができるので、下記のとおり編集し、JavaScript のプログラムとして保存します。

const rule_str = `000012
020012
050012
(..........)
877750
877760
877770`

準備その2:Canvasによるアニメーション

アニメーションに必要なプログラムを下記の通り作成します。

<html>
    <body>
        <script type="text/javascript" src="rule.js"></script>
        <script type="text/javascript" src="main.js"></script>
    </body>
</html>

const WIDTH = 630;
const HEIGHT = 340;

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 evo_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", "8":"brown","9":"gray"};

class Evoloop{
    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);
        }
        // Evoloopを真ん中あたりに配置する
        let offset_y = Math.round((N_ROW - evo_loops.length) / 2);
        let offset_x = Math.round((N_COL - evo_loops[0].length) / 2);

        for(let y=0;y<evo_loops.length;y++){
            for(let x=0;x<evo_loops[y].length;x++){
                this.field[y + offset_y][x + offset_x - 10] = evo_loops[y][x];
            }
        }
    }
    load_rule(){
       this.rules = {};
       let l = 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);
           let [c,n,e,s,w] = Array.from(neighbor);
           // rotate 4
           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);
        }
        // 2次元平面はドラクエ的なドーナツ世界にしておきます
        for(let y=0;y<N_ROW;y++){
            for(let x=0;x<N_COL;x++){
                let c = "" + this.field[y][x];
                let n = "" + this.field[(N_ROW + y - 1)%N_ROW][x];
                let e = "" + this.field[y][(x + 1)%N_COL];
                let s = "" + this.field[(y + 1)%N_ROW][x];
                let w = "" + this.field[y][(N_COL + x - 1)%N_COL];
                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 evoloop = new Evoloop();
count = 0;
function loop(timestamp) {

  context.clearRect(0, 0, WIDTH, HEIGHT);
  evoloop.render(context);
  evoloop.update();
  context.fillStyle = "white";
  context.font = "20px sans-serif";
  context.fillText("Step : " + count, 10, 30);
  count++;
  window.requestAnimationFrame((ts) => loop(ts));
}

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

デモ