screwlisp proposes kittens

LispGameJam NicCLIM game movement / call for you to submit an s-expression level to coauthor it with me

This article added the game movement mechanics and we are about done! Just need to make some s-expression maps.

Even though my map-editor has hextille game-map cursor movement, my gamejam cursor controls are going to have their own command/s, since they basically are the game logic as well as notion of movement.

EDIT: You could contribute a level to this, or you could use this as a base to do entirely your own thing (ask me on the Mastodon if you want any help or commentary with your own thing).

Back to the implementation bits

Game Movement Needs

  1. Cannot walk through impassable objects. I will define these to be symbols occuring in some list, *impassable*.
  2. After game-moving, any radio symbols in the new cell are executed in turn.
  3. Moving should be controlled by ←↓↑→=hjkl With ⎇↑ and ⎇↓ to complete the hexagonal movement on the hextille.

NicCLIM setup as normal now (keep scrolling)

• (setq inferior-lisp-program "ecl")
• (setq eepitch-buffer-name "*slime-repl ECL*")
• (slime)

(ql:Quickload :McCLIM)
(compile-file "~/Downloads/nicclim.lisp" :load t)
(in-package :nic)
(string '~/game)
(ensure-directories-exist *)
(uiop:chdir (string '~/game/))
(uiop:chdir (string '~/game/)) ;; Twice, for some reason.

Defining game movement

There are two problems with writing this:

  1. representing a rectangular grid as hextille, even and odd rows are different. This effects up, down, the other up and the other down.
  2. Even though I have com-peek which does this, I think that nesting commands is a performance problem on slow machines (like all of mine).

My approach was to explicitly write the peek. Even though we could write macros to make the code look mathematically cuter, well, we can just literally write what is literally happening. So with some apologies. All of them are just

  1. Peek in the movement direction. If the intersection with *impassable* is non-nil, do not move, else move.
  2. If we moved, run any radio-selections in that cell.
  3. Walking off an edge in the game is implicitly an error. This is intentional, since you can make what you wanted to happen instead happen in the lisp debugger, for example. Wall your edges maybe. Walking off the edge not-in-a-game is not considered an error by NicCLIM itself.

This looks kinda long, but I am just specifying what directions mean on odd/even rows and you can basically skip past this.

(defvar *impassable*
  '())
(defmacro
    do-events ()
  `(with-slots
	 (table-list cursor-locn)
       *application-frame*
     (let ((contents
	     (copy-list
	      (nth (cadr cursor-locn)
		   (nth (car cursor-locn)
			table-list)))))
       (dolist (c contents)
	 (when (get c :radio-choices)
	   (execute-frame-command
	    *application-frame*
	    `(com-popup ,c)))))))
(define-map-editor-command (com-game-h :name t
				       :keystroke :left)
    ()
  (with-slots (table-list cursor-locn) *application-frame*
    (unless
	(intersection *impassable*
		      (nth (1- (cadr cursor-locn))
			   (nth (car cursor-locn)
				table-list)))
      (execute-frame-command *application-frame* '(com-h))
      (do-events))))

(define-map-editor-command (com-game-j :name t
				       :keystroke :down)
    ()
  (with-slots (table-list cursor-locn) *application-frame*
    (unless
	(intersection *impassable*
		      (nth (+ (cadr cursor-locn)
			      (if (oddp (car cursor-locn))
				  1 0))
			   (nth (1+ (car cursor-locn))
				table-list)))
      (execute-frame-command *application-frame* '(com-j))
      (do-events))))

(define-map-editor-command (com-game-k :name t
				       :keystroke :up)
    ()
  (with-slots (table-list cursor-locn) *application-frame*
    (unless
	(intersection *impassable*
		      (nth (+ (cadr cursor-locn)
			      (if (evenp (car cursor-locn))
				  -1 0))
			   (nth (1- (car cursor-locn))
				table-list)))
      (execute-frame-command *application-frame* '(com-k))
      (do-events))))

(define-map-editor-command (com-game-l :name t
				       :keystroke :right)
      ()
  (with-slots (table-list cursor-locn) *application-frame*
    (unless
	(intersection *impassable*
		      (nth (1+ (cadr cursor-locn))
			   (nth (car cursor-locn)
				table-list)))
      (execute-frame-command *application-frame* '(com-l))
      (do-events))))

(define-map-editor-command (com-game-kl :name t
					:keystroke
					(:up :control))
    ()
  (with-slots (table-list cursor-locn) *application-frame*
    (unless
	(intersection *impassable*
		      (nth (+ (1+ (cadr cursor-locn))
			      (if (evenp (car cursor-locn))
				  -1 0))
			   (nth (1- (car cursor-locn))
				table-list)))
      (execute-frame-command *application-frame* '(com-kl))
      (do-events))))

(define-map-editor-command (com-game-jh :name t
					:keystroke
					(:down :control))
    ()
  (with-slots
	(table-list cursor-locn) *application-frame*
    (unless
	(intersection *impassable*
		      (nth (+ (1- (cadr cursor-locn))
			      (if (oddp (car cursor-locn))
				  1 0))
			   (nth (1+ (car cursor-locn))
				table-list)))
      (execute-frame-command *application-frame* '(com-jh))
      (do-events))))

Okay, decode the symbol into movement, then execute any contents that are radio-button-choices.

See that working on a :radio-choice-haver

I threw together an example. We can see that (EXAMPLE), which is radio select example symbol for NicCLIM, triggers a radio select with the game movement (you can’t see it, but I am using the left arrowkey ← to move left, and com-l (M-S-l) to move right). The arrowkeys are the game controls, while hjkl are the NicCLIM controls. The left arrow game movement triggers the radio button, and the left arrow game movement fails to pass the wall since I pushed wall to *impassable*.

Well, I want you personally to contribute a level or map or whatever these are, so let us go over that exhaustively. Currently the radio button chooses something, i.e. sets the cur2 cursor according to the choice, but I will add a game button to activate-cursor-2 sometime this weekend. I am imagining the main choice as “go up/down these stairs”.

Everything to make that level

The map

Sooo the map, however you would make it is

(WALL)	(WALL)	(WALL)	(WALL)	(WALL)	(WALL)	(WALL)	
(WALL)	NIL	NIL	NIL	NIL	NIL	(WALL)	
(WALL)	NIL	NIL	NIL	NIL	NIL	(WALL)	
(WALL)	NIL	NIL	(EXAMPLE)	NIL	NIL	(WALL)	
(WALL)	NIL	NIL	NIL	NIL	NIL	(WALL)	
(WALL)	NIL	NIL	NIL	NIL	NIL	(WALL)	
(WALL)	(WALL)	(WALL)	(WALL)	(WALL)	(WALL)	(WALL)	

this file (in my working directory, it is ~/GAME/WALL-RADIO.MAP) is just an s-expression file right? You could make this as a tsv with a spreadsheet program. You can have more than one thing in a list; the one closer to the end of the list will print last, or display on top if they are images.

Walls are impassable

(push 'wall *impassable*)

deep, I know.

The EXAMPLE radio choice from before

(setf (get 'EXAMPLE :radio-description)
      '("Lots of
text!"
       asdf grass solid
	(1 2 (3 4) 5))

      (get 'EXAMPLE :radio-lisp-hook)
      (list (lambda () 'nothing)
	    (lambda () 'nothing-else))

      (get 'EXAMPLE :radio-choices)
      '(member
	("choice the first" (etc 1))
	("choice the second" (etc 2))
	("choice the third" (etc 3))))

The :radio-description is a list where symbols with a :bitmap property that is a path to an image will inline images, and everything else will be understood as text (including newlines).

:radio-lisp-hook: obviously those lambdas do nothing, I just put that there in case I wanted to run an audio player in the background with a shell call or something.

:radio-choices : this is just what this is like in clim. The text displayed on the radio box is the first thing like “choice the first”, what is returned (i.e. what the cursor 2 gets set to) is the second thing (like (etc 1)).

We saw pictures in a previous devlog.

Anyway, now I am hoping to be inundated with s-expression maps. We could imagine that everyone who wants to co-author this lispgamejam game with me could send me / link me their own s-expression file just like this (and any symbols/radio-choices you used, and art).

If you did not get how to do something, just tell me what you wanted to happen and I will make it so.

Two days left of the jam, we could have a lot of levels!

#unix_surrealism : https://analognowhere.com/ is contributing reuseable art to this game

So if you want some incredible surrealist art to go with your symbols, snip it out of technomage comics and scale it down (I think 50x50 pixels is probably a good size).

You can contribute your own art or do whatever compatible with lispgamejam but I will be working in terms of technomage and openblade.

Conclusions

This was a pretty conclusive article

Fin.

See you on the Mastodon *where I am looking forward to you inundating me with s-expression files like the above, possibly using technomage or other art, to make up our shared dungeons of technomage doom lispgamejam submission. (I will submit NicCLIM with a game map / lisp config tar there at the end of this weekend).

screwlisp proposes kittens