general backtracking algorithm in Lisp

Discussion of Common Lisp
Warren Wilkinson
Posts: 117
Joined: Tue Aug 10, 2010 11:24 pm
Location: Calgary, Alberta
Contact:

Re: general backtracking algorithm in Lisp

Post by Warren Wilkinson » Mon Nov 22, 2010 4:47 pm

Your points are clearer, it sounds like you want to apply a heuristic throughout the search --- is this by any chance a MIN/MAX algorithm for a game?

Given your requirements, I would recommend against backtracking the way I've already layed out. The way I've explained it keeps everything on the stack, and so backtracks without extra data structures, which is very generic. A different approach is to search by maintaining a collection of in-memory states, a function to compute the possible next states from a given state, and a heuristic function that tells us if a given state is likely to go anywhere.

A user in another post (viewtopic.php?f=2&t=868&start=0) asked about how he could implement a board AI for a simple tic-tac-toe game. I gave him this code to illustrate how a simple search goes. You might find that discussion relevant.

Code: Select all

(defpackage :tictac
  (:use :common-lisp))

(in-package :tictac)

;; Using a vector to represent the board makes access
;; easier and faster e.g. (elt *board* (+ (* 3 x) y))
(defun make-board () (make-array 9 :initial-element nil))
(defun empty-board-p (board) (every #'null board))

;; +search-depth+ controls recursion depth.
(defconstant +search-depth+ 4)

;; Gen-next takes a current board position, and must generate
;; all valid positions that can be made from it. Because its
;; NIL, the game will assume there are never any valid
;; positions, and will be over very soon.
(defun gen-next (board our-turn-p) (declare (ignore board our-turn-p)) nil)

;; A board-score of 0 is neutral. Use negative numbers to communicate
;; a board is BAD, and positive to communicate a board is good.
(defun board-score (board) (declare (ignore board)) 0)
;; Score calculates the boards score then adds the score for the
;;  boards children (upto +search-depth+ deep)  to produce the
;; branches total score.
(defun score (board depth)
  (let ((score (board-score board)))
    (if (= depth +search-depth+)
	score
	(loop for child in (gen-next board (evenp depth)) ;; Even-p means its our move, odd means theirs.
	      do (incf score (score child (1- depth)))
	      finally (return score)))))

;; Choose computes the children for a board and runs SCORE on each
;; one. It returns the board with the highest score, or NIL if
;; there where no boards.
(defun choose (board)
  (caar (sort (loop for child in (gen-next board t) collecting (cons child (score child 0))) #'> :key #'cdr)))
  	
;; AI will return the next board or NIL if the game is over.
(defun ai (board) (format t "~%I'm thinking...") (choose board))

The function CHOOSE arranges all the possible next states by their score and takes the best one, which is generally what you want. With slighly different code, you could display the next-states to the user and let him pick one.
Need an online wiki database? My Lisp startup http://www.formlis.com combines a wiki with forms and reports.

imba
Posts: 35
Joined: Sun Nov 21, 2010 2:13 pm

Re: general backtracking algorithm in Lisp

Post by imba » Fri Nov 26, 2010 11:33 am

I implemented it as follows:

Code: Select all

(backtrack foo ...
  (if (foo
      (when (bar
          (baz))
    (loop for continuation in (create-continuations)
          as Continuation = (backtrack ))
          collecting Continuation)))
But I only want to collect those Continuations which aren't NIL. How do I implement this (without removing NIL only at the end).

EDIT: I found it out: just add a "when (not (null Cont.)))".

Post Reply