--- layout: ../Site.layout.js --- # Just a devlog: Quick common lisp interface manager GUI for the simulation, experiments. My experience has been that I personally need a high level general user interface into my Leonardo system plant/insect/bird simulation. I will use [McCLIM](https://mcclim.common-lisp.dev/main.html) [common lisp interface manager spec](http://bauhh.dyndns.org:8000/clim-spec/index.html) implementation with [embeddable common lisp](https://ecl.common-lisp.dev/) as normal. ## World-looking interface ```  (setq eepitch-buffer-name "*slime-repl ECL*") (require :mcclim) (in-package :clim-user) (define-application-frame plant-insect-bird () ((ymin :initform 0) (ymax :initform 18) (xmin :initform 0) (xmax :initform 16) (world :initform nil)) (:panes (worldview :application :display-function 'table-world :incremental-redisplay t)) (:layouts (default (horizontally (:min-width 512 :min-height 512) worldview)))) (defun table-world (f p) (with-slots (world xmin xmax ymin ymax) f (updating-output (p) (formatting-table (p) (loop :for y :from ymax :downto ymin :do (formatting-row (p) (loop :for x :from xmin :to xmax :for ch := (cadr (assoc `(,x ,y) world :test 'equal)) :do (formatting-cell (p) (cond (ch (princ ch)) (t (princ '\\#))))))))))) ``` ## Getting the leonardo system world to common lisp ` (setq eepitch-buffer-name "*slime-repl clisp*")` and recalling the "one-liner" ``` . (defun world-to-emacs () (swank:eval-in-emacs `(prog1 t (setq *world* ',(loop :for thing :in (cdadr (get 'world 'contents)) :collect (symbol-plist thing)))))) ``` and ` (setq eepitch-buffer-name "*slime-repl ECL*")` ``` (defun interpret-world (&aux (plists (swank:eval-in-emacs '*world*))) (loop :with x-position := (intern "X-POSITION" "SWANK-IO-PACKAGE") :with y-position := (intern "Y-POSITION" "SWANK-IO-PACKAGE") :with char-code := (intern "CHAR-CODE" "SWANK-IO-PACKAGE") :for plist :in plists :collect `((,(floor (if (symbolp (getf plist x-position)) (parse-integer (symbol-name (getf plist x-position)) :junk-allowed t) (getf plist x-position))) ,(floor (if (symbolp (getf plist y-position)) (parse-integer (symbol-name (getf plist y-position)) :junk-allowed t) (getf plist y-position)))) ,(code-char (getf plist char-code))))) ``` ## Some organisms. ### A very simple plant ` (setq eepitch-buffer-name "*slime-repl clisp*")` Empty the world first: `deluge 0 0 0 0` `writefil world` `loadk world` recalling that `nil`ed entities are removed on `loadk`. `put simple-plant type plant` `addmember (get world contents) simple-plant` `put simple-plant x-position 10` `put simple-plant y-position 9` `put simple-plant starvation-rate 0` `put simple-plant current-direction n` `put simple-plant opening-angle 180` `put simple-plant sensor-scale 2` `put simple-plant natality-rate 50` `put simple-plant mortality-rate 10` `put simple-plant propagation-distance 2` `put simple-plant lethality-rate 0` `put simple-plant prey-species {}` `put simple-plant sensor-weights < >` `put simple-plant char-code 112` # Let's have a look at this ` (setq eepitch-buffer-name "*slime-repl clisp*")` `. (world-to-emacs)` ` (setq eepitch-buffer-name "*slime-repl ECL*")` `(make-application-frame 'plant-insect-bird)` `(defparameter *pib* *)` `(interpret-world)` `(with-slots (world) *pib* (setf world *))` `(run-frame-top-level *pib*)` Looking good. ### Propagating that plant a bit. ` (setq eepitch-buffer-name "*slime-repl clisp*")` `ssv .target (en (random (- (length (get world contents)) 1)) (get world contents))` ``` ses.121) .target => simple-plant ``` `eat .target` as https://mdhughes.tech points out, spatial stuff will need to happen spatially relatively soon, but for now I am just always traversing the world sequence to do anything. However, when I do add it, this will need to be in the context of adding basically adding a new thingtype representing the optimisation data structure. `sense2 .target` ``` ses.124) (get .target current-direction) => e ``` east is the first direction checked for propagation: It succeeded (there was no reason not to go east). `propagate .target` ``` ses.129) propagate .target ses.130) (get world contents) => ``` I was initially confused because `simple-plant` only has a `50%` chance to `propagate`. `. (world-to-emacs)` ` (setq eepitch-buffer-name "*slime-repl ECL*")` `(interpret-world)` `(with-slots (world) *pib* (setf world *))` `(run-frame-top-level *pib*)` The way we are doing things inch by inch is because I am manually triggering actions that will make up the actual user interface sequences-of-actions. So it's more like you are reading me interactively `step` through what I am incrementally deciding should happen. ### Watching plants spread out If we move the new `PLANT40198` into a sensor location of the original `simple-plant`, then calling `sense2` should reorient `simple-plant` to `propagate` in a different direction next. ` (setq eepitch-buffer-name "*slime-repl clisp*")` `put PLANT40198 x-position 14` `put PLANT40198 y-position 11` `sense2 .target` ``` ses.137) (get simple-plant current-direction) => n ``` Since we put `PLANT40198` in `simple-plant`'s `ene` sensor, `e` and `ne` received scores of `-1`. Counterclockwise from `e`, the first max-score was `n`, as we have seen. `propagate .target` `propagate .target` Boy is natality-rate being 50% annoying. `. (world-to-emacs)` ` (setq eepitch-buffer-name "*slime-repl ECL*")` `(interpret-world)` `(with-slots (world) *pib* (setf world *))` `(run-frame-top-level *pib*)` This makes me think that for plants that propagate distance 2, we need a lightning bolt shape, after which sensors of scale 2 should eventually fill whatever garden. I have also just disallowed autophagy (self-eating), so that plants can now 'prey on' other plants close to them with a possible meaning of competing for space or other resources, which will also help stop a certain kind of population explosion. ### Propagate up a fourth plant ` (setq eepitch-buffer-name "*slime-repl clisp*")` `ssv .target PLANT40306` `(get .target current-direction)` `propagate .target` ``` ses.150) (get world contents) => ``` ### Put the four plants in a lightning bolt shape `put PLANT40198 x-position 10` `put PLANT40198 y-position 10` `put PLANT40306 x-position 10` `put PLANT40306 y-position 11` `put PLANT40396 x-position 9` `put PLANT40396 y-position 10` `put simple-plant x-position 9` `put simple-plant y-position 9` `. (world-to-emacs)` ` (setq eepitch-buffer-name "*slime-repl ECL*")` `(interpret-world)` `(with-slots (world) *pib* (setf world *))` `(run-frame-top-level *pib*)` ### emacs eepitch-send ``` (defun eepitch-send (buffername line) (setq eepitch-buffer-name buffername) (setq line (eepitch-preprocess-line line)) (eepitch-prepare) (eepitch-line line)) ``` ### ECL e-e-str ``` (defun e-e-str (buffername line) "'external-eepitch-send' buffername string (emacs buffer name string) line string (as eepitch) " (require "asdf") (uiop:launch-program (let ((*print-pretty* nil)) (format nil "emacsclient --eval '(eepitch-send ~s \\"~a\\")'" buffername line)))) ``` ### Add a clim command that updates one plant ``` (define-plant-insect-bird-command (com-once :menu t) () (e-e-str "*slime-repl clisp*" "ssv .target (en (random (- (length (get world contents)) 1)) (get world contents))") (sleep 0.1) (e-e-str "*slime-repl clisp*" "") (sleep 0.1) (e-e-str "*slime-repl clisp*" "sense2 .target") (sleep 0.1) (e-e-str "*slime-repl clisp*" "propagate .target") (sleep 0.1) (e-e-str "*slime-repl clisp*" "eat .target") (sleep 0.1) (e-e-str "*slime-repl clisp*" "senesce .target")) ``` # Conclusions Oh I see, because the 9th row is an open interval, the only upwards move that ever happens is from the bottom of the lightning bolt's row to the top of the lightning bolt's row, and the top and middle of the lightning bolt never see a reason to leave their row. Bears some experiment. Emacs has more complete unicode than mcclim, but having the application-frame command on the interface really beats wading through code, so. Another thing is that I think deployment of a simulation might end up generating a `load`able ansi cl `.lisp` file which is deployable into other projects with a kind of light touch, whereas the living version inside the leonardo system is interactive and amenable to reasoning in ways the deployed version isn't thought to be. I feel closer now than I felt this morning. I'm a bit annoyed my modern unicode emoji weren't all in McCLIM but I feel like this probably wasn't a high priority. # Fin [On the Mastodon](https://gamerplus.org/@screwlisp/114884828384041096) as always. I can't say this is a very deep article for sharing; I guess it's my own internal exploration of my own systems.