screwlisp proposes kittens

Lisp’s prog feature - tagbody-go and my antifunctional game devlog tootcoding exploration

In openbsd C style, GOTO is used because it is transparent. How does this clean up? Well it does GOTO cleanup - you scroll to cleanup:, and that is what is going to happen in imperative code. Switching back to lisp, the with- macros which imply a cleanup happens have good cause. If you see a macro named with-foo, you know in your lisp heart that something like this has been done:

(defmacro with-foo
    ((&rest foo-args)
     &body body)
  `(let ((foo nil))
     (unwind-protect
	  (progn
	      (setq foo (create-foo ,@foo-args))
	      (progn ,@body))
       (when foo
	 (cleans-up foo)))))

In lisp, the unwind-protect special form guarantees that the second form happens/cannot be avoided (it pre-allocates it or something).

Now that we all know how important unwind-protect is, let us start to roll Dijkstra in his grave.

#tootCoding on the Mastodon

I am exploring presenting my game jam as reuseable shared fediverse social media infrastructure.

In practice this has meant that each step I am writing some toots containing code, then my game implementation is some data, followed by an elisp-link to an eev anchor in that toot. Some code will be worth a thousand words:

Toot for associating an image with a game symbol

(literally, this toot from my second devlog)

#tootCoding (read my jam articles about the experiment) #commonLisp #NicCLIM

;; «🎭» (to “.🎭”)

(setf (get ** :bitmap) *)

i.e. it sets the :bitmap property of the second-last arguement to the last arguement.

after which giving my owngame’s symbols pictoral representations instead of printing in the game is like this:

'SOLID
'IMGS/SOLID.PNG
;; (to "🎭")
'ROCK
'IMGS/ROCK.PNG
;; (to "🎭")

Kind of forth-style game-specific-knowledge entry. To make explicit what is implicitly happening here

The buttons I press

  1. 'SOLID
  2. 'IMGS/SOLID.PNG
  3. ;; (to "🎭")
  4. (setf (get ** :bitmap) *)

Admittedly, I currently do not have a favourite way to get back to where I was after following the anchor. There are lots of ways but I have not picked a best one for this interactive use. Anyway, most of my game dev is entering knowledge describing my particular game using generic GOTO-like infrastructure.

In practice I am tapping these keys interactively:

eev supports using red star / bullet lines for using <F8> for elisp as well, but M-e lets you evaluate nested elisp expressions in the middle of a line, so.

• (to "🎭")

But what is the “lisp program” of these interactions?

Here is the lisp code I came up with that closely tracks my interactive useage. I wrote this to try and understand the dynamic; forgive me if something is already obvious to you.

Basic prog premise

(prog
    ((goer (lambda ())))
 start
   (print 'hej)
   (funcall goer)
 start-done
   (print 'ho)
   (setq goer (lambda () (go end)))
   (go start)
 end)

as we can see this prints HEJ, then HO, sets goer to (lambda () (go end)), goes back to the start, HEJ, but then it calls the lambda and goes to the end so there is no infinite loop.

This seems like enough to explicitly duplicate my interactive flow as a lisp program.

My lispgamejam gamedev so far as one lisp prog

I introduced starset to simulate the repl special * variables. Technically I should have -, /, + as well. Also for slime to treat § as a tag, I had to prefix it with an underscore or something.

NOTE: I since dropped the cute unicode/latin1 for lisp-style symbol names as tags since memorizing these arcane sigils is like trying to use APL.

The key point here is that this prog represents operating more or less exactly like I do interactively and I am curious to know what that looks like.

(macrolet
    ((starset ((form))
       "starset ((form)) simulates lisp repl's * ** *** vars"
       `(psetq * ,form ** * *** **))
     (bounce ((tag1) (tag2))
       "bounce ((tag1) (tag2)) achieves (go tag1)..(go tag2)"
       `(progn (setq resumer (lambda () (go ,tag2)))
	       (go ,tag1))))
  (prog
      ((resumer (lambda () (go devlog-0)))
       * ** ***)
   resume
     (funcall resumer)
     
   devlog-0-tags
   make-map
     (starset
      (apply 'rect-file
	     ***
	     (append ** (list *))))
     (go resume)
   gui-edit-map
     (starset (enclose-map *))
     (go resume)
   clobber-abc-y1y2x1x2
     (starset (append ** *))
     (starset (apply 'clobber-rect *))
     (go resume)
     
   devlog-0
     ;; Making maps
     (starset ('grass-clearing.map))
     (starset ('(12 8)))
     (starset ('(grass)))
     (bounce (make-map) (grass-mapped))
   grass-mapped
     (starset ('grass-clearing.map))
     (bounce (gui-edit-map) (viewed-grass-map))
   viewed-grass-map
     (starset ('solid-rock.map))
     (starset ('(12 8)))
     (starset ('(solid rock)))
     (bounce (make-map) (solid-rock-mapped))
   solid-rock-mapped
     (starset ('solid-rock.map))
     (bounce (gui-edit-map) (viewed-solid-rock-map))
   viewed-solid-rock-map
     (starset ('rock-cavern.map))
     (starset ('(12 8)))
     (starset ('(rock cavern)))
     (bounce (gui-edit-map) (viewed-rock-cavern-map))
   viewed-rock-cavern-map
     (starset ('(solid-rock.map rock-cavern.map mountain-cave.map)))
     (starset ('(1 3 2 4))) ; argh y1 y2 x1 x2
     (bounce (clobber-abc-y1y2x1x2) (composed-mountain-cave))
   composed-mountain-cave
     (starset ('mountain-cave.map))
     (bounce (gui-edit-map) (viewed-mountain-cave-map))

   devlog-1-tags
   assign-bitmap
     (starset ((setf (get ** :bitmap) *)))
     (go resume)
   create-door
     `(lambda
	  (&rest r)
	(execute-frame-command
	 *application-frame*
	 '(com-change-map ,*)))
     (go resume)
     
   devlog-1
     (starset ('SOLID))
     (starset ('IMGS/SOLID.PNG))
     (bounce (assign-bitmap) (bitmapped-solid))
   bitmapped-solid
     (starset ('ROCK))
     (starset ('IMGS/ROCK.PNG))
     (bounce (assign-bitmap) (bitmapped-rock))
   bitmapped-rock
     (starset ('TREE))
     (starset ('IMGS/TREE.PNG))
     (bounce (assign-bitmap) (bitmapped-tree))
   bitmapped-tree
     (starset ('GRASS))
     (starset ('IMGS/GRASS.PNG))
     (bounce (assign-bitmap) (bitmapped-grass))
   bitmapped-grass
     (starset ('LAMBDA))
     (starset ('IMGS/LAMBDA.PNG))
     (bounce (assign-bitmap) (bitmapped-lambda))
   bitmapped-lambda
     (starset ('rock-cavern.map))
     (bounce (gui-edit-map) (viewed-rocky-cavern))
   viewed-rocky-cavern
     (starset ('grass-clearing.map))
     (bounce (create-door) (created-grass-door))
   created-grass-door
   end))

I have a few thoughts!

This is a less sophisticated version of interlisp medley’s USE x FOR y IN lines

Whereas each executive in an interlisp medley lispm has conversational lisp (negotiating with this executive rather than requesting the executive perform a lisp action) history features such as REDO and USE for out-of-order-redoing previous lines, possibly with symbolic substitutions (USE), my ANSI CL mechanism must use a tagbody (in my case, prog’s implicit tagbody).

Further I needed to place a new tag immediately after my bounce macro which stores a tagbody-go in a lambda, and the redoable tags conclude by funcalling the stored lambda.

Furthermore, and annoyingly I have to witchcraft in the * ** *** repl special (my fake ones are lexical, I guess) variables. I chose to write both starset and bounce macrolets to strongly imply what they are doing every moment. This was annoying to type (I say type but I used an emacs keyboard macro and pressed e a lot).

On the other hand, in emacs slime, it seems straightforward to programmatically process the elisp strings file of inputs-received-by-a-slime-repl exposed by e.g. slime-repl-save-history (I do not know where the documentation lives in slime’s documentation). Either slime or ANSI common lisp’s READTABLE could automate generating my prog here for a lisp image as it happens. Since it is easily automatable, I am somewhat happy with the more-transparent-with-what-is-happening form I wrote.

Having observed it is possible to do, it is deeply unhygeinic. Basically we issue a GOTO and hope that the place we went to eventually cedes control back to a second GOTO we set a local variable to. The lexical environment of the tagbody being modified is the same one we also were in and stay in.

prog tags are basically programmatic markdown headings

I am tempted to say that the tagbody tags make a prog=repl-history almost directly useable as my blog’s article format which would be pretty cool. I seem to remember https://mdhughes.tech describing REPL-driven programming as being the exact opposite of literate programming. I would like it if my repl useage reasonably was the literacy.

Flatness and lack of hygeine in the tagbody environment could be abrogated by closure-driven functional programming, also popular in interactive REPL useage.

Redundancy with defun versus local transparency

Admittedly, I feel like I am jumping through a lot of hoops to use two gotos instead of just using special scope defuns on the justification that fairly pure tagbody-gos are more transparently and locally traversable than functions accrued from different packages. Both seem to be points - it is abstruse not to use defun when you basically want defun, and defuns and closures are inspectable via function-lambda-expression after all - on the other hand, a single goto-based openbsd-style-main() single prog is directly followable and unmysterious.

Clear meaning of what programming-the-game meant in one case

While their is no enduring, perfect formula of what making-a-good-game means moment to moment, here we can see that the day I wrote my 0th devlog, I wrote what is at devlog-0, the day I wrote my 1th devlog, I wrote what is at devlog-1; and I jammed these hot into a repl and file source is a post-ex-facto processing of that repl experience’s easily available history.

This lets me inspect and aggregate what my first person experience of lispgamejam was devlog to devlog, jam to jam (my repl’s first person experiences anyway).

It seems like

Not having dependencies is important

to this transparency and direct scrutibility of the openbsd-main() style lisp prog implicit tagbody, and maybe as a general principle. Let us review my nascent game’s dependencies.

Embeddable common lisp = purports to conform to ANSI common lisp

Compiler to the popular and perhaps uniquely reliably static modern language standard, ANSI CL. Some allowed extensions (multiprocessing, utf8, compiler particulars, the MOP, weak hashtables etc) generally being direct yet also historical support for the small number of canonical additional de facto standards. To first order it is just a conformant ANSI common lisp implementation directly comparable to other conformant ANSI common lisp implementations.

So dependency one - conformant ANSI CL compiler with allowed extensions.

McCLIM = a common lisp interface manager 2 spec implementation

While it is a spec and not a standard, this spec is currently implemented at least by McCLIM, Lispworks and Franz Inc, historically by Symbolics and seems to have become eternal. I note that Symbolics’ great manual starts with a note about integrating opengl and their CLIM 2 spec implementation. McCLIM has extensions and demos, also I guess it is worth noting that CLIM 2 implementation is defined in terms of implementing the CLIM middleware out of whatever available nonportable environmental tools, and hence having ported your CLIM implementation which sits atop that middleware to a new environment.

My own NicCLIM map editor

This is the only fluffy, non-spec/standard item being my own contribution of the last few months. In my heart of hearts’ highest aspirations it is my exploration in search for something like VI in a universe where ANSI CL and ex(1) have been confused (very confused) made using McCLIM instead of curses. At some point, if I am programming I must also contribute something. For the jam I guess I am exploring using my NicCLIM as intended to create and edit map files, where the NicCLIM map files as such are a game and might be independently deployable as a game.

NicCLIM itself is pretty close to being a thin wrapper on top of using-CLIM-spec-commands-execute-frame-command-and-tabling-macro-like-the-oft-reproduced-tutorials and just reads and writes files of ANSI CL s-expressions with prin1 and read-delimited-list.

Not dragging in anything else

Almost everything I am touching is formally specified. The thing that is not formally specified or standardised is my interpretations of VI’s the-rogue-keys (er, adapted to hextille), two cursors, clim:command ~VI macros, and instead of host shell calls it makes host lisp calls.

My game creation then, is basically feeding knowledge into well-knowns. Adding/calling a command? It is the CLIM 2 thing. A map cell contains a lambda? The ANSI CL thing. Displayed in a table? clim:formatting-table. The word lambda renders as a beautiful green lisp alien looming behind the lambda-list and body? The clim thing (+ bitmap cached in an ANSI CL hash-table).

The idea that one can pip install or npm something and copy gigantic source unknowns into what then is no longer your own code is a disaster. The implementor of your CLIM 2 spec implementation has taken the burden of every upstream dependency’s weight onto their own shoulders; Atlas holding up the sky so that we do not need to know anything but the CLIM 2 spec (and lisp purporting to conform to the ANSI CL standard similarly) with a handful of implementation notes. Aside: for me, both of these are one specific person (and all the contributors, I guess).

Aside from that repl history as a prog form earlier, what is a game?

In terms of NicCLIM, a game is a collection of presumably connected map s-expression files generatable and manipulable via NicCLIM.

So to make a game I can (in no particular order despite the numbers)

  1. Make some maps. Each cell of the map is a list of symbols and lists. The symbols and dimensions of a map are a thing you make with NicCLIM.
  2. Specify any non-textual depictions of stacks of map symbols like the pictures in my 1th game jam devlog.
  3. Add whatever your game’s e.g. movement mechanic is as a CLIM command (using the easy way).
  4. Movement/game-command-triggered game mechanics are either host lisp lambdas or macros (in the VI sense) of NicCLIM’s and your own clim commands (if you move here, trigger moving that symbol there at 2 moves/second)
  5. All the technical stuff is done and there is nothing but authoring the game’s story and other art.

Conclusions

This article got kind of weird (other peoples’ code is the work of the devil!!) as the day drew on.

I implemented a lisp REPL history multiline goto-based redo as an ANSI CL implicit tagbody. Then I suggested that for code you include that you did not personally author, you are still responsible for that code despite not being its author and hence suggest only dragging in to-first-order-immutable standards/specs being ANSI CL and the CLIM 2 spec (assuming that an implementation purporting to conform does so: if it turns out not to for your purposes, another implementation of the same standard/spec would be switched to).

My NicCLIM commands are in alpha and hence the-NicCLIM-commands are the-NicCLIM-commands. After revising them based on available experience and commentary, I will specify them as formally as I can concommitantly to the NicCLIM beta.

Lastly, I proposed five game activities for a NicCLIM-maps-as-a-game:

Where my first two lispgamejam devlogs attempted the first two.

This article basically just viewed my NicCLIM useage this game as a flat list of lisp REPL interactions rather than as a social tree of fediverse toots, though it is interesting to see the same thing from different vantages sometimes.

Fin.

Talk on the Mastodon as always please. The first-four-jam-days-retrospective nature of this article makes it suitable for weighing in with your opinion, surely! Also remember this week’s Tuesday-night-in-the-Americas show with gopher://gopher.someodd.zip (https://someodd.zip) is less than 24 hours away, same time as always (for years). There will be a new unix_surrealism banner this week!

screwlisp proposes kittens