commit 4186f80a09be65e4a00044aac6c596fd8716128c Author: Michael Soukup Date: Wed Aug 18 23:47:45 2021 +0200 Revive and add recordings and README. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a9540ed --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Cellular automata experiments + +Cellular automata experiments and game ideas from 2014. + + +## Hexagonal + +Cellular automata on a hexagonal grid and a home-cooked rule set. + +![Hex](hex/recording.gif "Hexagonal") + + +## Interactive + +Not really an automata since this one is interactive. Control the thing +with the arrow keys. + +![Interactive](interactive/recording.gif "Interactive") + + +## Broken + +This one is not working. Not bothering to fix it, got no git history. diff --git a/broken/ca.html b/broken/ca.html new file mode 100755 index 0000000..ad0f2ea --- /dev/null +++ b/broken/ca.html @@ -0,0 +1,35 @@ + + + + CA + + + + + +
+ +
+ + + + diff --git a/broken/ca.js b/broken/ca.js new file mode 100755 index 0000000..972d3c5 --- /dev/null +++ b/broken/ca.js @@ -0,0 +1,877 @@ +/* + * Michael Soukup, 2014 +*/ + + +//var DIM = [window.innerWidth, window.innerHeight]; + +function zeros(m, n) { + var mat = []; + for (var i=0; i 0 && this.dim.x > dx) || (dx < 0 && f.dim.x < -dx)), + inter_y = ((dy > 0 && this.dim.y > dy) || (dy < 0 && f.dim.y < -dy)); + + return inter_x && inter_y +} +Frame.prototype.join = function(f) { + // Expands frame to also fit f + var x_min = Math.min(this.coords.x, f.coords.x), + y_min = Math.min(this.coords.y, f.coords.y), + x_max = Math.max(this.coords.x+this.dim.x, f.coords.x+f.dim.x), + y_max = Math.max(this.coords.y+this.dim.y, f.coords.y+f.dim.y); + + this.coords.x = x_min; + this.coords.y = y_min; + this.dim.x = x_max - x_min; + this.dim.y = y_max - y_min; +} +Frame.prototype.intersect = function(f) { + // this AND f + var dx = f.coords.x - this.coords.x, + dy = f.coords.y - this.coords.y; + + if (dx > 0) { + this.coords.x = f.coords.x; + this.dim.x = Math.min(this.dim.x - dx, f.dim.x); + } + else { + this.dim.x = Math.min(f.dim.x + dx, this.dim.x); + } + if (dy > 0) { + this.coords.y = f.coords.y; + this.dim.y = Math.min(this.dim.y - dy, f.dim.y); + } + else { + this.dim.y = Math.min(f.dim.y + dy, this.dim.y); + } +} + + +var CELL_COL = { + 0: '#000', + 1: '#222', + 2: '#484', + 3: '#a44', + 4: '#8a8a8a' + }, + CELL_T = { + 'empty': 0, + 'wall': 1, + 'player': 2, + 'enemy': 3, + 'conway': 4 + }, + CELL_CONFIGS = { + 'player0': [ + [0,2,2,0,0,0], + [0,0,0,0,0,0], + [0,0,0,0,0,0], + [0,0,0,4,4,0], + [0,0,4,0,4,0], + [0,0,0,4,0,0] + ], + 'enemy0': [ + [0,3,3,3,3,0], + [3,4,4,0,3,3], + [3,4,0,0,0,3], + [3,0,0,4,4,3], + [3,0,4,0,3,3], + [0,3,3,3,0,0] + ], + 'organism0': [ + [0,0,0,0,0,0,4,4], + [0,0,4,4,4,4,4,4], + [0,0,0,4,0,0,0,0], + [0,4,0,0,0,0,0,0], + [0,4,4,4,0,4,0,4], + [4,4,4,0,4,4,4,4], + [0,0,4,4,4,0,0,0] + ], + 'box': [ + [0,0,0,0], + [0,4,4,0], + [0,4,4,0], + [0,0,0,0] + ], + 'diamond': [ + [0,4,0,0], + [4,0,4,0], + [0,4,0,0], + [0,0,0,0] + ] + }; + + +function Ndist(N, r) { + var dist = [0,0,0,0,0]; + for (var x=0; x 2) { + return 2 + } + else if (dist[4] == 2 || dist[4] == 3) { + return 4 + } + else { + return 0 + } +} +function cellNext(N) { + // Size of neighbourhood N is nxn where n is (r*2 + 1), r in {1,2,3...}: + // 3x3, 5x5, 7x7, ... + + var r = (N.length - 1) / 2, // must be int + cell = N[r][r]; + + switch (cell) { + case 0: + return cell_0_update(N, r); + case 1: + return cell_1_update(N, r); + case 2: + return cell_2_update(N, r); + case 3: + return cell_3_update(N, r); + case 4: + return cell_4_update(N, r); + } +} + + +// Every organism has a pointer to the environment + +// dim_cam, coords_cam, px_tile, dim_frame, coords_frame, tiles_frame + +// function Organism(configuration, x, y, tilesize) { +// // Tiles can be of any size px_tile_min*n, n in N + +// this.p = new Vector(x, y); // continuous +// this.v = new Vector(0, 0); + +// this.px_tile = tilesize; +// this.tiles = new Vector(configuration[0].length, configuration.length); + +// this.config = configuration; +// this.config_buf = zeros(this.tiles.x, this.tiles.y); + +// this.frame = new Frame(new Vector(nearestMod0(x, tilesize), nearestMod0(y, tilesize)), +// new Vector(tilesize*this.tiles.x, tilesize*this.tiles.y)); + +// //this.coords_center = null // keep track of center for steering and battle + +// } +// Organism.prototype.getNeighbourhood = function(cx, cy, r) { +// var n = (r*2)+1, +// N = zeros(n,n); + +// for (var x=cx-r; x<=cx+r; x++) { +// for (var y=cy-r; y<=cy+r; y++) { +// if ((x < 0 || x >= this.tiles.x) || (y < 0 || y >= this.tiles.y)) { +// N[x+r-cx][y+r-cy] = 0; // Tiles outside constraints are 0 anyways +// } +// else { +// N[x+r-cx][y+r-cy] = this.config[x][y]; +// } +// } +// } +// return N +// } +// Organism.prototype.updateCells = function() { +// // find player cell center +// for (var x=0; x= this.tiles.x) || (y < 0 || y >= this.tiles.y)) { + N[x+r-cx][y+r-cy] = 0; // Tiles outside constraints are 0 anyways + } + else { + N[x+r-cx][y+r-cy] = this.config[x][y]; + } + } + } + return N +} +Organism.prototype.updateCells = function() { + var config_buf = zeros(this.tiles.x, this.tiles.y); + + for (var x=0; x 0 + dy = y - this.frame.dim.y; + + this.frame.coords.x += -dx/2; + this.frame.coords.y += -dy/2; + this.frame.dim.x = x; + this.frame.dim.y = y; + + this.move(dF, dt); + + // fit frame and simulate + this.frame.align(this.px_tile); + var new_tiles = new Vector(this.frame.dim.x / this.px_tile, this.frame.dim.y / this.px_tile); + this.setConfig(new_tiles); + this.updateCells(); +} + + + + +var Game = (function(canvas_id) { + + var canvas = document.getElementById(canvas_id), + ctx = canvas.getContext('2d'), + + // Set up camera and simulation frame + // Camera moves continuously, frame size and coords must fit tiles. + + dim_cam = new Vector(canvas.width, canvas.height), + coords_cam = new Vector(0.0, 0.0), // Initial view: coords_cam -> coords_cam+dim_cam + + px_tile_min = 5, // 5x5 pixels + dim_frame = new Vector(nearestMod0(2*dim_cam.x, px_tile_min), nearestMod0(2*dim_cam.y, px_tile_min)), + coords_frame = new Vector(nearestMod0(coords_cam.x - 0.25*dim_frame.x, px_tile_min), nearestMod0(coords_cam.y - 0.25*dim_frame.y, px_tile_min)), + tiles_frame = new Vector(dim_frame.x/px_tile_min, dim_frame.y/px_tile_min), + + player, enemies = [], organisms = [], + + level0 = [ + 'player0,55,60', + 'enemy0,120,24', + 'organism0,-5,0', + 'box,120,32', + 'diamond,87,32' + ]; + + //cells = zeros(tiles_frame.x, tiles_frame.y), + //cells_buf = (tiles_frame.x, tiles_frame.y), + + + // function detectNearbyEnemies(organism) { + // // Returns a list of indices of nearby enemies + // } + + // function _setOrganismBox(organism, nearby) { + // // assume all coords are tile fitted + + // // pos_o = box_coord + 0.5*box + // var pos_o = Vector(organism.box_coords.x + 0.5*organism.box.x, organism.box_coords.y + 0.5*organism.box.y), + // old_coords = Vector(organism.box_coords.x, organism.box_coords.y), + // old_box = Vector(organism.box.x, organism.box.y) + + // for (var i=0; i 0) { + // ctx.fillStyle = cellcol[configuration[j][i]]; + // ctx.beginPath(); + // ctx.rect((i+x)*tilesize+0.5, (j+y)*tilesize+0.5, tilesize-0.5, tilesize-0.5); + // ctx.closePath(); + // ctx.fill(); + // } + // } + // } + // } + // function update(vx, vy) { + // updateCells(vx, vy); + // } + + // function drawCells() { + // for (var i=0; i 0) { + // ctx.fillStyle = cellcol[cells[i][j]]; + // ctx.beginPath(); + // ctx.rect(i*tilesize+0.5, j*tilesize+0.5, tilesize-0.5, tilesize-0.5); + // ctx.closePath(); + // ctx.fill(); + // } + // } + // } + // } + + // function drawGrid() { + // ctx.translate(0.5, 0.5); // Transform canvas to get sharp lines + // ctx.strokeStyle = 'black'; + // ctx.lineWidth = 0.1; + + // for (var i=1; i 0) { + // ctx.fillStyle = CELL_COL[organism.config[j][i]]; + // ctx.beginPath(); + + // // Draw every 5x5 tile at x = (i+x)*tile_min + x_t0, y = (j+y)*tile_min + y_t0 + // ctx.rect((i+x)*tile_min+x_t0+0.5, (j+y)*tile_min+y_t0+0.5, tile_min-0.5, tile_min-0.5); + // ctx.closePath(); + // ctx.fill(); + // } + // } + // } + // } + function framesOverlap(o1, o2) { + + } + + + function minimumDist(o1, o2, d_gap, cell_sep) { + // returns true if o1 and o2 are touching or have a layer of maximum width d_gap consisting only of celltype cell_sep. + + // try finding a layer. If no layer, return true + // try expanding the layer. If d_layer > d_gap return false + + } + + function updatePlayer() { + var new_enemies = [], new_organisms = []; + + // Split player? Implement later + + var gap = 2; + for (var i=0; i + + + CA + + + + + +
+ +
+ + + + diff --git a/hex/ca-hex.js b/hex/ca-hex.js new file mode 100755 index 0000000..c682156 --- /dev/null +++ b/hex/ca-hex.js @@ -0,0 +1,529 @@ +/* + 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*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= 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 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(); diff --git a/hex/recording.gif b/hex/recording.gif new file mode 100644 index 0000000..a963d23 Binary files /dev/null and b/hex/recording.gif differ diff --git a/interactive/ca-interactive.html b/interactive/ca-interactive.html new file mode 100755 index 0000000..f4bd006 --- /dev/null +++ b/interactive/ca-interactive.html @@ -0,0 +1,35 @@ + + + + CA + + + + + +
+ +
+ + + + diff --git a/interactive/ca-interactive.js b/interactive/ca-interactive.js new file mode 100755 index 0000000..edce9eb --- /dev/null +++ b/interactive/ca-interactive.js @@ -0,0 +1,580 @@ +/** + Interactive cellular automata. + + Michael Soukup 2014. +*/ + + +function zeros(m, n) { + var mat = []; + for (var i=0; i 7) { + return 0; + } + else if (dist[2] == 1 && dist[4] < 3) { + return 0; + } + else { + return 2; + } +} + +function cell_3_update(X) { + // enemy cell + return 3 +} + +function cell_4_update(X) { + // conway live cells + var dist = _ndist(X); + if (dist[2] == 3 && dist[4] > 2) { + return 2 + } + else if (dist[2] > 0 && (dist[4] != 5 || dist[4] == 4)) { + return 4 + } + else if (dist[4] == 2 || dist[4] == 3) { + return 4 + } + else { + return 0 + } +} + +function cell_next(c, X) { + + 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); + case 5: + return cell_0_update(X); + } +} + + + + +/** + Board holds the world variables and a complete map over all tiles. + All tiles on the board are simlulated in each step. +*/ +function Board() { + this.x = 0; + this.y = 0; +} + +Board.prototype.on_board = function(x, y) { + // TODO check walls + return (x >= this.x && y >= this.y && x < this.x+this.w && y < this.y+this.h); +} + +Board.prototype.set_tile = function(val, x, y) { + if (this.on_board(x, y)) { + this.map[x][y] = val; + } +} + +Board.prototype.get_tile = function(x, y) { + if (this.on_board(x, y)) { + return this.map[x][y]; + } + else { + return 0; + } +} + +Board.prototype.load_tileset = function(tileset, x, y) { + for (var i=0; i frame.x+frame.w && this.y+this.h > frame.y+frame.h); +} + +TileFrame.prototype.intersects = function(frame) { + return (((this.x > frame.x && this.x < frame.x+frame.w) || (frame.x > this.x && frame.x < this.x+this.w)) && + ((this.y > frame.y && this.y < frame.y+frame.h) || (frame.y > this.y && frame.y < this.y+this.h))); +} + + + + +/** + Holds the canvas context and camera coordinates. Implements methods for + rendering tile sets. + + Coordinates are continuous and in tile space. +*/ +function Camera(canvas_id) { + this.canvas = document.getElementById(canvas_id); + this.ctx = this.canvas.getContext('2d'); + this.width = this.canvas.width; + this.height = this.canvas.height; + + this.tilesize = 5; // Minimum tilesize (px). + this.tw = this.width/this.tilesize; + this.th = this.height/this.tilesize; +} + +Camera.prototype.zoom = function(tilesize) { + var factor = tilesize/this.tilesize, + _tw = this.width/tilesize, + _th = this.height/tilesize, + diff_x = (this.tw - _tw)/2, + diff_y = (this.th - _th)/2; + + this.x += diff_x; + this.tw = _tw; + this.y += diff_y; + this.th = _th; + this.tilesize = tilesize; +} + +Camera.prototype.center = function(x, y) { + this.x = x - this.tw/2; + this.y = y - this.th/2; +} + +Camera.prototype.draw_grid = function() { + var i = (Math.ceil(this.x) - this.x)*this.tilesize, + j = (Math.ceil(this.y) - this.y)*this.tilesize; + + this.ctx.strokestyle = 'black'; + this.ctx.lineWidth = 0.1; + + // Vertical lines + while (i < this.width) { + this.ctx.beginPath(); + this.ctx.moveTo(i, 0); + this.ctx.lineTo(i, this.height); + this.ctx.closePath(); + this.ctx.stroke(); + i += this.tilesize; + } + // Horizontal lines + while (j < this.height) { + this.ctx.beginPath(); + this.ctx.moveTo(0, j); + this.ctx.lineTo(this.width, j); + this.ctx.closePath(); + this.ctx.stroke(); + j += this.tilesize; + } +} + +Camera.prototype.draw_tiles = function(board) { + var x, y, i, j, tile_margin = this.tilesize*0.01; + + x = Math.floor(this.x); + i = (x - this.x) * this.tilesize; + while (i < this.width) { + y = Math.floor(this.y); + j = (y - this.y)*this.tilesize; + + while (j < this.height) { + if (board.get_tile(x,y) != 0) { + this.ctx.fillStyle = CELLCOL[board.get_tile(x, y)]; + this.ctx.beginPath(); + this.ctx.rect(i+tile_margin, j+tile_margin, this.tilesize-2*tile_margin, this.tilesize-2*tile_margin); + this.ctx.closePath(); + this.ctx.fill(); + } + y++; + j += this.tilesize; + } + x++; + i += this.tilesize; + } +} + +Camera.prototype.clear = function() { + this.ctx.clearRect(0, 0, this.width, this.height); +} + + + + +function testsim(canvas_id, world, dt) { + var board = new Board(), + player = new TileFrame(world.player[1], world.player[2], CELLSETS[world.player[0]][0].length, CELLSETS[world.player[0]].length), + cam = new Camera(canvas_id), + tilesize = 8; + + board.load_world(world); + cam.set(world.player[1]-1.5, world.player[1]-1.5, tilesize); // cam.center(..., + cam.draw_grid(); + cam.draw_tiles(board); + setInterval(function (){ + board.simulate(); + cam.clear(); + cam.draw_grid(); + cam.draw_tiles(board); + }, dt); +} + +function testmove(canvas_id, world, dt) { + var board = new Board(), + player = new TileFrame(world.player[1], world.player[2], CELLSETS[world.player[0]][0].length, CELLSETS[world.player[0]].length), + cam = new Camera(canvas_id), + tilesize = 8.8; + + board.load_world(world); + cam.zoom(tilesize); + cam.center(player.x, player.y); + cam.draw_grid(); + cam.draw_tiles(board); + + function expand_player() { + var X; + console.log('exp'); + for (var x=player.x; x 0) { + tilesize *= 0.9; + cam.zoom(tilesize); + } + else { + tilesize *= 1.1; + cam.zoom(tilesize); + } + cam.clear(); + cam.draw_grid(); + cam.draw_tiles(board); + }); +} + + + +// Run +//testsim("canvas", WORLD_0, 1000); +testmove("canvas", WORLD_0, 1000); + + + diff --git a/interactive/recording.gif b/interactive/recording.gif new file mode 100644 index 0000000..2b6e11c Binary files /dev/null and b/interactive/recording.gif differ