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