Add assignment 4
This commit is contained in:
parent
3ee1155e1f
commit
5c03647a4a
178
assignments/A4/life.js
Normal file
178
assignments/A4/life.js
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
// 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;
|
102
assignments/A4/moarlife.js
Normal file
102
assignments/A4/moarlife.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
let life=require("./life.js");
|
||||||
|
|
||||||
|
let View=life.View;
|
||||||
|
|
||||||
|
let actionTypes = Object.create(null);
|
||||||
|
|
||||||
|
actionTypes.grow = function(critter) {
|
||||||
|
critter.energy += 0.5;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
actionTypes.move = function(critter, vector, action) {
|
||||||
|
let dest = this.checkDestination(action, vector);
|
||||||
|
if (dest == null ||
|
||||||
|
critter.energy <= 1 ||
|
||||||
|
this.grid.get(dest) != null)
|
||||||
|
return false;
|
||||||
|
critter.energy -= 1;
|
||||||
|
this.grid.set(vector, null);
|
||||||
|
this.grid.set(dest, critter);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
actionTypes.eat = function(critter, vector, action) {
|
||||||
|
let dest = this.checkDestination(action, vector);
|
||||||
|
let atDest = dest != null && this.grid.get(dest);
|
||||||
|
if (!atDest || atDest.energy == null)
|
||||||
|
return false;
|
||||||
|
critter.energy += atDest.energy;
|
||||||
|
this.grid.set(dest, null);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
actionTypes.reproduce = function(critter, vector, action) {
|
||||||
|
let baby = life.World.elementFromChar(this.legend,
|
||||||
|
critter.originChar);
|
||||||
|
let dest = this.checkDestination(action, vector);
|
||||||
|
if (dest == null ||
|
||||||
|
critter.energy <= 2 * baby.energy ||
|
||||||
|
this.grid.get(dest) != null)
|
||||||
|
return false;
|
||||||
|
critter.energy -= 2 * baby.energy;
|
||||||
|
this.grid.set(dest, baby);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LifelikeWorld extends life.World {
|
||||||
|
constructor(map,legend){
|
||||||
|
super(map,legend);
|
||||||
|
}
|
||||||
|
letAct(critter, vector) {
|
||||||
|
let action = critter.act(new View(this, vector));
|
||||||
|
let handled = action &&
|
||||||
|
action.type in actionTypes &&
|
||||||
|
actionTypes[action.type].call(this, critter,
|
||||||
|
vector, action);
|
||||||
|
if (!handled) {
|
||||||
|
critter.energy -= 0.2;
|
||||||
|
if (critter.energy <= 0)
|
||||||
|
this.grid.set(vector, null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Plant {
|
||||||
|
constructor() {
|
||||||
|
this.energy = 3 + Math.random() * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
act(view) {
|
||||||
|
if (this.energy > 15) {
|
||||||
|
let space = view.find(" ");
|
||||||
|
if (space)
|
||||||
|
return {type: "reproduce", direction: space};
|
||||||
|
}
|
||||||
|
if (this.energy < 20)
|
||||||
|
return {type: "grow"};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlantEater{
|
||||||
|
constructor () {
|
||||||
|
this.energy = 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
act(view) {
|
||||||
|
let space = view.find(" ");
|
||||||
|
if (this.energy > 60 && space)
|
||||||
|
return {type: "reproduce", direction: space};
|
||||||
|
let plant = view.find("*");
|
||||||
|
if (plant)
|
||||||
|
return {type: "eat", direction: plant};
|
||||||
|
if (space)
|
||||||
|
return {type: "move", direction: space};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.LifelikeWorld=LifelikeWorld;
|
||||||
|
exports.BouncingCritter=life.BouncingCritter;
|
||||||
|
exports.Wall=life.Wall;
|
||||||
|
exports.PlantEater = PlantEater;
|
||||||
|
exports.Plant = Plant;
|
136
assignments/A4/spec/life.spec.js
Normal file
136
assignments/A4/spec/life.spec.js
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
let life=require ("../life.js");
|
||||||
|
let plan = ["############################",
|
||||||
|
"# # # o ##",
|
||||||
|
"# #",
|
||||||
|
"# ##### #",
|
||||||
|
"## # # ## #",
|
||||||
|
"### ## # #",
|
||||||
|
"# ### # #",
|
||||||
|
"# #### #",
|
||||||
|
"# ## o #",
|
||||||
|
"# o # o ### #",
|
||||||
|
"# # #",
|
||||||
|
"############################"];
|
||||||
|
|
||||||
|
let Vector = life.Vector;
|
||||||
|
|
||||||
|
describe("Grid",
|
||||||
|
function() {
|
||||||
|
it("initially undefined",
|
||||||
|
function() {
|
||||||
|
let grid = new life.Grid(5, 5);
|
||||||
|
expect(grid.get(new life.Vector(1, 1))).toBe(undefined);
|
||||||
|
});
|
||||||
|
it("setting a value",
|
||||||
|
function() {
|
||||||
|
let grid = new life.Grid(5, 5);
|
||||||
|
grid.set(new Vector(1, 1), "X");
|
||||||
|
expect(grid.get(new Vector(1, 1))).toEqual("X");
|
||||||
|
});
|
||||||
|
it("forEach",
|
||||||
|
function() {
|
||||||
|
let grid = new life.Grid(5, 5);
|
||||||
|
let test = {grid: grid, sum: 0,
|
||||||
|
method: function () {
|
||||||
|
this.grid.forEach(function() { this.sum++; }, this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test.grid.set(new Vector(2,3), "#");
|
||||||
|
test.grid.set(new Vector(3,4), "#");
|
||||||
|
test.method();
|
||||||
|
expect(test.sum).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("BouncingCritter",
|
||||||
|
function() {
|
||||||
|
let bob = null;
|
||||||
|
beforeEach(function () {
|
||||||
|
spyOn(Math, 'random').and.returnValue(0.5);
|
||||||
|
bob=new life.BouncingCritter();
|
||||||
|
});
|
||||||
|
it("constructor",
|
||||||
|
function() {
|
||||||
|
expect('direction' in bob).toBe(true);
|
||||||
|
expect(bob.direction).toBe('s');
|
||||||
|
});
|
||||||
|
it("act, clear path",
|
||||||
|
function () {
|
||||||
|
let clear = {look: function () {return " ";}};
|
||||||
|
expect(bob.act(clear)).toEqual({type: "move", direction: "s"});
|
||||||
|
});
|
||||||
|
it("act, unclear path",
|
||||||
|
function () {
|
||||||
|
let unclear = {look: function () {return "#";}, find: function () { return "n";}};
|
||||||
|
expect(bob.act(unclear)).toEqual({type: "move", direction: "n"});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("World",
|
||||||
|
function () {
|
||||||
|
it("roundtrip",
|
||||||
|
function() {
|
||||||
|
let world = new life.World(plan, {"#": life.Wall, "o": life.BouncingCritter});
|
||||||
|
let rows = world.toString().split("\n");
|
||||||
|
// drop blank row
|
||||||
|
rows.pop();
|
||||||
|
expect(rows).toEqual(plan);
|
||||||
|
});
|
||||||
|
it("turn",
|
||||||
|
function () {
|
||||||
|
let world = new life.World(plan, {"#": life.Wall, "o": life.BouncingCritter});
|
||||||
|
let count=0;
|
||||||
|
spyOn(world, 'letAct').and.callFake(function(critter,vector) {count++;});
|
||||||
|
world.turn();
|
||||||
|
expect(count).toBe(4);
|
||||||
|
});
|
||||||
|
it("checkDestination",
|
||||||
|
function () {
|
||||||
|
let world = new life.World(plan, {"#": life.Wall, "o": life.BouncingCritter});
|
||||||
|
expect(world.checkDestination({direction: 's'},
|
||||||
|
new life.Vector(19,1))).toEqual(new life.Vector(19,2));
|
||||||
|
expect(world.checkDestination({direction: 'n'},
|
||||||
|
new life.Vector(0,0))).toEqual(undefined);
|
||||||
|
});
|
||||||
|
it("letAct",
|
||||||
|
function () {
|
||||||
|
let world = new life.World(plan, {"#": life.Wall, "o": life.BouncingCritter});
|
||||||
|
let src=new life.Vector(19,1);
|
||||||
|
let dest=new life.Vector(19,2);
|
||||||
|
let bob=world.grid.get(src);
|
||||||
|
spyOn(bob,'act').and.returnValue({type: 'move', direction: 's'});
|
||||||
|
world.letAct(bob, src);
|
||||||
|
expect(world.grid.get(dest)).toEqual(bob);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("View",
|
||||||
|
function () {
|
||||||
|
let world = new life.World(plan, {"#": life.Wall, "o": life.BouncingCritter});
|
||||||
|
let View=life.View;
|
||||||
|
let position=new Vector(15,9);
|
||||||
|
it("constructor",
|
||||||
|
function () {
|
||||||
|
let view=new View(world, position);
|
||||||
|
expect(view.vector).toEqual(position);
|
||||||
|
});
|
||||||
|
it("look",
|
||||||
|
function () {
|
||||||
|
let view=new View(world, position);
|
||||||
|
expect(view.look("s")).toBe(" ");
|
||||||
|
});
|
||||||
|
it("findAll",
|
||||||
|
function () {
|
||||||
|
let view=new View(world, position);
|
||||||
|
let directionNames = [ 'e', 'n', 'ne', 'nw', 's', 'se', 'sw', 'w' ];
|
||||||
|
expect(view.findAll(" ").sort()).toEqual(directionNames);
|
||||||
|
});
|
||||||
|
it("find",
|
||||||
|
function () {
|
||||||
|
let view=new View(world, position);
|
||||||
|
spyOn(Math, 'random').and.returnValue(0.5);
|
||||||
|
expect(view.find(" ")).toBe('s');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
30
assignments/A4/spec/moarlife.spec.js
Normal file
30
assignments/A4/spec/moarlife.spec.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
let life=require ("../moarlife.js");
|
||||||
|
|
||||||
|
let plan= ["############################",
|
||||||
|
"##### ######",
|
||||||
|
"## *** **##",
|
||||||
|
"# *##** ** O *##",
|
||||||
|
"# *** O ##** *#",
|
||||||
|
"# O ##*** #",
|
||||||
|
"# ##** #",
|
||||||
|
"# O #* #",
|
||||||
|
"#* #** O #",
|
||||||
|
"#*** ##** O **#",
|
||||||
|
"##**** ###*** *###",
|
||||||
|
"############################"];
|
||||||
|
|
||||||
|
|
||||||
|
describe("World",
|
||||||
|
function () {
|
||||||
|
let valley = new life.LifelikeWorld(plan,
|
||||||
|
{"#": life.Wall,
|
||||||
|
"O": life.PlantEater,
|
||||||
|
"*": life.Plant});
|
||||||
|
it("roundtrip",
|
||||||
|
function() {
|
||||||
|
let rows = valley.toString().split("\n");
|
||||||
|
// drop blank row
|
||||||
|
rows.pop();
|
||||||
|
expect(rows).toEqual(plan);
|
||||||
|
});
|
||||||
|
});
|
13
assignments/A4/spec/support/jasmine.json
Normal file
13
assignments/A4/spec/support/jasmine.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"spec_dir": "spec",
|
||||||
|
"spec_files": [
|
||||||
|
"**/*[sS]pec.?(m)js"
|
||||||
|
],
|
||||||
|
"helpers": [
|
||||||
|
"helpers/**/*.?(m)js"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"stopSpecOnExpectationFailure": false,
|
||||||
|
"random": true
|
||||||
|
}
|
||||||
|
}
|
28
assignments/A4/valley.js
Normal file
28
assignments/A4/valley.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
let life=require("./moarlife.js");
|
||||||
|
|
||||||
|
let valley = new life.LifelikeWorld(
|
||||||
|
["############################",
|
||||||
|
"##### ######",
|
||||||
|
"## *** **##",
|
||||||
|
"# *##** ** O *##",
|
||||||
|
"# *** O ##** *#",
|
||||||
|
"# O ##*** #",
|
||||||
|
"# ##** #",
|
||||||
|
"# O #* #",
|
||||||
|
"#* #** O #",
|
||||||
|
"#*** ##** O **#",
|
||||||
|
"##**** ###*** *###",
|
||||||
|
"############################"],
|
||||||
|
{"#": life.Wall,
|
||||||
|
"O": life.PlantEater,
|
||||||
|
"*": life.Plant}
|
||||||
|
);
|
||||||
|
|
||||||
|
function loop () {
|
||||||
|
valley.turn();
|
||||||
|
console.log("\33c");
|
||||||
|
console.log(valley.toString());
|
||||||
|
setTimeout(function() { loop(); },250);
|
||||||
|
}
|
||||||
|
|
||||||
|
loop();
|
Loading…
Reference in New Issue
Block a user