agentscript
Version:
AgentScript Model in Model/View architecture
165 lines (147 loc) • 5.45 kB
JavaScript
/*
Note: this is the only model that has dom methods.
To keep the model/view split clean, we provide a
pre-built dataset created from a 256x256 gis tile (needing dom methods)
scaled to 101x101 and thus does not need any of the dom methods
*/
import * as util from 'https://code.agentscript.org/src/utils.js'
import World from 'https://code.agentscript.org/src/World.js'
import Model from 'https://code.agentscript.org/src/Model.js'
import tileDataSet from './data/tile101x101.js'
// Current tile dataSet functions:
// redfishUSDataSet
// redfishWorldDataSet
// mapzenDataSet
// mapboxDataSet
import { mapzen as provider } from 'https://code.agentscript.org/src/TileData.js'
import BBoxDataSet from 'https://code.agentscript.org/src/BBoxDataSet.js'
export default class DropletsModel extends Model {
speed = 0.5
// stepType choices:
// 'minNeighbor',
// 'patchAspect',
// 'dataSetAspectNearest',
// 'dataSetAspectBilinear',
stepType = 'minNeighbor'
moves = 0 // how many moves in a step
// Installed datasets:
elevation
dzdx
dzdy
slope
aspect
// ======================
constructor(worldOptions = World.defaultOptions(50)) {
super(worldOptions)
}
// data can be gis [z, x, y], [bbox, z], z, or a DataSet
// if world is a geoworld: [world.bbox, z]
async startup(data = tileDataSet) {
if (util.isDataSet(data)) {
// data is a dataset, use as is.
// workers: reconstruct the dataset object via
// new DataSet(width, height, data)
this.elevation = data
} else if (Array.isArray(data)) {
// && data.length === 3) {
if (data.length === 3) {
// data is [z, x, y] array
this.elevation = await provider.zxyToDataSet(...data)
} else if (data.length === 2) {
// data is [bbox, zoom]
this.elevation = await new BBoxDataSet().getBBoxDataSet(...data)
}
} else if (typeof data === 'number') {
// data is zoom; use world.bbox for bbox
const bbox = this.world.bbox
const zoom = data
if (bbox) {
const bboxDataSet = new BBoxDataSet()
this.elevation = await bboxDataSet.getBBoxDataSet(bbox, zoom)
}
}
if (!this.elevation)
throw Error(
'model startup: data argument is not one of [z, x, y], [bbox, z], z, or a DataSet'
)
}
installDataSets(elevation) {
const slopeAndAspect = elevation.slopeAndAspect()
const { dzdx, dzdy, slope, aspect } = slopeAndAspect
Object.assign(this, { elevation, dzdx, dzdy, slope, aspect })
this.patches.importDataSet(elevation, 'elevation', true)
this.patches.importDataSet(aspect, 'aspect', true)
}
setup() {
this.installDataSets(this.elevation)
// handled by step():
// this.turtles.setDefault('atEdge', 'die')
this.turtles.ask(t => (t.done = false))
this.localMins = []
this.patches.ask(p => {
p.isLocalMin =
p.neighbors.minOneOf('elevation').elevation > p.elevation
if (p.isLocalMin) this.localMins.push(p)
p.sprout(1, this.turtles)
})
}
faceDownhill(t) {
if (this.stepType === 'minNeighbor') {
// Face the best neighbor if better than me
const n = t.patch.neighbors.minOneOf('elevation')
if (t.patch.elevation > n.elevation) t.face(n)
} else if (this.stepType === 'patchAspect') {
t.theta = t.patch.aspect
} else if (this.stepType.includes('dataSet')) {
// Move in direction of aspect DataSet:
const { minXcor, maxYcor, numX, numY } = this.world
// bilinear many more minima
const nearest = this.stepType === 'dataSetAspectNearest'
t.theta = this.aspect.coordSample(
t.x,
t.y,
minXcor,
maxYcor,
numX,
numY,
nearest
)
} else {
throw Error('bad stepType: ' + this.stepType)
}
}
handleLocalMin(t) {
if (t.patch.isLocalMin) {
// center t in patch, and mark as done
t.setxy(t.patch.x, t.patch.y)
t.done = true
}
}
patchOK(t, p) {
return p.elevation < t.patch.elevation
}
step() {
if (this.done) return
this.moves = 0
this.turtles.ask(t => {
if (t.done) return
this.faceDownhill(t)
const pAhead = t.patchAtHeadingAndDistance(t.heading, this.speed)
if (!pAhead) {
t.die()
} else if (pAhead === t.patch || this.patchOK(t, pAhead)) {
t.forward(this.speed)
this.handleLocalMin(t)
this.moves++
} else {
t.done = true
}
})
this.done = this.moves === 0
if (this.done) console.log('No moves, stopping at step', this.ticks)
}
// Useful informational function: (not used by model but can be used by "app")
turtlesOnLocalMins() {
return this.localMins.reduce((acc, p) => acc + p.turtlesHere.length, 0)
}
}