Debugging this the Lisp way was a pleasure, though. I hit C-c C-c to trigger a condition at the current point of evaluation, which naturally throws me into the debugger. It presented me with this screen1 Which I have abbreviated mostly because it is hard(er) to read without the correct typography ….

In[1]: Interrupt from Emacs [Condition of type SIMPLE-ERROR] Restarts: 0: [CONTINUE] Continue from break. 1: [ABORT-READ] Abort reading input from Emacs. 2: [RETRY] Retry SLIME REPL evaluation request. 3: [*ABORT] Return to SLIME's top level. 4: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {1001A8FFA3}>) Backtrace: 1: (SWANK::DEBUG-IN-EMACS #<SIMPLE-ERROR "Interrupt from Emacs" {10057293D3}>) 2: (SWANK:INVOKE-SLIME-DEBUGGER #<SIMPLE-ERROR "Interrupt from Emacs" {10057293D3}>) 3: (SWANK:SIMPLE-BREAK "Interrupt from Emacs") 4: (SWANK/BACKEND:CHECK-SLIME-INTERRUPTS) ... 13: ((:METHOD STREAM-READ-CHAR (SWANK/GRAY::SLIME-INPUT-STREAM)) #<SWANK/GRAY::SLIME-INPUT-STREAM {1001997A13}>) [fast-method] 14: (READ-CHAR #<SWANK/GRAY::SLIME-INPUT-STREAM {1001997A13}> NIL NIL #<unused argument>) 15: (READ-LINE #<TWO-WAY-STREAM :INPUT-STREAM #<SWANK/GRAY::SLIME-INPUT-STREAM {1001997A13}> :OUTPUT-STREAM #<SWANK/GRAY::SLIME-OUTPUT-STREAM {1001A77973}>> T NIL #<unused argument>) 16: (GAME-ROUND 3 3) 17: (PLAY 3 3) 18: (SB-INT:SIMPLE-EVAL-IN-LEXENV (PLAY 3 3) #<NULL-LEXENV>) 19: (EVAL (PLAY 3 3)) 20: (SWANK::EVAL-REGION "(play 3 3) ..)" 21: ((LAMBDA NIL :IN SWANK-REPL::REPL-EVAL)) ...

I know the check for a winning game happens in the game-round function, so I expanded that stack frame in the backtrace seen above. It showed me the local variables, none of which were particularly surprising.

In[2]:

16: (GAME-ROUND 3 3) Locals: BOARD = #2A((_ O X) (X _ X) (X O X)) M = 3 N = 3 REMOVALS = 2

To get to the root of the problem, I wanted to run the won-game check manually, with the values in that stack frame. Doing so is trivial in sldb : I simply put the cursor somewhere in the frame and pressed e for eval. I entered (print (unit-columns? board)) and it gave me back NIL , meaning it didn’t detect the situation as it should have.

A quick look at the function,

In[3]:

( defun unit-columns? (board) "If all columns are single-coloured, the game is won!" ( loop for i from 0 below (array-dimension board 0) always ( loop for j from 0 below (array-dimension board 1) for elem = (aref board i j) for previous = elem then ( if (tile? previous) previous elem) always (same-colour previous elem))))

and it was clear that I had accidentally swapped the dimensions of the board for this check! I normally iterate the board in row-major order, but this was the one case where I needed to do it in column-major order. I swapped the two (loop for i … board 0) and (loop for j … board 1) lines, and pressed C-c C-c to recompile the function and replace the old one.

In the debugger, the old stack frame was still highlighted, so just to make sure I pressed e again, and evaluated the same print as before. This time it detected the situation correctly!

The only thing now that remains is to continue running the program with the correct function. Still with the cursor on the stack frame of interest, I press r and it restarts that specific stack frame but now with the correct definitions in place.

Then this happened.

Remove? e _ (a) O (b) X (c) X (d) _ (e) X (f) X (g) O (h) X (i) You won the game! It took 2 removals.

I guess it works!