--- layout: ../Site.layout.js --- # 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](https://gitlab.common-lisp.net/rtoy/cl-series/-/wikis/Series-User's-Guide) 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](https://mastodon.sdf.org/@screwtape/114492793643489839) to talk about it ;p. - Show in less than 48 hours interviewing [KMP](https://climatejustice.social/@KentPitman) live (watch the mastodon). - [Lispgamejam.](https://itch.io/jam/spring-lisp-game-jam-2025/topic/4858711/what-genre-is-simulation-eat-things-or-you-die#post-12835618)