/
World.scala
361 lines (294 loc) · 11.6 KB
/
World.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
// (C) Uri Wilensky. https://github.com/NetLogo/NetLogo
package org.nlogo.agent
import java.lang.{Double => JDouble, Integer => JInteger}
import java.util.{Arrays, List => JList, Map => JMap}
import org.nlogo.agent.ImporterJ.{ErrorHandler => ImporterErrorHandler, StringReader => ImporterStringReader}
import org.nlogo.api.{AgentException, Color, ImporterUser, MersenneTwisterFast, RandomSeedGenerator, Timer}
import org.nlogo.core.{AgentKind, Program, WorldDimensions}
import org.nlogo.log.LogManager
object World {
val Zero = JDouble.valueOf(0.0)
val One = JDouble.valueOf(1.0)
val NegativeOneInt = JInteger.valueOf(-1)
trait VariableWatcher {
/**
* Called when the watched variable is set.
* @param agent The agent for which the variable was set
* @param variableName The name of the variable as an upper case string
* @param value The new value of the variable
*/
def update(agent: Agent, variableName: String, value: Object): Unit
}
trait InRadiusOrCone {
def inRadius(agent: Agent, sourceSet: AgentSet, radius: Double, wrap: Boolean): JList[Agent]
def inCone(turtle: Turtle, sourceSet: AgentSet, radius: Double, angle: Double, wrap: Boolean): JList[Agent]
}
}
import org.nlogo.agent.World._
trait WorldKernel {
def program: Program
def observer: Observer
def observers: AgentSet
def patches: IndexedAgentSet
def turtles: TreeAgentSet
def links: TreeAgentSet
def topology: Topology
def breeds: JMap[String, TreeAgentSet]
def linkBreeds: JMap[String, TreeAgentSet]
def clearAll(): Unit = {}
}
trait CoreWorld
extends org.nlogo.api.WorldWithWorldRenderable
with WorldKernel
with WatcherManagement {
// anything that affects the outcome of the model should happen on the
// main RNG
val mainRNG: MersenneTwisterFast = new MersenneTwisterFast()
// anything that doesn't and can happen non-deterministically (for example monitor updates)
// should happen on the auxiliary rng. JobOwners should know which RNG they use.
val auxRNG: MersenneTwisterFast = new MersenneTwisterFast()
/// random seed generator
def generateSeed = RandomSeedGenerator.generateSeed()
val tieManager: TieManager
val tickCounter: TickCounter = new TickCounter()
val timer: Timer = new Timer()
def linkManager: LinkManager
// Patches are indexed in row-major order. See `getPatchAt`.
// This is also true in 3D (with the x-coordinate corresponding to a stride
// of 1, the y-coordinate with a stride of world-width, and the z-coordinate
// with a stride of world-width * world-height)
private[agent] var _patches: IndexedAgentSet = null
def patches: IndexedAgentSet = _patches
protected val _links: TreeAgentSet
def links: TreeAgentSet = _links
private[agent] var _topology: Topology = _
def topology: Topology = { _topology }
def getLinkVariablesArraySize(breed: AgentSet): Int
def getBreedSingular(breed: AgentSet): String
def getLinkBreedSingular(breed: AgentSet): String
abstract override def clearAll(): Unit = {
super.clearAll()
tickCounter.clear()
}
def ticks: Double = tickCounter.ticks
def allStoredValues: scala.collection.Iterator[Object] = AllStoredValues.apply(this)
def worldWidth: Int
def worldHeight: Int
def minPxcor: Int
def maxPxcor: Int
def minPycor: Int
def maxPycor: Int
}
abstract class World
extends WorldJ
with DimensionManagement
with CoreWorld
with GrossWorldState
with AgentManagement
with WatcherManagement {
def inRadiusOrCone: World.InRadiusOrCone
def clearDrawing(): Unit
def protractor: Protractor
def diffuse(param: Double, vn: Int): Unit
def diffuse4(param: Double, vn: Int): Unit
def stamp(agent: Agent, erase: Boolean): Unit
def changeTopology(xWrapping: Boolean, yWrapping: Boolean): Unit
def exportWorld(writer: java.io.PrintWriter, full: Boolean): Unit
@throws(classOf[java.io.IOException])
def importWorld(errorHandler: ImporterErrorHandler, importerUser: ImporterUser,
stringReader: org.nlogo.agent.ImporterJ.StringReader,
reader: java.io.BufferedReader): Unit
def sprout(patch: Patch, breed: AgentSet): Turtle
def copy(): World
}
// A note on wrapping: normally whether x and y coordinates wrap is a
// product of the topology. But we also have the old "-nowrap" primitives
// that don't wrap regardless of what the topology is. So that's why many
// methods like distance() and towards() take a boolean argument "wrap";
// it's true for the normal prims, false for the nowrap prims. - ST 5/24/06
class World2D extends World with CompilationManagement {
val protractor: Protractor = new Protractor(this)
val linkManager: LinkManager =
new LinkManagerImpl(this,
{ (world: World, src: Turtle, dest: Turtle, breed: AgentSet) =>
val l = new Link(world, src, dest, breed)
l.setId(newLinkId())
l
})
protected val _links: TreeAgentSet = new TreeAgentSet(AgentKind.Link, "LINKS")
val tieManager: TieManager = new TieManager(_links, linkManager, protractor)
protected val dimensionVariableNames =
Seq("MIN-PXCOR", "MAX-PXCOR", "MIN-PYCOR", "MAX-PYCOR", "WORLD-WIDTH", "WORLD-HEIGHT")
val inRadiusOrCone: InRadiusOrCone = new InRadiusOrCone(this)
/// observer/turtles/patches
changeTopology(true, true)
// create patches in the constructor, it's necessary in case
// the first model we load is 1x1 since when we do create patches
// in the model loader we only do the reallocation if the dimensions
// are different than the stored dimensions. This doesn't come up
// often because almost always load the default model first, and there
// aren't many 1x1 models. ev 2/5/07
createPatches(_minPxcor, _maxPxcor, _minPycor, _maxPycor)
setUpShapes(true)
protected def createObserver(): Observer =
new Observer(this)
def changeTopology(xWrapping: Boolean, yWrapping: Boolean): Unit = {
_topology = Topology.get(this, xWrapping, yWrapping)
if (_patches != null) { // is null during initialization
val it = _patches.iterator
while (it.hasNext) {
it.next().asInstanceOf[Patch].topologyChanged()
}
}
}
/// export world
def exportWorld(writer: java.io.PrintWriter, full: Boolean): Unit =
new Exporter(this, writer).exportWorld(full)
@throws(classOf[java.io.IOException])
def importWorld(errorHandler: ImporterErrorHandler, importerUser: ImporterUser,
stringReader: ImporterStringReader, reader: java.io.BufferedReader): Unit =
new Importer(errorHandler, this, importerUser, stringReader).importWorld(reader)
/// equality
def drawLine(x0: Double, y0: Double, x1: Double, y1: Double, color: Object, size: Double, mode: String): Unit = {
trailDrawer.drawLine(x0, y0, x1, y1, color, size, mode)
}
@throws(classOf[AgentException])
@throws(classOf[PatchException])
def diffuse(param: Double, vn: Int): Unit =
topology.diffuse(param, vn)
@throws(classOf[AgentException])
@throws(classOf[PatchException])
def diffuse4(param: Double, vn: Int): Unit =
topology.diffuse4(param, vn)
def getDimensions: WorldDimensions =
WorldDimensions(_minPxcor, _maxPxcor, _minPycor, _maxPycor, patchSize, wrappingAllowedInX, wrappingAllowedInY)
// used by Importer and Parser
def getOrCreateTurtle(id: Long): Turtle = {
val turtle = getTurtle(id)
if (turtle == null) {
val newTurtle = new Turtle2D(this, id)
nextTurtleIndex(StrictMath.max(nextTurtleIndex, id + 1))
newTurtle
} else {
turtle
}
}
def createTurtle(breed: AgentSet): Turtle = {
val baby = new Turtle2D(this, breed, Zero, Zero)
LogManager.turtleCreated(baby.id, breed.printName)
baby
}
// c must be in 0-13 range
// h can be out of range
def createTurtle(breed: AgentSet, c: Int, h: Int): Turtle = {
val baby = new Turtle2D(this, breed, Zero, Zero)
baby.colorDoubleUnchecked(JDouble.valueOf(5 + 10 * c))
baby.heading(h)
LogManager.turtleCreated(baby.id, breed.printName)
baby
}
@throws(classOf[AgentException])
def getPatchAt(x: Double, y: Double): Patch = {
val xc = wrapAndRoundX(x)
val yc = wrapAndRoundY(y)
val id = (_worldWidth * (_maxPycor - yc)) + xc - _minPxcor
getPatch(id)
}
// this procedure is the same as calling getPatchAt when the topology is a torus
// meaning it will override the Topology's wrapping rules and
def getPatchAtWrap(x: Double, y: Double): Patch = {
val minPx = _minPxcor
val maxPy = _maxPycor
val wrappedX = Topology.wrap(x, minPx - 0.5, _maxPxcor + 0.5)
val wrappedY = Topology.wrap(y, _minPycor - 0.5, maxPy + 0.5)
val xc =
if (wrappedX > 0) {
(wrappedX + 0.5).toInt
} else {
val intPart = wrappedX.toInt
val fractPart = intPart - wrappedX
if (fractPart > 0.5) intPart - 1 else intPart
}
val yc =
if (wrappedY > 0) {
(wrappedY + 0.5).toInt
} else {
val intPart = wrappedY.toInt
val fractPart = intPart - wrappedY
if (fractPart > 0.5) intPart - 1 else intPart
}
val patchid = (_worldWidth * (maxPy - yc)) + xc - minPx
getPatch(patchid)
}
def getPatchAtWrap(x: Int, y: Int): Patch =
fastGetPatchAt(
Topology.wrapPcor(x, _minPxcor, _maxPxcor),
Topology.wrapPcor(y, _minPycor, _maxPycor))
def fastGetPatchAt(xc: Int, yc: Int): Patch =
getPatch(_worldWidth * (_maxPycor - yc) + xc - _minPxcor)
def copy(): World = {
val newWorld = new World2D()
newWorld.tickCounter.ticks = tickCounter.ticks
newWorld.program(program)
copyDimensions(newWorld)
copyAgents(newWorld, newWorld)
copyGrossState(newWorld)
newWorld
}
def createPatches(minPxcor: Int, maxPxcor: Int,
minPycor: Int, maxPycor: Int): Unit = {
_patchScratch = null
_minPxcor = minPxcor
_maxPxcor = maxPxcor
_minPycor = minPycor
_maxPycor = maxPycor
_worldWidth = maxPxcor - minPxcor + 1
_worldHeight = maxPycor - minPycor + 1
_worldWidthBoxed = JDouble.valueOf(_worldWidth)
_worldHeightBoxed = JDouble.valueOf(_worldHeight)
_minPxcorBoxed = JDouble.valueOf(_minPxcor)
_minPycorBoxed = JDouble.valueOf(_minPycor)
_maxPxcorBoxed = JDouble.valueOf(_maxPxcor)
_maxPycorBoxed = JDouble.valueOf(_maxPycor)
rootsTable = new RootsTable(_worldWidth, _worldHeight)
recreateAllBreeds()
_turtles.clear() // so a SimpleChangeEvent is published
_links.clear() // so a SimpleChangeEvent is published
_links.directed = Directedness.Undetermined
val patchArray = new Array[Agent](_worldWidth * _worldHeight)
_patchColors = new Array[Int](_worldWidth * _worldHeight)
Arrays.fill(_patchColors, Color.getARGBbyPremodulatedColorNumber(0.0))
_patchColorsDirty = true
val numVariables = program.patchesOwn.size
observer.resetPerspective()
var i = 0
var x = minPxcor
var y = maxPycor
while (i < _worldWidth * _worldHeight) {
val patch = new Patch(this, i, x, y, numVariables)
x += 1
if (x > maxPxcor) {
x = minPxcor
y -= 1
}
patchArray(i) = patch
i += 1
}
_patches = new ArrayAgentSet(AgentKind.Patch, "patches", patchArray)
_patchesWithLabels = 0
_patchesAllBlack = true
_mayHavePartiallyTransparentObjects = false
}
override def clearAll(): Unit = {
super.clearAll()
}
// in a 2D world the drawing lives in the
// renderer so the workspace takes care of it.
def clearDrawing(): Unit = { }
def stamp(agent: Agent, erase: Boolean): Unit = {
trailDrawer.stamp(agent, erase)
}
def sprout(patch: Patch, breed: AgentSet): Turtle = {
new Turtle2D(this, breed, patch.pxcor, patch.pycor)
}
}