530 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			530 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| /*
 | |
|     Cellular automata, polar coordinates.
 | |
| 
 | |
|     Michael Soukup, 2014
 | |
| */
 | |
| 
 | |
| 
 | |
| function nearestMod0(f, n) {
 | |
|     var mod = f % n;
 | |
|     if (mod < n/2) { // floor
 | |
|         return f-mod
 | |
|     }
 | |
|     else { // ceil
 | |
|         return f+n-mod
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| function Cartesian(x, y) {
 | |
|     this.x = x;
 | |
|     this.y = y;
 | |
| }
 | |
| 
 | |
| Cartesian.prototype.r = function() {
 | |
|     return Math.sqrt(Math.pow(this.x,2) + Math.pow(this.y, 2));
 | |
| }
 | |
| 
 | |
| Cartesian.prototype.theta = function() {
 | |
|     return Math.atan2(this.y, this.x);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| function Polar(r, theta) {
 | |
|     this.r = r;
 | |
|     this.theta = theta;
 | |
| }
 | |
| 
 | |
| Polar.prototype.x = function() {
 | |
|     return this.r*Math.cos(this.theta);
 | |
| }
 | |
| 
 | |
| Polar.prototype.y = function() {
 | |
|     return this.r*Math.sin(this.theta);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| function Vector(x, y) {
 | |
|     this.x = x;
 | |
|     this.y = y;
 | |
| }
 | |
| 
 | |
| Vector.prototype.add = function(v) {
 | |
|     this.x += v.x;
 | |
|     this.y += v.y;
 | |
| }
 | |
| 
 | |
| Vector.prototype.sub = function(v) {
 | |
|     this.x -= v.x;
 | |
|     this.y -= v.y;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| function Frame(o, v) {
 | |
|     this.o = o;
 | |
|     this.v = v;
 | |
| }
 | |
| 
 | |
| Frame.prototype.center = function() {
 | |
|     return new Cartesian(this.o.x + this.v.x/2, this.o.y + this.v.y/2);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| var CELL_COL = {
 | |
|         0: '#eee',
 | |
|         1: '#222',
 | |
|         2: '#484',
 | |
|         3: '#a44',
 | |
|         4: '#8a8a8a'
 | |
|     },
 | |
|     CELL_STROKE = {
 | |
|         0: '#bbb',
 | |
|         1: '#222',
 | |
|         2: '#484',
 | |
|         3: '#a44',
 | |
|         4: '#8a8a8a'
 | |
|     },
 | |
|     CELL_LINEWIDTH = {
 | |
|         0: 0.1,
 | |
|         1: 0.8,
 | |
|         2: 0.5,
 | |
|         3: 0.5,
 | |
|         4: 0.2
 | |
|     },
 | |
|     CELL_T = {
 | |
|         'empty': 0,
 | |
|         'wall': 1,
 | |
|         'player': 2,
 | |
|         'enemy': 3,
 | |
|         'conway': 4
 | |
|     },
 | |
|     CELL_CONFIGS = {
 | |
|         'player0': [2,2,2,2,2,2],
 | |
|         'enemy0': [3,3,3,3,3,3],
 | |
|         'organism0': [4,0,4,0,0,0,4,0,0,4,4,4],
 | |
|         'box': [4,4,0,0,0,0],
 | |
|         'diamond': [4,0,0,4,0,0]
 | |
|     };
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| var Game = (function(canvas_id) {
 | |
|     var canvas = document.getElementById(canvas_id),
 | |
|         ctx = canvas.getContext('2d'),
 | |
| 
 | |
|         view = new Frame(new Cartesian(0,0), new Vector(canvas.width, canvas.height)),
 | |
| 
 | |
|         n_poly, r_poly, alpha_poly;
 | |
| 
 | |
| 
 | |
|     function Organism(cell_seq, x, y) {
 | |
|         this.cells = cell_seq; // Must be of length % n == 0. When big enough, cell sequence becomes a new cell. Cells expand recursively
 | |
|         this.o = new Cartesian(x, y); // Continuous. Fit to r_sim before simulation
 | |
| 
 | |
|         this.layers = cell_seq.length/world.n_cell;// find radius = f(length sequence)
 | |
|         //this.r = this.layers*world.r_cell;
 | |
|     }
 | |
| 
 | |
|     function loadWorld(w) {
 | |
|         n_poly = w.n_poly;
 | |
|         r_poly = w.r_poly;
 | |
|         alpha_poly = Math.PI / n_poly;
 | |
|     }
 | |
| 
 | |
|     function drawPolygon(N, R, phi, x, y, fill) {
 | |
|         var vertice = new Polar(R, phi - Math.PI / N),
 | |
|             dtheta = 2*Math.PI / N;
 | |
| 
 | |
|         ctx.fillStyle = fill;
 | |
|         ctx.beginPath();
 | |
|         ctx.moveTo(x+vertice.x(), y+vertice.y());
 | |
|         for (var i=0; i<N; i++) {
 | |
|             vertice.theta += dtheta;
 | |
|             ctx.lineTo(x+vertice.x(), y+vertice.y());
 | |
|         }
 | |
|         ctx.closePath();
 | |
|         ctx.fill();
 | |
|     }
 | |
| 
 | |
|     function drawPolySeq(seq, N, R, phi, x, y) {
 | |
|         var l = 1, incr = 0, c_0l = 1, dtheta = 2*Math.PI / N,
 | |
|             pol = new Polar(0,0);
 | |
| 
 | |
|         drawPolygon(N, R, phi, x, y, CELL_COL[seq[0]]);
 | |
|         for (var i=1; i<seq.length; i++) {
 | |
|             if (++incr > N*l) {
 | |
|                 l++;
 | |
|                 incr = 0;
 | |
|                 c_0l = i;
 | |
|                 dtheta = (2*Math.PI) / (l*N);
 | |
|             }
 | |
|             pol.theta = (i - c_0l)*dtheta + phi;
 | |
|             pol.r = Math.sqrt(3*Math.pow(R*l, 2) + Math.pow((l/2 - (i - c_0l) % l)*2*R, 2));
 | |
| 
 | |
|             drawPolygon(N, R, phi, x+pol.x(), y+pol.y(), CELL_COL[seq[i]]);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function getNeighbourhood(i, l, N) {
 | |
|         var X = [];
 | |
|         if (i == 0) { // origo is special
 | |
|             for (var _i=0; _i<N; _i++) {
 | |
|                 X.push(_i+1);
 | |
|             }
 | |
|             return X;
 | |
|         }
 | |
| 
 | |
|         var c_0l = 3*l*(l-1)+1, // c_0l sequence expression :)
 | |
|             _diff = N*(l-1) + Math.ceil((i-c_0l)/l),
 | |
|             _add = N*l + Math.ceil((i-c_0l)/l);
 | |
| 
 | |
|         if ((i - c_0l) % l == 0) { // crown cell
 | |
|             if (i != c_0l) {
 | |
|                 X.push(i+_add-1);
 | |
|             }
 | |
|             X.push(i+_add);
 | |
|             X.push(i+_add+1);
 | |
|             X.push(i+1);
 | |
|             if (l == 1) { // watch out for origo
 | |
|                X.push(i-_diff-1);
 | |
|             }
 | |
|             else {
 | |
|                 X.push(i-_diff);
 | |
|             }
 | |
|             if (i == c_0l) {
 | |
|                 X.push(i + N*l - 1);
 | |
|                 X.push(i + N*l + N*(l+1) - 1);
 | |
|             }
 | |
|             else {
 | |
|                 X.push(i-1);
 | |
|             }
 | |
| 
 | |
|         }
 | |
|         else {
 | |
|             if ((i - N*l + 1) == c_0l) {
 | |
|                 X.push(i+_add);
 | |
|                 X.push(c_0l);
 | |
|                 X.push(c_0l-N*(l-1));
 | |
|                 X.push(i-_diff);
 | |
|                 X.push(i-1);
 | |
|                 X.push(i+_add-1);
 | |
|             }
 | |
|             else {
 | |
|                 X.push(i+_add-1);
 | |
|                 X.push(i+_add);
 | |
|                 X.push(i+1);
 | |
|                 X.push(i-_diff+1);
 | |
|                 X.push(i-_diff);
 | |
|                 X.push(i-1);
 | |
|             }
 | |
|         }
 | |
|         return X;
 | |
|     }
 | |
| 
 | |
|     function neighbours(org, i, l, N) {
 | |
|         var rv = [], X = getNeighbourhood(i, l, N);
 | |
| 
 | |
|         for (var n=0; n<X.length; n++) {
 | |
|             if (n < org.length) {
 | |
|                 rv.push(org[X[n]]);
 | |
|             }
 | |
|             else {
 | |
|                 rv.push(0);
 | |
|             }
 | |
|         }
 | |
|         return rv
 | |
|     }
 | |
| 
 | |
|     function neighbourDist(X) {
 | |
|         var dist={0:0, 1:0, 2:0, 3:0, 4:0};
 | |
|         for (var n=0; n<X.length; n++) {
 | |
|             dist[X[n]]++;
 | |
|         }
 | |
|         return dist;
 | |
|     }
 | |
| 
 | |
|     function cell_0_update(X) {
 | |
|         // count neighbours for now
 | |
|         var nd = neighbourDist(X);
 | |
|         if (nd[2] >= 2 && nd[4] > 0) { // Inside of player
 | |
|             return 4
 | |
|         }
 | |
|         else if (nd[4] == 2) { // rebirth rule
 | |
|             return 4
 | |
|         }
 | |
|         else if (nd[1] > 2) { // wall rule
 | |
|             return 4
 | |
|         }
 | |
|         else  {
 | |
|             return 0
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function cell_1_update(X) {
 | |
|         return 1
 | |
|     }
 | |
| 
 | |
|     function cell_2_update(X) {
 | |
|         // player cell
 | |
|         var nd = neighbourDist(X);
 | |
|         if (nd[4] > 2 || nd[2] > 4) {
 | |
|             return 0
 | |
|         }
 | |
|         else {
 | |
|             return 2
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function cell_3_update(X) {
 | |
|         // enemy cell
 | |
|         var nd = neighbourDist(X);
 | |
|         if (nd[4] > 3 || nd[3] > 4) {
 | |
|             return 0
 | |
|         }
 | |
|         else {
 | |
|             return 3
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function cell_4_update(X) {
 | |
|         // conway live cells
 | |
|         var nd = neighbourDist(X);
 | |
|         if (nd[2] > 2 && nd[4] >= 2) { // Inside corner of player
 | |
|             return 2
 | |
|         }
 | |
|         else if (nd[4] == 2){
 | |
|             return 4
 | |
|         }
 | |
|         else if (nd[1] > 2) { // wall rule
 | |
|             return 4
 | |
|         }
 | |
|         else {
 | |
|             return 0
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function cellNext(c, X) {
 | |
|         // Size of neighbourhood N is nxn where n is (r*2 + 1), r in {1,2,3...}:
 | |
|         // 3x3, 5x5, 7x7, ...
 | |
| 
 | |
|         switch (c) {
 | |
|             case 0:
 | |
|                 return cell_0_update(X);
 | |
|             case 1:
 | |
|                 return cell_1_update(X);
 | |
|             case 2:
 | |
|                 return cell_2_update(X);
 | |
|             case 3:
 | |
|                 return cell_3_update(X);
 | |
|             case 4:
 | |
|                 return cell_4_update(X);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function simulate(org, dt) {
 | |
|         var org_buf = [],
 | |
|             l = 0,
 | |
|             incr = 0,
 | |
|             X,
 | |
|             N = 6;
 | |
| 
 | |
|         X = neighbours(org, 0, l, N);
 | |
|         org_buf.push(cellNext(org[0], X))
 | |
|         for (var i=1; i<org.length; i++) {
 | |
|             if (++incr > N*l) {
 | |
|                 l++;
 | |
|             }
 | |
|             X = neighbours(org, i, l, N);
 | |
|             org_buf.push(cellNext(org[i], X));
 | |
|         }
 | |
|         return org_buf;
 | |
|     }
 | |
| 
 | |
|     function drawOrganism(organism, depth) {
 | |
|         // recursive
 | |
|         return 0
 | |
|     }
 | |
| 
 | |
|     function drawGrid() {
 | |
|         var o = view.center(),
 | |
|             o_polar = new Polar(o.r(),-o.theta());
 | |
| 
 | |
|         o_polar.r = nearestMod0(o_polar.r, world.r_cell); // set r to fit nearest
 | |
|         o_polar.theta = nearestMod0(o_polar.theta, world.dtheta) + world.theta_cell;// set theta to nearest
 | |
| 
 | |
|         // Set relative to canvas
 | |
|         o.x = o_polar.x() - view.o.x;
 | |
|         o.y = o_polar.y() - view.o.y;
 | |
|         o_polar.r = 0; o_polar.theta = world.theta_cell;
 | |
|         var i = 0,
 | |
|             r_max = Math.sqrt(Math.pow(o.x, 2), Math.pow(o.y, 2)); // Upper left corner
 | |
| 
 | |
|         console.log(o);
 | |
|         console.log(o_polar.r);
 | |
|         console.log(r_max);
 | |
| 
 | |
|         console.log(o_polar.x());
 | |
|         var dtheta;
 | |
|         while (o_polar.r < r_max) {
 | |
|             o_polar.r = Math.ceil(i/world.n_cell)*2*world.r_cell;
 | |
|             dtheta = 2*Math.PI/(Math.ceil(i/world.n_cell)*world.n_cell);
 | |
|             o_polar.theta = Math.ceil(i/world.n_cell)*world.n_cell*i + world.theta_cell;
 | |
|             drawPolygon(o_polar.x()+o.x, o_polar.y()+o.y,
 | |
|                         world.r_cell, world.n_cell, 0,
 | |
|                         '#bbb');
 | |
|             i++;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function update() {
 | |
|         // move and update physics;
 | |
|         // fit origo to tiles
 | |
|         // check for collision, merge and expand radius
 | |
|         // simulate
 | |
| 
 | |
|         // update camera
 | |
|         // load simulation frame from world config?
 | |
|     }
 | |
| 
 | |
|     return {
 | |
|         loadWorld: loadWorld,
 | |
|         testDraw: function() {
 | |
|             //drawGrid();
 | |
| 
 | |
|             //drawPolygon(N, R, phi, x, y, fill)
 | |
|             drawPolygon(6, 10, 0, 40, 80, '#aaa');
 | |
|             drawPolygon(6, 15, (2*Math.PI/6)/10, 100, 80, '#aaa', '#ddd', 0.4);
 | |
|             drawPolygon(3, 15, 0, 180, 80, '#aaa', '#ddd', 0.4);
 | |
|             drawPolygon(3, 15, 2*Math.PI/12, 240, 80, '#aaa', '#ddd', 0.4);
 | |
|             drawPolygon(8, 15, 0, 300, 80, '#aaa', '#ddd', 0.4);
 | |
| 
 | |
|             //drawPolySeq(seq, N, R, phi, x, y)
 | |
|             var n=6, px=10, phi=Math.PI/n;
 | |
|             drawPolySeq([2,2,3,3,3,2,4,2,2,1,2,2,2,3,2,2,2,2,2,2,3,4,1,1,1,1,2,2,2,2,2,3,3,3,2,2,2,4], n, px, phi, 101.5, 201.5);
 | |
|             drawPolySeq([2,2,3,3,3,2,4,2,2,1,2,2,2,3,2,2,2,2,2,2,3,4,1,1,1,1,2,2,2,2,2,3,3,3,2,2,2,4], n, px/2, phi, 301.5, 201.5);
 | |
| 
 | |
|             n=4; px=5; phi=Math.PI/n;
 | |
|             drawPolySeq([2,2,2,3,3,2,2,2,2,3,3,3,3,2,2,4,4,2,2,4,2,3,2,2,3,3,2], n, px, phi, 501.5, 201.5);
 | |
| 
 | |
|         },
 | |
|         testLogic: function() {
 | |
|             var n=6,l=2,i=7;;
 | |
|             console.log('c_02 neighbourhood: (i=7)');
 | |
|             X = getNeighbourhood(i, l, n);
 | |
|             console.log(X);
 | |
| 
 | |
|             i=1;l=1;n=6
 | |
|             console.log('c_01 neighbourhood: (i=1)');
 | |
|             X = getNeighbourhood(i, l, n);
 | |
|             console.log(X);
 | |
| 
 | |
|             i=22;l=3;n=6
 | |
|             console.log('crown 1 l=3 neighbourhood (i=22):');
 | |
|             X = getNeighbourhood(i, l, n);
 | |
|             console.log(X);
 | |
| 
 | |
|             i=34;l=3;n=6
 | |
|             console.log('crown 5 l=3 neighbourhood (i=34):');
 | |
|             X = getNeighbourhood(i, l, n);
 | |
|             console.log(X);
 | |
| 
 | |
|             i=8;l=2;n=6
 | |
|             console.log('middle 1 l=2 neighbourhood (i=8):');
 | |
|             X = getNeighbourhood(i, l, n);
 | |
|             console.log(X);
 | |
| 
 | |
|             i=60;l=4;n=6
 | |
|             console.log('middle step layer 6 l=4 neighbourhood (i=60):');
 | |
|             X = getNeighbourhood(i, l, n);
 | |
|             console.log(X);
 | |
| 
 | |
|             i=4;l=1;n=6
 | |
|             console.log('crown 3 l=1 (i=4):');
 | |
|             X = getNeighbourhood(i, l, n);
 | |
|             console.log(X);
 | |
| 
 | |
|             i=3;l=1;n=6
 | |
|             console.log('crown 3 l=1 (i=3):');
 | |
|             X = getNeighbourhood(i, l, n);
 | |
|             console.log(X);
 | |
| 
 | |
|             i=13;l=2;n=6
 | |
|             console.log('crown 3 l=2 (i=13):');
 | |
|             X = getNeighbourhood(i, l, n);
 | |
|             console.log(X);
 | |
| 
 | |
|             i=0;l=0;n=6
 | |
|             console.log('center cell (i=0):');
 | |
|             X = getNeighbourhood(i, l, n);
 | |
|             console.log(X);
 | |
|         },
 | |
|         testSimulation: function() {
 | |
|             var n=6, px=10, phi=Math.PI/n, dt=1000,
 | |
|                 player=[4,4,4,0,0,0,0,4,0,0,0,4,0,0,4,4,4,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2],
 | |
|                 enemy1=[4,0,0,0,4,4,0,0,4,0,0,4,4,4,4,0,0,0,4,4,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3];
 | |
|                 enemy2=[4,4,4,0,4,4,0,0,4,0,0,4,4,4,4,0,0,0,4,4,0,0,0,0,4,4,4,0,0,4,4,0,0,4,4,0,0,4,4,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3];
 | |
|                 wallor=[1,0,1,0,1,0,4,4,4,0,1,0,1,0,1,0,0,4,4,4,4,4,0,0,0,4,4,4,0,0,4,4,0,0,4,4,4,0]
 | |
| 
 | |
|             drawPolySeq(player, n, px, phi, canvas.width*0.25, canvas.height*0.25);
 | |
|             drawPolySeq(enemy1, n, px, phi, canvas.width*0.75, canvas.height*0.75);
 | |
|             drawPolySeq(enemy2, n, px, phi, canvas.width*0.8, canvas.height*0.2);
 | |
|             drawPolySeq(wallor, n, px, phi, canvas.width*0.2, canvas.height*0.8);
 | |
|             setInterval(function (){
 | |
|                 player = simulate(player, dt);
 | |
|                 enemy1 = simulate(enemy1, dt);
 | |
|                 enemy2 = simulate(enemy2, dt);
 | |
|                 wallor = simulate(wallor, dt);
 | |
|                 ctx.clearRect(0, 0, canvas.width, canvas.height);
 | |
|                 drawPolySeq(player, n, px, phi, canvas.width*0.25, canvas.height*0.25);
 | |
|                 drawPolySeq(enemy1, n, px, phi, canvas.width*0.75, canvas.height*0.75);
 | |
|                 drawPolySeq(enemy2, n, px, phi, canvas.width*0.8, canvas.height*0.2);
 | |
|                 drawPolySeq(wallor, n, px, phi, canvas.width*0.2, canvas.height*0.8);
 | |
|             }, dt);
 | |
|         }
 | |
|     }
 | |
| });
 | |
| 
 | |
| 
 | |
| // conway
 | |
| // static rule
 | |
| 
 | |
| // CELL MAP
 | |
| /**
 | |
| 0: dead or empty tile
 | |
| 1: solid wall
 | |
| 2: skin cell (player)
 | |
| 3: skin cell (enemy)
 | |
| 4: living cell
 | |
| 4:
 | |
| 
 | |
| **/
 | |
| var WORLD0 = {
 | |
|     "n_poly": 6, // Must be 3, 6, ..
 | |
|     "r_poly": 15,
 | |
|     "alpha_poly": 0,
 | |
|     "dtheta": 2*Math.PI / 6,
 | |
| 
 | |
|     "r_sim": 2000,
 | |
|     "o_f_init": new Cartesian(0, 0),
 | |
| 
 | |
|     "level": 0,
 | |
|     "config": [] // sequence of cell configs
 | |
| };
 | |
| 
 | |
| var game = Game('board');
 | |
| //game.loadWorld(WORLD0);
 | |
| //game.testDraw();
 | |
| //testLogic();
 | |
| game.testSimulation();
 |