import * as util from './utils.js'
// import AgentArray from './AgentArray.js'
import AgentList from './AgentList.js'
import AgentSet from './AgentSet.js'
/**
* A {@link Turtle} is an object living on the {@link Patches} world.
* Their coordinates are floats, unlike Patches with integer coordinates.
* They can morph between types of turtles (breeds) and storee information,
* both within themselves but also on Patches and Links.
*
* The Turtles (plural) array, (AgentSet) is a collection of Turtle objects
* that the Turtles array creates and manages.
*
* You do not create either individual Turtle objects, the Turtles array does.
* You also do not create the Turtles array, class {@link Model} does.
*
* You use both, however, using the methods they both provide.
*
* @param {Model} model The model I belong to
* @param {Turtle|Turtle3d} AgentClass The Turtle class
* @param {string} name The name of this new Turtles instance
* @param {null|Turtles} [baseSet=null] Used to create a breed subclass
*/
class Turtles extends AgentSet {
constructor(model, AgentClass, name, baseSet = null) {
super(model, AgentClass, name, baseSet)
if (!name) console.log('Turtles empty name', this)
}
/**
* Create a single Turtle, adding it to this Turtles array.
* The init function is called to initialize the new Turtle.
* Returns the new Turtle.
*
* @param {Function} [initFcn=turtle => {}]
* @returns {Turtle} The newly created Turtle
*/
createOne(initFcn = turtle => {}) {
const turtle = this.addAgent()
// NetLogo docs: Creates number new turtles at the origin.
// New turtles have random integer headings
turtle.theta = util.randomFloat(Math.PI * 2)
// turtle.heading = this.model.fromRads(util.randomFloat(Math.PI * 2))
const p = turtle.patch
if (p.turtles != null) {
p.turtles.push(turtle)
}
initFcn(turtle)
return turtle
}
/**
* Create num Turtles, adding them to this Turtles array.
* The init function is called to initialize each new Turtle.
* Returns an array of the new Turtles
*
* @param {number} number Number of Turtles to create
* @param {Function} [initFcn=turtle => {}] A function to initialize new turtles.
* @returns {Array} The newly created Turtles
*/
create(num, initFcn = turtle => {}) {
return util.repeat(num, (i, a) => {
a.push(this.createOne(initFcn))
})
}
/**
* Return the closest turtle within radius distance of x,y.
* Return null if no turtles within radius.
* If I am a breed, return the closest fellow breed.
*
* @param {number} x X coordinate
* @param {number} y Y coordinate
* @param {number} radius Radius in patches units
* @returns {Turtle} The closest Turtle
*/
closestTurtle(x, y, radius) {
const ts = this.inPatchRectXY(x, y, radius)
if (ts.length === 0) return null
return ts.minOneOf(t => t.distanceXY(x, y))
//pDisk.minOneOf(t => t.dist)
}
/**
* Return an array of Turtles within the array of patchs.
* If I am a breed, return only the Turtles of my breed.
*
* @param {Patch[]} patches Array of patches
* @returns {AgentList} The turtles withn the Patches array.
*/
inPatches(patches) {
let array = new AgentList(this.model)
for (const p of patches) array.push(...p.turtlesHere)
// REMIND: can't use withBreed .. its not an AgentSet. Move to AgentArray?
if (this.isBreedSet()) array = array.filter(a => a.agentSet === this)
return array
}
/**
* Return an array of Turtles within the dx,dy patchRect centered on turtle.
* If I am a breed, return only the Turtles of my breed.
*
* @param {Turtle} turtle The Turtle at the patchRect center.
* @param {number} dx The integer x radius of the patchRect
* @param {number} [dy=dx] The integer y radius of the patchRect
* @param {boolean} [meToo=false] Whether or not to return me as well
* @returns {AgentList} The turtles within the patchRect
*/
inPatchRect(turtle, dx, dy = dx, meToo = false) {
const agents = this.inPatchRectXY(turtle.x, turtle.y, dx, dy)
if (!meToo) util.removeArrayItem(agents, turtle)
return agents
}
/**
* Return an array of Turtles within the dx,dy patchRect centered on x,y.
* If I am a breed, return only the Turtles of my breed.
*
* @param {number} x the patchRect center's integer x value
* @param {number} y the patchRect center's integer y value
* @param {number} dx The integer x radius of the patchRect
* @param {number} [dy=dx] The integer y radius of the patchRect
* @param {boolean} [meToo=false] Whether or not to return me as well
* @returns {AgentList} The turtles within the patchRect
*/
inPatchRectXY(x, y, dx, dy = dx) {
const patches = this.model.patches.patchRectXY(x, y, dx, dy, true)
return this.inPatches(patches)
}
/**
* Return all the Turtles within radius of me.
* If I am a breed, return only fellow breeds.
*
* @param {Turtle} turtle
* @param {number} radius
* @param {boolean} [meToo=false] Whether or not to return me as well
* @returns {AgentList} The turtles within radius of me
*/
inRadius(turtle, radius, meToo = false) {
const agents = this.inPatchRect(turtle, radius, radius, true)
return agents.inRadius(turtle, radius, meToo)
}
/**
* Return all the Turtles with a cone of me.
* The cone is coneAngle wide, centered on my heading.
* If I am a breed, return only fellow breeds.
*
* @param {Turtle} turtle
* @param {number} radius
* @param {boolean} [meToo=false] Whether or not to return me as well
* @returns {AgentList} The turtles within the cone.
*/
inCone(turtle, radius, coneAngle, meToo = false) {
const agents = this.inPatchRect(turtle, radius, radius, true)
return agents.inCone(turtle, radius, coneAngle, turtle.heading, meToo)
}
/**
* Position the Turtles in this breed in an equally spaced circle
* of the given center and radius.
* The turtle headings will be away from the center.
*
* @param {number} [radius=this.model.world.maxX * 0.9] The circle's radius
* @param {Array} [center=[0, 0]] An x,y array
*/
layoutCircle(radius = this.model.world.maxX * 0.9, center = [0, 0]) {
const startAngle = Math.PI / 2 // up
const direction = -1 // Clockwise
const dTheta = (2 * Math.PI) / this.length
const [x0, y0] = center
this.ask((turtle, i) => {
turtle.setxy(x0, y0)
turtle.theta = startAngle + direction * dTheta * i
turtle.forward(radius)
})
}
}
// export default Turtles
export default Turtles