terra/app/creature.js

211 lines
6.0 KiB
JavaScript

var _ = require('./util.js');
// abstract factory that adds a superclass of baseCreature
var factory = (function () {
function baseCreature() {
this.age = -1;
}
function baseCA() {
this.age = -1;
}
baseCreature.prototype.initialEnergy = 50;
baseCreature.prototype.maxEnergy = 100;
baseCreature.prototype.efficiency = 0.7;
baseCreature.prototype.size = 50;
baseCreature.prototype.actionRadius = 1;
baseCreature.prototype.sustainability = 2;
// used as percentages of maxEnergy
baseCreature.prototype.reproduceLv = 0.70;
baseCreature.prototype.moveLv = 0;
baseCreature.prototype.boundEnergy = function() {
if (this.energy > this.maxEnergy)
this.energy = this.maxEnergy;
};
baseCreature.prototype.isDead = function() {
return this.energy <= 0;
};
baseCreature.prototype.reproduce = function (neighbors) {
var spots = _.filter(neighbors, function (spot) {
return !spot.creature;
});
if (spots.length) {
var step = spots[_.random(spots.length - 1)];
var coords = step.coords;
var creature = factory.make(this.type);
var successFn = (function () {
this.energy -= this.initialEnergy;
return true;
}).bind(this);
var failureFn = this.wait;
return {
x: coords.x,
y: coords.y,
creature: creature,
successFn: successFn,
failureFn: failureFn
};
} else return false;
};
baseCreature.prototype.move = function (neighbors) {
var creature = this;
// first, look for creatures to eat
var spots = _.filter(neighbors, (function (spot) {
return spot.creature.size < this.size;
}).bind(this));
// if there's not enough food, try to move
if (spots.length < this.sustainability) {
spots = _.filter(neighbors, function (spot) {
return !spot.creature;
});
}
// if we've got a spot to move to...
if (spots.length) {
// ...pick one
var step = spots[_.random(spots.length - 1)];
var coords = step.coords;
var successFn = (function () {
var foodEnergy = step.creature.energy * this.efficiency;
// add foodEnergy if eating, subtract 10 if moving
this.energy = this.energy + (foodEnergy || -10);
// clear the original location
return false;
}).bind(this);
return {
x: coords.x,
y: coords.y,
creature: creature,
successFn: successFn
};
} else return false;
};
baseCreature.prototype.wait = function () {
this.energy -= 5;
return true;
};
baseCreature.prototype.process = function (neighbors, x, y) {
var step = {};
var maxEnergy = this.maxEnergy;
if (this.energy > maxEnergy * this.reproduceLv && this.reproduce) {
step = this.reproduce(neighbors);
} else if (this.energy > maxEnergy * this.moveLv && this.move) {
step = this.move(neighbors);
}
var creature = step.creature;
if (creature) {
creature.successFn = step.successFn || creature.wait;
creature.failureFn = step.failureFn || creature.wait;
return {
x: step.x,
y: step.y,
creature: creature,
observed: true
};
} else return this.energy !== this.maxEnergy;
};
baseCA.prototype.actionRadius = 1;
baseCA.prototype.boundEnergy = function () {};
baseCA.prototype.isDead = function () { return false; };
baseCA.prototype.process = function (neighbors, x, y) {};
baseCA.prototype.wait = function () {};
// Storage for our creature types
var types = {};
return {
make: function (type, options) {
var Creature = types[type];
return (Creature ? new Creature(options) : false);
},
registerCreature: function (options, init) {
// required attributes
var type = options.type;
// only register classes that fulfill the creature contract
if (typeof type === 'string' && typeof types[type] === 'undefined') {
// set the constructor, including init if it's defined
if (typeof init === 'function') {
types[type] = function () {
this.energy = this.initialEnergy;
init.call(this);
};
} else {
types[type] = function () {
this.energy = this.initialEnergy;
};
}
var color = options.color || options.colour;
// set the color randomly if none is provided
if (typeof color !== 'object' || color.length !== 3) {
options.color = [_.random(255), _.random(255), _.random(255)];
}
types[type].prototype = new baseCreature();
types[type].prototype.constructor = types[type];
_.each(options, function(value, key) {
types[type].prototype[key] = value;
});
types[type].prototype.successFn = types[type].prototype.wait;
types[type].prototype.failureFn = types[type].prototype.wait;
types[type].prototype.energy = options.initialEnergy;
return true;
} else return false;
},
registerCA: function (options, init) {
// required attributes
var type = options.type;
// only register classes that fulfill the creature contract
if (typeof type === 'string' && typeof types[type] === 'undefined') {
// set the constructor, including init if it's defined
types[type] = typeof init === 'function' ?
function () { init.call(this); } :
function () {};
var color = options.color = options.color || options.colour;
// set the color randomly if none is provided
if (typeof color !== 'object' || color.length !== 3) {
options.color = [_.random(255), _.random(255), _.random(255)];
}
options.colorFn = options.colorFn || options.colourFn;
types[type].prototype = new baseCA();
types[type].prototype.constructor = types[type];
_.each(options, function(value, key) {
types[type].prototype[key] = value;
});
return true;
} else return false;
}
};
})();
module.exports = factory;