---
layout: ../Site.layout.js
---
# NicCLIM McCLIM Common Lisp Interface Manager simple grid map editor
Sorry about the fallow week. Also, I read [somewhere](https://macadie.info/about-page/) that "Mc-" specifically means son-of, whereas daughter-of would be "Nic-". So [McCLIM](https://mcclim.common-lisp.dev/main.html) would be the open source son-of [CLIM (common lisp interface manager II spec)](http://bauhh.dyndns.org:8000/clim-spec/index.html). So when I have parsed McCLIM as child-of-CLIM I have been incorrect.
Here, I will make and present a minimal map editor. I played Warcraft 3: The Frozen Throne in my misbegotten youth before I knew about computers, which was a sharing-based map editor fulcrum you used to make tower defenses, boardgame clones and pseudo-saveable open world RPG games with. It's odd seeing megacorporate triple-A games that are at best 1:1 copies of Custom Maps teenagers wrangled into shape before the next LAN, where a LAN was something where you carried your monstrous CRT monitor into disapproving friends' parents' garages and/or basements for a weekend, and were not managed superbowl-like pro sports broadcasting at all.
I don't expect this to be like that, but that's where the words map editor arrived in my mind. The map will be [CLIM tables](https://www.lispworks.com/documentation/lw80/clim/clim-ch17-1.htm#CLIM) using formatting-table (I get so much mileage from that link alone) that I intend to be larger than the view port in general: the contents of each cell will be a list of symbols. *Later* the symbols will be parsed as a sequence of images to overlay. It should be very easy to do.
## Setup
Assuming you [have lisp like I detailed over here](/fundamental/installing-lisp-etc/).
```
• (setq inferior-lisp-program "ecl")
• (slime)
• (setq eepitch-buffer-name "*slime-repl ECL*")
```
as usual. In particular we want clim I guess.
```
(require :asdf)
(require :mcclim)
```
outputting on the bottom half of the screen for me:
```
; SLIME 2.29.1
CL-USER> (require :asdf)
NIL
CL-USER> (require :mcclim)
;;; Computing Hangul syllable names
("DEFLATE")
CL-USER>
```
Let's take that name, `NicCLIM` for our nascent `map-editor` package.
```
(uiop:define-package :NicCLIM (:mix :clim :clim-lisp :cl))
(in-package :NicCLIM)
```
being exactly analogous to `CLIM-USER` at this point. But our [eev](https://anggtwu.net/#eev) `code-c-d` of this file will contain our user code later.
## The usual table `application-frame`
```
(define-application-frame map-editor ()
((table-list :initarg :table-list))
(:panes
(int :interactor)
(map :application
:display-function 'map-display
:incremental-redisplay t))
(:layouts
(default
(horizontally
(:height 512 :width 512)
int map))))
(defun map-display (frame pane)
(formatting-table
(pane)
(with-slots
(table-list)
frame
(loop :for row :in table-list :do
(formatting-row
(pane)
(loop :for cell :in row :do
(formatting-cell
(pane)
(updating-output
(pane)
(loop :for symbol :in cell
:do (present symbol)
(terpri))))))))))
```
## All important test rig
let's take a peek at that.
```
'(((foo) () (bar baz))
((foo) () (bar baz))
()
((buz)))
(make-application-frame 'map-editor :table-list *)
(run-frame-top-level *)
```
seems to be in order.
## Some commands
### First, adding a cursor to the `application-frame` definition
We can update `map-editor` by just `define-application-frame`ing it again. This is largely the same as updating a `defclass`, so you will have to call `reinitialize-instance` on existing objects to get them to pick up the new definitions.
My idea is that the simplest map editor will just insert or delete at a cursor location known by the `map-editor`. It seems inevitable to double-handle this to me.
```
(define-application-frame map-editor ()
((table-list :initarg :table-list)
(cursor-locn :initarg :cursor-locn))
(:panes
(int :interactor)
(map :application
:display-function 'map-display
:incremental-redisplay t))
(:layouts
(default
(horizontally
(:height 512 :width 512)
int map)))
(:default-initargs :cursor-locn '(0 0)))
```
### Cursor inserting command
```
(define-map-editor-command
(com-nconc :name t :menu t)
((cursor 'symbol :default "cursor"))
(with-slots
(table-list cursor-locn)
*application-frame*
(nconc
(nth (cadr cursor-locn)
(nth (car cursor-locn)
table-list))
(list cursor))))
```
Let's do our test again but code-ily try our command. I found I jumped back here to see our program working throughout writing this article.
```
'(((foo) () (bar baz))
((foo) () (bar baz))
()
((buz)))
(make-application-frame 'map-editor :table-list *)
(run-frame-top-level *)
```
### `nsubst` and `delete`
I guess you can imagine but I need to literally write them in, since this is literally our source document after all.
Note: Initially I worked in terms of a symbol named `CURSOR`, though later I made everything arguements with a default of `CURSOR` instead.
```
(define-map-editor-command
(com-nsubst :name t :menu t)
((cursor 'symbol :default "cursor")
(replacement 'symbol))
(with-slots
(table-list cursor-locn)
*application-frame*
(nsubst
replacement
cursor
(nth (cadr cursor-locn)
(nth (car cursor-locn)
table-list)))))
(define-map-editor-command
(com-delete :name t :menu t)
((to-delete 'symbol :default "cursor"))
(with-slots
(table-list cursor-locn)
*application-frame*
(setf
(nth (cadr cursor-locn)
(nth (car cursor-locn)
table-list))
(delete
to-delete
(nth (cadr cursor-locn)
(nth (car cursor-locn)
table-list))
:test 'string=
:key 'symbol-name))))
```
### Rotating a list.
`McCLIM` already dragged `alexandria` in for us. The only good way to rotate is to smash the underlying list, so we have to `setf` again.
```
(define-map-editor-command
(com-rot :name t :menu t)
((n 'integer :default 1))
(with-slots
(table-list cursor-locn)
*application-frame*
(setf
(nth (cadr cursor-locn)
(nth (car cursor-locn)
table-list))
(alexandria:rotate
(nth (cadr cursor-locn)
(nth (car cursor-locn)
table-list))
n))))
```
### Navigation `M-` `h` `j` `k` `l`
`cursor-locn` is row-by-column, so h being x- left means `(decf (cadr *))`.
```
(define-map-editor-command
(com-h :menu t :name t
:keystroke (#\h :control))
()
(with-slots
(cursor-locn)
*application-frame*
(decf (cadr cursor-locn))
(print cursor-locn *terminal-io*)))
(define-map-editor-command
(com-j :menu t :name t
:keystroke (#\j :control))
()
(with-slots
(cursor-locn)
*application-frame*
(incf (car cursor-locn))
(print cursor-locn *terminal-io*)))
(define-map-editor-command
(com-k :menu t :name t
:keystroke (#\k :control))
()
(with-slots
(cursor-locn)
*application-frame*
(decf (car cursor-locn))
(print cursor-locn *terminal-io*)))
(define-map-editor-command
(com-l :menu t :name t
:keystroke (#\l :control))
()
(with-slots
(cursor-locn)
*application-frame*
(incf (cadr cursor-locn))
(print cursor-locn *terminal-io*)))
```
and maybe an arbitrary jump.
```
(define-map-editor-command
(com-jump :menu t :name t)
((x 'integer :default 0)
(y 'integer :default 0))
(with-slots
(cursor-locn)
*application-frame*
(setf (cadr cursor-locn) x
(car cursor-locn) y)
(print cursor-locn *terminal-io*)))
(define-map-editor-command
(com-add :menu t :name t)
((x 'integer :default 0)
(y 'integer :default 0))
(with-slots
(cursor-locn)
*application-frame*
(incf (cadr cursor-locn) x)
(incf (car cursor-locn) y)
(print cursor-locn *terminal-io*)))
```
# Use that to make something.
Okay, I can't say it's going to be great, but let's make my notion of an rpg 'room' by hand.
```
(loop :repeat 30
:collect
(loop :repeat 35 :collect (list '_)))
(nsubst '— '_ *) ; W is wider than `_` but not `—` (em dash). Avoid resizes.
(make-application-frame 'map-editor :table-list *)
(run-frame-top-level *)
```
# Conclusions
I think McCLIM's default tabling is a promising map editor. On my extremely slow computer, dynamically resizing columns and rows is pretty slow, though I think the idea is that generally it wouldn't happen- I could stretch the columns to desired max-width along the top and max-height along the sides. The thing is if the max changes, I *would* like the cells to be updated dynamically, even if I avoid doing it.
I'm kind of envisaging a Castle of the Winds thing here where the world is flat and you have default scrollbars if you feel like checking what's really far away on your map (that you have already visited).
In terms of useage; one confusing behaviour I encountered is that when using control- hjkl keyboard shortcuts to navigate, the interactor does not echo the command, which is why I hand-typed hjkl etcs in the GIF. This useage really makes sense with *output recording*, such that you could make a local design by hand, and then replay it with substitutions elsewhere. Actually, we already have access to this in the `application-frame` we made, but *output-records* and *output-replaying* are big topics that warrant their own careful discussion.
I think it nascently exists.
Having the symbols substitute out for a stack of semi-transparent images will make it feel very finished, but loading and blitting images needs its own section as well.
After that, I guess the question is how much logic about the map's evolution will actually be inside the map.
On my never-shrinking to-read list lies [the Symbolics CLIM reference](http://lispm.de/docs/Publications/UI/1994%20Symbolics%20CLIM%202.0.pdf) which I am excited to augment my academic/Franz/Lispworks triumverate with, but I just haven't gotten into it yet.
# Fin.
See everyone [on the Mastodon thread to receive your wisdom](https://gamerplus.org/@screwlisp/115008854780829372).
(I hope `NicCLIM` was a reasonable thing to write in a language I don't know).
There will be some more `NicCLIM` `map-editor` articles tying some of our other works to https://itch.io lispgames finally finally.