import * as util from './utils.js'
import AgentList from './AgentList.js'
// Class Patch instances represent a rectangle on a grid. They hold variables
// that are in the patches the turtles live on. The set of all patches
// is the world on which the turtles live and the model runs.
// Flyweight object creation:
// Objects within AgentSets use "prototypal inheritance" via Object.create().
// Here, the Patch class is given to Patches for use creating Proto objects
// (new Patch(agentSet)), but only once per model/breed.
// The flyweight Patch objects are created via Object.create(protoObject),
// This lets the new Patch(agentset) object be "defaults".
// https://medium.com/dailyjs/two-headed-es6-classes-fe369c50b24
/**
* Class Patch instances represent a square on the {@link Patches} grid.
* They hold variables
* that are in the patches the turtles live on. The set of all patches
* is the world on which the turtles live and the model runs.
*
* You do not use `new Patch`, rather Class {@link Model} creates the patches
* for you, using the {@link World} data passed to the Model.
*
* You *never* do this:
*
*/
class Patch {
static defaults = {
turtles: null,
z: 0,
// Set by AgentSet
agentSet: null,
model: null,
name: null,
}
static variables = {
// none
}
constructor() {
Object.assign(this, Patch.defaults)
}
newInstance(agentProto) {
const insstance = Object.create(agentProto)
Object.assign(insstance, Patch.variables)
return insstance
}
get x() {
return (this.id % this.model.world.numX) + this.model.world.minX
}
get y() {
return (
this.model.world.maxY - Math.floor(this.id / this.model.world.numX)
)
}
// get z() {
// return 0
// }
// set z(z) {}
/**
* Return whether or not this patch is on the edge of the atches.
*
* @returns {boolean}
*/
isOnEdge() {
return this.patches.isOnEdge(this)
}
// Getter for neighbors of this patch.
// Uses lazy evaluation to promote neighbors to instance variables.
// To avoid promotion, use `patches.neighbors(this)`.
// Promotion makes getters accessed only once.
// defineProperty required: can't set this.neighbors when getter defined.
/**
* Return an array of this patch's 8
* [Moore neighbors](https://en.wikipedia.org/wiki/Moore_neighborhood).
*
* @returns {AgentArray}
*/
get neighbors() {
// lazy promote neighbors from getter to instance prop.
const n = this.patches.neighbors(this)
Object.defineProperty(this, 'neighbors', { value: n, enumerable: true })
return n
}
/**
* Return an array of this patch's 4
* [Von Neumann neighbors](https://en.wikipedia.org/wiki/Von_Neumann_neighborhood)
* (north, south, east, west).
*
* @returns {AgentArray}
*/
get neighbors4() {
const n = this.patches.neighbors4(this)
Object.defineProperty(this, 'neighbors4', {
value: n,
enumerable: true,
})
// console.log('n4', n, this.neighbors4)
return n
}
// Promote this.turtles on first call to turtlesHere.
/**
* Return an Array of the turtles on this patch.
*
* @returns {AgentArray}
*/
get turtlesHere() {
if (this.turtles == null) {
this.patches.ask(p => {
p.turtles = new AgentList(this.model)
})
this.model.turtles.ask(t => {
t.patch.turtles.push(t)
})
}
return this.turtles
}
// Returns above but returning only turtles of this breed.
/**
* Returns an Array of the particular breed on this patch.
*
* @param {String} breed
* @returns {AgentArray}
*/
breedsHere(breed) {
const turtles = this.turtlesHere
return turtles.withBreed(breed)
}
// 6 methods in both Patch & Turtle modules
// Distance from me to x, y.
// 2.5D: use z too if both z & this.z exist.
// REMIND: No off-world test done
distanceXY(x, y, z = null) {
const useZ = z != null && this.z != null
return useZ
? util.distance3(this.x, this.y, this.z, x, y, z)
: util.distance(this.x, this.y, x, y)
}
// Return distance from me to object having an x,y pair (turtle, patch, ...)
// 2.5D: use z too if both agent.z and this.z exist
// distance (agent) { this.distanceXY(agent.x, agent.y) }
distance(agent) {
const { x, y, z } = agent
return this.distanceXY(x, y, z)
}
// distanceXY(x, y) {
// return util.distance(this.x, this.y, x, y)
// }
// // Return distance from me to object having an x,y pair (turtle, patch, ...)
// distance(agent) {
// return this.distanceXY(agent.x, agent.y)
// }
// Return heading towards agent/x,y using current geometry
towards(agent) {
return this.towardsXY(agent.x, agent.y)
}
towardsXY(x, y) {
// return util.radiansTowardXY(this.x, this.y, x, y)
let rads = util.radiansTowardXY(this.x, this.y, x, y)
return this.model.fromRads(rads)
}
// Return patch w/ given parameters. Return undefined if off-world.
// Return patch dx, dy from my position.
patchAt(dx, dy) {
return this.patches.patch(this.x + dx, this.y + dy)
}
patchAtHeadingAndDistance(heading, distance) {
return this.patches.patchAtHeadingAndDistance(this, heading, distance)
}
sprout(num = 1, breed = this.model.turtles, initFcn = turtle => {}) {
return breed.create(num, turtle => {
turtle.setxy(this.x, this.y)
initFcn(turtle)
})
}
}
export default Patch