Prims - Update one-of to take lists. Rename agent-set to agentset
[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-agentset fn)
123  "ASK AGENT-OR-AGENTSET FN => RESULT
124
125   AGENT-OR-AGENTSET: AGENT | AGENTSET
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   AGENTSET: a NetLogo agentset
133
134 DESCRIPTION:
135
136   ASK is equivalent to ask in NetLogo.
137
138   The specified AGENTSET or AGENT runs the given FN.  In the case of an
139   AGENTSET, 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   ((agentset-p agent-or-agentset)
145    (let
146     ((iter (shufflerator (agentset-list agent-or-agentset))))
147     (loop
148      :for agent := (funcall iter)
149      :while agent
150      :do (let ((*myself* *self*) (*self* agent)) (funcall fn)))))
151   ((agent-p agent-or-agentset)
152    (let ((*myself* *self*) (*self* agent-or-agentset)) (funcall fn)))
153   (t
154    (error "Ask requires an agentset or agent but got: ~A" agent-or-agentset))))
155
156 (defun count (agentset)
157  "COUNT AGENTSET => N
158
159 ARGUMENTS AND VALUES:
160
161   AGENTSET: 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 AGENTSET.
168
169   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#count"
170  (coerce (length (agentset-list agentset)) 'double-float))
171
172 (defun of (fn agent-or-agentset)
173  "OF FN AGENT-OR-AGENTSET => RESULT
174
175   AGENT-OR-AGENTSET: AGENT | AGENTSET
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   AGENTSET: 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 AGENTSET or AGENT runs the given FN.  In the case of an
191   AGENTSET, 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 AGENTSET, 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   ((agentset-p agent-or-agentset)
200    (let
201     ((iter (shufflerator (agentset-list agent-or-agentset))))
202     (loop
203      :for agent := (funcall iter)
204      :while agent
205      :collect (let ((*myself* *self*) (*self* agent)) (funcall fn)))))
206   ((agent-p agent-or-agentset)
207    (let ((*myself* *self*) (*self* agent-or-agentset)) (funcall fn)))
208   (t
209    (error "Of requires an agentset or agent but got: ~A" agent-or-agentset))))
210
211 (defun shufflerator (agentset-list)
212  (let
213   ((copy (copy-list agentset-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 (list-or-agentset)
317  "ONE-OF LIST-OR-AGENTSET => RESULT
318
319   LIST-OR-AGENTSET: LIST | AGENTSET
320   RESULT: RANDOM-VALUE | RANDOM-AGENT | :nobody
321
322 ARGUMENTS AND VALUES:
323
324   LIST: A list
325   AGENTSET: An agent set
326   RANDOM-VALUE: a value in LIST
327   RANDOM-AGENT: an agent if AGENTSET is non empty
328
329 DESCRIPTION:
330
331   From an AGENTSET, returns a RANDOM-AGENT. If the agentset is empty, returns :nobody.
332   From a list, returns a RANDOM-VALUE.  If the list is empty, an error occurs.
333
334   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#one-of"
335  (cond
336   ((agentset-p list-or-agentset)
337    (let*
338     ((agentset-list (agentset-list list-or-agentset))
339      (length (length agentset-list)))
340     (if (zerop length) :nobody (nth (clnl-random:next-int length) agentset-list))))
341   ((listp list-or-agentset)
342    (let*
343     ((length (length list-or-agentset)))
344     (if (zerop length)
345      (error "one-of requires a nonempty list")
346      (nth (clnl-random:next-int length) list-or-agentset))))
347   (t (error "one-of requires a list or agentset"))))
348
349 (defun jump (n)
350  (when (not (turtle-p *self*)) (error "Gotta call jump in turtle scope, dude (~A)" *self*))
351  (setf
352   (turtle-xcor *self*)
353   (wrap-x *topology*
354    (+ (turtle-xcor *self*) (* n (using-cached-sin (turtle-heading *self*))))))
355  (setf
356   (turtle-ycor *self*)
357   (wrap-y *topology*
358    (+ (turtle-ycor *self*) (* n (using-cached-cos (turtle-heading *self*)))))))
359
360 (defun setxy (x y)
361  "SETXY X Y => RESULT
362
363 ARGUMENTS AND VALUES:
364
365   X: a double
366   Y: a double
367   RESULT: undefined
368
369 DESCRIPTION:
370
371   Sets the x-coordinate and y-coordinate for the turle.  Equivalent to
372   set xcor x set ycor y, except it happens in one step inside of two.
373
374   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#setxy"
375  (when (not (turtle-p *self*)) (error "Gotta call setxy in turtle scope, dude (~A)" *self*))
376  (setf (turtle-xcor *self*) (wrap-x *topology* x))
377  (setf (turtle-ycor *self*) (wrap-y *topology* y)))
378
379 (defun forward (n)
380  "FORWARD N => RESULT
381
382 ARGUMENTS AND VALUES:
383
384   N: a double, the amount the turtle moves forward
385   RESULT: undefined
386
387 DESCRIPTION:
388
389   Moves the current turtle forward N steps, one step at a time.
390
391   This moves forward one at a time in order to make the view updates look
392   good in the case of a purposefully slow running instance.  If the number
393   is negative, the turtle moves backward.
394
395   If the current agent is not a turtle, it raises an error.
396
397   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#forward"
398  (when (not (turtle-p *self*)) (error "Gotta call fd in turtle scope, dude (~A)" *self*))
399  (labels
400   ((internal (i)
401     (cond
402      ((< (abs i) 3.2e-15) nil)
403      ((< (abs i) 1d0) (jump i))
404      (t (jump (if (> i 0d0) 1d0 -1d0)) (internal (- i (if (> i 0d0) 1d0 -1d0)))))))
405   (internal n)))
406
407 (defun turn-right (n)
408  "TURN-RIGHT N => RESULT
409
410 ARGUMENTS AND VALUES:
411
412   N: a double, the amount the turtle turns
413   RESULT: undefined
414
415 DESCRIPTION:
416
417   The turtle turns right by number degrees. (If number is negative, it turns left.)
418
419   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#right"
420  (when (not (turtle-p *self*)) (error "Gotta call fd in turtle scope, dude (~A)" *self*))
421  (let
422   ((new-heading (+ (turtle-heading *self*) n)))
423   (setf (turtle-heading *self*)
424    (cond
425     ((< new-heading 0) (+ (mod new-heading -360) 360))
426     ((>= new-heading 360) (mod new-heading 360))
427     (t new-heading)))))
428
429 (defun turn-left (n)
430  "TURN-LEFT N => RESULT
431
432 ARGUMENTS AND VALUES:
433
434   N: a double, the amount the turtle turns
435   RESULT: undefined
436
437 DESCRIPTION:
438
439   The turtle turns left by number degrees. (If number is negative, it turns right.)
440
441   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#right"
442  (turn-right (- n)))
443
444 (defun create-turtles (n)
445  "CREATE-TURTLES N => RESULT
446
447 ARGUMENTS AND VALUES:
448
449   N: an integer, the numbers of turtles to create
450   RESULT: undefined
451
452 DESCRIPTION:
453
454   Creates number new turtles at the origin.
455
456   New turtles have random integer headings and the color is randomly selected
457   from the 14 primary colors.  If commands are supplied, the new turtles
458   immediately run them (unimplemented).
459
460   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#create-turtles"
461  (loop :for i :from 1 :to n :do (create-turtle)))
462
463 (defun reset-ticks ()
464  "RESET-TICKS => RESULT
465
466 ARGUMENTS AND VALUES:
467
468   RESULT: undefined
469
470 DESCRIPTION:
471
472   Resets the tick counter to zero, sets up all plots, then updates all plots.
473
474   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#reset-ticks"
475  (setf *ticks* 0d0))
476
477 (defun tick ()
478  "RESET-TICKS => RESULT
479
480 ARGUMENTS AND VALUES:
481
482   RESULT: undefined
483
484 DESCRIPTION:
485
486   Advances the tick counter by one and updates all plots.
487
488   If the tick counter has not been started yet with reset-ticks, an error results.
489
490   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#tick"
491
492  (when (not *ticks*) (error "reset-ticks must be called"))
493  (incf *ticks*))
494
495 (defun ticks ()
496  "TICKS => CURRENT-TICKS
497
498 ARGUMENTS AND VALUES:
499
500   CURRENT-TICKS: A positiv double, representing the current number of ticks
501
502 DESCRIPTION:
503
504   Reports the current value of the tick counter. The result is always a number and never negative.
505
506   If the tick counter has not been started yet with reset-ticks, an error results.
507
508   See http://ccl.northwestern.edu/netlogo/docs/dictionary.html#ticks"
509  (when (not *ticks*) (error "reset-ticks must be called"))
510  *ticks*)
511
512 (defun create-world (&key dims)
513  "CREATE-WORLD &key DIMS => RESULT
514
515   DIMS: (:xmin XMIN :xmax XMAX :ymin YMIN :ymax YMAX)
516
517 ARGUMENTS AND VALUES:
518
519   RESULT: undefined
520   XMIN: An integer representing the minimum patch coord in X
521   XMAX: An integer representing the maximum patch coord in X
522   YMIN: An integer representing the minimum patch coord in Y
523   YMAX: An integer representing the maximum patch coord in Y
524
525 DESCRIPTION:
526
527   Initializes the world in the NVM.
528
529   This should be called before using the engine in any real capacity.  If
530   called when an engine is already running, it may do somethign weird."
531  (setf *dimensions* dims)
532  (setf
533   *patches*
534   (loop
535    :for y :from (max-pycor) :downto (min-pycor)
536    :append (loop
537             :for x :from (min-pxcor) :to (max-pxcor)
538             :collect (make-patch
539                       :xcor (coerce x 'double-float)
540                       :ycor (coerce y 'double-float)
541                       :color 0d0))))
542  (setf *turtles* nil)
543  (setf *current-id* 0))
544
545 ; These match netlogo's dump
546 (defgeneric dump-object (o))
547
548 (defmethod dump-object ((n double-float))
549  (multiple-value-bind (int rem) (floor n)
550   (if (eql 0d0 rem)
551    (format nil "~A" int)
552    (let
553     ((output (format nil "~D" n)))
554     ; Someday we'll have d<posint>, but this is not that day!
555     (cl-ppcre:regex-replace "d-" (cl-ppcre:regex-replace "d0" output "") "E-")))))
556
557 (defmethod dump-object ((o string)) (format nil "~A" (cl-ppcre:regex-replace-all "\"" (format nil "~S" o) "\"\"")))
558
559 (defmethod dump-object ((o (eql t))) "true")
560 (defmethod dump-object ((o (eql nil))) "false")
561
562 (defmethod dump-object ((o list)) (format nil "[~{~A~^ ~}]" (mapcar #'dump-object o)))
563
564 (defmethod dump-object ((o patch))
565  (format nil "(patch ~A ~A)" (dump-object (patch-xcor o)) (dump-object (patch-ycor o))))
566
567 (defmethod dump-object ((o turtle)) (format nil "(turtle ~A)" (dump-object (turtle-who o))))
568 (defmethod dump-object ((o (eql :nobody))) (format nil "nobody"))
569
570 (defun current-state ()
571  "CURRENT-STATE => WORLD-STATE
572
573 ARGUMENTS AND VALUES:
574
575   WORLD-STATE: A list, the current state of the whole world
576
577 DESCRIPTION:
578
579   Dumps out the state of the world.
580
581   This is useful for visualizations and also storing in a common lisp
582   data structure for easy usage in a common lisp instance.  It's preferable
583   to use this when working with the nvm than the output done by export-world.
584
585   Currently this only dumps out turtle and patch information.
586
587   This is called CURRENT-STATE because export-world is an actual primitive
588   used by NetLogo."
589  (list
590   (mapcar
591    (lambda (turtle)
592     (list
593      :color (turtle-color turtle)
594      :xcor (turtle-xcor turtle)
595      :ycor (turtle-ycor turtle)
596      :heading (turtle-heading turtle)
597      :size (turtle-size turtle)))
598    *turtles*)
599   (mapcar
600    (lambda (patch)
601     (list
602      :color (patch-color patch)
603      :xcor (patch-xcor patch)
604      :ycor (patch-ycor patch)))
605    *patches*)))
606
607 (defun export-turtles ()
608  (append
609   (list
610    "\"TURTLES\""
611    (format nil "~A~A"
612     "\"who\",\"color\",\"heading\",\"xcor\",\"ycor\",\"shape\",\"label\",\"label-color\","
613     "\"breed\",\"hidden?\",\"size\",\"pen-size\",\"pen-mode\""))
614   (mapcar
615    (lambda (turtle)
616     (format nil
617      "\"~A\",\"~A\",\"~A\",\"~A\",\"~A\",\"\"\"default\"\"\",\"~A\",\"~A\",\"{all-turtles}\",\"false\",\"~A\",~A"
618      (dump-object (turtle-who turtle))
619      (dump-object (turtle-color turtle))
620      (dump-object (turtle-heading turtle))
621      (dump-object (turtle-xcor turtle))
622      (dump-object (turtle-ycor turtle))
623      (dump-object (turtle-label turtle))
624      (dump-object (turtle-label-color turtle))
625      (dump-object (turtle-size turtle))
626      "\"1\",\"\"\"up\"\"\""))
627    *turtles*)))
628
629 (defun export-patches ()
630  (append
631   (list
632    "\"PATCHES\""
633    "\"pxcor\",\"pycor\",\"pcolor\",\"plabel\",\"plabel-color\"")
634   (mapcar
635    (lambda (patch)
636     (format nil
637      "\"~A\",\"~A\",\"~A\",\"\"\"\"\"\",\"9.9\""
638      (dump-object (patch-xcor patch))
639      (dump-object (patch-ycor patch))
640      (dump-object (patch-color patch))))
641    *patches*)))
642
643 (defun export-world ()
644  "EXPORT-WORLD => WORLD-CSV
645
646 ARGUMENTS AND VALUES:
647
648   WORLD-CSV: A string, the csv of the world
649
650 DESCRIPTION:
651
652   Dumps out a csv matching NetLogo's export world.
653
654   This is useful for serializing the current state of the engine in order
655   to compare against NetLogo or to reimport later.  Contains everything needed
656   to boot up a NetLogo instance in the exact same state."
657  (format nil "~{~A~%~}"
658   (list
659    (format nil "~S" "RANDOM STATE")
660    (format nil "~S" (clnl-random:export))
661    ""
662    (format nil "~S" "GLOBALS")
663    (format nil "~A~A"
664     "\"min-pxcor\",\"max-pxcor\",\"min-pycor\",\"max-pycor\",\"perspective\",\"subject\","
665     "\"nextIndex\",\"directed-links\",\"ticks\",")
666    (format nil "\"~A\",\"~A\",\"~A\",\"~A\",\"0\",\"nobody\",\"~A\",\"\"\"NEITHER\"\"\",\"-1\""
667     (min-pxcor) (max-pxcor) (min-pycor) (max-pycor) *current-id*)
668    ""
669    (format nil "~{~A~^~%~}" (export-turtles))
670    ""
671    (format nil "~{~A~^~%~}" (export-patches))
672    ""
673    (format nil "~S" "LINKS")
674    "\"end1\",\"end2\",\"color\",\"label\",\"label-color\",\"hidden?\",\"breed\",\"thickness\",\"shape\",\"tie-mode\""
675    "")))