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 common lisp interface manager spec implementation with embeddable common lisp as normal.
(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 '\#)))))))))))
(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)))))
(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 <<plant -1> <insect -2> <bird +1>>
put simple-plant char-code 112
(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.
(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)
=> <world simple-plant PLANT40198>
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.
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.
(setq eepitch-buffer-name "*slime-repl clisp*")
ssv .target PLANT40306
(get .target current-direction)
propagate .target
ses.150) (get world contents)
=> <world simple-plant PLANT40198 PLANT40306 PLANT40396>
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*)
(defun eepitch-send
(buffername line)
(setq eepitch-buffer-name buffername)
(setq line (eepitch-preprocess-line line))
(eepitch-prepare)
(eepitch-line line))
(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))))
(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"))
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.
On the Mastodon 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.
screwlisp proposes kittens