CS2613/assignments/A4/life.js

178 lines
4.4 KiB
JavaScript

// 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;