Prims - Implement patches, pcolor, set
[clnl] / src / main / nvm / nvm.lisp
1 (in-package #:clnl-nvm)
2
3 ; Implementations of all the things the nvm can do.
4
5 (defun show (value)
6  "SHOW VALUE => RESULT
7
8 ARGUMENTS AND VALUES:
9
10   VALUE: a NetLogo value
11   RESULT: undefined
12
13 DESCRIPTION:
14
15   A command that prints the given NetLogo value to the command center.
16
17   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#show"
18  (format t "Showing: ~A~%" (dump-object value)))
19
20 (defun lookup-color (color)
21  "LOOKUP-COLOR COLOR => COLOR-NUMBER
22
23 ARGUMENTS AND VALUES:
24
25   COLOR: a symbol representing a color
26   COLOR-NUMBER: the NetLogo color integer
27
28 DESCRIPTION:
29
30   Returns the number used to represent colors in NetLogo.
31
32   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#Constants"
33  (case color
34   (:black 0d0)
35   (:gray 5d0)
36   (:white 9.9d0)
37   (:red 15d0)
38   (:orange 25d0)
39   (:brown 35d0)
40   (:yellow 45d0)
41   (:green 55d0)
42   (:lime 65d0)
43   (:turquoise 75d0)
44   (:cyan 85d0)
45   (:sky 95d0)
46   (:blue 105d0)
47   (:violet 115d0)
48   (:magenta 125d0)
49   (:pink 135d0)))
50
51 (defun create-turtle ()
52  (setf
53   *turtles*
54   (nconc
55    *turtles*
56    (list
57     (make-turtle
58      :who (coerce *current-id* 'double-float)
59      :color (coerce (+ 5 (* 10 (clnl-random:next-int 14))) 'double-float)
60      :heading (coerce (clnl-random:next-int 360) 'double-float)
61      :xcor 0d0
62      :ycor 0d0))))
63  (incf *current-id*))
64
65 (defun die ()
66  "DIE => RESULT
67
68 ARGUMENTS AND VALUES:
69
70   RESULT: undefined, commands don't return
71
72 DESCRIPTION:
73
74   The turtle or link dies
75
76   A dead agent ceases to exist. The effects of this include:
77   - The agent will not execute any further code.
78   - The agent will disappear from any agentsets it was in, reducing the size of those agentsets by one.
79   - Any variable that was storing the agent will now instead have nobody in it.
80   - If the dead agent was a turtle, every link connected to it also dies.
81   - If the observer was watching or following the agent, the observer's perspective resets.
82
83   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#die"
84  (when (not (turtle-p *self*)) (error "Gotta call die in turtle scope, dude (~A)" *self*))
85  (setf (turtle-who *self*) -1)
86  (setf *turtles* (remove *self* *turtles*)))
87
88 (defun patches ()
89  "PATCHES => ALL-PATCHES
90
91 ARGUMENTS AND VALUES:
92
93   ALL-PATCHES: a NetLogo agentset, all patches
94
95 DESCRIPTION:
96
97   Reports the agentset consisting of all the patches.
98
99   This agentset is special in that it represents the living patches
100   each time it's used, so changes depending on the state of the engine.
101
102   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#patches"
103  *patches*)
104
105 (defun turtles ()
106  "TURTLES => ALL-TURTLES
107
108 ARGUMENTS AND VALUES:
109
110   ALL-TURTLES: a NetLogo agentset, all turtles
111
112 DESCRIPTION:
113
114   Reports the agentset consisting of all the turtles.
115
116   This agentset is special in that it represents the living turtles
117   each time it's used, so changes depending on the state of the engine.
118
119   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#turtles"
120  *turtles*)
121
122 (defun ask (agent-set fn)
123  "ASK AGENT-SET FN => RESULT
124
125 ARGUMENTS AND VALUES:
126
127   AGENT-SET: a NetLogo agentset
128   FN: a function, run on each agent
129   RESULT: undefined, commands don't return
130
131 DESCRIPTION:
132
133   ASK is equivalent to ask in NetLogo.
134
135   The specified AGENT-SET runs the given FN.  The order in which the agents
136   are run is random each time, and only agents that are in the set at the
137   beginning of the call.
138
139   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#ask"
140  (let
141   ((iter (shufflerator agent-set)))
142   (loop
143    :for agent := (funcall iter)
144    :while agent
145    :do (let ((*myself* *self*) (*self* agent)) (funcall fn)))))
146
147 (defun of (fn agent-set)
148  "OF FN AGENT-SET => RESULT
149
150 ARGUMENTS AND VALUES:
151
152   FN: a function, run on each agent
153   AGENT-SET: a NetLogo agentset
154   RESULT: a list
155
156 DESCRIPTION:
157
158   OF is equivalent to of in NetLogo.
159
160   The specified AGENT-SET runs the given FN.  The order in which the agents
161   are run is random each time, and only agents that are in the set at the
162   beginning of the call.  A list is returned of the returned valuse of
163   FN.
164
165   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#of"
166  (let
167   ((iter (shufflerator agent-set)))
168   (loop
169    :for agent := (funcall iter)
170    :while agent
171    :collect (let ((*myself* *self*) (*self* agent)) (funcall fn)))))
172
173 (defun shufflerator (agent-set)
174  (let
175   ((copy (copy-list agent-set))
176    (i 0)
177    (agent nil))
178   (flet
179    ((fetch ()
180      (let
181       ((idx (when (< i (1- (length copy))) (+ i (clnl-random:next-int (- (length copy) i))))))
182       (when idx (setf agent (nth idx copy)))
183       (when idx (setf (nth idx copy) (nth i copy)))
184       (incf i))))
185    (fetch) ; we pre-fetch because netlogo does, rng sync hype!
186    (lambda ()
187     (cond
188      ((> i (length copy)) nil)
189      ((= i (length copy)) (incf i) (car (last copy)))
190      (t (let ((result agent)) (fetch) result)))))))
191
192 (defun random-float (n)
193  "RANDOM-FLOAT N => RANDOM-NUMBER
194
195 ARGUMENTS AND VALUES:
196
197   N: a double, the upper bound of the random float
198   RANDOM-NUMBER: a double, the random result
199
200 DESCRIPTION:
201
202   Returns a random number strictly closer to zero than N.
203
204   If number is positive, returns a random floating point number greater than
205   or equal to 0 but strictly less than number.
206
207   If number is negative, returns a random floating point number less than or equal
208   to 0, but strictly greater than number.
209
210   If number is zero, the result is always 0.
211
212   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#random-float"
213  (clnl-random:next-double n))
214
215 (defun jump (n)
216  (when (not (turtle-p *self*)) (error "Gotta call fd in turtle scope, dude (~A)" *self*))
217  (setf
218   (turtle-xcor *self*)
219   (wrap-x *topology*
220    (+ (turtle-xcor *self*) (* n (using-cached-sin (turtle-heading *self*))))))
221  (setf
222   (turtle-ycor *self*)
223   (wrap-y *topology*
224    (+ (turtle-ycor *self*) (* n (using-cached-cos (turtle-heading *self*)))))))
225
226 (defun forward (n)
227  "FORWARD N => RESULT
228
229 ARGUMENTS AND VALUES:
230
231   N: a double, the amount the turtle moves forward
232   RESULT: undefined
233
234 DESCRIPTION:
235
236   Moves the current turtle forward N steps, one step at a time.
237
238   This moves forward one at a time in order to make the view updates look
239   good in the case of a purposefully slow running instance.  If the number
240   is negative, the turtle moves backward.
241
242   If the current agent is not a turtle, it raises an error.
243
244   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#forward"
245  (when (not (turtle-p *self*)) (error "Gotta call fd in turtle scope, dude (~A)" *self*))
246  (labels
247   ((internal (i)
248     (cond
249      ((< (abs i) 3.2e-15) nil)
250      ((< (abs i) 1d0) (jump i))
251      (t (jump (if (> i 0d0) 1d0 -1d0)) (internal (- i (if (> i 0d0) 1d0 -1d0)))))))
252   (internal n)))
253
254 (defun turn-right (n)
255  "TURN-RIGHT N => RESULT
256
257 ARGUMENTS AND VALUES:
258
259   N: a double, the amount the turtle turns
260   RESULT: undefined
261
262 DESCRIPTION:
263
264   The turtle turns right by number degrees. (If number is negative, it turns left.)
265
266   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#right"
267  (when (not (turtle-p *self*)) (error "Gotta call fd in turtle scope, dude (~A)" *self*))
268  (let
269   ((new-heading (+ (turtle-heading *self*) n)))
270   (setf (turtle-heading *self*)
271    (cond
272     ((< new-heading 0) (+ (mod new-heading -360) 360))
273     ((>= new-heading 360) (mod new-heading 360))
274     (t new-heading)))))
275
276 (defun turn-left (n)
277  "TURN-LEFT N => RESULT
278
279 ARGUMENTS AND VALUES:
280
281   N: a double, the amount the turtle turns
282   RESULT: undefined
283
284 DESCRIPTION:
285
286   The turtle turns left by number degrees. (If number is negative, it turns right.)
287
288   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#right"
289  (turn-right (- n)))
290
291 (defun create-turtles (n)
292  "CREATE-TURTLES N => RESULT
293
294 ARGUMENTS AND VALUES:
295
296   N: an integer, the numbers of turtles to create
297   RESULT: undefined
298
299 DESCRIPTION:
300
301   Creates number new turtles at the origin.
302
303   New turtles have random integer headings and the color is randomly selected
304   from the 14 primary colors.  If commands are supplied, the new turtles
305   immediately run them (unimplemented).
306
307   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#create-turtles"
308  (loop :for i :from 1 :to n :do (create-turtle)))
309
310 (defun reset-ticks ()
311  "RESET-TICKS => RESULT
312
313 ARGUMENTS AND VALUES:
314
315   RESULT: undefined
316
317 DESCRIPTION:
318
319   Resets the tick counter to zero, sets up all plots, then updates all plots.
320
321   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#reset-ticks"
322  (setf *ticks* 0d0))
323
324 (defun tick ()
325  "RESET-TICKS => RESULT
326
327 ARGUMENTS AND VALUES:
328
329   RESULT: undefined
330
331 DESCRIPTION:
332
333   Advances the tick counter by one and updates all plots.
334
335   If the tick counter has not been started yet with reset-ticks, an error results.
336
337   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#tick"
338
339  (when (not *ticks*) (error "reset-ticks must be called"))
340  (incf *ticks*))
341
342 (defun ticks ()
343  "TICKS => CURRENT-TICKS
344
345 ARGUMENTS AND VALUES:
346
347   CURRENT-TICKS: A positiv double, representing the current number of ticks
348
349 DESCRIPTION:
350
351   Reports the current value of the tick counter. The result is always a number and never negative.
352
353   If the tick counter has not been started yet with reset-ticks, an error results.
354
355   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#ticks"
356  (when (not *ticks*) (error "reset-ticks must be called"))
357  *ticks*)
358
359 (defun create-world (&key dims)
360  "CREATE-WORLD &key DIMS => RESULT
361
362   DIMS: (:xmin XMIN :xmax XMAX :ymin YMIN :ymax YMAX)
363
364 ARGUMENTS AND VALUES:
365
366   RESULT: undefined
367   XMIN: An integer representing the minimum patch coord in X
368   XMAX: An integer representing the maximum patch coord in X
369   YMIN: An integer representing the minimum patch coord in Y
370   YMAX: An integer representing the maximum patch coord in Y
371
372 DESCRIPTION:
373
374   Initializes the world in the NVM.
375
376   This should be called before using the engine in any real capacity.  If
377   called when an engine is already running, it may do somethign weird."
378  (setf *dimensions* dims)
379  (setf
380   *patches*
381   (loop
382    :for y :from (max-pycor) :downto (min-pycor)
383    :append (loop
384             :for x :from (min-pxcor) :to (max-pxcor)
385             :collect (make-patch
386                       :xcor (coerce x 'double-float)
387                       :ycor (coerce y 'double-float)
388                       :color 0d0))))
389  (setf *turtles* nil)
390  (setf *current-id* 0))
391
392 ; These match netlogo's dump
393 (defgeneric dump-object (o))
394
395 (defmethod dump-object ((n double-float))
396  (multiple-value-bind (int rem) (floor n)
397   (if (eql 0d0 rem)
398    (format nil "~A" int)
399    (let
400     ((output (format nil "~D" n)))
401     ; Someday we'll have d<posint>, but this is not that day!
402     (cl-ppcre:regex-replace "d-" (cl-ppcre:regex-replace "d0" output "") "E-")))))
403
404 (defmethod dump-object ((o string)) o)
405
406 (defmethod dump-object ((o (eql t))) "true")
407 (defmethod dump-object ((o (eql nil))) "false")
408
409 (defmethod dump-object ((o list)) (format nil "[~{~A~^ ~}]" (mapcar #'dump-object o)))
410
411 (defun current-state ()
412  "CURRENT-STATE => WORLD-STATE
413
414 ARGUMENTS AND VALUES:
415
416   WORLD-STATE: A list, the current state of the whole world
417
418 DESCRIPTION:
419
420   Dumps out the state of the world.
421
422   This is useful for visualizations and also storing in a common lisp
423   data structure for easy usage in a common lisp instance.  It's preferable
424   to use this when working with the nvm than the output done by export-world.
425
426   Currently this only dumps out turtle and patch information.
427
428   This is called CURRENT-STATE because export-world is an actual primitive
429   used by NetLogo."
430  (list
431   (mapcar
432    (lambda (turtle)
433     (list
434      :color (turtle-color turtle)
435      :xcor (turtle-xcor turtle)
436      :ycor (turtle-ycor turtle)
437      :heading (turtle-heading turtle)))
438    *turtles*)
439   (mapcar
440    (lambda (patch)
441     (list
442      :color (patch-color patch)
443      :xcor (patch-xcor patch)
444      :ycor (patch-ycor patch)))
445    *patches*)))
446
447 (defun export-turtles ()
448  (append
449   (list
450    "\"TURTLES\""
451    (format nil "~A~A"
452     "\"who\",\"color\",\"heading\",\"xcor\",\"ycor\",\"shape\",\"label\",\"label-color\","
453     "\"breed\",\"hidden?\",\"size\",\"pen-size\",\"pen-mode\""))
454   (mapcar
455    (lambda (turtle)
456     (format nil
457      "\"~A\",\"~A\",\"~A\",\"~A\",\"~A\",~A"
458      (dump-object (turtle-who turtle))
459      (dump-object (turtle-color turtle))
460      (dump-object (turtle-heading turtle))
461      (dump-object (turtle-xcor turtle))
462      (dump-object (turtle-ycor turtle))
463      "\"\"\"default\"\"\",\"\"\"\"\"\",\"9.9\",\"{all-turtles}\",\"false\",\"1\",\"1\",\"\"\"up\"\"\""))
464    *turtles*)))
465
466 (defun export-patches ()
467  (append
468   (list
469    "\"PATCHES\""
470    "\"pxcor\",\"pycor\",\"pcolor\",\"plabel\",\"plabel-color\"")
471   (mapcar
472    (lambda (patch)
473     (format nil
474      "\"~A\",\"~A\",\"~A\",\"\"\"\"\"\",\"9.9\""
475      (dump-object (patch-xcor patch))
476      (dump-object (patch-ycor patch))
477      (dump-object (patch-color patch))))
478    *patches*)))
479
480 (defun export-world ()
481  "EXPORT-WORLD => WORLD-CSV
482
483 ARGUMENTS AND VALUES:
484
485   WORLD-CSV: A string, the csv of the world
486
487 DESCRIPTION:
488
489   Dumps out a csv matching NetLogo's export world.
490
491   This is useful for serializing the current state of the engine in order
492   to compare against NetLogo or to reimport later.  Contains everything needed
493   to boot up a NetLogo instance in the exact same state."
494  (format nil "~{~A~%~}"
495   (list
496    (format nil "~S" "RANDOM STATE")
497    (format nil "~S" (clnl-random:export))
498    ""
499    (format nil "~S" "GLOBALS")
500    (format nil "~A~A"
501     "\"min-pxcor\",\"max-pxcor\",\"min-pycor\",\"max-pycor\",\"perspective\",\"subject\","
502     "\"nextIndex\",\"directed-links\",\"ticks\",")
503    (format nil "\"~A\",\"~A\",\"~A\",\"~A\",\"0\",\"nobody\",\"~A\",\"\"\"NEITHER\"\"\",\"-1\""
504     (min-pxcor) (max-pxcor) (min-pycor) (max-pycor) *current-id*)
505    ""
506    (format nil "~{~A~^~%~}" (export-turtles))
507    ""
508    (format nil "~{~A~^~%~}" (export-patches))
509    ""
510    (format nil "~S" "LINKS")
511    "\"end1\",\"end2\",\"color\",\"label\",\"label-color\",\"hidden?\",\"breed\",\"thickness\",\"shape\",\"tie-mode\""
512    "")))