cl-series to the rescue for game logic

One property of cl-series is that it generates native lisp (if you made certain implementation errors, it has a fallback mode that papers over your mistakes with non-native cl, but in general you end up with pure lisp). This is suitable for our software-individuals KRF which only wants new actions in terms of its own abilities and pure lisp.

This devlog goes at a gallop because of the pressure of me-taking-an-extra-day-for-my-jam-submission out of time I do not really have.

IMPORTANT ADDITION

Raymond Toy (rtoy) updated / modernised the series documentation here for us particularly yesterday.

Recap of series

Let us use a fresh sbcl lisp image

 (setq inferior-lisp-program "sbcl")
 (slime)
 (setq eepitch-buffer-name "*slime-repl sbcl*")
(lisp-implementation-type)

And, as expected

CL-USER> (lisp-implementation-type)
"SBCL"

Drag in series

(require "series")

Install series

Series /installs/ generates pure lisp and then /uninstalls/ so there is nothing but pure lisp left. I guess it is an uncommon idiom otherwise. (I do this in sbcl only).

(series::install)

The sane package (default) for series is the current package, so CL-USER currently has series installed in it which we will later remove.

A series program

I think my sensor regions can always be found by collecting a sequence of symbols from a rectangle (dr1 dr2 dc1 dc2), relative to a target (row, col).

(let ((sensor
	(lambda (target sequence row col
		 dr1 dr2 dc1 dc2)
	  (let* ((S (scan (cadr sequence)))
		 (nos (scan-range))
		 (lists (#Mcadr S)))
	    ;;okay, let's actually stop here.
	    (collect
		(#Mcons nos lists))))))
  (apply sensor nil '(seq& ((seq& (a b c))
			    (seq& (d e f))))
	 NA NA
	 '(NA NA NA NA)))

As expected, this returns ((0 A B C) (1 D E F)) while being written in an endearing and declarative tone.

Cutting out a rectangle

It turns out I cannot install series into a software-individual because of name conflicts, so we will have to do a small tribute to Nicholas Martyanoff by namespacing all the series macros (he always namespaces absolutely everything).

(let ((sensor
	(lambda (target sequence row col
		 dr1 dr2 dc1 dc2)
	  (series::let*
	      ((S (series::scan (cadr sequence)))
	       (nos (series::scan-range))
	       (mask1 (#M<= (series::series
			     (+ row dr1))
			    nos))
	       (mask2 (#M< nos
			   (series::series
			    (+ row dr2))))
	       (lists (#Mcadr S))
	       (sublists (#Msubseq
			  lists
			  (series::series
			   (+ col dc1))
			  (series::series
			   (+ col dc2)))))
	    (series::collect
		(series::choose
		 (#Mand mask1 mask2 nos)
		 (#Mcons nos sublists)))))))
  (print
   (apply sensor nil '(seq& ((seq& (a b c))
			     (seq& (d e f))))
	  0 1
	  '(1 2 0 2)))
  (yes-or-no-p "Are things great"))

((1 e f)) as we may have thought. Note my use of LET* to build declarations on prior declarations, sequentially. Series guarantees a tight in-order traversal version (of your valid declarations). It is a miracle in a can.

Make a place in our board entityfile

 (setq eepitch-buffer-name "*slime-repl clisp<2>*")
put sequence-rectangle type lispdef
addmember (get board contents) sequence-rectangle
writefil board

this creates a stub where we can define our function in #p"~/leocommunity/plant-insect-bird/demus/Game/board.leo":

---------------------------------------------------------
-- sequence-rectangle

[: type lispdef]
[: latest-rearchived nil]

Theories of Series

Series has a design principle that you must always directly show people the series macro itself that is happening with no obfuscation or “help” (obviously you can define multiple functions). (And obviously never show people the long-form generated low-level lisp code).

Here is my internal-lisp-only function. I had it poke us for a yes-or-no (did this seem to work) when the loadk board happens, which I will turn off.

---------------------------------------------------------
-- sequence-rectangle

[: type lispdef]
[: latest-rearchived nil]
(prog ()
   (require "asdf")
   (require "series")
   (let ((sensor
	   (lambda (target sequence row col
		    dr1 dr2 dc1 dc2)
	     (series::let*
		 ((S (series::scan (cadr sequence)))
		  (nos (series::scan-range))
		  (mask1 (#M<= (series::series
				(+ row dr1))
			       nos))
		  (mask2 (#M< nos
			      (series::series
			       (+ row dr2))))
		  (lists (#Mcadr S))
		  (sublists (#Msubseq
			     lists
			     (series::series
			      (+ col dc1))
			     (series::series
			      (+ col dc2)))))
	       (series::collect
		   (series::choose
		    (#Mand mask1 mask2 nos)
		    (#Mcons nos sublists)))))))
     (print
      (apply sensor nil '(seq& ((seq& (a b c))
				(seq& (d e f))))
	     0 1
	     '(1 2 0 2)))
     (yes-or-no-p "Are things great")
     (defun apply-sensor (&key
			    target sequence
			    row col
			    dr1 dr2 dc1 dc2)
       (apply sensor
	      (list target sequence row col
		    dr1 dr2 dc1 dc2)))))

Hard going but we did it

I can happily use series inside my software-individuals’ internal lisp functions. I will do some more beginner-focused series articles in the future (if you search, you can probably already find some by me in past places).

See everybody on the mastodon to talk about it ;p.