In the end, my lispgamejam25 submission 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.
crek game-kb
loadk game-kb
setk game-kb
Entered interactively. This seems reasonable to create, load, and set as active the knowledge that will constitute a game.
crefil types-and-fun
loadk types-and-fun
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.
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
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 organismscrefil 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
A new catch-all location for any entity
of type
, organism
and creating a first particular organism.
tile
d ‘test-map’ organisms might be increfil 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
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.
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))))))))
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.
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)))
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.
loadk types-and-fun
. (loop :repeat 5 :do (live-life))
I am not sure how many steps I triggered interactively, about five. The grid after which was
ses.052) do-render
""*"*
*****
**""*
*""""
*""""
"
ses.053)
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.