screwlisp proposes kittens

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 common lisp interface manager spec implementation with embeddable common lisp 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 niled 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

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)
  => <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.

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)
  => <world simple-plant PLANT40198 PLANT40306 PLANT40396>

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 loadable 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 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