--- layout: ../Site.layout.js --- # (lisp)Games As Knowledge - plant-insect-bird post-mortem In the end, my [lispgamejam25 submission](https://itch.io/jam/spring-lisp-game-jam-2025/rate/3546880) was two hours of implementing a trivial random walk. On the other hand, this was two hours of representing a random walk as a conjunction of disparate pieces of knowledge, some of which were arbitrary lisp programs, and some of which are a sitcalc oriented extension to first order logic. Let us, one more time and in painstaking detail review what this knowledge was and how it was created and accessed. ## The knowledgebase to store the knowledge ### Code ``` crek game-kb loadk game-kb setk game-kb ``` ### Details Entered interactively. This seems reasonable to create, load, and set as active the knowledge that will constitute a game. ## Add a place for types and functions ### Code ``` crefil types-and-fun loadk types-and-fun ``` ### Details Entered interactively. Creating an entityfile to store entities. The idea is that this entityfile will hold type definitions (`thingtype`s) and functions relating to those types as a common resource shared by other entityfiles in this knowledgebase. ## Defining new type entities ### Code ``` put organism type thingtype put organism attributes {row col map sensor-range} addmember (get types-and-fun contents) organism put plant type thingtype put plant attributes {row col map sensor-range} put plant subsumed-by {organism} addmember (get types-and-fun contents) plant put insect type thingtype put insect attributes {row col map sensor-range} put insect subsumed-by {organism} addmember (get types-and-fun contents) insect put tile type thingtype put tile attributes {row col contains} addmember (get types-and-fun contents) tile writefil types-and-fun loadk types-and-fun ``` ### Details Of course, the `insect` type got written out of my gamejam time budget as well. `subsumed-by` seems not very useful so far, but I understand it to be more a piece of commentary that such that in a different context, where a type `organism` is being refered to, apparently disjoint to this game, `organism` might be able to be understood as subsuming the `plant` and `insect` types here. My plant definition could have and probably should have been: ``` put plant type thingtype put plant subsumed-by {organism} put plant attributes (get organism attributes) addmember (get types-and-fun contents) plant ``` or more generally ``` put plant type thingtype put plant subsumed-by {organism} put plant attributes (union (get organism attributes) {extra, plant, attributes}) addmember (get types-and-fun contents) plant ``` I think that the reason you do not delete attributes as such (our KRF only provides `union`) is that an unset, i.e. `nil` attribute is understood to be a lack of that feature. In fact `attributes` literally means if-these-are-present-their-values-must-be-persisted. A whale's `legs` attribute would be `nil`. It is reasonable that a whale's known attributes contains legs because whales are closely related to horses, which have legs. One could imagine a whale re-evolving a meaningful value for legs. ## `entityfile` for tracking particular organisms ### Code ``` crefil organisms loadk organisms put dandelion-01 type plant put dandelion-01 row 1 put dandelion-01 col 2 put dandelion-01 map test-map addmember (get organisms contents) dandelion-01 writefil organisms ``` ### Details A new catch-all location for any `entity` of `type`, `organism` and creating a first particular organism. ## Add a `tile`d 'test-map' organisms might be in ### Code ``` crefil test-map loadk test-map . (defparameter *tiles-in* (loop :for idx :below 25 :for (row col) := (multiple-value-list (truncate idx 5)) :collect (intern (format nil "tile-~d-~d" row col)))) . ;; cle uses readline. Sorry about the one-liners. . (loop :for tile :in *tiles-in* :do (setf (get tile 'type) 'tile)) . (setf (get 'test-map 'contents) `(seq& ,(append '(test-map) *tiles-in*))) . (loop :for idx :from 0 :for (row col) := (multiple-value-list (truncate idx 5)) :for tile :in *tiles-in* :do (setf (get tile 'row) row (get tile 'col) col)) writefil test-map ``` ### Details Here, I have to say I am a little shakier. I respect that `(cle)` is readline based. I think that similarly to `eev`, the intent is to stop people from introducing lengthy data via the interactive interface. On the other hand, as turns up in `shell` useage, I basically want to use `loop` to auto-populate all the tile entities to make up a rectangular map using the access to ansi common lisp, the extremely high level implementation language of our KRF. So far as shell one-liners are acceptable, I think this is an acceptable useage. Ideally each line would be completely readable as a sentence. I guess it depends how deep into lisp you are for how readable the loop facility is. I could probably have written those neater too. ## A plant propagating function ### Code ``` put living-life type lispdef addmember (get types-and-fun contents) living-life writefil types-and-fun ``` doing this interactively stubs this inside `Game/types-and-fun.leo`: ``` --------------------------------------------------------- -- living-life [: type lispdef] [: latest-rearchived nil] ``` into which I `lisp-mode` wrote: ``` --------------------------------------------------------- -- living-life [: type lispdef] [: latest-rearchived nil] (leodef live-life live-life () (loop :for organism :in (cdadr (get 'organisms 'contents)) :do (case (get organism 'type) (plant (let* ((new-name (gensym "dandelion-")) (prev-row (get organism 'row)) (prev-col (get organism 'col)) (tile (intern (format nil "tile-~d-~d" prev-row prev-col))) (new-row (+ prev-row (1- (random 3)))) (new-col (+ prev-col (1- (random 3)))) (new-tile (intern (format nil "tile-~d-~d" new-row new-col)))) (when (and (get new-tile 'type) (null (get new-tile 'contains))) (setf (get new-tile 'contains) new-name (get new-name 'row) new-row (get new-name 'col) new-col (get new-name 'type) 'plant) (nconc (cadr (get 'organisms 'contents)) `(,new-name)))))))) ``` ### Details This is the flow when not entering interactive one-liners. The flip from writing `(get .thing foo)` to the lisp exact equivalent `(get 'thing 'foo)` is a bit jarring but the use of `cadr` is not. This is because loaded objects can be `cdadr`ed to interactively, to check that your guess was right / feel around a little bit for where you are trying to get. This is a somewhat bizarre point of interactive lisp useage that Sandewall leaned in to. My interpretation of typed programming was to introduce `(case (get thing 'type) (plant '(do some plant type things)))` reflecting that we cannot `(typecase thing (plant ..))` because `type` is simply a key/value in `thing`'s `symbol-plist` in lisp terms, and they have different useages. I used `when` on a hypothetical `new-tile`'s `type` to logically filter out tiles that do not exist. ## "Rendering" to ascii ### Code ``` put render type lispdef addmember (get types-and-fun contents) render writefil types-and-fun ``` similarly to `live-life`. ``` --------------------------------------------------------- -- render [: type lispdef] [: latest-rearchived nil] (leodef do-render do-render () (loop :for tile :in (cadr (get 'test-map 'contents)) :for idx :from 0 :for (row col) := (multiple-value-list (truncate idx 5)) :for thing := (get tile 'contains) :when (zerop col) :do (terpri) :when thing :do (princ "*") :else :do (princ #\") :finally (terpri))) ``` ### Details A simple lisp `loop` to show the `tile`s to standard output for a magic-number-5 width grid on the hardcoded-in-name `test-map` entityfile contents. Mistakenly prints a final " character. Useable either as an action or in lisp again. ## The game ### Code ``` loadk types-and-fun . (loop :repeat 5 :do (live-life)) ``` ### Details I am not sure how many steps I triggered interactively, about five. The grid after which was ``` ses.052) do-render ""*"* ***** **""* *"""" *"""" " ses.053) ``` # Broad overview This was not very many interactive steps, as expected to achieve the small task: A persisted random walk of dandelions growing in a grid field. Key attributes were the persisted nature, and the inclusion of the two lisp definitions as pieces of the data being persisted. My initial plans for a fancy-research game overlooked that I did not know how this simple scenario truly shook out before doing it in this jam. In hindsight, the use of the three entityfiles is trivial. The plant-eating `insect`s could be defined into the `case` statement in `live-life`. And `sensor` type of thing with a `last-reading` and `scan` values could be added and integrated algorithmically to insects and plants to become more `Vehicles`-ish as was originally planned.