2014-08-17 03:23:48 +00:00
|
|
|
var _ = require('./util.js');
|
|
|
|
|
|
|
|
// abstract factory that adds a superclass of baseCreature
|
2014-08-26 06:22:47 +00:00
|
|
|
var factory = (function () {
|
2014-08-17 03:23:48 +00:00
|
|
|
function baseCreature() {
|
2014-09-02 17:41:38 +00:00
|
|
|
this.age = -1;
|
2014-08-17 03:23:48 +00:00
|
|
|
}
|
2014-09-02 09:22:39 +00:00
|
|
|
function baseCA() {
|
2014-09-02 17:41:38 +00:00
|
|
|
this.age = -1;
|
2014-09-02 09:22:39 +00:00
|
|
|
}
|
2014-08-17 03:23:48 +00:00
|
|
|
|
|
|
|
baseCreature.prototype.initialEnergy = 50;
|
|
|
|
baseCreature.prototype.maxEnergy = 100;
|
2014-08-21 08:04:40 +00:00
|
|
|
baseCreature.prototype.efficiency = 0.7;
|
2014-08-17 03:23:48 +00:00
|
|
|
baseCreature.prototype.size = 50;
|
2014-08-21 08:04:40 +00:00
|
|
|
baseCreature.prototype.actionRadius = 1;
|
2014-08-17 03:23:48 +00:00
|
|
|
baseCreature.prototype.sustainability = 2;
|
|
|
|
// used as percentages of maxEnergy
|
|
|
|
baseCreature.prototype.reproduceLv = 0.70;
|
2014-08-24 01:23:47 +00:00
|
|
|
baseCreature.prototype.moveLv = 0;
|
2014-08-17 03:23:48 +00:00
|
|
|
|
|
|
|
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;
|
2014-08-26 06:22:47 +00:00
|
|
|
var creature = factory.make(this.type);
|
2014-08-17 03:23:48 +00:00
|
|
|
|
|
|
|
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 () {
|
2014-08-21 08:04:40 +00:00
|
|
|
var foodEnergy = step.creature.energy * this.efficiency;
|
2014-08-22 23:18:53 +00:00
|
|
|
// add foodEnergy if eating, subtract 10 if moving
|
2014-08-17 03:23:48 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2014-08-22 23:18:53 +00:00
|
|
|
baseCreature.prototype.wait = function () {
|
2014-08-17 03:23:48 +00:00
|
|
|
this.energy -= 5;
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
2014-09-02 09:22:39 +00:00
|
|
|
baseCreature.prototype.process = function (neighbors, x, y) {
|
2014-08-17 03:23:48 +00:00
|
|
|
var step = {};
|
|
|
|
var maxEnergy = this.maxEnergy;
|
|
|
|
|
2014-08-22 23:18:53 +00:00
|
|
|
if (this.energy > maxEnergy * this.reproduceLv && this.reproduce) {
|
2014-08-17 03:23:48 +00:00
|
|
|
step = this.reproduce(neighbors);
|
2014-08-22 23:18:53 +00:00
|
|
|
} else if (this.energy > maxEnergy * this.moveLv && this.move) {
|
2014-08-17 03:23:48 +00:00
|
|
|
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,
|
2014-09-19 20:53:51 +00:00
|
|
|
creature: creature,
|
|
|
|
observed: true
|
2014-08-17 03:23:48 +00:00
|
|
|
};
|
2014-09-19 20:53:51 +00:00
|
|
|
} else return this.energy !== this.maxEnergy;
|
2014-08-17 03:23:48 +00:00
|
|
|
};
|
|
|
|
|
2014-11-04 09:48:30 +00:00
|
|
|
baseCA.prototype.actionRadius = 1;
|
2014-08-26 06:22:47 +00:00
|
|
|
baseCA.prototype.boundEnergy = function () {};
|
|
|
|
baseCA.prototype.isDead = function () { return false; };
|
2014-09-02 09:22:39 +00:00
|
|
|
baseCA.prototype.process = function (neighbors, x, y) {};
|
2014-08-26 06:22:47 +00:00
|
|
|
baseCA.prototype.wait = function () {};
|
|
|
|
|
2014-08-17 03:23:48 +00:00
|
|
|
// Storage for our creature types
|
|
|
|
var types = {};
|
|
|
|
|
|
|
|
return {
|
|
|
|
make: function (type, options) {
|
|
|
|
var Creature = types[type];
|
|
|
|
return (Creature ? new Creature(options) : false);
|
|
|
|
},
|
|
|
|
|
2014-08-26 06:22:47 +00:00
|
|
|
registerCreature: function (options, init) {
|
2014-08-17 03:23:48 +00:00
|
|
|
// required attributes
|
|
|
|
var type = options.type;
|
|
|
|
// only register classes that fulfill the creature contract
|
2014-08-17 06:23:39 +00:00
|
|
|
if (typeof type === 'string' && typeof types[type] === 'undefined') {
|
2014-08-17 03:23:48 +00:00
|
|
|
// 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;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2014-11-04 09:48:30 +00:00
|
|
|
var color = options.color || options.colour;
|
2014-08-17 06:23:39 +00:00
|
|
|
// set the color randomly if none is provided
|
|
|
|
if (typeof color !== 'object' || color.length !== 3) {
|
|
|
|
options.color = [_.random(255), _.random(255), _.random(255)];
|
|
|
|
}
|
|
|
|
|
2014-08-17 03:23:48 +00:00
|
|
|
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].wait;
|
|
|
|
types[type].prototype.failureFn = types[type].wait;
|
|
|
|
types[type].prototype.energy = options.initialEnergy;
|
|
|
|
|
2014-08-26 06:22:47 +00:00
|
|
|
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 () {};
|
|
|
|
|
2014-11-04 09:48:30 +00:00
|
|
|
var color = options.color = options.color || options.colour;
|
2014-08-26 06:22:47 +00:00
|
|
|
// set the color randomly if none is provided
|
|
|
|
if (typeof color !== 'object' || color.length !== 3) {
|
|
|
|
options.color = [_.random(255), _.random(255), _.random(255)];
|
|
|
|
}
|
|
|
|
|
2014-11-04 09:48:30 +00:00
|
|
|
options.colorFn = options.colorFn || options.colourFn;
|
|
|
|
|
2014-08-26 06:22:47 +00:00
|
|
|
types[type].prototype = new baseCA();
|
|
|
|
types[type].prototype.constructor = types[type];
|
|
|
|
|
|
|
|
_.each(options, function(value, key) {
|
|
|
|
types[type].prototype[key] = value;
|
|
|
|
});
|
|
|
|
|
2014-08-17 03:23:48 +00:00
|
|
|
return true;
|
|
|
|
} else return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
2014-08-26 06:22:47 +00:00
|
|
|
module.exports = factory;
|