Prims - Optional arguments
[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  (let
53   ((new-turtle (make-turtle
54                 :who (coerce *current-id* 'double-float)
55                 :color (coerce (+ 5 (* 10 (clnl-random:next-int 14))) 'double-float)
56                 :heading (coerce (clnl-random:next-int 360) 'double-float)
57                 :xcor 0d0
58                 :ycor 0d0)))
59   (setf *turtles* (nconc *turtles* (list new-turtle)))
60   (incf *current-id*)
61   new-turtle))
62
63 (defun die ()
64  "DIE => RESULT
65
66 ARGUMENTS AND VALUES:
67
68   RESULT: undefined, commands don't return
69
70 DESCRIPTION:
71
72   The turtle or link dies
73
74   A dead agent ceases to exist. The effects of this include:
75   - The agent will not execute any further code.
76   - The agent will disappear from any agentsets it was in, reducing the size of those agentsets by one.
77   - Any variable that was storing the agent will now instead have nobody in it.
78   - If the dead agent was a turtle, every link connected to it also dies.
79   - If the observer was watching or following the agent, the observer's perspective resets.
80
81   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#die"
82  (when (not (turtle-p *self*)) (error "Gotta call die in turtle scope, dude (~A)" *self*))
83  (setf (turtle-who *self*) -1)
84  (setf *turtles* (remove *self* *turtles*)))
85
86 (defun patches ()
87  "PATCHES => ALL-PATCHES
88
89 ARGUMENTS AND VALUES:
90
91   ALL-PATCHES: a NetLogo agentset, all patches
92
93 DESCRIPTION:
94
95   Reports the agentset consisting of all the patches.
96
97   This agentset is special in that it represents the living patches
98   each time it's used, so changes depending on the state of the engine.
99
100   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#patches"
101  :patches)
102
103 (defun turtles ()
104  "TURTLES => ALL-TURTLES
105
106 ARGUMENTS AND VALUES:
107
108   ALL-TURTLES: a NetLogo agentset, all turtles
109
110 DESCRIPTION:
111
112   Reports the agentset consisting of all the turtles.
113
114   This agentset is special in that it represents the living turtles
115   each time it's used, so changes depending on the state of the engine.
116
117   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#turtles"
118  :turtles)
119
120 (defun ask (agent-or-agentset fn)
121  "ASK AGENT-OR-AGENTSET FN => RESULT
122
123   AGENT-OR-AGENTSET: AGENT | AGENTSET
124
125 ARGUMENTS AND VALUES:
126
127   FN: a function, run on each agent
128   RESULT: undefined, commands don't return
129   AGENT: a NetLogo agent
130   AGENTSET: a NetLogo agentset
131
132 DESCRIPTION:
133
134   ASK is equivalent to ask in NetLogo.
135
136   The specified AGENTSET or AGENT runs the given FN.  In the case of an
137   AGENTSET, the order in which the agents are run is random each time,
138   and only agents that are in the set at the beginning of the call.
139
140   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#ask"
141  (cond
142   ((agentset-p agent-or-agentset)
143    (let
144     ((iter (shufflerator (agentset-list agent-or-agentset))))
145     (loop
146      :for agent := (funcall iter)
147      :while agent
148      :do (let ((*myself* *self*) (*self* agent)) (funcall fn)))))
149   ((agent-p agent-or-agentset)
150    (let ((*myself* *self*) (*self* agent-or-agentset)) (funcall fn)))
151   (t
152    (error "Ask requires an agentset or agent but got: ~A" agent-or-agentset))))
153
154 (defun count (agentset)
155  "COUNT AGENTSET => N
156
157 ARGUMENTS AND VALUES:
158
159   AGENTSET: a NetLogo agentset
160   N: a number
161
162 DESCRIPTION:
163
164   COUNT is equivalent to count in NetLogo.  Returns N, the number of
165   agents in AGENTSET.
166
167   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#count"
168  (coerce (length (agentset-list agentset)) 'double-float))
169
170 (defun of (fn agent-or-agentset)
171  "OF FN AGENT-OR-AGENTSET => RESULT
172
173   AGENT-OR-AGENTSET: AGENT | AGENTSET
174   RESULT: RESULT-LIST | RESULT-VALUE
175
176 ARGUMENTS AND VALUES:
177
178   FN: a function, run on each agent
179   AGENT: a NetLogo agent
180   AGENTSET: a NetLogo agentset
181   RESULT-LIST: a list
182   RESULT-VALUE: a single value
183
184 DESCRIPTION:
185
186   OF is equivalent to of in NetLogo.
187
188   The specified AGENTSET or AGENT runs the given FN.  In the case of an
189   AGENTSET, the order in which the agents are run is random each time,
190   and only agents that are in the set at the beginning of the call.
191
192   RESULT-LIST is returned when the input is an AGENTSET, but RESULT-VALUE
193   is returned when only passed an AGENT.
194
195   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#of"
196  (cond
197   ((agentset-p agent-or-agentset)
198    (let
199     ((iter (shufflerator (agentset-list agent-or-agentset))))
200     (loop
201      :for agent := (funcall iter)
202      :while agent
203      :collect (let ((*myself* *self*) (*self* agent)) (funcall fn)))))
204   ((agent-p agent-or-agentset)
205    (let ((*myself* *self*) (*self* agent-or-agentset)) (funcall fn)))
206   (t
207    (error "Of requires an agentset or agent but got: ~A" agent-or-agentset))))
208
209 (defun shufflerator (agentset-list)
210  (let
211   ((copy (copy-list agentset-list))
212    (i 0)
213    (agent nil))
214   (flet
215    ((fetch ()
216      (let
217       ((idx (when (< i (1- (length copy))) (+ i (clnl-random:next-int (- (length copy) i))))))
218       (when idx (setf agent (nth idx copy)))
219       (when idx (setf (nth idx copy) (nth i copy)))
220       (incf i))))
221    (fetch) ; we pre-fetch because netlogo does, rng sync hype!
222    (lambda ()
223     (cond
224      ((> i (length copy)) nil)
225      ((= i (length copy)) (incf i) (car (last copy)))
226      (t (let ((result agent)) (fetch) result)))))))
227
228 (defun random-float (n)
229  "RANDOM-FLOAT N => RANDOM-NUMBER
230
231 ARGUMENTS AND VALUES:
232
233   N: a double, the upper bound of the random float
234   RANDOM-NUMBER: a double, the random result
235
236 DESCRIPTION:
237
238   Returns a random number strictly closer to zero than N.
239
240   If number is positive, returns a random floating point number greater than
241   or equal to 0 but strictly less than number.
242
243   If number is negative, returns a random floating point number less than or equal
244   to 0, but strictly greater than number.
245
246   If number is zero, the result is always 0.
247
248   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#random-float"
249  (clnl-random:next-double n))
250
251 (defun random (n)
252  "RANDOM N => RANDOM-NUMBER
253
254 ARGUMENTS AND VALUES:
255
256   N: an integer, the upper bound of the random
257   RANDOM-NUMBER: an integer, the random result
258
259 DESCRIPTION:
260
261   Returns a random number strictly closer to zero than N.
262
263   If number is positive, returns a random integer greater than or equal to 0,
264   but strictly less than number.
265
266   If number is negative, returns a random integer less than or equal to 0,
267   but strictly greater than number.
268
269   If number is zero, the result is always 0.
270
271   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#random"
272  (coerce (clnl-random:next-long (truncate n)) 'double-float))
273
274 (defun random-xcor ()
275  "RANDOM-XCOR => RANDOM-NUMBER
276
277 ARGUMENTS AND VALUES:
278
279   RANDOM-NUMBER: a float, the random result
280
281 DESCRIPTION:
282
283   Returns a random floating point number in the allowable range of turtle
284   coordinates along the x axis.
285
286   These range from min-pxcor - 0.5 (inclusive) to max-pxcor + 0.5 (exclusive)
287
288   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#random-cor"
289  (let
290   ((min (- (min-pxcor) 0.5d0))
291    (max (+ (max-pxcor) 0.5d0)))
292   (+ min (clnl-random:next-double (- max min)))))
293
294 (defun random-ycor ()
295  "RANDOM-YCOR => RANDOM-NUMBER
296
297 ARGUMENTS AND VALUES:
298
299   RANDOM-NUMBER: a float, the random result
300
301 DESCRIPTION:
302
303   Returns a random floating point number in the allowable range of turtle
304   coordinates along the y axis.
305
306   These range from min-pycor - 0.5 (inclusive) to max-pycor + 0.5 (exclusive)
307
308   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#random-cor"
309  (let
310   ((min (- (min-pycor) 0.5d0))
311    (max (+ (max-pycor) 0.5d0)))
312   (+ min (clnl-random:next-double (- max min)))))
313
314 (defun one-of (list-or-agentset)
315  "ONE-OF LIST-OR-AGENTSET => RESULT
316
317   LIST-OR-AGENTSET: LIST | AGENTSET
318   RESULT: RANDOM-VALUE | RANDOM-AGENT | :nobody
319
320 ARGUMENTS AND VALUES:
321
322   LIST: A list
323   AGENTSET: An agent set
324   RANDOM-VALUE: a value in LIST
325   RANDOM-AGENT: an agent if AGENTSET is non empty
326
327 DESCRIPTION:
328
329   From an AGENTSET, returns a RANDOM-AGENT. If the agentset is empty, returns :nobody.
330   From a list, returns a RANDOM-VALUE.  If the list is empty, an error occurs.
331
332   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#one-of"
333  (cond
334   ((agentset-p list-or-agentset)
335    (let*
336     ((agentset-list (agentset-list list-or-agentset))
337      (length (length agentset-list)))
338     (if (zerop length) :nobody (nth (clnl-random:next-int length) agentset-list))))
339   ((listp list-or-agentset)
340    (let*
341     ((length (length list-or-agentset)))
342     (if (zerop length)
343      (error "one-of requires a nonempty list")
344      (nth (clnl-random:next-int length) list-or-agentset))))
345   (t (error "one-of requires a list or agentset"))))
346
347 (defun jump (n)
348  (when (not (turtle-p *self*)) (error "Gotta call jump in turtle scope, dude (~A)" *self*))
349  (setf
350   (turtle-xcor *self*)
351   (wrap-x *topology*
352    (+ (turtle-xcor *self*) (* n (using-cached-sin (turtle-heading *self*))))))
353  (setf
354   (turtle-ycor *self*)
355   (wrap-y *topology*
356    (+ (turtle-ycor *self*) (* n (using-cached-cos (turtle-heading *self*)))))))
357
358 (defun setxy (x y)
359  "SETXY X Y => RESULT
360
361 ARGUMENTS AND VALUES:
362
363   X: a double
364   Y: a double
365   RESULT: undefined
366
367 DESCRIPTION:
368
369   Sets the x-coordinate and y-coordinate for the turle.  Equivalent to
370   set xcor x set ycor y, except it happens in one step inside of two.
371
372   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#setxy"
373  (when (not (turtle-p *self*)) (error "Gotta call setxy in turtle scope, dude (~A)" *self*))
374  (setf (turtle-xcor *self*) (wrap-x *topology* x))
375  (setf (turtle-ycor *self*) (wrap-y *topology* y)))
376
377 (defun forward (n)
378  "FORWARD N => RESULT
379
380 ARGUMENTS AND VALUES:
381
382   N: a double, the amount the turtle moves forward
383   RESULT: undefined
384
385 DESCRIPTION:
386
387   Moves the current turtle forward N steps, one step at a time.
388
389   This moves forward one at a time in order to make the view updates look
390   good in the case of a purposefully slow running instance.  If the number
391   is negative, the turtle moves backward.
392
393   If the current agent is not a turtle, it raises an error.
394
395   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#forward"
396  (when (not (turtle-p *self*)) (error "Gotta call fd in turtle scope, dude (~A)" *self*))
397  (labels
398   ((internal (i)
399     (cond
400      ((< (abs i) 3.2e-15) nil)
401      ((< (abs i) 1d0) (jump i))
402      (t (jump (if (> i 0d0) 1d0 -1d0)) (internal (- i (if (> i 0d0) 1d0 -1d0)))))))
403   (internal n)))
404
405 (defun turn-right (n)
406  "TURN-RIGHT N => RESULT
407
408 ARGUMENTS AND VALUES:
409
410   N: a double, the amount the turtle turns
411   RESULT: undefined
412
413 DESCRIPTION:
414
415   The turtle turns right by number degrees. (If number is negative, it turns left.)
416
417   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#right"
418  (when (not (turtle-p *self*)) (error "Gotta call fd in turtle scope, dude (~A)" *self*))
419  (let
420   ((new-heading (+ (turtle-heading *self*) n)))
421   (setf (turtle-heading *self*)
422    (cond
423     ((< new-heading 0) (+ (mod new-heading -360) 360))
424     ((>= new-heading 360) (mod new-heading 360))
425     (t new-heading)))))
426
427 (defun turn-left (n)
428  "TURN-LEFT N => RESULT
429
430 ARGUMENTS AND VALUES:
431
432   N: a double, the amount the turtle turns
433   RESULT: undefined
434
435 DESCRIPTION:
436
437   The turtle turns left by number degrees. (If number is negative, it turns right.)
438
439   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#right"
440  (turn-right (- n)))
441
442 (defun create-turtles (n &optional fn)
443  "CREATE-TURTLES N &optional FN => RESULT
444
445 ARGUMENTS AND VALUES:
446
447   N: an integer, the numbers of turtles to create
448   FN: A function, applied to each turtle after creation
449   RESULT: undefined
450
451 DESCRIPTION:
452
453   Creates number new turtles at the origin.
454
455   New turtles have random integer headings and the color is randomly selected
456   from the 14 primary colors.  If a function is supplied, the new turtles
457   immediately run it.
458
459   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#create-turtles"
460  (let
461   ((new-turtles (loop :repeat n :collect (create-turtle))))
462   (when fn (ask (list->agentset new-turtles) fn))))
463
464 (defun reset-ticks ()
465  "RESET-TICKS => RESULT
466
467 ARGUMENTS AND VALUES:
468
469   RESULT: undefined
470
471 DESCRIPTION:
472
473   Resets the tick counter to zero, sets up all plots, then updates all plots.
474
475   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#reset-ticks"
476  (setf *ticks* 0d0))
477
478 (defun tick ()
479  "RESET-TICKS => RESULT
480
481 ARGUMENTS AND VALUES:
482
483   RESULT: undefined
484
485 DESCRIPTION:
486
487   Advances the tick counter by one and updates all plots.
488
489   If the tick counter has not been started yet with reset-ticks, an error results.
490
491   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#tick"
492
493  (when (not *ticks*) (error "reset-ticks must be called"))
494  (incf *ticks*))
495
496 (defun ticks ()
497  "TICKS => CURRENT-TICKS
498
499 ARGUMENTS AND VALUES:
500
501   CURRENT-TICKS: A positiv double, representing the current number of ticks
502
503 DESCRIPTION:
504
505   Reports the current value of the tick counter. The result is always a number and never negative.
506
507   If the tick counter has not been started yet with reset-ticks, an error results.
508
509   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#ticks"
510  (when (not *ticks*) (error "reset-ticks must be called"))
511  *ticks*)
512
513 (defun create-world (&key dims)
514  "CREATE-WORLD &key DIMS => RESULT
515
516   DIMS: (:xmin XMIN :xmax XMAX :ymin YMIN :ymax YMAX)
517
518 ARGUMENTS AND VALUES:
519
520   RESULT: undefined
521   XMIN: An integer representing the minimum patch coord in X
522   XMAX: An integer representing the maximum patch coord in X
523   YMIN: An integer representing the minimum patch coord in Y
524   YMAX: An integer representing the maximum patch coord in Y
525
526 DESCRIPTION:
527
528   Initializes the world in the NVM.
529
530   This should be called before using the engine in any real capacity.  If
531   called when an engine is already running, it may do somethign weird."
532  (setf *dimensions* dims)
533  (setf
534   *patches*
535   (loop
536    :for y :from (max-pycor) :downto (min-pycor)
537    :append (loop
538             :for x :from (min-pxcor) :to (max-pxcor)
539             :collect (make-patch
540                       :xcor (coerce x 'double-float)
541                       :ycor (coerce y 'double-float)
542                       :color 0d0))))
543  (setf *turtles* nil)
544  (setf *current-id* 0))
545
546 ; These match netlogo's dump
547 (defgeneric dump-object (o))
548
549 (defmethod dump-object ((n double-float))
550  (multiple-value-bind (int rem) (floor n)
551   (if (eql 0d0 rem)
552    (format nil "~A" int)
553    (let
554     ((output (format nil "~D" n)))
555     ; Someday we'll have d<posint>, but this is not that day!
556     (cl-ppcre:regex-replace "d-" (cl-ppcre:regex-replace "d0" output "") "E-")))))
557
558 (defmethod dump-object ((o string)) (format nil "~A" (cl-ppcre:regex-replace-all "\"" (format nil "~S" o) "\"\"")))
559
560 (defmethod dump-object ((o (eql t))) "true")
561 (defmethod dump-object ((o (eql nil))) "false")
562
563 (defmethod dump-object ((o list)) (format nil "[~{~A~^ ~}]" (mapcar #'dump-object o)))
564
565 (defmethod dump-object ((o patch))
566  (format nil "(patch ~A ~A)" (dump-object (patch-xcor o)) (dump-object (patch-ycor o))))
567
568 (defmethod dump-object ((o turtle)) (format nil "(turtle ~A)" (dump-object (turtle-who o))))
569 (defmethod dump-object ((o (eql :nobody))) (format nil "nobody"))
570
571 (defun current-state ()
572  "CURRENT-STATE => WORLD-STATE
573
574 ARGUMENTS AND VALUES:
575
576   WORLD-STATE: A list, the current state of the whole world
577
578 DESCRIPTION:
579
580   Dumps out the state of the world.
581
582   This is useful for visualizations and also storing in a common lisp
583   data structure for easy usage in a common lisp instance.  It's preferable
584   to use this when working with the nvm than the output done by export-world.
585
586   Currently this only dumps out turtle and patch information.
587
588   This is called CURRENT-STATE because export-world is an actual primitive
589   used by NetLogo."
590  (list
591   (mapcar
592    (lambda (turtle)
593     (list
594      :color (turtle-color turtle)
595      :xcor (turtle-xcor turtle)
596      :ycor (turtle-ycor turtle)
597      :heading (turtle-heading turtle)
598      :size (turtle-size turtle)))
599    *turtles*)
600   (mapcar
601    (lambda (patch)
602     (list
603      :color (patch-color patch)
604      :xcor (patch-xcor patch)
605      :ycor (patch-ycor patch)))
606    *patches*)))
607
608 (defun export-turtles ()
609  (append
610   (list
611    "\"TURTLES\""
612    (format nil "~A~A"
613     "\"who\",\"color\",\"heading\",\"xcor\",\"ycor\",\"shape\",\"label\",\"label-color\","
614     "\"breed\",\"hidden?\",\"size\",\"pen-size\",\"pen-mode\""))
615   (mapcar
616    (lambda (turtle)
617     (format nil
618      "\"~A\",\"~A\",\"~A\",\"~A\",\"~A\",\"\"\"default\"\"\",\"~A\",\"~A\",\"{all-turtles}\",\"false\",\"~A\",~A"
619      (dump-object (turtle-who turtle))
620      (dump-object (turtle-color turtle))
621      (dump-object (turtle-heading turtle))
622      (dump-object (turtle-xcor turtle))
623      (dump-object (turtle-ycor turtle))
624      (dump-object (turtle-label turtle))
625      (dump-object (turtle-label-color turtle))
626      (dump-object (turtle-size turtle))
627      "\"1\",\"\"\"up\"\"\""))
628    *turtles*)))
629
630 (defun export-patches ()
631  (append
632   (list
633    "\"PATCHES\""
634    "\"pxcor\",\"pycor\",\"pcolor\",\"plabel\",\"plabel-color\"")
635   (mapcar
636    (lambda (patch)
637     (format nil
638      "\"~A\",\"~A\",\"~A\",\"\"\"\"\"\",\"9.9\""
639      (dump-object (patch-xcor patch))
640      (dump-object (patch-ycor patch))
641      (dump-object (patch-color patch))))
642    *patches*)))
643
644 (defun export-world ()
645  "EXPORT-WORLD => WORLD-CSV
646
647 ARGUMENTS AND VALUES:
648
649   WORLD-CSV: A string, the csv of the world
650
651 DESCRIPTION:
652
653   Dumps out a csv matching NetLogo's export world.
654
655   This is useful for serializing the current state of the engine in order
656   to compare against NetLogo or to reimport later.  Contains everything needed
657   to boot up a NetLogo instance in the exact same state."
658  (format nil "~{~A~%~}"
659   (list
660    (format nil "~S" "RANDOM STATE")
661    (format nil "~S" (clnl-random:export))
662    ""
663    (format nil "~S" "GLOBALS")
664    (format nil "~A~A"
665     "\"min-pxcor\",\"max-pxcor\",\"min-pycor\",\"max-pycor\",\"perspective\",\"subject\","
666     "\"nextIndex\",\"directed-links\",\"ticks\",")
667    (format nil "\"~A\",\"~A\",\"~A\",\"~A\",\"0\",\"nobody\",\"~A\",\"\"\"NEITHER\"\"\",\"-1\""
668     (min-pxcor) (max-pxcor) (min-pycor) (max-pycor) *current-id*)
669    ""
670    (format nil "~{~A~^~%~}" (export-turtles))
671    ""
672    (format nil "~{~A~^~%~}" (export-patches))
673    ""
674    (format nil "~S" "LINKS")
675    "\"end1\",\"end2\",\"color\",\"label\",\"label-color\",\"hidden?\",\"breed\",\"thickness\",\"shape\",\"tie-mode\""
676    "")))