// ES2015 classes based on https://eloquentjavascript.net/2nd_edition/07_elife.html class Vector { constructor(x, y) { this.x = x; this.y = y; } plus(other) { return new Vector(this.x + other.x, this.y + other.y); }; }; class Grid { constructor (width, height) { this.space = new Array(width * height); this.width = width; this.height = height; }; isInside(vector) { return vector.x >= 0 && vector.x < this.width && vector.y >= 0 && vector.y < this.height; }; get(vector) { return this.space[vector.x + this.width * vector.y]; }; set(vector, value) { this.space[vector.x + this.width * vector.y] = value; }; forEach(f, context) { for (let y = 0; y < this.height; y++) { for (let x = 0; x < this.width; x++) { let value = this.space[x + y * this.width]; if (value != null) f.call(context, value, new Vector(x, y)); } } }; } let directions = { "n": new Vector( 0, -1), "ne": new Vector( 1, -1), "e": new Vector( 1, 0), "se": new Vector( 1, 1), "s": new Vector( 0, 1), "sw": new Vector(-1, 1), "w": new Vector(-1, 0), "nw": new Vector(-1, -1) }; function randomElement(array) { return array[Math.floor(Math.random() * array.length)]; } let directionNames = "n ne e se s sw w nw".split(" "); function BouncingCritter() { this.direction = randomElement(directionNames); }; BouncingCritter.prototype.act = function(view) { if (view.look(this.direction) != " ") this.direction = view.find(" ") || "s"; return {type: "move", direction: this.direction}; }; class View { constructor(world, vector) { this.world = world; this.vector = vector; } look(dir) { let target = this.vector.plus(directions[dir]); if (this.world.grid.isInside(target)) return charFromElement(this.world.grid.get(target)); else return "#"; } findAll(ch) { let found = []; for (let dir in directions) if (this.look(dir) == ch) found.push(dir); return found; } find(ch) { let found = this.findAll(ch); if (found.length == 0) return null; return randomElement(found); } } class World { constructor(map, legend) { let grid = new Grid(map[0].length, map.length); this.grid = grid; this.legend = legend; map.forEach(function(line, y) { for (let x = 0; x < line.length; x++) grid.set(new Vector(x, y), World.elementFromChar(legend, line[x])); }); } static elementFromChar(legend, ch) { if (ch == " ") return null; let element = new legend[ch](); element.originChar = ch; return element; } toString() { let output = ""; for (let y = 0; y < this.grid.height; y++) { for (let x = 0; x < this.grid.width; x++) { let element = this.grid.get(new Vector(x, y)); output += charFromElement(element); } output += "\n"; } return output; } turn () { let acted = []; this.grid.forEach(function(critter, vector) { if (critter.act && acted.indexOf(critter) == -1) { acted.push(critter); this.letAct(critter, vector); } }, this); } letAct(critter, vector) { let action = critter.act(new View(this, vector)); if (action && action.type == "move") { let dest = this.checkDestination(action, vector); if (dest && this.grid.get(dest) == null) { this.grid.set(vector, null); this.grid.set(dest, critter); } } } checkDestination(action, vector) { if (directions.hasOwnProperty(action.direction)) { let dest = vector.plus(directions[action.direction]); if (this.grid.isInside(dest)) return dest; } return undefined; } }; function charFromElement(element) { if (element == null) return " "; else return element.originChar; } function Wall() {}; exports.BouncingCritter=BouncingCritter; exports.Grid=Grid; exports.Wall=Wall; exports.World=World; exports.Vector=Vector; exports.View=View;