Programming in Scheme
Programming in Scheme
Springer
New York
Berlin
Heidelberg
Barcelona
Budapest
Hong Kong
London
Milan
Paris
Santa Clara
Singapore
Tokyo
Mark Watson
PROGRAMMING IN SCHEME
LEARN SCHEME THROUGH ARTIFICIAL
INTELLIGENCE PROGRAMS
With 22 Illustrations
Springer
Mark Watson
535 Mar Vista Drive
Solana Beach, CA 92075, USA
987654321
Preface ix
Acknowledgments Xl
CHAPTER 1 Introduction 1
Bibliography 235
Index 237
PREFACE
Mark Watson
Solana Beach, California
mwa@netcom.com
MarkWatson@aol.com
ACKNOWLEDGMENTS
I have always believed that computer programmers who only know one
programming language are at a competitive disadvantage. Depending
on the application and audience for a computer program, some lan-
guages are more suitable than others. I have enjoyed programming
in LISP, and have often been disappointed when colleagues whom I
respect seem to have a closed mind with respect to LISP languages.
The Scheme language, a dialect of LISP, is a modern, efficient, dy-
namic language. I hope that this book, in addition to being a good
first programming book, will also convince C, C++, Ada, COBOL, and
FORTRAN programmers that dynamic, list-oriented languages like
Scheme are appropriate for solving a wide range of programming
problems.
In choosing applications for the example programs in this book
I tried to select material that would be reusable in your own pro-
grams and would also show off some of the advantages of the Scheme
language.
I recommend that you read this book while sitting near your com-
puter with a Scheme programming environment running. This is
especially important when working through the Scheme tutorial mate-
rial in Chapter 2. Even though most readers will be using MIT Scheme,
since it is included with this book, the examples that do not use graph-
ics will work with any Scheme system. I use the following notation for
examples in the text:
> (+ 1 2)
;Value: 3
2 1. INTRODUCTION
Here, Scheme prompts you with a ''>'' character, letting you know that
it is ready to read any expression that you type in. The string ";Value:"
indicates the value, or result, of evaluating the last expression that you
typed.
Not all Scheme systems use the same type of prompts. For example,
in MIT Scheme the last example would look like this:
1 J=> (+ 1 2)
;Value: 3
Here the number 1 in "1 1=>" indicates the number of expressions that
the Scheme system has evaluated since starting.
The last example for Texas Instruments PC Scheme would look like
this:
> (+ 1 2) 3
Appendix A contains instructions for installing MIT Scheme. Ap-
pendix B contains references to both World Wide Web and FTP sites
on the Internet where the interested reader can find freely available
Scheme language implementations for most types of computer systems.
CHAPTER 2
TUTORIAL INTRODUCTION
TO SCHEME
2.1 LISTS
A list is an ordered collection of data elements. The fact that the ele-
ments in a list are ordered is important. What makes lists in Scheme
(and other LISP languages) so powerful is that the elements of a list
can be any data structure, including a procedure. List elements can be
numbers, character strings, other lists, and vectors.
Organizing data in your programs using lists is easy and convenient.
Many programmers do not like Scheme's notation (or syntax) for spec-
ifying lists, but I think that you will find it to be a natural notation to
use when you get used to it. Lists are defined by surrounding the list
elements with a pair of parentheses. For example, the following list has
four elements:
'(1 "the dog ran" 3.14159 cat)
4 2. TUTORIAL INTRODUCTION TO SCHEME
the Scheme system assumes that the first element in the list is a
procedure, and attempts to execute the non-procedure 1.
The following list is empty:
'0
The following list has only two elements, but the first element is itself
a list containing four elements:
'( C1 2 3 4) lithe dog ran")
We will see that we use lists for just about everything, including
defining variables and functions.
Figure 2.1 shows two lists typed into the Scheme listener window. The
first list uses the define keyword to declare a new variable function
definition. (The define keyword is also used to declare global variables.
We will discuss global and local variables and function definitions in
Sections 2.4 and 2.5.) As we will see in Section 2.5, we will usually use
the special form lambda to define procedures, rather than overloading
define both for the declaration of variables and as a short cut for defining
new procedures. The second list executes the procedure (or function)
factorial, which is defined in the window in Figure 2.1; the second list
element,S, is passed as an argument to function factorial. The syntax
for defining new functions will be covered in Section 2.5; now we will
concentrate on how the Scheme system evaluates lists. When you type
the expression
(factorial 5)
2.1. LISTS 5
~I .'
;Ualue: 3
1 ]-) (define (factorial n)
(if « n 2)
1
(* n (factorial (- n 1»»)
;Ualue: factorial
1 ]-) (factorial 5)
;Ualue: 120
1 ]-) I
FIGURE 2.1. Defining a new function, factorial, by typing a list in the Scheme listener window.
in the Scheme listener window (which I will refer to as either the "lis-
tener window" or the "listener"), the following steps are executed to
evaluate the list:
1. The last argument,S, is evaluated (value is 5)
2. The first argument, factorial, is evaluated (value is a globally defined
function)
3. The function factorial is called with the single argument 5
4. The function factorial calculates a return value of 120. 120 is the
value of the list
from the last list element to the first. What happens when a list element
is itself a list? The evaluation process immediately evaluates that list
element and calculates a value for it, then proceeds with the back-to-
front evaluation of the outer list. The following example illustrates the
evaluation of nested lists:
> (+ (* 2 4) (- 40 20»
;Value: 28
This example is easier to follow if we "pretty print" the list structure
(any list or function can be "pretty printed" using the built-in function
pp) by indenting sub-lists:
> (+
(* 2 4)
(- 40 20»
;Value: 28
The Scheme system performs the following steps to evaluate this list:
Evaluate the list (-40 20)
Evaluate the constant 20.
The value is 20
Evaluate the constant 40.
The value is 40
Call the built-in function - passing the two arguments 40 and 20.
The value is 20
Evaluate the list (* 2 4)
Evaluate the constant 4. The value is 4
Evaluate the constant 2. The value is 2
Call the built-in function * passing the two arguments 2 and 4. The
value is 8
Evaluate the list (+ 8 20)
Evaluate the constant 8. The value is 8
Evaluate the constant 20. The value is 20
Call the built-in function + passing the two arguments 8 and 20.
The value is 28
Assuming that you have your Scheme system running, try typing the
following lists into your listener window:
(+ 1 2 3 4)
"this is a string"
(pp '«1 2) «(a b) c) d) e ) f)
'(the dog chased the cat)
Notice that the value of the last list is simply the list itself:
'(the dog chased the cat)
;Value: (the dog chased the cat)
2.1. LISTS 7
• numbers
• built-in functions like +, -, and·
• a function that we defined (in Figure 2.1): factorial
• a string (list of characters enclosed in double quote marks: "like this")
• lists
All five of these list element types have something in common: they
all have a legitimate value when evaluated. Symbols like dog will, in
general, not have a value and must be quoted by preceding them with
the' character.
If you get an error in evaluating a list, you can restart MIT Scheme by
typing (evaluating) the following expression (which is evaluated, since
it is not quoted):
(restart 1)
Scheme provides built-in functions for creating and accessing lists. The
following examples illustrate the built-in functions for creating (or con-
structing) lists. The first thing that we will do is to define three variables
x, y, and z that we will use in our examples:
(define x)
(define y)
(define z)
Note that some Scheme systems (e.g., MacGambit for the Macintosh)
require some initial value in define special forms; for example:
(define x If)
(define y If)
(define z If)
These three variables are not yet assigned any values. As in other
programming languages a variable can be reused by changing its value.
Unlike most other programming languages, Scheme permits a variable
to be assigned a value of any type (e.g., we could set x to the value 3,
then change its value to the string "test string"). The built-in function set!
is used to change the value of a variable. For example:
8 2. TUTORIAL INTROOUCTION TO SCHEME
(set! x 1)
(set! y "test string")
(set! z (list x y)
Note that the function set! returns as its value the previous value
of the variable that it is changing. In this case, the variable z had the
value (1 "test string") from a previous example. The function write prints
out the values of Scheme variables. The function display is similar to
2.2. VECTORS 9
function write, except that character strings are printed with double
quotation marks.
2.2 VECTORS
As seen in Section 2.1, lists can be used to represent arbitrarily complex
data structures. Vectors are similar to lists, but add an implicit indexing
scheme: vector elements can be accessed by index (starting at zero) in
the vector. Scheme provides many built-in functions for manipulating
vectors. The MIT Scheme Help System documents these built-in func-
tions. The following functions are used in the example programs in this
book.
A vector contains zero or more elements. The built-in function make-
vector is used to make a new vector object, and has one required
argument (the size of the vector) and one optional argument (the initial
value that is assigned to each element of the vector). Try the following
example in your Scheme listener:
> (define v)
;Value: v
(set! v (make-vector 5 'cat»
;No value
> v
;Value: #(cat cat cat cat cat)
The built-in function vector is similar to the built-in function list; call
function vector with zero or more elements, and a new vector object is
created with the argument values copied into the vector. The built-in
function vector-length returns the number of elements in a vector. Try
the following example in your Scheme listener:
> (define v2)
;Value: v2
> (set! v2 (vector 'dog 'cat 3.14159»
;No value
> v2
;Value: #(dog cat 3.14159)
(vector-length v2)
;Value: 3
> (vector-ref v2 1)
;Value: cat
> (vector-set! v2 1 '(1 2 3»
;No value
> v2
;Value: #(dog (1 2 3) 3.14159)
In the first case, when we simply want to know whether the values of
two expressions are identical (but perhaps stored in different memory
locations), we use the built-in function equal? If we want to see whether
two expressions refer to the same memory location (i.e., they refer to
the same data), we use the built-in function eq? For example:
> (define x)
;Value: x
> (define y)
;Value:. y
> (set! x '(1 2 3 cat»
;Value: (1 2 3 'cat)
> (set! y '(1 2 3 cat»
;Value: (1 2 3 cat)
> (equal? x y)
;Value: #T
> (eq? x y)
;Value: #F
2.3. LOGICAL TESTS 11
The following examples show the "greater than" and "less than"
predicates:
> (> 4 1)
;Value: #T
> « 4 1)
;Value: #F
> (>= 3 3)
;Value: #T
> «= 10 2)
;Value: #F
The logical predicate equal? can be used to test numbers for equality:
> (equal? 3 3)
;Value: #T
> (equal? 3 4.45)
;Value: #F
The Boolean value for false, #F, might also print as a null list (i.e., a
list with no elements) on some Scheme systems:
> (equal? 3.14159 3.14)
;Value: 0
(if
<expression-1>
<expression-2>
<expression-3>
<expression 1>
<expression 2>
You do not have to define any variables in a let special form. The value
of a let special form is the value of the last expression evaluated in the let
special form. The order of the definition of local variables in a let special
form is undefined. A let special form is a single expression, so it provides
a convenient way to group a list of expressions. The begin special form
can also be used to group a list of expressions in, for example, a call to
the built-in if function:
(if (> count *max-value*)
(begin
(display "Illegal value for count: ")
(display count)
(newline) )
(set! count (+ count 1») ; increment count
The value of a begin special form is the value of the last expression
evaluated in the begin special form.
Let special forms usually define one or more local variables, as seen
in Ptoblem 2.1:
Hint: type this into your Scheme listener window to check your
answer.
This is not a practical example, but it does show the syntax for nesting
function definitions. While this example works, it is very inefficient
because the inner function definition for local-double is executed each
time function quadruple is executed! This inefficiency is easier to see if
we rewrite this example explicitly using lambda expressions:
(define quadruple
(lambda (n)
(define local-double
(lambda (x)
16 2. TUTORIAL INTROOUCTION TO SCHEME
(+ X x)))
(local-double (local-double n))))
> (arg-test)
o
;No value
> (arg-test 1)
(1)
;No value
(write args)
(newline)))
;Value: arg-test
> (arg-test)
o
;No value
> (arg-test 1)
(1)
;No value
2.5.1. RECURSION
What do you think would happen if you defined this function and
then executed
(double 10)
The recursion takes place in the local function local-iterator. The vari-
able sum is visible to all three lambda expressions (i.e., procedures or
functions) defined inside the letrec special form. This is a good example
of a Scheme function in that it uses
• Local variables defined in a lef special form that contains a lexically
scoped function definition.
2.5. DEFINING FUNCTIONS 19
• Variable sum
• Lambda expression bound to variable check-for-even
• Lambda expression bound to variable local-iterator
• Lambda expression bound to variable calc
The value of the letrec special form is the compiled lambda expression
bound to variable calc.
For readers who have experience with other programming lan-
guages, I hope that you have some critical questions about Scheme
and this example. Specifically, you may think that this program is too
long and cumbersome to do something as simple as loop through a
range of numbers, summing the even ones. You are right! Scheme is
not an optimal language for dealing with numeric data. You will see in
later examples of non-numeric data (e.g., graph structures, search trees,
etc.) that Scheme programs are much shorter than programs written
in languages like CH.
Experienced programmers might also object to having to use recur-
sion for iterating over a range of numbers. Scheme implementations
convert most forms of recursion to iteration in compiled code; the use of
recursion does not cause any performance penalty in compiled Scheme
programs.
)
«test expression for continuing the do special form»
«command expression 1»
«command expression 2»
The do special form is similar to the let special form because it al-
lows you to specify local variables, and the initial values of those local
2.5. DEFINING FUNCTIONS 21
variables. You can optionally define "step" expressions that define how
a local variable will be modified during each iteration.
The iteration proceeds until the test condition is true. A few examples
will illustrate the syntax of the do special form:
Note that the built-in function display prints its argument immedi-
ately, so we see the output
o
1
2
3
4
5
appear before the value of the do special form. The built-in function
newline prints a newline character.
The following example shows how to iterate through the elements
of two lists, printing the values extracted from each list as a new list,
until either list runs out of elements:
> test-list
;Value 4: (the dog ran down the street)
> test-list
;Value 4: (the dog ran down the street)
The built-in function read is used to read in an expression from
the keyboard. If the expression is a list, the function read inputs all
characters until you type a closing right parenthesis.
> (define x)
;Value: x
(this is a list) I typed this list while (read) waited for input
;No value
> x
;Value: (this is a list)
The function symbol->strlng is useful for converting an internal
Scheme symbol to a character string.
> (symbol->string 'cat)
;Value: "cat"
The syntax for writing Scheme programs was discussed in the Scheme
tutorial in chapter 2. We must certainly understand the syntax of a
programming language, but there is more to writing programs than
writing a sequence of instructions to solve a problem. It is expensive
to write software, so it important to write programs so that portions
of the program can be reused in future programs. It is also important
to document properly what the program is supposed to do (analysis
of problem) and the design decisions that were used to implement the
program. These topics will be introduced in chapter 4. We will discuss
modularity and cohesion in this chapter. Modularity refers to separating
functions into separate program modules. These fairly independent
modules can more easily be reused in future programs that we write.
Cohesion refers to placing in a code module (or library) functions that
have similar functionality and that tend to operate on the same type of
data.
We will develop reusable Scheme libraries for genetic algorithms in
this chapter as an example of how to design and implement modular
and cohesive libraries. Genetic algorithms can be used to search effi-
ciently for solutions to difficult problems. We will see that this is a
good starting example: the internal behavior of the genetic algorithm
libraries is complex, but the interface to the library is simple to use. The
interface to a library is a set of one or more functions in the library
that can be called by a program using the library. We will see another
example of designing for modularity and cohesion in chapter 6, when
we design and implement a library for supporting pattern matching
using neural networks.
26 3. DESIGNING FOR REUSE
3. 1 MODULARITY
When I first started programming in high school (in 1967), I learned
FORTRAN, which was an excellent language for numerical calcula-
tions. Unfortunately, the FORTRAN I language had one severe flaw:
programs were written in a single file, often without using separate
functions. In the Scheme tutorial in chapter 2 the functions that we
studied were all very short. Writing short functions is good program-
ming style. A function should have one task to perform. Complex
behavior should be implemented by building a hierarchy of simple
functions: short simple functions that call other short simple functions.
Complex behavior can be hidden inside a code module, with a few
functions visible outside the library. There are thousands of types of
libraries; for example:
Business graphics
Pie charts
Bar graphs
Signal processing
Fast Fourier Transform
Convolution
Communications
Modem control
Ethernet access
The important point is to collect similar functionality in a module
or library for reuse in future programs. As an example, you might need
to write a simple program to read your monthly household expenses
and plot either pie charts or bar charts showing your expenditures by
category. Given a low-level graphics library to draw lines and shapes,
you could place Scheme code in your program to produce the desired
plots. However, later you might need to write a simple program to plot
rainfall for each day in a month. You could "cut and paste" the plotting
code from your first program into your new program, but this would
take time and possibly introduce errors into your new program. It is
much better to plan ahead and place potentially reusable code in a
separate library. All of the functions in a library should be kept in a
single file. Comments at the beginning of this file should indicate the
names of other required libraries.
3.3. LOOSE COUPLING BETWEEN LIBRARY MODULES 27
(define sum-even-numbers
(lambda (first-number s~cond-number)
(if « first-number second-number)
(let ((work-proc
(letrec
((sum 0) ..
( check-for~even
(lambda (n) ... )) details omitted
( local-iterator
(lambda (n) ... )) details omitted
( calc
(lambda (n) ... ))) details omitted
caI6))) ;; return value from letrec
(work-proc first-number))
0)))
I have left out the details, but remember that the functions check-
and calc are not defined outside the scope of the
for-even, local-iterator,
outer function definition sum-even-numbers.
with low fitness values, and replace them with new chromo-
somes produced by mutation and crossover of the remaining
chromosomes
• Genetic experiment: a definition of the length of the chromosomes
in the population, the number of chromosomes in the population,
and the data for encoding the chromosomes
3.4.1. REOUIREMENTS
(define update-population
(lambda (genetic-experiment-data)
30 3. DESIGNING FOR REUSE
(define get-best-chromosome
(lambda (genetic-experiment-data)
We also know that we will need to design the data structures for
chromosomes and genetic experiments. To hold the bits representing
a chromosome we will use a Scheme vector whose elements will equal
zero or one.
One of the most powerful aspects of interactive languages like
Scheme is that we can define and experiment with new data structures
by using a Scheme listener window. Follow the next example in your
Scheme environment. We start by demonstrating the use of Scheme
functions as data; then we create a vector to hold a chromosome,
manually building a list containing
1. A function pointer
2. The size of each chromosome
3. The number of chromosomes
4. A list of the chromosomes (a list of vectors)
(test f1 10)
;Value: 11
(test f2 10)
;Value: 12
The functions f1 and f2 simply add one and two, respectively to their
arguments. Here, I have used define to directly define a function instead
of using lambda; please remember that the following two definitions are
equivalent:
(define (f1 x)
(+ x 1))
(define f1
3.4. EXAMPLE: A LIBRARY FOR GENETIC ALGORITHMS 31
(lambda (x)
(+ xl)))
The function test has two arguments: a function pointer and a value.
Function test executes its first argument as a function.
We will store a pointer to the fitness function as data in the genetic
experiment data structure. The following listing shows a complete pro-
totype for creating a fitness function and building a genetic experiment
data structure. The fitness function returns as its value the number of
bits in the chromosome set to 1. Note that I enter comments in the
following example as any text on a line after a semicolon. (I like to use
two semicolons to set off comments.) Any text following a semicolon is
a comment that the Scheme system ignores.
Define a variable 'z' and set it to a vector:
> z
iValue: #(0 1 1 0 1)
> (vector-length z)
iValue: 5
> (fitness-1 z)
iValue: 3
random allele (gene index). Both chromosomes are split at this allele,
and the chromosomes swap halves at this randomly chosen allele.
The genetic algorithm library is especially simple in that no complex
programming logic is necessary to implement the library; we have only
to create one data structure, iterate through the data structure applying
a function to each chromosome in the population, and to retrieve the
chromosome with the highest numerical fitness value. Other libraries
created in this book will be much more complex algorithmically, so the
design process will include not only understanding the data structures
required for the library, but also a clear description of what needs to be
calculated and how the calculation is performed.
3.4.3. AN IMPLEMENTATION
chromosome-size
population-size
the-population
(make-vector population-size -9999) initial fitness values
)
)
)
(define update-population
(let «gene-ref vector-ref)
(gene-set! vector-set!»
(lambda (experiment)
(apply
(lambda (fitness-function
chromosome-size
population-size
population
fitness-values)
(let «chromosome-ref (lambda (c) (vector-ref population c»)
(chromosome-set! (lambda (c value)
(vector-set!
population
c
value»))
(fitness-ref (lambda (f) (vector-ref fitness-values f))
(fitness-set! (lambda (f value)
(vector-set!
fitness-values
f
value»»
(letrec
«set-fitness-values
Iterate through all chromosomes in the population
updating the fitness value in the fitness value
vector for each chromosome.
(lambda 0
(do «c 0 (+ c 1»)
«equal? c population-size»
3.4. EXAMPLE: A LIBRARY FOR GENETIC ALGORITHMS 35
(vector-set!
fitness-values
c
(fitness-function (vector-ref population c) c»»)
(bubble-sort
(lambda 0
We need to sort the fitness-values vector in descending
order. As we move fitness values in the fitness-value
vector, we move the corresponding chromosome in the
population vector.
population
(+ j 1)
c))))))) best chromosomes now are
(cross-over
;; Local function to do a "cross over" of genetic material:
(lambda (inqex-1 index-2)
(if DEBUG
(begin
(display "crossover at indicies: ")
(display index-1)
(display ", ")
(display index-2)
(display": cross over allele is ")))
(do «i 0 (+ i 1)))
«equal? i (truncate (/ population-size 3))))
(let «index-1
(+ 1 (random (- population-size 1))))
(index-2
(+ 1 (random (- population-size 1)))))
(cross-over index-1 index-2)))
(let «num-mutations
(truncate
3.4. EXAMPLE: A LIBRARY FOR GENETIC ALGORITHMS 37
(define best-chromosome-and-fitness
(lambda (experiment)
(let «fitness-function (car experiment))
(chromosome-size (cadr experiment))
(population-size (caddr experiment))
(population (cadddr experiment))
(fitness-values (cadddr (cdr experiment)))
(best-chromosome)
(best-fitness-value)
(best-index))
;; Iterate through all chromosomes in the population
;; finding the chromosome with the best fitness value:
(set! best-index 0)
(set!
best-fitness-value
(vector-ref fitness-values best-index))
;; start at the second chromosome (index 1):
(do «c 1 (+ c 1)))
«equal? c population-size))
(if (>
(vector-ref fitness-values c)
best-fitness-value)
(begin
(set! best-index c)
(set!
best-fitness-value
(vector-ref fitness-values c)))))
(set! best-chromosome (vector-ref population best-index))
(list
best-chromosome
(fitness-function best-chromosome best-index)))))
The following two functions are used to test this library. The first
function is a fitness function that returns as its value the number of
genes in a chromosome that are set to a non-zero value. The second
function simply creates a new genetic experiment data structure and
iterates through several new generations.
3.4. EXAMPLE: A LIBRARY FOR GENETIC ALGORITHMS 39
TEST FUNCTIONS:
(define fitness-1
(lambda (a-chrom chromosome-index)
(let ((sum 0)
(vector-len (vector-length a-chrom»)
(do ((i 0 (+ i 1»)
((= i vector-len) sum)
(if (equal? (vector-ref a-chrom i) 1)
(set! sum (+ sum 1»»»)
Test function
(define (test)
(let ((ge ;; experiment with 10 chromosomes, each with 6 genes
(create-genetic-experiment 6 10 fitness-1»)
(do ((i 0 (+ i 1»)
((equal? i 20» ;; 20 "generations"
(update-population ge)
(let ((temp (best-chromosome-and-fitness ge»)
(newline)
(display "best fitness: ")
(display (cadr temp»
(display ", and best chromosome: ")
(display (car temp»
(newline)
(display ge)
(newline»»)
(test)
The following listing shows the output generated from running the
function test in a Scheme listener window:
(test)
crossover at indides: 1, 3: cross over allele is 3
crossover at indides: 2, 3: cross over allele is 3
crossover at indides: 9, 4: cross over allele is 4
mutating chromosome at index5, value: #(0 0 1 0 0 1) at gene index 1
mutating chromosome at index6, value: #0 0 0 1 0 0) at gene index 5
(define Company-X-sales)
(define ge)
(set! Company-X-sales
(make-vector number-of-sales-staff 0.0)))
;; Test code:
(load "gen_exam.s")
;Loading "gen_exam.s"
crossover at indicies: 9, 5: cross over allele is 6
crossover at indicies: 1, 1: cross over allele is 2
crossover at indicies: 8, 1: cross over allele is 3
mutating chromosome at index5, value: #(0 0 0 0 1 0 0 0 1) at gene index 1
mutating chromosome at index9, value: #(0 0 0 0 1 0 0 1 0) at gene index 1
mutating chromosome at index5, value: #(0 1 0 0 1 0 0 0 1) at gene index 0
(#(0 0 1 1 0 0 0 0 0) 100.) -- done
;No value
You would use the chromosomes with the highest fitness values to
specify the sales strategies used for the following month.
CHAPTER 4
WRITING PORTABLE
SCHEME CODE
We never know when we first write a program what the futures uses
for the program will be. Developing software costs money, so there are
strong economic reasons to make software reusable. Software has a
greater probability of reuse if it is written to be (relatively) portable
on different types of computers using different compilers and run-time
environments. Not all Scheme systems are completely compatible. For
example, the following Scheme statement is accepted by only some
Scheme systems:
(define X)
(define X If)
Some Scheme systems allow you not to specify initial values for
variables in let and let* statements; for example:
(let «x 1)
(y 2)
(z))
(set! z 3)
(display (+ x y z))
Some Scheme systems will not accept this statement because the
variable z has not been assigned an initial value in the let statement.
46 4. WRITING PORTABLE SCHEME CODE
Listing 4.1
File: OS_FUNeS.S
(define (get-time)
(cadr ($system-get-date-and-time))
Our programs will always call our function get-time instead of the
non-portable, system-specific call $system-get-date-and-time.
We will design and implement a portable graphics library in Section
4.3.
4.3. A PORTABLE GRAPHICS LIBRARY 47
Listing 4.2
File: graph. s
(plot-line xl yl x2 y2)
(plot-string x y str)
48 4. WRITING PORTABLE SCHEME COOE
(pen-width width)
(define open-gr
(lambda 0
(if (null? g-c)
(begin
(set! g-c (make-graphics-device #f»
(graphics-set-coordinate-limits g-c 0 0 1024 1024»
(begin
(clear-plot»»)
(define close-gr
(lambda 0
(clear-plot»)
(define clear-plot
(lambda 0
(graphics-clear g-c»)
(define plot-line
(lambda (xl yl x2 y2)
(graphics-draw-line g-c xl yl x2 y2»)
(define plot-string
(lambda (x y str)
(graphics-draw-text
g-c
(inexact->exact (floor x»
(inexact->exact (floor y»
str»)
(define plot-ellipse
(lambda (left top right bottom color-string)
(graphics-operation g-c 'set-foreground-color color-string)
4.3. A PORTABLE GRAPHICS LIBRARY 49
(define plot-fill-rect
(lambda (x y xsize ysize color-string)
(graphics-operation g-c 'set-foreground-color color-string)
(let ((x2 (+ x xsize))
(y2 (+ Y ysize)))
(vector-set! graph_temp_vec 0 x)
(vector-set! graph_temp_vec 1 y)
(vector-set! graph_temp_vec 2 x)
(vector-set! graph_temp_vec 3 y2)
(vector-set! graph_temp_vec 4 x2)
(vector-set! graph_temp_vec 5 y2)
(vector-set! graph_temp_vec 6 x2)
(vector-set! graph_temp_vec 7 y)
(vector-set! graph~temp_vec 8 x)
(vector-set! graph_temp_vec 9 y)
(graphics-operation g-c 'fill-polygon graph_temp_vec))
(graphics-operation g-c 'set-foreground-color "black")))
(define plot-fill-rect-gray-scale
(lambda (x y xsize ysize int255)
(let ((color (inexact->exact (floor int255))))
(vector-set! graph_temp_vec_3 0 color)
(vector-set! graph_temp_vec_3 1 color)
(vector-set! graph_temp_vec_3 2 color)
(plot-fill-rect
(inexact->exact x)
(inexact->exact y) xsize ysize graph_temp_vec_3))))
(define pen-width
(lambda (w)
(graphics-operation g-c 'set-line-width w)
1)
(define test
(lambda 0
(plot-fill-rect 200 100 300 300 "blue")
(let ((xx '(0 25 50 75 100 125 150 175 200 225 250)))
(do ((x xx (cdr x)))
((null? x))
(plot-fill-rect-gray-scale
(car x) (car x) 25 25 (car x))))
50 4. WRITING PORTABLE SCHEME CODE
(open-gr)
(test)
(close-gr)
Listing 4.3
File: MAC GRAF. S
(define PLOT-SCALE 4)
(define (open-gr)
(if (equal? local-window #f)
(set! local-window
(mac#newwindow
(mac#rect 40 10 (+ Y-MAX-VALUE 100) 300)
"Portable Scheme Graphics"
#t visible
4 ; nogrowdoc
-1 ; in front of all windows
#t» ; goawaybox
(clear-plot»)
(define (clear-plot)
(mac#eraserect local-window (mac#rect 0 0 512 512»)
;; test function
(define (test)
(open-gr)
(plot-string 50 200 "Test of Portable Scheme graphics for MacGambit")
(plot-line 10 10 100 200)
(plot-fill-rect 50 100 330 220 "red")
(pen-width 10)
(plot-line 200 200 190 190)
(pen-width 1)
(plot-ellipse 150 150 200 100 "blue"))
The function y-Iayout is called with a node index and the plot level.
The plot level is increased by one every time the function y-Iayout calls
itself recursively. Function y-Iayout creates a list of all child nodes from
the parent node passed in its argument list. Function y-Iayout calls itself
recursively for each child node on this list. Actual Y plot coordinates
are only assigned to a node after the Y coordinates are set for all child
nodes.
The function x-layout is similar to y-Iayout. A list of all child nodes is
created, and function x-layout calls itself recursively for each of these
child nodes.
The root node name is set when the function make-grapher is called
to create a new Grapher object. Child nodes are created by calling the
function add-node with the new node name, the parent node name, and
any arbitrary data that should be stored with the new node. After any
child nodes are added, the function do-layout can be called to reset the
plot coordinates of all nodes in the Grapher object. The function draw
is used to draw the nodes in a Grapher object.
Listing 4.4
File: grapher.s
(get-node-id-from-position a-grapher x y)
-> integer node ID of closest node to the
specified screen coordinates x and y.
(layout-graph)
(define _MAX-NODES 3)
(define _LAST-Y 4)
;; Miscelanious "constants":
(define _V-SPACING 90)
(define _X-SPACING 300)
(define _CHAR-WIDTH 18) width of character of screen in pixels
(define make-grapher
(lambda (root-node-name)
(if (not (string? root-node-name))
(begin
(write "Error: node names must be trings.")
(newline) )
;; Name OK, so create a ne grapher object:
(let ((max-nodes 7)
(num-nodes 1)
(last-y 0) ;; used for layout calculations
(new-node (make-vector 6))
(tree #f))
create the root node:
(set! tree (make-vector max-nodes))
(vector-set! new-node 0 0) ; x coordinate
(vector-set! new-node 1 0) ; y coordinate
(vector-set! new-node 2 root-node-name)
(vector-set! new-node 3 -1)
(vector-set! new-node 4 0) ; selection flag
(vector-set! new-node 5 #f) ;; data for node
;; insert the root node into the tree:
(vector-set! tree 0 new-node)
;; create the list which is a new grapher object:
(vector num-nodes tree root-node-name max-nodes last-y)))))
(define get-num-nodes
(lambda (a-grapher)
(vector-ref a-grapher _NUM-NODES)))
(define get-tree
(lambda (a-grapher) (vector-ref a-grapher _TREE)))
(define get-node
(lambda (a-grapher node)
(let ((tree (get-tree a-grapher))
(num (get-num-nodes a-grapher))
(node-index -1))
node can either be a string name of a node,
56 4. WRITING PORTABLE SCHEME CODE
;; or an integer index:
(if (string? node)
(do ((i 0 (+ i 1)))
«equal? i num))
(if (string=?
node
(vector-ref (vector-ref tree i) _ROOT_NAME))
(set! node-index i)))
(set! node-index node))
(if (and
(> node-index -1)
« node-index (vector-ref a-grapher _MAX-NODES)))
(vector-ref tree node-index) ;; found the node
;; node not in grapher object, so return If:
If))))
(define get-node-index
(lambda (a-grapher node)
(let (tree (get-tree a-grapher))
(num (get-num-nodes a-grapher))
(node-index -1))
;; node can either be a string name of a node,
;; or an integer index:
(if (string? node)
(do ((i 0 (+ i 1)))
«(equal? i num))
(if (string=?
node
(vector-ref (vector-ref tree i) _ROOT-NAME))
(set! node-index i)))
(set! node-index node))
node-index)))
(define add-node
(lambda (a-grapher node-name parent-node data)
(let ((tree (get-tree a-grapher))
(parent-index (get-node-index a-grapher parent-node))
(num (get-num-nodes a-grapher))
(new-node (make-vector 6)))
make sure that there is enough space for a new node:
(if (> (+ num 2) (vector-ref a-grapher _MAX-NODES))
(begin
(display "No space for a new node.")
(newline)
If)
(begin
(vector-set! new-node _X-COORD 0) ; x coordinate
(vector-set! new-node _Y-COORD 0) ; y coordinate
(vector-set! new-node _NAME node-name)
4.4. EXAMPLE LIBRARY FOR DISPLAYING GRAPH STRUCTURES 57
(define do-layout
(lambda (a-grapher)
(let ((tree (get-tree a-grapher»
(parent-index (get-node-index a-grapher _PARENT-ID»
(num (get-num-nodes a-grapher»)
(do ((i 0 (+ i 1»)
((equal? i num»
(let ((node (vector-ref tree i»)
(vector-set! node _X-COORD 0)
(vector-set! node _Y-COORD 0»)
(vector-set! a-grapher _LAST-Y 0)
(y-Iayout a-grapher 0 0)
(x-layout a-grapher 0 0»»
(define y-Iayout
(lambda (a-grapher node-id level)
(let ((tree (get-tree a-grapher»
(parent-index (get-node-index a-grapher _PARENT-ID»
(average-y 0)
(last-y (vector-ref a-grapher _LAST-Y»
(num (get-num-nodes a-grapher»)
(let ((node (vector-ref tree node-id»)
(if (equal? (vector-ref node _Y-COORD) 0) ;; check y coord.
(let ((child-nodes (get-children a-grapher node-id»)
(if (null? child-nodes)
(let ((new-y-value
(+ (vector-ref node _Y-COORD) _Y-SPACING»)
(vector-set! node _Y-COORD
(+ last-y _Y-SPACING»
(display "last-y=") (display last-y) (newline)
(vector-set! a-grapher _LAST-Y new-y-value»
(let ((copy-child-nodes (list-copy child-nodes»
(len (length child-nodes»)
(do ((child-nodes child-nodes (cdr child-nodes»)
((null? child-nodes»
(y-Iayout a-grapher (car child-nodes) (+ level 1»)
(set! average-y 0)
(do ((copy-child-nodes copy-child-nodes
(cdr copy-child-nodes»)
((null? copy-child-nodes»
(set! average-y
(+ average-y
58 4. WRITING PORTABLE SCHEME CODE
(vector-ref
(vector-ref tree (car copy-child-nodes))
3-COORD ))))
(set! average-y (/ average-y len))
(vector-set! node _V-COORD average-y)))
(vector-set! node _X-COORD 0)))))))
(define x-layout
(lambda (a-grapher node-id level)
(let «tree (get-tree a-grapher))
(num (get-num-nodes a-grapher)))
(if (equal? level 0)
;; top level root node:
(let «node (vector-ref tree node-id))
(child-nodes (get-children a-grapher node-id)))
(vector-set! node _X-COORD _X-SPACING)
(do «child-nodes child-nodes (cdr child-nodes)))
«null? child-nodes))
(x-layout a-grapher (car child-nodes) (+ level 1))))
not the root node, so calculate the spacing
based on the number of characters in the
parent node's name:
(let «node (vector-ref tree node-id))
(child-nodes (get-children a-grapher node-id)))
(let «parent-id (vector-ref node _PARENT-ID)))
(let «parent (vector-ref tree parent-id)))
(let «len (string-length (vector-ref parent _NAME))))
(vector-set!
node _X-COORD
(+
(vector-ref parent _X-COORD)
(* len _CHAR-WIDTH)))
layout any unprocessed child nodes:
(do «child-nodes child-nodes (cdr child-nodes)))
«null? child-nodes))
(x-layout
a-grapher
(car child-nodes)
(+ level 1)))))))))))
(define get-children
(lambda (a-grapher node-id)
(let «tree (get-tree a-grapher))
(num (get-num-nodes a-grapher))
(count 0)
(return-list 'C)))
(do «i 0 (+ i 1)))
«equal? i num))
(if (equal? node-id (vector-ref (vector-ref tree i) _PARENT-ID))
4.4. EXAMPLE LIBRARY FOR DISPLAYING GRAPH STRUCTURES 59
(define draw
(lambda (a-grapher)
(let «tree (get-tree a-grapher))
(num (get-num-nodes a-grapher)))
(do «i 0 (+ i i)))
«equal? i num))
(let «node (vector-ref tree i)))
(let «parent-id (vector-ref node _PARENT-ID)))
(if (> parent-id -i)
(let «parent (vector-ref tree parent-id))
(y_half (truncate (/ _Y-SPACING 8))))
(plot-line
(+
(vector-ref parent _X-COORD)
(* _CHAR-WIDTH
(string-length
(vector-ref parent _NAME)))
-20)
(+
(vector-ref parent _Y-COORD)
y_half)
(vector-ref node _X-COORD)
(+
(vector-ref node _Y-COORD)
y_half)))))))
(do «i 0 (+ i i)))
«equal? i num))
(let «node (vector-ref tree i)))
(plot-string
(vector-ref node _X-COORD)
(vector-ref node _Y-COORD)
(vector-ref node _NAME)))))))
test code:
; (define g)
;(set! g (make-grapher "root"))
; (display g)
; (pp g)
; (add-node g "node-i" "root" '(this is data 1))
; (add-node g "node-2" "root" '(this is data 2))
; (add-node g "node-i-i" "node-i" '(this is data ii1))
; (get-children g 0)
60 4. WRITING PORTABLE SCHEME CODE
; (get-children g 1)
; (get-children g 3)
; (do-layout g)
;(pp g)
; (open-gr)
; (clear-plot)
; (draw g)
; (c1ose-gr)
Listing 4.5
(load "graph.s")
;Loading "graph.s" -- done
;Value: test
,flode-l-flode-l-l
root(
\ode-2
;Value: g
(pp g)
#(1 #(#(0 0 "root" -1 0 0) 0 0 0 0 0 0) "root" 7 0)
;No value
(get-children g 0)
;Value 8: (2 1)
(get-children g 1)
; Value 9: (3)
(get-children g 3)
;Value: 0
(do-layout g)
last-y=O
last-y=90
;Value: #t
(pp g)
#(4
#(#(300 135 "root" -1 0 ())
#(372 180 "node-1" 0 0 (this is data 1))
#(372 90 "node-2" 0 0 (this is data 2))
#(480 180 "node-1-1" 1 0 (this is data 1 1 1))
o
o
0)
"root"
7
90)
;No value
(open-gr)
;No value
(draw g)
;Value: #t
62 4. WRITING PORTABLE SCHEME CODE
Listing 4.6
File: complex.s
(define complex
(lambda (x y)
4.5. EXAMPLE: PLOTTING MANDELBROT SETS 63
(list x y)))
(define realpart
(lambda (z)
(car z)))
(define imagpart
(lambda (z)
(cadr z)))
(define complex_+
(lambda (zl z2)
(list (+ (realpart zl) (realpart z2))
(+ (imagpart zl) (imagpart z2)))))
(define complex_*
(lambda (zl z2)
(list
(-
(* (realpart zl) (realpart z2))
(* (imagpart zl) (imagpait z2)))
(+
(* (realpart zl) (imagpart z2))
(* (realpart z2) (imagpart zl))))))
The equation for calculating Mandelbrot sets is
Z-at-time-n+ 1 = (Z-at-time-n) * (Z-at-time-n) + Constant
We can set the constant term to zero, and we can use the following al-
gorithm to determine whether a complex number Z is in the Mandelbrot
set:
Loop N times:
set Z = Z * Z
if Z > small constant, then exit loop
A complex point Z is a member of the Mandelbrot set if the ex-
pression Z=Z*Z stays small. If the complex number Z is not in the
Mandelbrot set, then this expression quickly becomes large. For points
not in the Mandelbrot set we count the number of times we execute the
expression Z = Z * Z before the value of Z becomes larger than some
pre-defined, small threshold value; a color is selected for plotting based
on this count.
Listing 4.7 shows the file MANDELBR.S.
Listing 4.7
; File: Mandelbr.s
64 4. WRITING PORTABLE SCHEME CODE
Set the cell width and height for plotting a single point
on the complex plane:
(define M
(lambda 0
(open-gr) ;; open a graphics window
(plot-string 100 900 "Mandelbrot Plot")
(let ((x 0.5) (y 0.5) (z (complex 0 0)))
(do ((ix 0 (+ ix 1)))
((= ix num-x-cells))
4.6. EXAMPLE: PLOTTING CHAOTIC POPULATION GROWTH 65
FIGURE 4.3. Plot produced by the program in Listing 4.7. Note that the graphics library for MIT
Scheme frames filled rectangles with a black border; these borders were manually removed
by setting the color map entry of black to white in the screen dump for this figure.
Listing 4.8
File: Mayl. s
(define bifur
(lambda 0
(open-gr)
(clear-plot)
4.6. EXAMPLE: PLOTTING CHAOTIC POPULATION GROWTH 67
Listing 4.9
File: May2. s
_ -------- ExtInction
Study atate -------~ ..
....
~..........
Perlod doubled
Chaos
FIGURE 4.4. Effect of population growth rate on population. The vertical axis shows the popula-
tion growth rate parameter lambda increasing from 0 (top of plot) to thd value 1 (at the bottom
of the plot). The horizontal axis represents the simulated population (larger values to the right
side of the plot).
(define bifur
(lambda 0
(open-gr)
(clear-plot)
(let ((x-axis 0)
(delta-value 0)
(x 0.1)
(population 0))
(do ((y-axis 0 (+ y-axis 1)))
((> y-axis 219))
(set! delta-value (* 4 (+ 0.85 (/ y-axis 1500.0)))) change the
growth rate
(do ((iter 0 (+ iter 1)))
((> iter 197))
(set! population (* delta-value x (- 1 x)))
(set! x-axis (truncate (* population 500.02)))
(if (and (> x-axis 0) « x-axis 501))
(plot-line
4.6. EXAMPLE: PLOTTING CHAOTIC POPULATION GROWTH 69
FIGURE 4.5. Effect of population growth rate on population. This plot is an enlargement of the
area showing frequency doubling in Figure 4.4.
(* 2 x-axis)
(- 1000 (* 2 y-axis»
(* 2 x-axis)
(- 1000 (* 2 y-axis»»
(set! x population»»»
In Figure 4.5 notice the clear, non-chaotic areas near the bottom of
the diagram. These are regimes in which the system becomes stable and
oscillates among three possible values. If the sample program in Listing
4.9 is modified to magnify this stable regime, the resulting plot will
look surprisingly like the plot in Figure 4.4. Indeed, this repeatability is
infinitely deep as we continue to magnify areas of the plot near stable
regimes. This self-similarity under magnification is also seen in the
Mandlebrot set plots generated in Section 4.5.
CHAPTER 5
AN ITERATIVE ApPROACH
TO ANALYSIS, DESIGN,
AND IMPLEMENTATION
The example programs that we have developed so far in this book have
been fairly simple. More complex programs usually require an iterative
approach: we perform initial requirements analysis, design, and a first-
stage implementation. After using the first-stage implementation we
revisit the analysis and design, then re-implement parts of the program,
building a second-stage implementation.
nll
search ends without finding the goal node, the search backs up to the
last node with unexplored connected nodes. A depth first search can be
very inefficient because if we are unlucky enough to start searching the
wrong path, we search that path exhaustively before trying alternative
paths. Depth-first search has the advantage of not requiring very much
storage.
A breadth-first search uniformly "fans out" from the starting node
until the goal node is reached. The breadth-first search has the ad-
vantage of a fairly constant execution time for a given network. The
disadvantage of a breadth-first search is the storage required to keep
track of all the nodes that we visit while "fanning out" from the starting
node.
We will implement a depth-first search in this section. In Section 5.3,
we will improve the depth-first search program by by modifying it to
perform a breadth-first search.
For the example network shown in Figure 5.1, successive iterations
of our depth-first search from node n1 to node n11 will visit the following
nodes:
5.1. PRELIMINARY ANALYSIS, DESIGN, AND IMPLEMENTATION Of A NETWORK SEARCH PROGRAM 73
1. nl
2. nl n2
3. nl n2 n3
4. nl n2 n3 n5
5. nl n2 n3 n5 n8
6. nl n2 n3 n5 n8 n4
7. nl n2 n3 n5
8. nl n2 n3 n6
9. nl n2 n3 n6 nlO
10. nl n2 n3 n6 nlO n9
11. nl n2 n3 n6 nlO n9 n7
12. nl n2 n3 n6 nlO n9 n7 nIl
Notice how the search proceeds in a given direction until it finds the
goal node, runs into a "dead end," or finds only nodes that are on the
current path list.
Listing 5.1 shows the contents of file DEPTH.S on the disk in-
cluded with this book. The variable nodes defines the coordinates of
each node in the network. Referring to Figure 5.1, the reader can
add additional nodes to the example program in Listing 5.1. The
variable paths is used to specify which nodes in the network are con-
nected. The function init-path-Iengths is used to redefine the path list
to include the distance between nodes. The function slow-path-Iength
calculates the shortest possible path between two nodes. The function
dist-between-points calculates the Cartesian distance between two nodes.
The function find-connected-nodes returns a list of all nodes directly con-
nected to a specified node. The function depth performs a depth-first
search for a path between two specified nodes. The two functions test1
and test2 are used to test the search; these functions generated the plots
in Figures 5.1 and 5.2.
Listing 5.1
File: depth. s
(define nodes
,(
(nl (120 804))
(n2 (100 620))
(n3 (220 120))
(n4 (440 750))
(n5 (385 440))
(n6 (520 88))
(n7 (610 600))
(n8 (695 808))
(n9 (702 515))
(nl0 (800 220))
(nll (830 808))))
(define paths
,(
(nl n2) (n2 n3) (n3 n5) (n3 n6) (n6 nl0)
(n9 nl0) (n7 n9) (nl n4) (n4 n2) (n5 n8)
(n8 n4) (n7 nll)))
(define init-lengths
(lambda (pathlist)
(let ((new-path-list 'C))
(pathlength 0)
(path-with-length 'C)))
(do ((path pathlist (cdr path)))
( (null? path))
(set! pathlength (slow-path-length (car path)))
(set! path-with-length (append (car path) (list pathlength)))
(set! new-path-list (cons path-with-length new-path-list)))
new-path-list)))
(define slow-path-length
(lambda (path)
(let ((nodel (car path))
(node2 (cadr path)))
(let ((nl (assoc nodel nodes))
(n2 (assoc node2 nodes)))
(dist-between-points (cadr nl) (cadr n2))))))
(define dist-between-points
(lambda (pointl point2)
(let «x-dif (- (X-coord point2) (X-coord pointl»)
(y-dif (- (V-coord point2) (V-coord pointl»»
(sqrt (+ (* x-dif x-dif) (* y-dif y-dif»»»
; (pp paths)
(define find-connected-nodes
(lambda (a-node)
(let «ret-list 'C»~)
(do «1 paths (cdr 1»)
«null? 1»
(let «path (car 1») ; (nodel node2 distance)=path
(if (equal? a-node (car path»
(set! ret-list (cons (cadr path) ret-list»)
(if (equal? a-node (cadr path»
(set! ret-list (cons (car path) ret-list»»)
ret-list»)
(find-connected-nodes 'n2)
(define depth
(lambda (path goal)
(if (equal? (car (last-pair path» goal)
path ;; done with search
(let «new-nodes (find-connected-nodes (car (last-pair path»»
(keep-searching #t)
(ret-val #f)
(do «nn new-nodes (cdr nn»)
«or
(null? nn)
(equal? keep-searching #f»)
(if (not (member (car nn) path»
(let «temp (depth (append path (list (car nn»)
goal»)
(if (not (null? temp»
(begin
(set! ret-val temp)
(set! keep-searching #f»»»
ret-val»»
(define testl
(lambda 0
(open-gr)
(pen-width 1)
(do ((p paths (cdr p)))
((null? p))
(display "(car p)=") (display (car p)) (newline)
(let ((from (cadr (assoc (caar p) nodes)))
(to (cadr (assoc (cadar p) nodes))))
(plot-line
(x-coord from)
(y-coord from)
(x-coord to)
(y-coord to))))
(do ((n nodes (cdr n)))
((null? n))
(let ((n-val (cadar n)))
(plot-string
(+ 2 (x-coord n-val))
(y-coord n-val)
(symbol->string (caar n)))))))
(define test2
(lambda 0
(define (draw-path pI)
(pen-width 3)
(let ((nodel (cadr (assoc (car pI) nodes))))
(set! pI (cdr pI))
(do ((p pI (cdr p)))
((null? p))
(plot-line (x-coord nodel)
(y-coord node!)
(x-coord (cadr (assoc (car p) nodes)))
(y-coord (cadr (assoc (car p) nodes))))
(set! nodel (cadr (assoc (car p) nodes))))))
(draw-path (depth '(nl) 'nll))))
(test!)
(test2)
Listing 5.2 shows the values of the variables nodes and paths "pretty
printed" with the built-in Scheme function pp after loading the file
DEPTH.S.
Listing 5.2
(pp nodes)
((nl (120 804)) (n2 (100 620)) (n3 (220 120)) (n4 (440 750))
(n5 (385 440)) (n6 (520 88)) (n7 (610 600)) (n8 (695 808))
5.1. PRELIMINARY ANALYSIS, DESIGN, AND IMPLEMENTATION OF A NETWORK SEARCH PROGRAM 77
(pp paths)
«n7 nll 302.76063152266016) (n8 n4 261.51290599127225)
(n5 n8 481.1694088364305) (n4 n2 364.0054944640259)
(nl n4 324.5242671973854) (n7 n9 125.25573839150046)
(n9 nl0 310.85205484281425) (n6 nl0 309.5545186231337)
(n3 n6 301.7018395701292) (n3 n5 360.0347205478938)
(n2 n3 514.1984052872976) (nl n2 185.0837648201484»
;No value
Calculating and storing the path lengths between nodes are not
required for the example depth-first search in file DEPTH.S, but I an-
ticipated that this information would be useful for applications using
the search functions.
Listing 5.3 shows the output from executing the function depth. The
function is called twice; the second call has recursive calls to function
depth traced using the built-in Scheme trace function.
Listing 5.3
» (load "depth.s")
;Loading "depth.s" done
;Value: test2
» (trace depth)
;No value
FIGURE 5.2. A solution to the problem of finding an efficient path from node n1 to node n11.
Notice in Listing 5.3 that the depth-first search first looks at nodes in
the following order: n1 n2 n3 n5 n8 n4. This search is discarded since
node n4 is connected to nodes n1, n2, and n8, which have already been
searched.
Figure 5.1 was produced from executing the function test1 in file
DEPTH.S. Figure 5.2 was produced from executing function test2.
80 5. AN ITERATIVE ApPROACH TO ANALYSIS, DESIGN, ANO IMPLEMENTATION
Notice how the search uniformly "fans out" from the starting node
n1 towards the goal node n11.
Listing 5.4 shows the contents of file BREADTH.S on the disk in-
cluded with this book. The file BREADTH.s was created by copying
the file DEPTH.s, replacing the depth-first search function depth with
5.3. IMPROVING OUR ANALYSIS, DESIGN, AND IMPLEMENTATION 81
the breadth first search function search. The last line of the test function
test2was also modified to call function search instead of function depth.
Listing 5.4
;; File: breadth.s
(define nodes
,(
(nl (120 804))
(n2 (100 620))
(n3 (220 120))
(n4 (440 750))
(n5 (385 440))
(n6 (520 88))
(n7 (610 600))
(n8 (695 808))
(n9 (702 515))
(nl0 (800 220))
(nl1 (830 808))))
(define paths
,(
(nl n2) (n2 n3) (n3 n5) (n3 n6) (n6 nl0)
(n9 nl0) (n7 n9) (nl n4) (n4 n2) (n5 n8)
(n8 n4) (n7 nll)))
(define init-Iengths
(lambda (pathlist)
(let «new-path-list 'C))
(pathlength 0)
(path-with-Iength 'C)))
(do «path pathlist (cdr path)))
( (null? path))
(set! pathlength (slow-path-Iength (car path)))
(set! path-with-Iength (append (car path) (list pathlength)))
82 5. AN ITERATIVE ApPROACH TO ANALYSIS, DESIGN, ANO IMPLEMENTATION
(define slow-path-length
(lambda (path)
(let «nodel (car path))
(node2 (cadr path)))
(let «nl (assoc nodel nodes))
(n2 (assoc node2 nodes)))
(dist-between-points (cadr nl) (cadr n2))))))
(define dist-between-points
(lambda (pointl point2)
(let «x-dif (- (X-coord point2) (X-coord pointl)))
(y-dif (- (Y-coord point2) (Y-coord pointl))))
(sqrt (+ (* x-dif x-dif) (* y-dif y-dif))))))
(pp paths)
; (pp paths)
(define find-connected-nodes
(lambda (a-node)
(let «ret-list 'C)))
(do «1 paths (cdr 1)))
«null? 1))
(let «path (car 1))) (nodel node2 distance)=path
(if (equal? a-node (car path))
(set! ret-list (cons (cadr path) ret-list)))
(if (equal? a-node (cadr path))
(set! ret-list (cons (car path) ret-list)))))
ret-list)))
(find-connected-nodes 'n2)
5.3. IMPROVING OUR ANALYSIS, DESIGN, AND IMPLEMENTATION 83
(define search
(lambda (start-node goal-node)
(let. «a-good-path '(»
(visited-list (list start-node»
(search-list
(list (list start-node start-node 0.0»)
(search-func
(letrec
«next
(lambda (s-list)
(let «new-s-list '(»)
(do «1 s-list (cdr 1»)
«null? 1»
(let «path (car 1»)
(let «last-node
(car (last-pair (except-last-pair path»»)
(let «connected-nodes
(find-connected-nodes last-node»)
(do «n connected-nodes (cdr n»)
«null? n»
(if (not (member (car n) visited-list»
(begin
(set!
new-s-list
(cons
(append
(except-last-pair path)
(list (car n»
(list
(+
(car (last-pair path» old distance
(slow-path-length
(list
(car
(last-pair
(except-last-pair path»)
(car n»»»
new-s-list) )
(set!
visited-list
(cons (car n) visited-list»»»»)
new-s-list»)
(found-goal-node?
(lambda 0
(let «good-path '(»)
(do «1 search-list (cdr 1»)
«null? 1)
(not (null? good-path»)
84 5. AN ITERATIVE ApPROACH TO ANALYSIS, DESIGN, AND IMPLEMENTATION
(search-l
ClambdaO
(do ((iter 0 (+ iter 1»)
((or
(equal? iter (length nodes»
(not (null? a-good-path»»
(set!
search-list
(next search-list»
(newline)
(display "search level=") (display iter) (newline)
(display "current visited list:") (newline)
(pp visited-list) (newline)
(display "current search list:") (newline)
(pp search-list) (newline)
(set! a-good-path (found-goal-node?»)
(cdr a-good-path»»
search-l» )
(search-func)
a-good-path»)
(define testl
(lambda 0
(open-gr)
(pen-width 1)
(do ((p paths (cdr p»)
((null? p»
(display "(car p)=") (display (car p» (newline)
(let ((from (cadr (assoc (caar p) nodes»)
(to (cadr (assoc (cadar p) nodes»»
(plot-line
(x-coord from)
(y-coord from)
(x-coord to)
(y-coord to»»
5.3. IMPROVING OUR ANALYSIS, DESIGN, AND IMPLEMENTATION 85
(define test2
(lambda 0
(define draw-path (lambda (pI)
(pen-width 3)
(let ((nodel (cadr (assoc (car pI) nodes»»
(set! pI (cdr pI»
(do ((p pI (cdr p»)
((null? p»
(plot-line (x-coord nodel)
(y-coord node1)
(x-coord (cadr (assoc (car p) nodes»)
(y-coord (cadr (assoc (car p) nodes»»
(set! nodel (cadr (assoc (car p) nodes»»»)
(draw-path (except-Iast-pair (search 'nl 'nll»»)
(test1)
(test2)
The breadth-first search function search is considerably more com-
plex than the simple depth-first search function depth that it replaced.
Part of this added complexity is due to calculating and storing path
lengths during the search, but most of the additional complexity is
due to the requirements of storing all searched nodes and adding only
directly connected nodes to this list of searched nodes during each
iteration of the breadth-first search.
Listing 5.5 shows the output from executing the function search.
Listing 5.5
» (search 'nl 'nll)
search level=O
current visited list:
(n4 n2 nl)
current search list:
search level=l
current visited list:
86 5. AN ITERATIVE ApPROACH TO ANALYSIS, DESIGN, AND IMPLEMENTATION
(n3 n8 n4 n2 nl)
current search list:
search level=2
current visited list:
(n6 n5 n3 n8 n4 n2 nl)
current search list:
((nl nl n2 n3 n6 1000.9840096775753)
(nl nl n2 n3 n5 1059.3168906553399))
search level=3
current visited list:
(nl0 n6 n5 n3 n8 n4 n2 nl)
current search list:
search level=4
current visited list:
search level=5
current visited list:
search level=6
current visited list:
Figure 5.2 was produced from executing function test2 in the file
DEPTH.S. However, the same graph can be produced by executing
function test2 in the file BREADTH.s.
CHAPTER 6
NEURAL NETWORK
LIBRARY
Neural networks are useful for many pattern recognition and control
applications. In this chapter we design a library that supports "delta
rule," or backwards error propagation networks. For flexibility our
library supports any number of neuron layers. For efficiency our li-
brary uses momentum calculations to train networks. We will discuss
momentum in the discussion of requirements in Section 6.1.
We will also see how it is often useful to create separate low-
level utility libraries to support the library that we are designing and
implementing. Here we notice the requirement to manipulate two-
dimensional arrays. Since Scheme does not support two-dimensional
arrays, we are careful to provide this capability in a separate library
that will be reusable in other non-neural-network applications.
The code listed in this chapter can be found in the file NN.S on
the included disk. You will also need to load the array library in file
ARRAYS. The neural network library can optionally use the graphics
library developed in Chapter 4; if you use graphics, remember also to
load file GRAPH.s.
2 Hidden 2 Output
Neurons Neurons
3 Input II
Neurons HI 01
accept 12
system
inputs 13 02
There are six There are four
weights which weights which
connect each connect each of
of the three the two hidden
input neurons neurons with
with each each of the two
of the two output neurons
hidden neurons
FIGURE 6.1. A neural network with three layers of neurons (input, hidden, and output) and two
weight sets (one connecting each input layer neuron to each hidden layer neuron and one
connecting each hidden layer neuron to each output layer neuron).
• weights
• number of neurons in each neuron layer
In practice there are other data structures that we will need to calcu-
late and store. We will want to create one data object that completely
represents the state of a neural network. This is the information that
we will need to store:
We iisted the data required for maintaining the state of a neural network
object in Section 6.2. Figure 6.1 showed a simple three-layer neural
network with three input neurons (labeled 11,12, and 13), two hidden
layer neurons (labeled HI arid H2), and two output neurons (labeled
01 and 02). In this section we will describe how the ten weights in the
neural network shown in Figure 6.1 can be modified during supervised
learning.
There are two aspects to training a neural network:
Table 6.1
The training data in Table 6.1 have a simple pattern: if an even num-
ber of input neuron values are equal to 1, then the first output neuron
is set to 1, otherwise the second output neuron is set to 1. The following
steps are followed to train the weights in this neural network to recog-
nize these input/output patterns (these steps are repeated for each row
in Table 6.1, and then this entire process is repeated several times):
1.Set the input neuron layer (Il, 12, and 13) activation values to the
input values in a row in Table 6.1 and calculate the activation values
for each neuron in the second layer (HI and H2).
2. Using the new activation values for neurons HI and H2, calculate
the activation values for the neurons in the output layer (01,02,
and 03).
3. Calculate the error at each output neuron as the calculated value
minus the target value from the second column in Table 6.1.
4. Based on the errors at the output neurons, modify the weights
connecting the hidden layer neurons with the output-layer neurons.
5. Calculate an estimated error at each hidden layer neuron based
on the hidden-to-output-Iayer weight values and the errors at the
output layer neurons.
6. Based on the estimated errors at the hidden layer neurons, modify
the weights connecting the input layer neurons and the hidden layer
neurons.
This same process works for neural networks without a hidden layer
(i.e., only input and output neurons that are directly connected) and
for networks with multiple hidden neuron layers.
The calculation for new activation values is simple. For example, in
Figure 6.1 the new activation value for the hidden layer neuron HI can
be calculated as
The weight W31 refers to the weight value for the connection be-
tween neuron 13 with neuron HI. In our implementation of a neural
network simulator in Section 6.3 we will use one-dimensional arrays
for the sum of products and activation values, and two-dimensional
arrays for the weights connecting any two neuron layers.
The weights connecting the hidden layer neurons and the output
layer neurons can be modified by
Delta-weight-H2-to-Ol = LearningRate * Error-at-Ol
* Calculated-activation-O 1
The backwards-propagated error for hidden layer neuron H2 can be
calculated as
Error-at-H2 = SigmoidP(SumOfProducts-for-H2)
* (Error-at-Ol * W-H2-to-Ol + Error-at-02 * W-H2-to-02)
Once all the errors have been estimated for every hidden layer neu-
ron, the delta-weight values for the input-Iayer-to-hidden-Iayer weights
can be calculated.
In order to make the learning process more efficient, we should re-
member the delta-weight values between training cycles (momentum
values). If the delta-weight values for a specific weight for two consec-
utive training cycles have the same sign so that the weight is changing
in the same "direction," then we increase the rate of learning for that
weight.
Listing 6.1
File: array. s
Public functions:
(define make-2D-array
(lambda (num-1 num-2)
(let «data-size (* num-1 num-2»
(return-value #f»
(set!
return-value
(make-vector (+ 2 data-size) 0.0»
96 6. NEURAL NETWORK LIBRARY
(define 2D-array-ref
(lambda (an-array index-1 index-2)
(let ((size-1 (vector-ref an-array 0»
(size-2 (vector-ref an-array 1»)
(if (or
« index-1 0)
(>= index-1 size-1»
(begin
(display "illegal first index for 2D-array:")
(display index-1)
(newline)
0)
(if (or
« index-2 0)
(>= index-2 size-2»
(begin
(display "illegal first index for 2D-array:")
(display index-2)
(newline)
0)
;; indices are OK, proceed:
(vector-ref
an-array
(+
2
index-2
(* index-1 size-2»»»»
(define 2D-array-set!
(lambda (an-array index-1 index-2 new-value)
(let ((size-1 (vector-ref an-array 0»
(size-2 (vector-ref an-array 1»)
(if (or
« index-1 0)
(>= index-1 size-1»
(begin
(display "illegal first index for 2D-array:")
(display index-1)
(newline)
0)
(if (or
« index-2 0)
(>= index-2 size-2»
(begin
(display "illegal first index for 2D-array:")
6.3. IMPLEMENTATION OF A NEURAL NETWORK LIBRARY 97
(display index-2)
(newline)
0)
;; indices are OK, proceed:
(let ((old-value
(vector-ref
an-array
(+
2
index-2
(* index-l size-2)))))
(vector-set!
an-array
(+
2
index-2
(* index-l size-2))
new-value)
old-value))))))
(define 2D-array-Iength
(lambda (a-2D-array dimension)
(vector-ref a-2D-array dimension)))
Test code:
(define a)
(set! a (make-2D-array 3 4))
(pp a)
(define test
(lambda 0
(do ((i 0 (+ i 1)))
((equal? i 3))
(do ((j 0 (+ j 1)))
((equal? j 4))
(2D-array-set! a i j (+ i (* j 1000)))))
(do ((i 0 (+ i 1)))
((equal? i 3))
(do ((j 0 (+ j 1)))
((equal? j 4))
(display "a [")
(display i)
(display :OJ [")
(display j)
(display "] = ")
(display (2D-array-ref a i j))
(newline)))))
98 6. NEURAL NETWORK LIBRARY
(test)
(2D-array-ref a 4 5)
(2D-array-length a 0)
(2D-array-length a 1)
Table 6.2
20 array functions vector functions
make-2D-array make-vector
2D-array-ref vector-ref
2D-array-set! vector-set!
2D-array-Iength vector-length
learning with function DeltaLearn, and use the trained neural network
object with function DeltaRecall.
After a neural network is trained you can use function Writ-
eDeltaNetwork to save the network data to a disk file. The function
ReadDeltaNetwork reads a network data file from disk and generates a
new network object that can be used with function DeltaRecall in other
programs.
Listing 6.2
File NN.s
MODULE DEPENDENCIES:
(NewDeltaNetwork sizeList)
Args: sizeList list of sizes of slabs. This also defines
the number of slabs in the network.
(e. g. , ' (10 5 4) ==> a 3-slab network
with 10 input neurons, 5 hidden
neurons, and 4 output neurons).
(define frandom
(lambda (lower upper)
(+ lower
(random (- upper lower)))))
(define NewDeltaNetwork
(lambda (sizeList)
(let «numLayers (length sizeList))
(w-list '0) weights
(dw-list '0) delta weights
(old-dw-list '()) old delta weights for
momentum terms
(a-list' 0) activation values
(s-list '0) sum of products
(d-list '0)) back propagated deltas
(set! a-list
(map
(lambda (size) (make-vector size 0.0))
sizeList))
(set! s-list
(map
(lambda (size) (make-vector size 0.0))
(cdr sizeList)))
(set! d-list
(map
(lambda (size) (make-vector size 0.0))
(cdr sizeList)))
6.3. IMPLEMENTATION OF A NEURAL NETWORK LIBRARY 103
(do «i 0 (+ i 1»)
«equal? i (- numLayers 1»)
(set!
w-list
(cons
(list
(list-ref sizeList i)
(list-ref sizeList (+ i 1»)
w-list) »
(set! w-list
(map
(lambda (size)
(make-2D-array (car size) (cadr size»)
(reverse w-list»)
(do «i 0 (+ i 1»)
«equal? i (- numLayers 1»)
(set!
dw-list
(cons
(list (list-ref sizeList i)
(list-ref sizeList (+ i 1») dw-list»)
(set! dw-list
(map
(lambda (size)
(make-2D-array (car size) (cadr size»)
(reverse dw-list»)
(do «i 0 (+ i 1»)
«equal? i (- numLayers 1»)
(set!
old-dw-list
(cons
(list (list-ref sizeList i)
(list-ref sizeList (+ i 1») old-dw-list»)
(set! old-dw-list
(map
(lambda (size)
(make-2D-array (car size) (cadr size»)
104 6. NEURAL NETWORK LIBRARY
(reverse old-dw-list)))
(map
(lambda (x)
(let «num (vector-length x)))
(do «n 0 (+ n 1)))
«equal? n num))
(vector-set! x n (frandom 0.01 0.1)))))
a-list)
(map
(lambda (x)
(let «numI (2D-array-Iength x 0))
(numJ (2D-array-Iength xl)))
(do «j 0 (+ j 1)))
«equal? j numJ))
(do «i 0 (+ i 1)))
«equal? i numI))
(2D-array-set! x i j (frandom -0.5 0.5))))))
w-list)
(list numLayers sizeList a-list s-list w-list dw-list
d-list old-dw-list alpha beta))))
(if *delta-rule-debug-flag*
(display (list "Current targets:" targetOutputs)))
(vector-ref jDeltaVector j)
(*
delta
(dSigmoid (vector-ref sumOfProductsArray j))))))
«equal? i iDimension))
(2D-array-set!
weightArray i j
(+ (2D-array-ref weightArray i j)
(*
alpha
(2D-array-ref deltaWeightArray i j))
(*
beta
(2D-array-ref oldDeltaWeightArray i j))))
save current delta weights for next
momentum term:
(2D-array-set!
oldDeltaWeightArray 1 J
(2D-array-ref deltaWeightArray i j))))
(set! iDimension jDimension)))
•• return the average error of the output neurons:
(/ outputError jDimension)))
(define DeltaRecall
(lambda (net List inputs)
(let «nLayers (car netList))
(sizeList (cadr netList))
(activationList (caddr netList))
(weightList (cadr (cdddr netList)))
(iDimension 'C))
(jDimension 'C))
(iActivationVector 'C))
(jActivationVector .())
(n • 0)
(weightArray .())
(returnList '0)
(sum '0))
get the size of the input slab:
(set! iDimension (car sizeList))
; get array of input activations:
(set! iActivationVector (car activationList))
; copy training inputs to input slab:
(do «i 0 (+ i 1)))
«equal? i iDimension))
(vector-set! iActivationVector i (list-ref inputs i)))
update layer j to layer i
6.3. IMPLEMENTATION OF A NEURAL NETWORK LIBRARY 111
(define plotActivations
(lambda (title x y data dmin dmax)
(let «size (vector-length data))
(ypos 0) (xpos x))
(plot-string x (- y 60) title)
(do «i 0 (+ i 1)))
«equal? i size))
(if « size 20)
112 6. NEURAL NETWORK LIBRARY
(begin
(set! ypos y)
(set! xpos (+ x (* i 29))))
(if « i (/ size 2))
(begin
(set! ypos (- y 7))
(set! xpos (+ x (* i 29))))
(begin
(set! ypos (+ y 2))
(set! xpos (+ x (* (- i (/ size 2)) 29))))))
(plot-fill-rect-gray-scale
(truncate xpos)
(truncate ypos)
28 28
(truncate
(*
U
(- (vector-ref data i) dmin)
(- dmax dmin))
255)))))))
(define plotWeights
(lambda (title x y data dmin dmax deltaWeights)
(let «Xsize (2D-array-length data 0))
(YSize (2D-array-length data 1)))
; don't try to plot very large weight sets:
(if « (* Xsize Ysize) 200)
(begin
(plot-string (+ x 20) (- Y 60) title)
(do «i 0 (+ i 1)))
«equal? i xSize))
(do «j 0 (+ j 1)))
«equal? j Ysize))
(plot-fill-rect-gray-scale
(+ x (* i 29)) (+ y (* j 29)) 28 28
(truncate
(*
U
(- (2D-array-ref data i j) dmin)
(- dmax dmin)) 255)))
(plot-fill-rect-gray-scale
(+ (* Xsize 28) 30 x (* i 29))
(+ y (* j 29))
28 28
(truncate
(*
U
(- (2D-array-ref deltaWeights i j) -.05)
(- .05 -.05))
6.3. IMPLEMENTATION OF A NEURAL NETWORK LIBRARY 113
255))))))))))
(define DeltaPlot
(lambda (net List)
(let «nLayers (car netList))
(sizeList (cadr netList))
(activationList (caddr netList))
(sumOfProductsList (car (cdddr netList)))
(weightList (cadr (cdddr netList)))
(deltaWeightList (caddr (cdddr netList)))
(minScale -0.3)
(maxScale 0.3)
(n 0)
(y-start 0))
(plot-string 100 960 "Delta Network")
(set! y-start 100)
(plotActivations "slabl"
100
y-start
(list-ref ActivationList 0)
-0.5
0.5)
(do «n-l 0 (+ n-l 1)))
«equal? n-l (- nLayers 1)))
(set! n (+ n-l 1))
(if (equal? n (- nLayers 1))
(begin
(set! minScale -0.2)
(set! maxScale 0.2))) ; scale up output display
(plotActivations
(list-ref '("slab1" "slab2" "slab3" "slab4" "slab5") n)
100 ; x location for subplot
(+ y-start (* n 400)) ; y location for subplot
; data to plot as gray scale:
(list-ref ActivationList n)
minScale
maxScale))
(if « nLayers 4)
(begin
(set! y-start -140)
(do «n 0 (+ n 1)))
«equal? n (- nLayers 1)))
(set! y-start (+ y-start 310))
(plot-string 100 y-start "Weights and Delta Weights")
(set! y-start (+ y-start 90))
(plotWeights
(list-ref
'("slabl -> slab2" "slab2 -> slab3"
"slab3 -> slab4")
114 6. NEURAL NETWORK LIBRARY
n)
100 y-start ; x,y position of subplot
(list-ref WeightList n)
-1.0 1.0
(list-ref deltaWeightList n))))))))
(define Sigmoid
(lambda (x)
(/ 1.0 (+ 1.0 (exp (- x))))))
(define dSigmoid
(lambda (x)
(let ((temp (Sigmoid x)))
(* temp (- 1.0 temp)))))
(define WriteDeltaNetwork
(lambda (fileName netList)
(let ((fileStream (open-output-file fileName))
(nLayers (car netList))
(sizeList (cadr netList))
(activationList (caddr netList))
(weightList (cadr (cdddr netList)))
(deltaWeightList (caddr (cdddr netList)))
(oldDeltaWeightList (car (cddddr (cdddr netList))))
(alpha (cadr (cddddr (cdddr netList))))
(beta (caddr (cddddr (cdddr netList)))))
(do «n 0 (+ n 1)))
«equal? n (- nLayers 1)))
(let «w (list-ref oldDeltaWeightList n)))
(do «i 0 (+ i 1)))
(equal? i (2D-array-length w 0)))
(do «(j 0 (+ j 1)))
(equal? j (2D-array-length wi)))
(write (2D-array-ref w i j) fileStream)
(newline fileStream)))))
(close-output-port fileStream))))
(define ReadDeltaNetwork
(lambda (fileName)
(let «fileStream (open-input-file fileName))
(numLayers '0)
(sizeList '0)
(a-list '0)
(s-list '0)
(w-list '0)
(dw-list '0)
(old-dw-list '())
(d-list '0)
(alpha 0.0)
(beta 0.0))
6.3. IMPLEMENTATION OF A NEURAL NETWORK LIBRARY 117
; Read in header:
(set! a-list
(map
(lambda (size) (make-vector size 0.0»
sizeList»
(set! s-list
(map
(lambda (size) (make-vector size 0.0»
(cdr sizeList»)
(set! d-list
(map
(lambda (size) (make-ve~tor size 0.0»
(cdr sizeList»)
(do ((i a (+ i 1»)
((equal? i (- numLayers 1»)
(set!
w-list
(cons
(list (list-ref sizeList i)
(list-ref sizeList (+ i 1») w-list»)
(set! w-list
(map
(lambda (size) (make-2D-array (car size) (cadr size»)
(reverse w-list»)
(list-ref sizeList i)
(list-ref sizeList (+ i 1»)
old-dY-list»)
(set! dy-list
(map
(lambda (size) (make-2D-array (car size) (cadr size»)
(reverse dY-list»)
(set! old-dY-list
(map
(lambda (size) (make-2D-array (car size) (cadr size»)
(reverse old-dY-list»)
; Read in activations:
(do «n 0 (+ n 1»)
«equal? n (- numLayers 1»)
(do «i 0 (+ i 1)))
«equal? i (list-ref sizeList n»)
(vector-set!
(list-ref a-list n) i (read fileStream»)))
; Read in Yeights:
(do «n 0 (+ n 1)))
«equal? n (- numLayers 1)))
(let «y (list-ref Y-list n)))
(do «i 0 (+ i 1»))
«equal? i (2D-array-length yO»)
(do «j 0 (+ j 1))
«equal? j (2D-array-length Y 1))
(2D-array-set! Y i j (read fileStream»»»
(do «n 0 (+ n 1))
«equal? n (- numLayers 1)))
(let «y (list-ref dY-list n»)
(do «i 0 (+ i 1)))
«equal? i (2D-array-length yO»)
(do «j 0 (+ j 1))
«equal? j (2D-array-length Y 1»)
(2D-array-set! Y i j (read fileStream»))))
6.3. IMPLEMENTATION OF A NEURAL NETWORK LIBRARY 119
(close-input-port fileStream)
(list numLayers sizeList a-list s-list
w-list dw-list d-list
old-dw-list alpha beta))))
(display ii)
(display " RMS error II)
(display RMSerror)
(newline))))
old-network))
(define save-net)
(set! save-net (test2))
(pp save-net)
(test2 save-net) ;; try restarting the learning process
(test3)
temp
'«(1 0 0 0 0) (0 1 000»
«0 1 0 0 0) (0 0 1 0 0»
«0 0 1 0 0) (0 0 0 1 0»
«0 0 0 1 0) (0 0 0 0 1»
«0 0 0 0 1) (1 0 0 0 0»)
plotFlag»
;; print out every 5 cycles:
(if (equal? (modulo ii 10) 0)
(begin
(set! plotFlag #t)
(display " .... training cycle \#")
(display ii)
(display " RMS error = II)
(display RMSerror)
(newline»
(set! plotFlag #f»»
temp» ;; return neural network object for use in restarting
(define save-net)
(set! save-net (test2»
,. (WriteDeltaNetwork "test.net" save-net)
(set! save-net (ReadDeltaNetwork "test.net"»
(pp save-net)
(test2 save-net) ;; try restarting the learning process
The variable defaultEldaLlst in Listing 6.2 sets the default values for
the neural network learning rates. Each time function DeltaLearn is
called, the weights connecting each neuron layer are modified to re-
duce the error for a set of training data. The learning rate values in the
variable defaultEldaLlst specify how fast each set of weights is modified.
From experience I have learned that it is better to use relatively high
learning rates for weights connected to the input neurons, and lower
learning rates for weights connected to the output neurons. There are
six values in the list assigned to the variable defaultEldaLlst: the first
value is the learning rate for the weights connecting the input neurons
to the first hidden neuron layer; the second value specifies the learning
rate for the weights connecting the first hidden layer to the second hid-
den layer (or the output neuron layer if there is only one hidden neuron
layer defined in the call to function NewDeltaNetwork), etc.
6.3. IMPLEMENTATION OF A NEURAL NETWORK LIBRARY 123
• Copy the target input values to the activation values of the neurons
in the input layer
• Propagate the input layer activation energies to the next layer by
scaling the activation values by the appropriate weight value. Re-
member that each neuron in a layer is connected to each neuron
in the next layer by a different weight value. Continue propagating
activation energies from layer to layer until this process stops after
the new activation values of the output layer neurons have been
calculated
• Calculate the error for each output neuron by comparing the prop-
agated value of each neuron to the target value from the training
data. The derivative of the Sigmoid function is used to scale the sum
of products before calculating the delta weight values
• Calculate a back-propagated error to all hidden layers
• Use the back-propagated errors to calculate the delta weights for
the entire network. The delta weights for the current training data
case are summed with the delta weights for all other training cases
After this process is repeated for all training cases, all weights in the
network are updated by adding to each weight its corresponding delta
weight value.
The function DeltaRecall is fairly simple. A trained network is used
to calculate the output neuron values obtained by setting the input
layer neuron activation values to a specified set of values and using the
following steps to calculate the output layer neuron activation values:
• Copy the target input values to the activation values of the neurons
in the input layer
• Propagate the input layer activation energies to the next layer by
scaling the activation values by the appropriate weight value. Con-
tinue propagating activation energies from layer to layer until this
6.3. IMPLEMENTATION OF A NEURAL NETWORK LIBRARY 125
process stops after the new activation values of the output layer
neurons have been calculated
FIGURE 6.2. Listener window after loading source files (ARRAY.S, GRAPH.S, and NN.S) and
while executing the function test-gr. Figures 6.3 and 6.4 show the MIT Scheme graphics
windows while function test-gr is running.
126 6. NEURAL NETWORK LIBRARY
Delta Network
slab3
slab2 -) slab3
Weights and Delta Weights
slab2
• •
slabl -) slab2
Weights and Delta Weights
slabl
FIGURE 6.3. MIT Scheme graphics window shown after calling function DeltaLearn twenty
times. The output neuron layer is not yet producing the desired output pattern. The delta
weight arrays contain large values because the backwards-propagated errors are large, so the
corrections to the weights are large.
Listing 6.3
number of layers
2
number of neurons in each layer
(2 2)
;; activation values
-3.7871714036296716e-2
.9700991925062835
;; weights
-1.92433999441247
1.9013980514965736
1.0142383744643486
-1.0122338425505022
6.3. IMPLEMENTATION OF A NEURAL NETWORK LIBRARY 127
.,
Delta Network
I I II II
slab3
slab2 -) slab3
Weights and Delta Weights
slab2
slabl -) slab2
Weights and Delta Weights
slabl
FIGURE 6.4. MIT Scheme graphics window shown after calling function DeltaLearn four hun-
dred times. The output neuron layer is starting to show the desired output patterns. The delta
weight arrays have small values, since the backwards-propagated errors are now small, and
so the corrections to the weight arrays are small.
;; delta weights
-8. 573423663385411e-3
8.849681510894006e-3
1.5986012182692088e-2
-1.5779824336095313e-2
;; old delta weights
-8. 573423663385411e-3
8. 849681510894006e-3
1.5986012182692088e-2
-1.5779824336095313e-2
;; alpha
.2
;; beta
.8
128 6. NEURAL NETWORK LIBRARY
Listing 6.4
File: hand. s
(define example-t
(list
xx
x x
xx
x x
x x
xx II))
might encode an "8". This example is set
to work with 6x6 bit images of characters,
but you can easily change this to finer
resolution by changing the two constants
P-WIOTH and P-HEIGHT, and changing the training
and testing data.
(define P-WIOTH 6)
(define P-HEIGHT 6)
(set! training-data
6.4. EXAMPLE APPLICATION: CHARACTER RECOGNITION 129
(list
(list
(list o0 0) 0 in binary
(list
xx
" x X
"x x"
"x x"
" x x
xx "))
(list
(list 0 0 1) 1 in binary
(list
x
x
x
x
x
X "))
(list
(list o0 1) 1 in binary
(list
x
xx
x
x
x
xxx "))
(list
(list 0 1 0) 2 in binary
(list
" xxxx "
"x x"
x
x
X
" XXXXX"))
(list
(list 0 1 0) 2 in binary
(list
" xxx "
" X x"
X "
X "
X
"XXXXXX"))
(list
(list o1 1) 3 in binary
(list
130 6. NEURAL NETWORK liBRARY
" xxx
"x x
X
X
X X "
"xxxx "))
(list
(list 0 1 1) 3 in binary
(list
II xxx
"x X
"X
II xx "
"x x "
"XXXX "))
(list
(list 1 0 0) 4 in binary
(list
"x "
II
"X X
"x X
"XXXXXX It
" X
II X "))
(list
(list 1 o 0) 4 in binary
(list
x "
" xx
x x
"XXXXXX"
x "
II X II))))
(define convert-training-data
(lambda (input)
(let «output-pattern (car input))
(str-list (cadr input))
(input-list '()))
(do «a-str str-list (cdr a-str)))
«null? a-str) )
(do «i 0 (+ i 1)))
«equal? i P-WIDTH))
(if (equal? (string-ref (car a-str) i) #\
(set! input-list (cons 0 input-list))
(set! input-list (cons 1 input-list)))))
(list (reverse input-list) output-pattern))))
test:
6.4. EXAMPLE APPLICATION: CHARACTER RECOGNITION 131
(define train
(lambda 0
;; create the training data:
(let «t-data (map convert-training-data training-data))
(a-network (NewDeltaNetwork '(36 4 3)))
(error 0.0))
(do «i 0 (+ i 1)))
«equal? i 500))
(set!
error
(DeltaLearn a-network t-data))
;; print out every 4 cycles:
(if (equal? (modulo i 4) 0)
(begin
(display " .... training cycle \#")
(display i)
(display II error = II)
(display error)
(newline))))
a-network)))
(define test-net)
(set! test-net (train))
(define test-data
(list
(list
xx
II
X X
"X x"
"X x"
II II
X X
xx II)
(list
x
II
X
II II
X
X
X
II
X II)
(list
x II
xx
II
X
X
II
X
xxx II)
132 6. NEURAL NETWORK LIBRARY
(list
II xxxx II
"x XII
x
x
x
" xxxxx")
(list
xxx
X XU
x
x
x
"xxxxxx")
(list
IIxxx
II X X
x
x
x x
"xxxx II)
(list
" xxx
"x x
"x
" xx
"x X
t1
xxxx II)
(list
"x
"x x
"x x
II XXXXXX II
x
x II)
(list
x
xx
x x
"xxxxxx"
x
x ')))
(define convert-test-data
(lambda (input)
(let «input-list 'C)))
(do «a-str input (cdr a-str)))
«null? a-str))
(do «i 0 (+ i 1)))
6.4. EXAMPLE APPLICATION: CHARACTER RECOGNITION 133
((equal? i P-WIDTH»
(if (equal? (string-ref (car a-str) i) #\
(set! input-list (cons 0 input-list»
(set! input-list (cons 1 input-list»»)
(reverse input-list»»
test:
; (pp (convert-test-data (cadr test-data»)
(define test
(lambda (a-network)
;; create the training data:
(let ((data (map convert-test-data test-data»
(outputs #f»
(define test-helper
(lambda (inputs)
(set! outputs (DeltaRecall a-network inputs»
(let ((count 0»
(do ((i 0 (+ i 1»)
((equal? i P-HEIGHT»
(newline)
(do ((j 0 (+ j 1))
((equal? j P-WIDTH»
(display (list-ref inputs count»
(set! count (+ count 1»»)
(newline)
(display outputs)
(newline» )
(map test-helper data»»
; (test test-net)
You can change the grid size for representing characters by changing
the values of the variables P-WIDTH and P-HEIGHT in Listing 6.4. The
list assigned to the variable training-data is a list of lists, each sub-list
containing
• a list of target output activation values
• a list of P-HEIGHT character strings, each of length P-WIDTH (a blank
character represents an input activation value of zero, while a non-
blank character represents an input activation value of one)
each layer, and uses the function DeltaLearn to train the network object.
The variable test-data is assigned a list of test data sets that will be
converted from numeric values to input neuron activation values. The
function convert-test-data converts the string data in variable test-data
to numeric data. The function test applies a trained network object to
these numeric test data using the function DeltaRecal1.
Listing 6.5 shows both the training and recall of roughly drawn
characters.
Listing 6.5
> (load "array")
;Loading "array.com" -- done
;Value: test
100000
100100
100100
111111
000100
000100
(.9629369130119926 2.3591652822295566e-4 .07958494918278963)
011100
100010
100000
011000
100010
111100
(1.5008819602605697e-9 .9999999999999056 .9660293993948157)
011100
100010
010000
001000
010010
111100
(1.495301151263447e-9 .9999999999999052 .9659197873301495)
001110
010001
000010
000100
010000
138 6. NEURAL NETWORK LIBRARY
111111
(1.4278818438326796e-5 .982628810308463 .04224408466874724)
011110
100001
000010
000100
001000
011111
(1.427098181190951ge-5 .9826606308520179 .04226754800184698)
000100
001100
000100
000100
000100
001110
(1.1912104070509307e-8 4.190293098904157e-16 .9678453383799069)
000100
000100
000100
000100
000100
000100
(2.044963654211298e-8 3.419161877936915e-16 .9721188341697696)
001100
010010
100001
100001
010010
001100
(1.6645816482122926e-2 3.0009938718789622e-3 1.1384988204427855e-2)
Remember that the output neuron values printed in Listing 6.5 rep-
resent the binary encoding of the first five non-negative integers, as seen
in Table 6.3.
Table 6.3
Decimal number Binary encoding
o 000
1 001
2 010
3 011
4 100
6.4. EXAMPLE APPLICATION: CHARACTER RECOGNITION 139
Listing 6.6
;; original pattern:
(list
" XXXX It
"x x"
" x "
x "
x
" xxxxx")
x
x "
x
"x x xx")
CHAPTER 7
COMPLEX DATA
STRUCTURES
7.2.1. ANALYSIS
7.2.2. DESIGN
These data are easily implemented using a Scheme vector with six
elements.
We also want a mapping of verbs to CD actions. We can use a Scheme
list to hold verb-CD action pairs. Another simple list will contain names
of objects.
7.2.3. IMPLEMENTATION
We will start the implementation of our parser from the "bottom up,"
starting with:
144 7. COMPLEX DATA STRUCTURES
Listing 7.1
(define (make-frame) (make-vector 6 #f))
Listing 7.2
(define verb-list
'((gave ATRANS)
(give ATRANS)
(received ATRANS)))
We can map a verb stored in the Scheme variable word into its CD
equivalent (assume that the Scheme variable current-frame contains a
CD frame data object); this is shown in Listing 7.3.
Listing 7.3
(let ((action-item (assoc word verb-list)))
(if (not (null? action-item))
(let ((verb (list-ref action-item 0))
7.2. EXAMPLE APPLICATION: NATURAL LANGUAGE PROCESSING 145
Listing 7.4
(define verb-list
'«gave ATRANS #f)
(give ATRANS #f)
(received ATRANS #t»)
Function parse-verbs in Listing 7.5 shows how the "reverse" tags are
used. Listing 7.5 shows the contents offile CD.S.
Listing 7.5
File: CD.S
actor
action
object
time (units of hours)
place
recipient
(define make-frame
(lambda 0
(make-vector 6 #f»)
(define actor-set!
(lambda (frame actor)
(vector-set! frame 0 actor»)
146 7. COMPLEX DATA STRUCTURES
(define actor-ref
(lambda (frame)
(vector-ref frame 0)))
(define action-set!
(lambda (frame action)
(vector-set! frame 1 action)))
(define action-ref
(lambda (frame)
(vector-ref frame 1)))
(define object-set!
(lambda (frame object)
(vector-set! frame 2 object)))
(define object-ref
(lambda (frame)
(vector-ref frame 2)))
(define time-set!
(lambda (frame time)
(vector-set! frame 3 time)))
(define time-ref
(lambda (frame)
(vector-ref frame 3)))
(define place-set!
(lambda (frame place)
(vector-set! frame 4 place)))
(define place-ref
(lambda (frame)
(vector-ref frame 4)))
(define recipient-set!
(lambda (frame recipient)
(vector-set! frame 5 recipient)))
(define recipient-ref
(lambda (frame)
(vector-ref frame 5)))
english word
CD primative
actor <--> recipient reversal flag
;; Time:
(define parse-human-name
(lambda (current-frame rest-of-sentence)
(let «word (car rest-of-sentence)))
(if (member word human-list)
(if (equal? (actor-ref current-frame) #f)
(actor-set! current-frame word)
(if (equal? (recipient-ref current-frame) #f)
(recipient-set! current-frame word)))))))
(define parse-verbs
(lambda (current-frame rest-of-sentence)
(let «action-item (assoc (car rest-of-sentence) verb-list)))
(if (not (null? action-item))
(let «verb (list-ref action-item 0))
(cd-primitive (list-ref action-item 1))
(actor-recipient-reversal-flag (list-ref action-item 2)))
(action-set! current-frame cd-primitive)
(if actor-recipient-reversal-flag
(let «temp (actor-ref current-frame)))
(actor-set! current-frame (recipient-ref current-frame))
(recipient-set! current-frame temp))))))))
(define parse-nouns
(lambda (current-frame rest-of-sentence)
(let «word (car rest-of-sentence)))
(if (member word noun-list)
(if (equal? (object-ref current-frame) #f)
(object-set! current-frame word))))))
(define parse-time
(lambda (current-frame rest-of-sentence)
(let «time-item (assoc (car rest-of-sentence) time-list)))
(if (not (null? time-item))
(time-set! current-frame (cadr time-item))))
(if (> (length rest-of-sentence) 1)
148 7. COMPLEX DATA STRUCTURES
(let ((time-item
(assoc
(list (car rest-of-sentence) (cadr rest-of-sentence))
time-list)))
(if (not (null? time-item))
(time-set! current-frame (cadr time-item)))))))
(define parse-all
(lambda (current-frame rest-of-sentence)
(parse-human-name current-frame rest-of-sentence)
(parse-verbs current-frame rest-of-sentence)
(parse-nouns current-frame rest-of-sentence)
(parse-time current-frame rest-of-sentence)))
test code:
(define test
(lambda 0
(let ((f (make-frame)))
(do ((sen sentence (cdr sen)))
((null? sen))
(display "Sentence: II)
(display sen)
(newline)
(parse-all f sen))
(display "Parse: ")
(display f)
(newline))))
• actor-ref
• action-ref
• object-ref
• time-ref
7.2. EXAMPLE APPLICATION: NATURAL LANGUAGE PROCESSING 149
• place-ref
• recipient-ref
The function parse-all has two required arguments: a CD parser frame
object created with function make-frame and a list of words that are the
remainder of a sentence to parse. Function parse-all processes only the
first word passed in the second argument. It would be more convenient
in a production parser to process an entire sentence in a single func-
tion call. However, the parser shown in Listing 7.5 is useful only as an
example of building a natural language parser. For this intended use
function parse-all more conveniently parses a single word each time it
is called. The function test at the bottom of Listing 7.5 shows how to
iteratively call function parse-all for each word in a test sentence; the
current frame is printed after each word is processed. Function parse-all
calls the functions
• parse-human-name: can fill in either the actor or recipient slot
• parse-verbs: can fill in the verb slot, and can swap the actor and
object slots if the verb's reverse flag is set
• parse-nouns: can fill in the object slot
• parse-time: can fill in the time slot
Listing 7.6
> (load "cd. s")
;Loading "cd.s" -- done
;Value: test
> (test)
Sentence: (mark gave carol a book last week)
Parse: #(mark 0 0 0 0 0)
Sentence: (gave carol a book last week)
Parse: #(mark atrans () () () (»
Sentence: (carol a book last week)
Parse: #(mark atrans () () () carol)
Sentence: (a book last week)
Parse: #(mark atrans () () () carol)
Sentence: (book last week)
Parse: #(mark atrans book () () carol)
Sentence: (last week)
Parse: #(mark atrans book -168 () carol)
Sentence: (week)
Parse: #(mark atrans book -168 () carol)
;Value: #t
> {set! sentence '(Last week Carol received a book from Mark»
;Value 4: (mark gave carol a book last week)
> (test)
150 7. COMPLEX DATA STRUCTURES
In Listing 7.6 the CD parser produced the same output for the two
sentences "Mark gave Carol a book last week" and "Last week Carol
receiv.ed a book from Mark." The output from the CD parser is identical
for both sentences because both sentences have the same meaning.
CHAPTER 8
CHESS PLAYING PROGRAM
Computer chess programs have long been a testing ground for devel-
oping search techniques. Chess programs use some form of lookahead
(prediction of future moves) search combined with static evaluation
(judging the relative worth of a chess position without lookahead, that
is, by counting material worth of both black and white pieces and com-
paring the relative mobility of the black and white pieces). We will
develop a chess playing program in this chapter that relies mostly on
static evaluation using heuristic chess knowledge. The program will
usually give the impression of strong positional play, although it plays
a poor tactical game. The static evaluation function uses a two-ply (i.e.,
two half moves) lookahead to evaluate piece capture.
8. 1 ANALYSIS
Claude Shannon described two search techniques that he called type A
and type B. The type A search technique looked at all possible moves
from a given position. For example, there are usually about 40 possi-
ble (legal) chess moves from a typical board position; to look three
moves ahead three complete moves (one moves for each player for
each complete move) requires the examination of about 40(3*2), or
406 (4,096,000,000) board positions. Because of the impracticality of
deep searches using the type A strategy, Shannon predicted that high-
performance game playing programs would use the human-style type B
strategy, in which the program would not examine all possibilities, but
use heuristic rules derived from consultation with human chess experts
to reduce the number of moves searched from a given board position.
In this way a program would be able to examine only "good" moves,
without, it was hoped, overlooking too many strong moves. If a chess
program searches only six plausible moves per tum, then a three-move
152 8. CHESS PLAYING PROGRAM
8.2 DESIGN
The following seven parameters are considered in the static evalua-
tion of a given board position. The Chess program developed in this
chapter attempts to maximize a static evaluation score based on these
parameters.
1. Material (value of pieces left on the board)
2. Number of times pieces are guarded
3. Mobility of playing pieces
4. Control of central squares by pawns
5. Control of central squares by pieces
6. Control of squares adjacent to both kings
7. Attack on pieces by less valuable pieces
8.3 IMPLEMENTATION
The chess program developed in this chapter uses algebraic chess no-
tation for entering chess moves. Figure 8.1 shows the labeling of the
squares on a chess board using algebraic notation.
8.3. IMPLEMENTATION 153
A2 B2 C2 D2 E2F2 G2 H2
Al B 1 Cl DI ';·;·EI
.:"
FI Gl HI
. ';.., . :.;.; .
Figure 8.2 shows the indexing scheme for the chess board that is
used by the example program in this chapter. The square Al on the
chess board (using algebraic notation as seen in Figure 8.1) is at vector
index 22 in Figure 8.2. Similarly, the square H8, in the opposite corner
of the chess board in Figure 8.1, is at board vector index 99 in Figure
8.2. In Figure 8.2 all squares that lie on the chyss board are enclosed in
a rectangle; all other squares in the board vector have a numeric value
of 7, which is a flag indicating that the square is off of the chess board.
Listing 8.1 shows the file CHESS.S. In Listing 8.1 the variable *piece*
contains relative move values for all pieces except pawns. The variable
*index* is used to convert a piece type into a move index pointer in the
vector stored in *piece*. Table 8.1 shows the piece type indices.
154 8. CHESS PLAYING PROGRAM
110 111 112 113 114 115 116 117 118 119
100 101 102 103 104 105 106 107 108 109
90 91 92 93 94 95 96 97 98 99
80 81 82 83 84 85 86 87 88 89
70 71 72 73 74 75 76 77 78 79
60 61 62 63 64 65 66 67 68 69
50 51 52 53 54 55 56 57 58 59
40 41 42 43 44 45 46 47 48 49
30 31 32 33 34 35 36 37 38 39
20 21 22 23 24 25 26 27 28 29
10 11 12 13 14 15 16 17 18 19
o 1 234 56789
FIGURE 8.2. Chess board represented by a vector of length 120; 64 elements represent the
board squares, while the remaining elements of the vector contain the value 7, indicating that
the square is off of the board.
Table 8.1
Piece type Piece value in board vector
Pawn 1
Knight 2
Bishop 3
Rook 4
Queen 5
King 9
For example, if the square at index 24 (CIon the chess board) con-
tains a Bishop, the value of (vector-ref *index* 3) is equal to 10. The
move entries for a Bishop start at index 10 in the vector stored in *piece*
and end with a zero entry. The move entries for a Bishop are: -9 -11 9
11. The move entries for a rook are -1 1 10 -10. The move entries for a
Queen are 1 -1 10 -10 -9 -11 9 11. For Bishops, Rooks, and Queens
possible moves are calculated by taking each move entry and repeti-
tively adding it to the current board index until the piece runs off of
the edge of the board or collides with another piece. As seen in Listing
8.1, the move calculations for these three types of pieces is simple to
implement using these move tables. The moves for Kings and Knights
are similar, except that each move value is applied to the current piece
position only once. The move calculations for Pawns are complex. As
seen in function Mover in Listing 8.1, there is more code for handling
pawn moves than for all of the other piece types combined.
8.3. IMPLEMENTATION 155
The piece type values in Table 8.1 are shown as positive integers. The
black pieces on the Chess board (always played by the computer) are
stored as negative integers in the global variable *board*. The absolute
value of the piece type is used as an index into the *plece* move table.
The function goto calculates all possible moves for a chess piece on
a specified square. This function is used to calculate control arrays that
specify how many times each square on the chess board is controlled by
both the black and white pieces. The function posib calculates all possi-
ble moves from all squares on the chess board. The function InitChess is
used to set up the data for a new game. The function Mover calls function
posib to calculate all possible moves, then for each move calls function
value to rate the moves. Function Mover uses the global flag variables
*wking-moved*, *wrook1-moved*, *wrook2-moved*, *bklng-moved*, *brook1-
moved*, *brook2-moved* to determine whether a castle move is legal (a
castle move is legal if the king is not in check, the king has never moved,
the rook on the side to castle has never moved, the squares between the
king and rook are empty, and these empty squares are not controlled
by the opponent). If the global flag variable *debug* is not equal to #F,
then all possible moves are printed with their values. Function Mover
uses the non-standard MIT Scheme function sort to sort the moves for
printout when the debug flag is set. If you are not using MIT Scheme,
you must either supply your own sort function, or remove the call to
sort, printing the moves in unsorted order for debug purposes. This
program also uses the MIT Scheme random function as a tie-breaker
for moves with the same value. Most Scheme functions have a random
number generating function.
Listing 8.1
(define PAWN 1)
(define KNIGHT 2)
(define BISHOP 3)
(define ROOK 4)
(define QUEEN 5)
8.3. IMPLEMENTATION 157
(define KING 9)
92 93 94 95 96 97 98 99 BR BN BB BQ BK BB BN BR
82 83 84 85 86 87 88 89 BP BP BP BP BP BP BP BP
72 73 74 75 76 77 78 79 X X X X
62 63 64 65 66 67 68 69 X X X X
52 53 54 55 56 57 58 59 X X X X
42 43 45 45 46 47 48 49 X X X X
32 33 34 35 36 37 38 39 WP WP WP WP WP WP WP WP
22 23 24 25 26 27 28 29 WR WN WB WQ WK WB WN WR
(define initChess
(lambda 0
(set! *wking-moved* #f)
(set! *wrook1-moved* #f)
(set! *wrook2-moved* #f)
(set! *bking-moved* #f)
(set! *brook1-moved* #f)
(set! *brook2-moved* #f)
(set! *move-num* 0)
(do «i 0 (+ i 1)))
«equal? i 120))
(vector-set!
*board* i
(vector-ref
(vector
7 7 7 7 7 7 7 7 777 ; empty squares around outside of board
7 7 7 7 7 7 7 7 7 7 7 ; empty squares around outside of board
423 5 9 3 247 7 white pieces
158 8. CHESS PLAYING PROGRAM
1 1 1 1 1 1 1 1 7 7 white pawns
o0 0 0 0 0 0 0 7 7 8 blank squares and 2 empty squares
o0 0 0 0 0 0 0 7 7 8 blank squares and 2 empty squares
o0 0 0 0 0 0 0 7 7 8 blank squares and 2 empty squares
o0 0 0 0 0 0 0 7 7 8 blank squares and 2 empty squares
-1 -1 -1 -1 -1 -1 -1 -1 7 7 ; black pawns
-4 -2 -3 -5 -9 -3 -2 -4 7 7 ; black pieces
7 7 7 7 7 7 7 7 7 7 7 7 7 7 empty squares
7 7 7 7 7 7 7 7 7 7 7) empty squares
i)))))
(define neq
(lambda (a b)
(not (equal? a b))))
(define printPiece
(lambda (piece blackSquare?)
(if (zero? piece)
(if blackSquare? (display II X II) (display II • II))
(begin
(if « piece 0)
(display II B")
(display II W"))
(display (list-ref
'(llplt tlNlt "B" IIRII "Q'I ttll '111 "'1 I'K'I)
(- (abs piece) 1)))))))
(define printBoard
(lambda 0
(let «startColumnList '(92 82 72 62 52 42 32 22)))
(do «i 0 (+ i 1)))
«equal? i 8))
(newline)
(do «j 0 (+ j 1)))
«equal? j 8))
(let* «boardPos (+ (list-ref startColumnList i) j))
(blackSquare?
(member boardPos
'(22 24 26 28 33 35 37 39 42 44 46 48
8.3. 1M PLEM ENTATION 159
53 55 57 59 62 64 66 68 73 75 77 79
82 84 86 88 93 95 97 99))))
(printPiece
(vector-ref *board* boardPos)
blackSquare?))))
(newline))))
(define posib
(lambda 0
(let «returnedMoveList 'C)))
(do «ii 0 (+ ii 1)))
«equal? ii 78))
(let* «i (+ ii 22))
(boardVal (vector-ref *board* i)))
(if « boardVal 0) ;; valid piece to move?
;; collect all squares to which piece on
;; square i can move to:
(let «move-list (goto i #t))
(aMove #f))
(do «m move-list (cdr m)))
«null? m))
(set! aMove (car m))
(if (and
;; check for either an empty space
;; or opponent piece:
(>= (vector-ref *board* (cadr aMove)) 0)
(neq 7 (vector-ref *board* (cadr aMove))))
(set! returnedMoveList
(cons aMove returnedMoveList))))))))
(if (and
(not *bking-moved*)
(not *brook2-moved*)
(equal? (vector-ref *board* 97) 0)
(equal? (vector-ref *board* 98) 0)
« (vector-ref *human-square-control* 96) 1)
« (vector-ref *human-square-control* 97) 1)
« (vector-ref *human-square-control* 98) 1))
(set! returnedMoveList (cons '00 returnedMoveList)))
(if (and
(not *bking-moved*)
(not *brook1-moved*)
(equal? (vector-ref *board* 95) 0)
(equal? (vector-ref *board* 94) 0)
(equal? (vector-ref *board* 93) 0)
« (vector-ref *human-square-control* 96) 1)
160 8. CHESS PLAYING PROGRAM
(define goto
(lambda (squareNum captureFlag)
(let* ((piece (vector-ref *board* squareNum»
(retList '0)
(ivaI '(8 03»
(pieceType (abs piece»
(piece Index 0)
(pieceMovementIndex 0»
(set! pieceIndex (list-ref *index* pieceType»
(set! pieceMovementIndex (list-ref *piece* pieceIndex»
(if
(not (equal? piece 0» make sure that there is
; a piece on square
(case pieceType
((1) ; PAWN
(let ((sideIndex (if « piece 0) -1 +1»)
(do ((cd '(11 9) (cdr cd»)
((null? cd»
;; check for diagonal captures:
(let ((captureDelta (car cd»)
(let*
((movementOffsetInBoard
(+ squareNum (* sideIndex captureDelta»)
(targetPiece
(vector-ref *board* movementOffsetInBoard»)
(if (or
(and
«= targetPiece -1) ; enemy piece --> legal capture
(neq targetPiece 7) ; not off of board
(> piece 0» computer piece moving
(and
(>= targetPiece 1) ; computer piece
(neq targetPiece 7) ; not off of board
« piece 0») player piece moving
(set! retList (cons
(list
squareNum
(+
squareNum
8.3. IMPLEMENTATION 161
(* sideIndex captureDelta)))
retList))))))
;; Check for initial pawn move of two squares forward:
(let* «movementOffsetlnBoard
(+ squareNum (* sideIndex 20))))
(if (and
captureFlag
; move-to sq empty?:
(equal? (vector-ref *board* movementOffsetInBoard) 0)
(equal? (truncate (/ squareNum 10))
(if « piece 0) 8 3))
(if « piece 0)
(equal? (vector-ref *board* (- squareNum 10)) 0)
(equal? (vector-ref *board* (+ squareNum 10)) 0)))
(set!
ret List
(cons (list squareNum
(+ squareNum (* sideIndex 20)))
retList))))
(let*
«movementOffsetInBoard
(+ squareNum (* sideIndex 10))))
(if (and
captureFlag
(equal?
; move-to sq empty?
(vector-ref *board* movementOffsetInBoard) 0))
(set!
retList
(cons (list squareNum
(+ squareNum (* sideIndex 10)))
retList))))))
; was a capture,
(set! keep-going If»~ so break out of the inner loop.
(if (and
(equal? pieceType 1)
(equal? (truncate (/ squareNum 10» 3»
(set! keep-going If»~ ;; break out of inner loop
(if (or
(equal? pieceType KNIGHT)
(equal? pieceType KING»
(set! keep-going If»~ ;; break out of inner loop
(set! nextToSquare
(+ nextToSquare
(list-ref *piece* movementTableIndex»»
(set! movementTableIndex (+ movementTableIndex 1»
Lack of further move segments is indicated
;; by a zero in the next element of the move
;; index table:
(if (equal? (list-ref *piece* movementTableIndex) 0)
(set! keep-going-outer-loop If»~ ;; no more move segments
(set! nextToSquare
(+ squareNum
(list-ref *piece*,movementTableIndex»»»»
(if (equal? retList It) '0 retList»»
(define value
(lambda (toSq)
(let ((retVal 0.0»
(do ((i 0 (+ i 1»)
((equal? i 120»
(vector-set! *computer-square-control* i 0)
(vector-set! *human-square-control* i 0»
;; Calculate the number of times the computer's
;; pieces control each board square:
(do ((ii 0 (+ ii 1»)
((equal? ii 78»
(let ((i (+ ii 22»)
(if « (vector-ref *board* i) 0) computer piece
(let ((moveList (goto i If»~
(pawnFudge 0)
(move '0»
(do ((m moveList (cdr m»)
((null? m»
(set! move (car m»
(if (equal? (abs (vector-ref *board* (car move») 1)
(set! pawnFudge 1.15)
8.3. IMPLEMENTATION 163
(set! pawnFudge 1»
(vector-set!
*computer-square-control*
(cadr move)
(+
(vector-ref
*computer-square-control*
(cadr move»
pawnFudge»»»)
ret Val
(+ retVal
(* 2
(min ; limit value of attacked piece to 3
3 ; points since this side to move next
(list-ref
*value*
(abs (vector-ref *board* i»»»»
;; King attacked:
(if (and
(> (vector-ref *human-square-control* i) 0)
(equal? (vector-ref *board* i) -9»
(set! retVal (- retVal 5000»)
Queen attacked:
(if (and
(> (vector-ref *human-square-control* i) 0)
(equal? (vector-ref *board* i) -5»
(set! ret Val (- retVal 50»»»)
(do ((sq
'(44 45 46 47 54 55 56 57 64 65 66 67 74 75 76 77)
(cdr sq»)
( (null? sq»
(if (equal? (vector-ref *board* (car sq» -1)
(set! retVal (+ retVal 1») ; black pawn
(if (equal? (vector-ref *board* (car sq» 1)
(set! retVal (- retVal 1»» ; white pawn
;; Decrease value of moving queen in the first five moves:
(if (and « *move-num* 5) (equal? (vector-ref *board* toSq) -5»
(set! ret Val (- ret Val 10»)
;; Decrease value of moving king in the first 15 moves:
(if (and « *move-num* 15) (equal? (vector-ref *board* toSq) -9»
(set! ret Val (- ret Val 20»)
(+ retVal (random 2»»)
(define board-pr
(lambda (sq)
(let* «rank (truncate (/ sq 10)))
(file (- sq (* rank 10))))
(set! file (- file 2))
(set! rank (- rank 1))
(display (list-ref '("A" "B" "C" "D" "E" "F" "G" "H") file))
(display rank))))
(define board-sq
(lambda (sq)
(let* «rank (truncate (/ sq 10)))
(file (- sq (* rank 10))))
(set! file (- file 2))
(set! rank (- rank 1))
(list (list-ref '("A" "B" "C" "D" "E" "F" "G" "H") file)
rank))))
(define sort-func
(lambda (x y)
(> (cadr x) (cadr y))))
(define Mover
(lambda 0
(set! *move-num* (+ *move-num* 1))
(let «possibleMoves (posib))
(bestMove 'C))
(bestValue -100000)
(to' 0)
(moveValues 'C)) for debug output only
(tosave '0)
(fromsave '0)
(pm' 0)
(newVal 0))
(do «pm-list possibleMoves (cdr pm-list)))
«null? pm-list))
(set! pm (car pm-list))
(set! tosave 0)
(if (equal? pm '00)
(begin
8.3. IMPLEMENTATION 167
(vector-set! *board* 96 0)
(vector-set! *board* 97 -4)
(vector-set! *board* 98 -9)
(vector-set! *board* 99 0)
(set! to 10» ; off of board
(if (equal? pm '000)
(begin
(vector-set! *board* 96 0)
(vector-set! *board* 95 -4)
(vector-set! *board* 94 -9)
(set! to 10) ; off of board
(vector-set! *board* 92 0»
(begin
(set! fromsave (vector-ref *board* (car pm»)
(set! tosave (vector-ref *board* (cadr pm»)
(vector-set!
*board*
(cadr pm)
(vector-ref *board* (car pm»)
(vector-set! *board* (car pm) 0)
(set! to (cadr pm»»)
Listing 8.2 shows the beginning of a sample game played against the
program in Listing 8.1.
Listing 8.2
(load "chess.s")
;Loading "chess.s" -- done
;Value: chess
(chess)
BR BN BB BQ BK BB BN BR
BP BP BP BP BP BP BP BP
X X X X
X X X X
X X X X
X X X X
WP WP WP WP WP WP WP WP
WR WN WB WQ WK WB WN WR
Enter your move (e.g., d2-d4)
d2-d4
BR BN BB BQ BK BB BN BR
BP BP BP BP BP BP BP BP
X X X X
X X X X
X WP. X X
X X X X
WPWPWPX WPWPWPWP
WR WN WE WQ WI< WB WN WR
07 to 05 O.
E7 to E6 O.
C7 to C6 -1.
F7 to F5 -1.
G8 to F6 -2.
07 to 06 -2.
B7 to B6 -2.
B7 to B5 -2.
H7 to H5 -2.
A7 to A5 -2.
A7 to A6 -2.
F7 to F6 -2.
172 8. CHESS PLAYING PROGRAM
B8 to C6 -3.
B8 to A6 -3.
G7 to G6 -3.
H7 to H6 -3.
E7 to E5 -10.
C7 to C5 -14.
G7 to G5 -17.
G8 to H6 -45.
BR BN BB BQ BK BB BN BR
BP BP BP. BP BP BP BP
X X X X
X X BP X X
X WP. X X
X X X X
WP WP WP X WP WP WP WP
WR WN WB WQ WK WB WN WR
Enter your move (e.g., d2-d4)
gl-f3
BR BN BB BQ BK BB BN BR
BP BP BP. BP BP BP BP
X X X X
X X BP X X
X WP. X X
X X X WN X
WPWPWPX WPWPWPWP
WR WN WE WQ WK WB X WR
C8 to G4 6.
E7 to E6 1.
F7 to F5 1.
G8 to F6 1.
B8 to A6 O.
B7 to B6 O.
C7 to C6 O.
A7 to A5 O.
H7 to H5 O.
C8 to 07 O.
B7 to B5 O.
F7 to F6 O.
B8 to C6 O.
C8 to F5 O.
C8 to E6 O.
G7 to G6 O.
H7 to H6 O.
A7 to A6 O.
8.3. IMPLEMENTATION 173
B8 to D7 O.
D8 to D6 -8.
D8 to D7 -9.
E7 to E5 -10.
C7 to C5 -13.
G7 to G5 -14.
E8 to D7 -19.
G8 to H6 -41.
C8 to H3 -41.
BR BN. BQ BK BB BN BR
BP BP BP. BP BP BP BP
X X X X
X X BP X X
X WP. X BB X
X X X WNX
WP WP WP X WP WP WP WP
WR WN WB WQ WK WB X WR
Enter your move (e.g., d2-d4)
e2-e3
BR BN BQ BK BB BN BR
BP BP BP. BP BP BP BP
X X X X
X X BP X X
X WP. X BB X
X X WPWNX
WP WP WP X WP WP WP
WR WN WB WQ WK WE X WR
G4 to F3 27.
E7 to E6 O.
C7 to C6 O.
F7 to FS O.
G8 to F6 O.
B8 to D7 O.
G8 to H6 O.
A7 to AS O.
A7 to A6 O.
G4 to E6 O.
H7 to H6 O.
F7 to F6 O.
G4 to 07 O.
B8 to C6 O.
G4 to C8 O.
G4 to F5 -1.
H7 to HS -1.
174 8. CHESS PLAYING PROGRAM
G7 to G6 -1.
B7 to B6 -1.
G4 to H5 -1.
08 to 07 -10.
08 to 06 -10.
E7 to E5 -10.
08 to C8 -11.
C7 to C5 -14.
B7 to B5 -14.
G7 to G5 -14.
E8 to 07 -21.
B8 to A6 -42.
G4 to H3 -43.
BR BN. BQ BK BB BN BR
BP BP BP . BP BP BP BP
X X X X
X X BP X X
X WP. X X
X X WP BB X
WPWPWPX WPWPWP
WR WN WB WQ WI< WB X WR
Enter your move (e.g., d2-d4)
dl-f3
BR BN BQ BK BB BN BR
BP BP BP . BP BP BP BP
X X X X
X X BP X X
X WP. X X
X X WP WQ X
WP WP WP X WP WP WP
WR WN WB . WK WB X WR
B8 to C6 O.
E7 to E6 O.
C7 to C6 O.
G8 to H6 -1.
G7 to G6 -1.
B7 to B6 -1.
A7 to A6 -1.
G7 to G5 -2.
A7 to A5 -2.
H7 to H6 -2.
E7 to E5 -10.
08 to 07 -10.
08 to 06 -11.
8.3. IMPLEMENTATION 175
C7 to C5 -12.
F7 to F5 -14.
B8 to 07 -14.
H7 to H5 -15.
B7 to B5 -15.
F7 to F6 -15.
08 to C8 -26.
G8 to F6 -42.
B8 to A6 -42.
E8 to 07 -49.
BR X . BQ BK BB BN BR
BP BP BP. BP BP BP BP
X BN X X X
X X BP X . X
X WP. X . X
X X WP WQ X
WP WP WP X . WP WP WP
WRWNWB. WKWBX WR
Enter your move (e.g., d2-d4)
It is interesting to observe how the following seven heuristics guide
move selection to playa very reasonable game of chess:
1. Maximize the amount of material advantage
2. Try to guard your own pieces
3. Try to maximize the mobility of your own pieces while minimizing
the mobility of the enemy pieces
4. Try to control the center squares with pawns
5. Try to control the center squares with pieces
6. Try to control the squares adjacent to both Kings
7. Try to attack enemy pieces with your own pieces of lesser value
Listing 8,3
(load "chess.s")
;Loading "chess.s" -- done
176 8. CHESS PLAYING PROGRAM
;Value: chess
(chess)
Enter your move (e.g., d2-d4) e2-e4
Computer move : E7-E5
BR BN BB BQ BK BB BN BR
BP BP BP BP X BP BP BP
X X X X
X X BP X
X X WPX X
X X X X
WP WP WP WP WP WP WP
WR WN WB WQ WK WB WN WR
Enter your move (e.g., d2-d4) gl-f3
BR BN BB BQ BK BB BN BR
BP BP BP BP X BP BP BP
X X X X
X X BP. X
X X WPX X
X X X WN X
WP WP WP WP. WP WP WP
WR WN WB WQ WK WB X WR
BR X BB BQ BK BB BN BR
BP BP BP BP X BP BP BP
X BN X X X
X X BP X
X X WPX X
X X X WN X
WP WP WP WP WP WP WP
WR WN WB WQ WK WB X WR
Enter your move (e.g., d2-d4) fl-b5
Computer move : G8-E7
Enter your move (e.g., d2-d4) 00
Castle King side
BR X BB BQ BK BB BR
BP BP BP BP BN BP BP BP
X BN X X X
X WB X BP X
X X WPX X
X X X WN X
8.3. IMPLEMENTATION 177
WP WP WP WP WP WP WP
WR WN WB WQ X WR WK
BR X BB BQ BK BB. BR
BP BP BP BP BN BP BP
X BN X BP X
X WB X BP X
X WP WP X X
X X X WN X
WP WP WP X WP WP WP
WR WN WB WQ X WR WK
BR X BB X BK BB BR
BP BP BP BP BQ BP BP
X X BP BN X
X WB X X X
X WQ WP X X
X WN X X
WP WP WP WB WP WP WP
WR . X X WR WK
BR X BB X BK BB BR
BP BP BP BP BQ BP
X X BP BN X
X WB X WN X X BP
X WQ WP X X
X X X X
WP WP WP WB WP WP WP
WR. X X WR WK
BR X BB X BK BB BR
BP BP BP BP X BP
178 8. CHESS PLAYING PROGRAM
X BQ. BP BN X
X WB X WN X X BP
X WQ WP X X
X X X X
WP WP WP WB. WP WP WP
WR. X X WR WK .
Enter your move (e.g., d2-d4) d2-b4 (mistake!)
BR X BB X BK BB. BR
BP BP BP BP X BP.
X BQ. BP BN X
X WB X WN X X BP
WB. WQ WP X X
X X X X
WP WP WP X WP WP WP
WR. X X WR WK
BR X BB X BK BB. BR
BP BP X BP X BP.
X BQ. BP BN X
X WB BP WN X X BP
WB. WQ WP X X
X X X X
WPWPWPX WPWPWP
WR. X X WR WK .
Enter your move (e.g., d2-d4) b4-c5
BR X BB X BK BB. BR
BP BP X BP X BP .
X BQ. BP BN X
X WB WB WN X X BP
X WQ WP X X
X X X X
WP WP WP X WP WP WP
WR. X X WR WK
BR X BB X BK BB. BR
BP BP X BPX BP .
X X BQ BP BN X
X WB WB WNX X BP
X WQWP X X
X X X X
WP Wp·,JP X WP WP WP
WR. X X WR WK .
Enter your move (e.g., d2-d4) d5-c7
8.3. IMPLEMENTATION 179
BR X BB X BB. BR
BP BP WN BP X BK BP .
X X BQ BP BN X
X WB. X X BP
X WE WQ WP X X
X X X X
WP WP WP X WP WP WP
WR. X X WR WK
BR X BB X BB. BR
BP BP WN BP X BK BP .
X X BQ BP. X
X WB. X X BP
X WB WQ WP BN. X
X X X X
WP WP WP X WP WP WP
WR. X X WR WK .
Enter your move (e.g., d2-d4) c4-e6
Computer move : D7-E6
Enter your move (e.g., d2-d4) d4-d8
BR X BB WQ. BB. BR
BP BP WN. X BK BP .
X X BP BP. X
X WB. X X BP
X X WP BN. X
X X X X
WP WP WP X WP WP WP
WR. X X WR WK
BR BB X WQ BB BK BR
BP BP WN . X BP .
X X BP BP . X
X WB . X X BP
X X WP BN . X
X X X X
WPWPWPX WP WP WP
WR . X X WR WK .
Enter your move (e.g .• d2-d4) e8-f8
180 8. CHESS PLAYING PROGRAM
BR BB X WQ BK BR
BP BP WN X BP
X X BP BP X
X WB X X BP
X X WP BN X
X X X X
WP WP WP X WP WP WP
WR . X X WR WK
BR BB X WQ BR
BP BP WN . X BP BK
X X BP BP X
X WB X X BP
X X WP BN X
X X X X
WP WP WP X WP WP WP
VIR. X X WR WK .
Enter your move (e.g., d2-d4) £8-£7
Computer move : A7-A5
BR BB X X BR
X BP WN X WQ BP BK
X X BP BP X
BP WB X X BP
X X WP BN X
X X X X
WP WP WP X WP WP WP
VIR. X X WR WK .
Enter your move (e.g., d2-d4) c7-e8 (threatens mate!)
BR BB X WN X BR
X BP X X WQ BP BK
X X BP BP X
BP WB X X BP
X X WP BN X
X X X X
WP WP WP X WP WP WP
WR . X X WR WK
BR X BB X WQ X X
X BP X X BP
X X BP BP BK
BP WB X X BP
X X WP X X
X X X WP .
WP WP WP X BN WP WK WP
WR. X X WR X
Enter your move (e.g., d2-d4) al-el (knight trapped)
Computer move : E2-G3
Enter your move (e.g., d2-d4) f2-g3
BR X BB X WQ X X
X BP X X BP
X X BP BP BK
BP WB X X BP
X X WP X X
X X X WP .
WP WP WP X X WK WP
X X WR WR X
BR X BB X WQ X X
X BP X X BP
X X BP BP . X
BP WB X BK BP
X X WPX X
X X X WP
WP WP WP X X WK WP
X X WR WR X
Enter your move (e.g., d2-d4) c5-e3 (check)
BR X BB X WQ X X
X BP X X BP
X X BP BP X
BP X X BK BP
X X WP X X
X X WE WP
WP WP WP X X WK WP
X X WR WR X
BR X BB X WQ X X
X BP X X BP
X X BP BP X
BP X X BP
X
X X WP X BK X
182 8. CHESS PLAYING PROGRAM
x . X WE. WP.
WP WP WP X X WK WP
X . X WR WR X
Enter your move (e.g., d2-d4)
h2-h3
BR X BB X WQ X X
X BP X X . BP.
. X X BP BP. X
BP. X X X BP
X X WP X BK X
X . X WE. WPWP
WP WP WP X X WK X
X X WR WR X
Checkmate! !
The first move that the white pawn at H2 makes is to checkmate the
black king!
CHAPTER 9
Go PLAYING PROGRAM
Go is an ancient game of strategy that is purported to be over 4,000
years old. My older brother Ron taught me to play Go when I was
about eight years old. The rules of Go are simple, but it is very difficult
to play the game well. As an indication of how difficult it is to write a Go
program, a Taiwanese businessman, Mr. lng, has offered (seriously!) a
cash prize of about one million dollars to the first programmer who can
write a Go playing program that can win a match against a Taiwanese
professional Go player.
Go is an incredibly beautiful game. I have had the privilege of playing
casual games against the women's world champion and the national
champion of South Korea (I was crushed in both games!). Go played
by strong human players has a magical quality not seen in the play
of any computer Go program (yet!). I particularly enjoy playing over
the moves of ancient recorded Go games (see Appendix B for pointers
to Internet Go sites that contain ancient Go games, modem recorded
Go games, and programs for playing Go and for playing recorded Go
games).
The Go playing program developed in this chapter is very simple. It
plays at the level of a human player who is just learning how to play.
People learn to play Go by making mistakes and avoiding making sim-
ilar mistakes in the future. Go programs are very knowledge-intensive;
Go programs become stronger players by noting weak points in the
program's play and adding specialized code to handle special situa-
tions. The Go program developed in this chapter was designed and
implemented to be
• Easy to understand
• Flexible in its module and software architecture so that the pro-
gram can easily be modified and its playing abilities extended to
understand more game situations
• Able to make move selections quickly
5. Wrote the first file module GO_INITS, which contained the Scheme
functions required to initialize data structures for a Go game and
to access these data structures in other Scheme functions
6. Unit tested (i.e., "debugged") file module GO_INIT.S
7. Designed algorithms for utility calculations (e.g., checking for con-
ditions where stones on the Go board are touching, changing and
coalescing group IDs, updating the liberty counts of groups, adding
new stones to the board and updating all data structures, removing
groups of stones, and printing the current Go board)
8. Implemented the Go data utility functions and placed them in the
file module GO_DATA.S
9. Unit tested (i.e., "debugged") file module GO_DATA.S
10. Designed a minimal user interface for playing Go, including hooks
for future functions to refine computer move selection by using the
tactics and strategy file modules
11. Implemented the minimal user interface for playing Go and placed
these functions in file module GO_PLAYS
12. Unit tested (i.e., "debugged") file module GO_PLAYS
13. Designed and implemented simple graphics utilities for plotting
a Go board position, and placed these Scheme functions in file
module GO_PLOTS
14. Unit tested file module GO_PLOTS
15. Analyzed basic Go tactics that would be relatively simple to
implement
16. Designed algorithms for simple Go tactics
17. Implemented the tactics functions and placed them in file module
GO_TACTS
18. Unit tested file module GO_TACTS
19. Analyzed basic Go strategies that would be relatively simple to
implement
20. Designed algorithms for simple Go strategies
21. Implemented the strategy functions and placed them in file module
GO_STRATS
22. Unit tested file module GO_STRATS
23. Tested the entire game, returning to previous steps as required to
refine my analysis, design, and staged implementations
1
A B c D E
FIGURE 9.1. Three moves played on a tiny, 5x5 Go board. Black moves first. Once a stone is
placed on the board, it is never moved unless it is captured and removed from the board.
1
A B c E
FIGURE 9.2. This is the same game as shown in Figure 9.1 with five additional stones played.
stones are only placed on the edge for tactical purposes, like reducing
the liberty count of an enemy group of stones that is under attack.
A B c E
FIGURE 9.3. This is the same game as shown in Figure 9.2 with three additional stones played.
The white stones can be captured. Black continues the encirclement of the white stones.
from previous game turns and updated to make the program more
efficient
6. A counter used for numbering new groups as they are formed
The reader should note that we are not designing these data struc-
tures yet. We are, based on our current understanding of the game of
Go, noting the types of information that we will need to maintain in our
Go playing program. The two-dimensional array that maintains scores
for moves on the Go board is especially important: these calculated
scores will be reused when possible over multiple moves. Also, differ-
ent components of the program (i.e., tactics and strategy) can write
data to this array independently, separating tactics, strategy, and final
move selection implementations.
190 9. Go PLAYING PROGRAM
1
A B c
FIGURE 9.4. It is Black's turn to play. The three white stones at locations C-1, 0-1, and E-1 form
a single group with only one liberty. Black to play can capture the three white stones. In an
actual game White would never play along the edge of the board.
1
A c D E
FIGURE 9.5. This is a continuation of the position found in Figure 9.4. Black has just played at
location B-1 to capture the three white stones seen in Figure 9.4.
modules that we will use to hold the Go playing program. The optional
fourth file module is used for plotting a Go board using the portable
graphics library developed in Section 4.3.
Listing 9.1
;; File: go_init.s
(define COMPUTER 1)
(define HUMAN 2)
(define make-board
(lambda (size)
9.3. INTERACTIVELY PROTOTYPING OATA STRUCTURES 193
(define board-ref
(lambda (brd row column)
(vector-ref (vector-ref brd row) column)))
(define board-set!
(lambda (brd row column value)
(vector-set! (vector-ref brd row) column value)))
(define copy-Go-object
(lambda (old-Go-game)
(let* «size (game-size old-Go-game))
(new-Go-game (make-Go-Game size))
(old-board (game-board old-Go-game))
(new-board (game-board new-Go-game))
(old-groups (game-groups old-Go-game))
(new-groups (game-groups new-Go-game))
(old-liberties (game-liberties old-Go-game))
(new-liberties (game-liberties new-Go-game))
(old-move-values (game-move-values old-Go-game))
(new-move-values (game-move-values new-Go-game)))
(game-group-counter-set!
194 9. Go PLAYING PROGRAM
new-Go-game
(game-group-counter old-Go-Game»
(do ((row 0 (+ row 1»)
((>= row size»
(do ((colO (+ col 1»)
((>= col size»
(board-set! new-board row col (board-ref old-board row col»
(board-set! new-groups row col (board-ref old-groups row col»
(board-set! new-liberties row col (board-ref old-liberties row col»
(board-set! new-move-values row col
(board-ref old-move-values row col»»
new-Go-game»)
(define set-Joseki
(lambda (game)
(let ((size (vector-ref game 0»)
(if (equal? size 5)
(vector-set!
game
4
(vector
(vector 0 0 0 0 0)
(vector 0 1 1 1 0)
(vector 0 1 4 1 0)
(vector 0 1 1 1 0)
(vector 0 0 0 0 0»»
(if (equal? size 6)
(vector-set!
game
4
(vector
(vector 0 0 0 0 0 0)
(vector 0 1 1 1 1 0)
(vector 0 1 4 4 1 0)
(vector 0 1 4 4 1 0)
(vector 0 1 1 1 1 0)
(vector 0 0 0 0 0 0»»
(if (equal? size 7)
(vector-set!
9.3. INTERACTIVELY PROTOTYPING DATA STRUCTURES 195
game
4
(vector
(vector 0 0 0 0 0 0 0)
(vector 0 1 1 0 1 1 0)
(vector 0 1 3 4 3 1 0)
(vector 0 0 4 3 4 0 0)
(vector 0 1 3 4 3 1 0)
(vector 0 1 1 0 1 1 0)
(vector 0 0 0 0 0 0 0))))
(if (equal? size 8)
(vector-set!
game
4
(vector
(vector 0 0 0 0 0 0 0 0)
(vector 0 1 0 0 0 0 1 0)
(vector 0 0 3 4 4 3 0 0)
(vector 0 2 4 4 4 4 2 0)
(vector 0 2 4 4 4 4 2 0)
(vector 0 0 3 4 4 3 0 0)
(vector 0 1 0 0 0 0 1 0)
(vector 0 0 0 0 0 0 0 0))))
(if (equal? size 9)
(vector-set!
game
4
(vector
(vector o 0 0 0 0 0 0 0 0)
(vector o1 0 0 0 0 0 1 0)
(vector o0 3 4 2 4 3 0 0)
(vector o0 4 4 3 4 4 0 0)
(vector o0 3 2 2 2 3 0 0)
(vector o0 4 4 3 4 4 0 0)
(vector o0 342 430 0)
(vector o 1 0 0 0 001 0)
(vector o0 0 0 0 0 0 0 0))))
(vector 0 0 0 0 0 0 0 0 0 0 0)
(vector 0 0 0 0 0 0 0 0 0 0 0)
(vector 0 0 0 2 0 0 0 0 3 0 0)
(vector 0 0 2 0 0 0 0 0 0 0 0)
(vector 0 0 0 0 0 0 0 0 0 0 0))))
4
(vector
(vector 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
(vector 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
(vector 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0)
(vector 0 0 0 3 0 0 0 0 0 2 0 0 0 0 0 3 0 0 0)
(vector 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
(vector 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0)
(vector 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
(vector 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
(vector 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
(vector 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0)
(vector 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
(vector 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
(vector 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
(vector 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0)
(vector 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
(vector 0 0 0 3 0 0 0 0 0 2 0 0 0 0 0 3 0 0 0)
(vector 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
(vector 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
(vector 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0»»»)
This, however, will not work, because the second, inner call to make-
vector is made only once; in other words, each element of the first vector
has the same value (points to the same vector). The definition of make-
board in Listing 9.1 works correctly.
The functions board-ref and board-set! in Listing 9.1 are similar to
the built-in Scheme functions vector-ref and vector-set! that operate on
vectors. The only difference is an extra calling argument to access a
specified row and column in the array. I refer to row and column indices
since these terms map nicely onto a Go board, with row indices labeled
1, 2, 3, ... and column indices labeled A, B, C, ... as seen in Figures
9.1 through 9.5. However, as with Scheme vectors, the indexing of a
two-dimensional board array is "zero indexed:' that is, array indices
start counting at zero rather than one.
I refer to board data structures whenever I want to use a two-
dimensional array of data that maps, one for one, onto a Go board.
198 9. Go PLAYING PROGRAM
The following is a list of utilities that are contained in the file module
GO_DATA.S in Listing 9.2:
• Count the number of enemy stones touching a specified grid point
on the Go board
• Count the number of friendly stones touching a specified grid point
on the Go board
• Change all stones with a specified group ID to a new group ID
• Update the liberty count of a specified group of connected stones
• General utility to perform all "bookkeeping" for adding a stone to
a Go board
• Remove a stone from the Go board and update all Go program data
• Return a list of all stones connected to a specified Go board grid
point
• Print out the stone positions on the Go board (see Listing 9.7)
Listing 9.2 shows the final version of the file module GO_DATA.S.
Listing 9.2
;; File: GO_DATA.S
(define touched-enemy-stones
(lambda (game side-to-play row column)
(touched-friend-stones game (- 3 side-to-play) row column)))
200 9. Go PLAYING PROGRAM
(define touched-friend-stones
(lambda (game side-to-play row column)
(let ((ret '0)
(size (game-size game»
(board (game-board game»)
(if (> row 1)
(if (equal? side-to-play (board-ref board (- row 1) column»
(set!
ret
(cons (list (- row 1) column) ret»»
(if « row (- size 1»
(if (equal? side-to-play (board-ref board (+ row 1) column»
(set!
ret
(cons (list (+ row 1) column) ret»»
(if (> column 1)
(if (equal? side-to-play (board-ref board row (- column 1»)
(set!
ret
(cons (list row (- column 1» ret»»
(if « column (- size 1»
(if (equal? side-to-play (board-ref board row (+ column 1»)
(set!
ret
(cons (list row (+ column 1» ret»»
ret»)
(define change-group-ID
(lambda (game old-ID new-ID)
(let ((groups (game-groups game»
(size (game-size game»)
(do ((row 0 (+ row 1»)
((> row (- size 1»)
(do ((colO (+ col 1»)
((> col (- size 1»)
(if (equal?
(board-ref groups row col)
old-ID)
(board-set! groups row col new-ID»»
(update-liberties game new-ID»»
(define update-liberties
(lambda (game group-num)
(let* ((size (game-size game»
(scratch (make-board size»
(groups (game-groups game»
(board (game-board game»
(group-liberties (game-liberties game»
(num-lib 0»
9.4. Low LEVEL SCHEME FUNCTIONS TO MANIPULATE Go OATA STRUCTURES 201
(define add-stone
(lambda (game side-to-play row column)
(let «touched-friends
(touched-friend-stones game side-to-play row column))
(board (game-board game))
(groups (game-groups game))
(group-num (+ (game-group-counter game) 1)))
(game-group-counter-set! game group-num)
(board-set! (game-groups game) row column group-num)
(board-set! (game-board game) row column side-to-play)
;; loop over all friendly touched stones, changing all
;; group IDs of touched stones to 'group-num':
(do «grp touched-friends (cdr grp)))
«null? grp))
(let «a-row (caar grp))
(a-col (cadar grp)))
(let «a-grp-num (board-ref groups a-row a-col)))
(change-group-ID game a-grp-num group-num))))
(let «touched-stones
(append
(touched-friend-stones game COMPUTER row column)
(touched-enemy-stones game COMPUTER row column)))
(touched-groups 'C)))
(set! touched-groups
(map
(lambda (grid)
(let «row (car grid))
(col (cadr grid)))
(board-ref groups row col)))
touched-stones))
(display "touched groups: ")
(display touched-groups)
(newline)
;; note: should remove duplicates in list here
(do «group touched-groups (cdr group)))
«null? group))
(update-liberties game (car group))))
(update-liberties game (board-ref groups row column)))))
(define remove-group
(lambda (game group-num)
(let «size (game-size game))
(groups (game-groups game))
9.4. Low LEVEL SCHEME FUNCTIONS TO MANIPULATE Go DATA STRUCTURES 203
Return a list of all empty grid points on the Go board that are
touching a specified group:
(define group-attachment
(lambda (game group-num)
(let* «size (game-size game))
(scratch (make-board size))
(groups (game-groups game))
(board (game-board game))
(ret-list '0))
(do «i 0 (+ i 1)))
«>= i size))
(do «j 0 (+ j 1)))
«>= j size))
(if (equal?
group-num
(board-ref groups i j))
(begin
(do «ii 0 (+ ii 1)))
«>= ii 2))
(let «iii (+ i 1 (* -2 ii))))
(if (and
(> iii -1)
« iii size))
(if (and
(equal?
(board-ref board iii j)
0)
(equal?
(board-ref scratch iii j)
0))
(begin
(board-set! scratch iii j 1)))))
(let «jjj (+ j 1 (* -2 ii))))
204 9. Go PLAYING PROGRAM
(if (and
(> jjj -1)
« jjj size))
(if (and
(equal?
(board-ref board i jjj)
0)
(equal?
(board-ref scratch i jjj)
0))
(begin
(board-set! scratch i jjj 1))))))))))
(do «i 0 (+ i 1)))
«>= i size))
(do «j 0 (+ j 1)))
«>= j size))
(if
(not
(equal?
o
(board-ref scratch i j)))
(set! ret-list (cons (list i j) ret-list)))))
ret-list)))
(define group-count
(lambda (game group-num)
(let «size (game-size game))
(groups (game-groups game))
(ret-count 0))
(do «row 0 (+ row 1)))
«>= row size))
(do «colO (+ col 1)))
«>= col size))
(if (equal?
group-num
(board-ref groups row col))
(set! ret-count (+ ret-count 1)))))
ret-count)))
(define print-move
(lambda (row col)
(let «col-names '(IIA" liB lie" "D IIE "Fit "Gil "HII "Itt "J
II
II II II
"K" IIL" I'M'I "N" "D" Ilpl' IIQt' f'R" "S11 t'T"»
(row-names '("1 11 "2" "3" "4" "5" "6" "7" "8"
"9" "10" "11" "12" "13" "14" "15" "16"
9.4. Low LEVEL SCHEME FUNCTIONS TO MANIPULATE Go DATA STRUCTURES 205
(define print-board
(lambda (game)
(let ((col-names' ("A" "B" "CD "D" "E" "F" "Gil "H" "I" "l"
11K" IlL" '1M" IIN'I "Oil Ilplt IIQtl "R'f IISlt IIT II ) )
(row-names ,(n 1 11 II 2 11 II 3 11 II 4" " 5 1t II 6 1t II 7" II 8 1t
" 9" "10" "11" "12" "13" "14" "15" "16"
"17" "18" "19" "20"»
(size (game-size game»
(board (game-board game»)
(newline)
(do ((row 0 (+ row 1»)
((>= row size»
(newline)
(display (list-ref row-names (- size row 1»)
(display ": II)
(do ((colO (+ coIl»)
((>= col size»
(if (equal?
(board-ref board (- size row 1) col)
0)
(display "-1-"»
(if (equal?
(board-ref board (- size row 1) col)
COMPtITER)
(display "_C_"»
(if (equal?
(board-ref board (- size row 1) col)
HUMAN)
(display "-H-"»)
(newline»
(newline)
(display II II)
(do ((colO (+ coIl»)
((>= col size»
(begin
(display II II)
(display (list-ref col-names col»
(display II II»)
(newline)
(newline»»
Listing 9.2 shows the final version of the file module GO_DATA.S. Sev-
eral of the functions were added after the initial implementation; for
example, the utility function group-attachments was originally written
206 9. Go PLAYING PROGRAM
as part of the tactical module GO_TACT.S, and then moved to the data
structures module GO_DATA.S.
The function touched-friend-stones simply returns a list of coordinates
(i.e., grid positions on the Go board) of any friendly stones by checking
the neighboring points. This function is reused in function touched-
enemy-stones by changing the side-to-play indicator (second argument
to both functions).
The function change-group-ID simply changes all occurrences of the
old group 10 value to the new group 10 value in the group 10 array in
a Go game data structure.
The function update-liberties looks complicated, but the idea behind
the function is simple: we create a scratch board array (with all values
automatically initialized to zero). We then loop over every point on the
Go board looking for connected stones belonging to the group that we
are updating. For each stone in the group we look for empty connected
(adjacent) grid positions on the Go board and set the corresponding
point in the scratch array to one if its value is zero. We keep a running
count of the number of entries in the scratch array that we set to one,
and this will be the liberty count for the entire group. This process
allows us to count liberties without double-counting empty grid points
that are adjacent to two separate stones in the same group. Function
update-liberties also removes the specified group if it finds that the group
has zero liberties.
The function add-stone is a general-purpose utility that manages the
process of adding a stone to the Go board and updating all relevant data
structures in a Go game object. function add-stone uses other functions
in modules GO_DATA.S and GO_INIT.S:
touched-friend-stones
touched-enemy-stones
game-board
game-group-counter
game-group-counter-set!
board-set!
board-ref
change-group-ID
update-liberties
The function remove-group looks for all entries in the groups array
in a Go game object, and sets the corresponding entries in the liber-
ties, groups, and board arrays in the same Go game object to zero. The
function print-board simply prints out a Go board position (see Listing
9.7).
In order to unit test the functions in file module GO_DATA.S, the mod-
ule GO_PLAYS was written to provide a simple user interface for making
moves on the Go board for both the human and computer players.
9.4. Low LEVEL SCHEME FUNCTIONS TO MANIPULATE Go DATA STRUCTURES 207
Listing 9.3
;; File: GO_PLAY.S
(define make-player-move
(lambda (game)
(display "Enter move (e.g., D2 or fS, or a
single character for debug) II)
(newline)
(let* «response (string-capitalize (symbol->string (read»»
(col (- (char-code (string-ref response 0» (char-code # \A»)
(row 0»
(if « (string-length response) 2)
(begin
(Go-debug game)
(make-player-move game»
(begin
(set! row (- (char->digit (string-ref response 1» 1»
(if (>
(string-length response)
2)
(set!
row
(+
(* 10 (+ row 1»
(- (char->digit (string-ref response 2» 1»»
(display "Move: column=")
(display col)
(display ", row=")
(display row)
(newline)
(add-stone game HUMAN row col)
(board-set! (game-move-values game) row colO»»»
(define best-computer-move
(lambda (game)
(let «board (game-board game»
(size (game-size game»
208 9. Go PLAYING PROGRAM
(define Go-debug
(lambda (game)
(display "Entered dummy Go-debug function.")
(newline» )
tactical-update
strategic-update
to update the move values array in the specified Go game object. These
two functions are "stubbed out" (i.e., the functions are defined, but
these dummy function definitions do nothing) in file module GO_PLAY
listed in Listing 9.3. All of the code in file modules GO_INIT.S, GO_
DATA.S, and GO_PLAY.s can be tested with the stubbed-out definitions
for tactical-update and strategic-update. The file module GO_TACT.S will
eventually contain the working definition of function tactical-update, and
file module GO_STRAT.S will contain the working definition of function
strategic-update. The function best-computer-move calls these two update
functions, then selects the highest value move from the move values
array to make a move.
File module GO_PLAY.S also contains a stubbed-out definition for
function plot-board, which is defined in file module GO_PLOT.S. If you
load file GO_PLOT.S, then you will see a graphics display of the Go board.
File module GO_PLAYS also contains a stubbed-out version of the func-
tion Go-debug, which can be overridden to print out any data necessary
for debugging new code that you add to file modules GO_TACT.S and GO_
STRAT.S. The function go is the main test function for playing a game
of Go.
The reader will note that all of the functions in the Go playing pro-
gram require a first argument that is a Go data object created by the
function make-Go-game. By passing the current state of the Go game
through the argument lists of these functions (and not relying on any
global data!), we make it possible for the reader to use any of these
functions recursively to implement tactical look-ahead in his or her
own Go programs. This would require copying the Go data object be-
fore passing it to recursive function calls, because many of the functions
in this program modify the contents of the Go data object. The destruc-
tive modification of data passed to a function is sometimes done to
increase runtime performance (as is done in this program), but it is
important to understand that this modification is occurring. File mod-
ule GOJNIT.S contains an unused utility function copy-Go-object which
makes a new complete copy of a Go data object that can be used for
look-ahead search.
9.4. Low LEVEL SCHEME FUNCTIONS TO MANIPULATE Go DATA STRUCTURES 211
Listing 9.4
File: GO_PLOT.S
(define init-Go-plot
(lambda 0
(set! GO_PLOT_NEED_TO_INITIALIZE_GRAPHICS_RIGHT_NOW If)
(open-gr»)
(define plot-board
(lambda (game)
(define plot-solid-ellipse
(lambda (left top right bottom color)
(plot-ellipse left top right bottom color)
(if (and
(>= right left)
(>= top bottom»
(plot-solid-ellipse
(+ left 2)
(- top 2)
(- right 2)
(+ bottom 2)
color»)
212 9. Go PLAYING PROGRAM
(init-Go-plot))
(clear-plot)
(do «i 0 (+ i 1)))
«>= i size))
(plot-line (+ x-start (* i del-x))
y-start
(+ x-start (* i del-x))
(+ y-start (* (- size 1) del-y)))
(plot-line x-start
(+ y-start (* i del-y))
(+ x-start (* (- size 1) del-x))
(+ y-start (* i del-y)))
(plot-string
(+ x-start -4 (* i del-x))
(- y-start 55)
(list-ref '(ltA tt IIC't lID" IIEI IIFlt I'G'I I'Hlt 111 11
IIBII I
IIJII "K" ItLl 1 "M I' IIN't 110 1' Ilptt ItRtl IISI' IIT'I)
i))
(plot-string
(- x-start 80)
(+ y-start -5 (* i del-y))
(list-ref '("1" "2" "3" "4" "5" "6" "7" "8" "9" "10"
"11" "12" "13" "14" "15" "16" "17" "18" "19")
i))
COMPUTER)
(plot-solid-ellipse
(- (+ x-start (* col del-x)) (/ del-x 2))
(+ (+ y-start (* row del-y)) (/ del-x 2))
(+ (+ x-start (* col del-x)) (/ del-x 2))
(- (+ y-start (* row del-y)) (/ del-x 2))
"black"))))))))
'9
18 I
.1
1..1 !
Ij.i ~.
"
..
15
IJ
f-f-..
'2
11
.0
9
r
B I
I
,7
i
I
ABC 0 E F G H I J K L ~ HOP R S T
FIGURE 9.6. A 19x19 Go board display. The computer, playing the black stones, has just played
at E16 to avoid having the stone at 016 captured.
214 9. Go PLAYING PROGRAM
A B o
Figures 9.7 and 9.8 show part of a game played on a tiny, SxS Go
board.
FIGURE 9.8. The same game sequence as shown in Figure 9.7, six moves later. The computer
has just played at C5, capturing the white stone at C4.
Listing 9.5
;; File: Go_TACT.S
(define Go-debug
(lambda (game)
(pp game)
(neyline»)
(define tactical-update
(lambda (game)
(check-for-capture game)
(check-for-atari-human game)
(escape-from-atari game»)
(define check-for-capture
(lambda (game)
(let «size (game-size game»
(groups (game-groups game»
(board (game-board game»
(move-values (game-move-values game»
(group-liberties (game-liberties game»
(groups-processed 'C»~)
(do «roy 0 (+ roy 1»)
«>= roy size»
(do «colO (+ coli»)
«>= col size»
(if (and
(equal? HUMAN (board-ref board roy col»
(equal? 1 (board-ref group-liberties roy col»)
(if (not (member (board-ref groups roy col) groups-processed»
(let
We have found a group Yith only one liberty
that has not already been processed this turn:
«atari-list
(group-attachment
game
(board-ref groups roy col»»
(if (> (length atari-list) 0)
(let* «attack-roy (caar atari-list»
(attack-col (cadar atari-list»
(current-score
(board-ref move-values attack-roy attack-col»)
218 9. Go PLAYING PROGRAM
(define check-for-atari-human
(lambda (game)
(let «size (game-size game»
(groups (game-groups game»
(board (game-board game»
(move-values (game-move-values game»
(group-liberties (game-liberties game»
(groups-processed 'C»~)
(do «row 0 (+ row 1»)
«>= row size»
(do «colO (+ coli»)
«>= col size»
(if (and
(equal? HUMAN (board-ref board row col»
(equal? 2 (board-ref group-liberties row col»)
(if (not (member (board-ref groups row col) groups-processed»
(let
" We have found a group with only two liberties
that has not already been processed this turn:
«atari-list
(group-attachment
game
(board-ref groups row col»»
(if (> (length atari-list) 0)
(begin
(do «atari atari-list (cdr atari»)
«null? atari»
(let* «attack-row (caar atari»
(attack-col (cadar atari»
(current-score
(board-ref move-values attack-row attack-col»)
(display "possible to atari HUMAN group #")
(display (board-ref groups row col»
(newline)
9.6. Go PROGRAM IMPLEMENTATION 219
(board-set!
move-values
attack-row attack-col
(+ current-score
(*
0.7
(group-count
game
(board-ref groups row col)))))))
(set! groups-processed
(cons
(board-ref groups row col)
groups-processed))))))))))))
(define escape-from-atari
(lambda (game)
(let ((size (game-size game))
(groups (game-groups game))
(board (game-board game))
(move-values (game-move-values game))
(group-liberties (game-liberties game))
(groups-processed 'C)))
(do ((row 0 (+ row 1)))
((>= row size))
(do ((colO (+ col 1)))
((>= col size))
(if (and
(equal? COMPUTER (board-ref board row col))
(equal? 1 (board-ref group-liberties row col)))
(if (not (member (board-ref groups row col) groups-processed))
(let
We have found a group with only one liberty
that has not already been processed this turn:
((atari-list
(group-attachment
game
(board-ref groups row col))))
(if (> (length atari-list) 0)
(let* ((defend-row (caar atari-list))
(defend-col (cadar atari-list))
(current-score
(board-ref move-values defend-row defend-col)))
(display "computer group subject to capture: #")
(display (board-ref groups row col»
(newline)
(board-set!
move-values
defend-row defend-col
(+ current-score
220 9. Go PLAYING PROGRAM
4
(group-count game (board-ref groups row col)))))
(set! groups-processed
(cons
(board-ref groups row col)
groups-processed))))))))))))
Listing 9.6
;; File: GO_STRAT.S
(define Go-debug
(lambda (game)
9.6. Go PROGRAM IMPLEMENTATION 221
(pp game)
(newline)))
(define strategic-update
(lambda (game)
(let* ((size (game-size game))
(human-influence (make-board size))
(computer-influence (make-board size))
(board (game-board game)))
Fill in both computer and human "influence" arrays:
(do ((row 2 (+ row 1)))
((>= row (- size 2)))
(do ((col 2 (+ coli)))
((>= col (- size 2)))
(if (equal?
(board-ref board row col)
HUMAN)
(begin
(board-set!
human-influence (- row 2) col
(+ 1 (board-ref human-influence (- row 2) col)))
(board-set!
human-influence (+ row 2) col
(+ 1 (board-ref human-influence (+ row 2) col)))
(board-set!
human-influence row (- col 2)
(+ 1 (board-ref human-influence row (- col 2))))
(board-set!
human-influence row (+ col 2)
(+ 1 (board-ref human-influence row (+ col 2))))
(board-set!
human-influence (- row 1) col
(+ 2 (board-ref human-influence (- row 1) col)))
(board-set!
human-influence (+ row 1) col
(+ 2 (board-ref human-influence (+ row 1) col)))
(board-set!
human-influence row (- coli)
(+ 2 (board-ref human-influence row (- col 1))))
(board-set!
human-influence row (+ col 1)
(+ 2 (board-ref human-influence row (+ col 1))))))
(if (equal?
(board-ref board row col)
COMPUTER)
(begin
(board-set!
222 9. Go PLAYING PROGRAM
(define look-for-empty-territory
(lambda (game computer-influence human-influence)
(let «size (game-size game))
(move-values (game-move-values game))
(sum 0))
(do «row 1 (+ row 1)))
«>= row (- size 1)))
(do «coli (+ coli)))
«>= col (- size 1)))
(set!
sum
(+
(board-ref computer-influence row col)
(board-ref human-influence row col)))
(board-set! move-values row col
(+
(board-ref move-values row col)
(* 0.2 (- 3 sum)))))))))
(define connect-groups
(lambda (game)
If))
9.6. Go PROGRAM IMPLEMENTATION 223
(define approach-enemy-stones
(lambda (game computer-influence human-influence)
(let «size (game-size game»
(board (game-board game»
(move-values (game-move-values game»)
(do «row 1 (+ row 1»)
«>= row (- size 1»)
(do «coli (+ coli»)
«>= col (- size 1»)
(if (and
(equal? 0 (board-ref board row col»
(equal?
(board-ref computer-influence row col)
1)
(equal?
(board-ref human-influence row col)
1)
(begin
(board-set!
move-values row col
(+
(board-ref move-values row col)
1. 5»
(display "Approach move considered at ")
(print-move row col)
(newline»»»»
Listing 9.7 shows the text output from playing a 20-move sequence
against the Go program.
Listing 9.7
(load "go_init.s")
;Loading "go_init.s" -- done
;Value: set-joseki
(load "go_data.s")
;Loading "go_data.s" -- done
;Value: print-board
(load "go_play.s")
;Loading "go_play.s" -- done
;Value: go
(load "go_tact.s")
;Loading "go_tact.s" -- done
;Value: escape-from-atari
(load "go_strat.s")
;Loading "go_strat.s" -- done
;Value: approach-enemy-stones
224 9. Go PLAYING PROGRAM
(go)touched groups: ()
Computer move: D3
9: -1--1--1--1--1--1--1--1--1-
8: -1--1--1--1--1--1--1--1--1-
7: -1--1--1--1--1--1--1--1--1-
6: -1--1--1--1--1--1--1--1--1-
5: -1--1--1--1--1--1--1--1--1-
4: -1--1--1--1--1--1--1--1--1-
3: -I--I--j--C--I--I--I--I--I-
2: -1--1--1--1--1--1--1--1--1-
1: -1--1--1--1--1--1--1--1--1-
ABC D E F G H I
9: -1--1--1--1--1--1--1--1--1-
8: -1--1--1--1--1--1--1--1--1-
7: -I--I--I--I--I--H--I--I--I-
6: -1--1--1--1--1--1--1--1--1-
5: -1--1--1--1--1--1--1--1--1-
4: -I--I--C--I--I--I--I--I--I-
3: -I--I--I--C--I--I--I--I--I-
2: -1--1--1--1--1--1--1--1--1-
1: -1--1--1--1--1--1--1--1--1-
ABC D E F G H I
9: -1--1--1--1--1--1--1--1--1-
8: -1--1--1--1--1--1--1--1--1-
7: -I--I--I--I--I--H--I--I--I-
6: -1--1--1--1--1--1--1--1--1-
5: -1--1--1--1--1--1--1--1--1-
4: -I--I--C--I--I--I--C--I--I-
3: -I--I--I--C--:--H--I--I--I-
2: -1--1--1--1--: - ·1--1--1--1-
1: -1--1--1--1--1--1--1--1--1-
ABC D E F G H I
9.6. Go PROGRAM IMPLEMENTATION 225
c6
Move: column=2, row=5
touched groups: ()
touched groups: ()
Computer move: G6
9: -1--1--1--1--1--1--1--1--1-
8: -1--1--1--1--1--1--1--1--1-
7: -I--I--I--I--I--H--I--I--I-
6: -I--I--H--I--I--I--C--I--I-
5: -1--1--1--1--1--1--1--1--1-
4: -I--I--C--I--I--I--C--I--I-
3: -I--I--I--C--I--H--I--I--I-
2: -1--1--1--1--1--1--1--1--1-
1: -1--1--1--1--1--1--1--1--1-
ABC D E F G H I
f6
Move: column=5, row=5
touched groups: (7 8)
touched groups: ()
Computer move: D7
9: -1--1--1--1--1--1--1--1--1-
8: -1--1--1--1--1--1--1--1--1-
7: -I--I--I--C--I--H--I--I--I-
6: -I--I--H--I--I--H--C--I--I-
5: -1--1--1--1--1--1--1--1--1-
4: -I--I--C--I--I--I--C--I--I-
3: -I--I--I--C--I--H--I--I--I-
2: -1--1--1--1--1--1--1--1--1-
1: -1--1--1--1--1--1--1--1--1-
ABC D E F G H I
g7
Move: column=6, row=6
touched groups: (7 10)
touched groups: ()
Computer move: E5
226 9. Go PLAYING PROGRAM
9: -1--1--1--1--1--1--1--1--1-
8: -1--1--1--1--1--1--1--1--1-
7: -I--I--I--C--I--H--H--I--I-
6: -I--I--H--I--I--H--C--I--I-
5: -I--I--I--I--C--I--I--I--I-
4: -I--I--C--I--I--I--C--I--I-
3: -I--I--I--C--I--H--I--I--I-
2: -1--1--1--1--1--1--1--1--1-
1: -1--1--1--1--1--1--1--1--1-
ABC D E F G H I
f5
Move: column=5, row=4
touched groups: (11 12)
touched groups: ()
Computer move: B2
9: -1--1--1--1--1--1--1--1--1-
8: -1--1--1--1--1--1--1--1--1-
7: -I--I--I--C--I--H--H--I-~I
6: -I--I--H--I--I--H--C--I--I-
5: -I--I--I--I--C--H--I--I--I-
4: -I--I--C--I--I--I--C--I--I-
3: -I--I--I--C--I--H--I--I--I-
2: -I--C--I--I--I--I--I--I--I-
1: -1--1--1--1--1--1--1--1--1-
ABC D E F G H I
e2
Move: column=4, row=l
touched groups: ()
touched groups: ()
Computer move: H2
9: -1--1--1--1--1--1--1--1--1-
8: -1--1--1--1--1--1--1--1--1-
7: -I--I--I--C--I--H--H--I--I-
6: -I--I--H--I--I--H--C--I--I-
5: -I--I--I--I--C--H--I--I--I-
4: -I--I--C--I--I--I--C--I--I-
3: -I--I--I--C--I--H--I--I--I-
2: -I--C--I--I--H--I--I--C--I-
1: -1--1--1--1--1--1--1--1--1-
ABC D E F G H I
9.6. Go PROGRAM IMPLEMENTATION 227
g2
Move: column=6, row=l
touched groups: (15)
touched groups: ()
Computer move: B8
9: -1--1--1--1--1--1--1--1--1-
8: -I--C--I--I--I--I--I--I--I-
7: -I--I--I--C--I--H--H--I--I-
6: -I--I--H--I--I--H--C--I--I-
5: -I--I--I--I--C--H--I--I--I-
4: -I--I--C--I--I--I--C--I--I-
3: -I--I--I--C--I--H--I--I--I-
2: -I--C--I--I--H--I--H--C--I-
1: -1--1--1--1--1--1--1--1--1-
ABC D E F G H I
b7
Move: column=l, row=6
touched groups: (17)
touched groups: ()
Computer move: H8
9: -1--1--1--1--1--1--1--1--1-
8: -I--C--I--I--I--I--I--C--I-
7: -I--H--I--C--I--H--H--I--I-
6: -I--I--H--I--I--H--C--I--I-
5: -I--I--I--I--C--H--I--I--I-
4: -I--I--C--I--I--I--C--I--I-
3: -I--I--I--C--I--H--I--I--I-
2: -I--C--I--I--H--I--H--C--I-
1: -1--1--1--1--1--1--1--1--1-
ABC D E F G H I
d5
Move: column=3, row=4
touched groups: (11)
touched groups: ()
Computer move: E8
228 9. Go PLAYING PROGRAM
9: -1--1--1--1--1--1--1--1--1-
8: -I--C--I--I--C--I--I--C--I-
7: -I--H--I--C--I--H--H--I--I-
6: -I--I--H--I--I--H--C--I--I-
5: -I--I--I--H--C--H--I--I--I-
4: -I--I--C--I--I--I--C--I--I-
3: -I--I--I--C--I--H--I--I--I-
2: -I--C--I--I--H--I--H--C--I-
1: -1--1--1--1--1--1--1--1--1-
ABC D E F G H I
9: -1--1--1--1--1--1--1--1--1-
8: -I--C--I--I--C--I--I--C--I-
7: -I--H--I--C--I--H--H--I--I-
6: -I--I--H--I--I--H--C--I--I-
5: -I--C--I--H--C--H--I--I--I-
4: -I--I--C--I--H--I--C--I--I-
3: -I--I--I--C--I--H--I--I--I-
2: -I--C--I--I--H--I--H--C--I-
1: -1--1--1--1--1--1--1--1--1-
ABC D E F G H I
9: -1--1--1--1--1--1--1--1--1-
8: -I--C--I--I--C--I--I--C--I-
7: -I--H--I--C--I--H--H--I--I-
6: -I--I--H--I--H--H--C--I--I-
5: -I--C--I--H--I--H--I--C--I-
4: -I--I--C--I--H--I--C--I--I-
3: -I--I--I--C--I--H--I--I--I-
2: -I--C--I--I--H--I--H--C--I-
1: -1--1--1--1--1--1--1--1--1-
ABC D E F G H I
Enter move (e.g., D2 or f5, or a single character for debug)
9.7. IDEAS FOR IMPROVING THE Go PLAYING PROGRAM 229
There are many ways to improve the strategic update function; for
example:
1. Recognize friendly groups (i.e., groups made up of the same color
stones) that can be attacked, and try to connect weak groups to
other friendly groups
2. Recognize large areas controlled by the opponent, and determine
either a safe place to play inside this area (an invasion), or approach
the area from the outside to reduce its territory
3. Calculate a safety attribute for each group. Groups with two eyes
are absolutely safe. When calculating influence around stones, take
into account the safety of the stones
APPENDIX A
INSTALLING AND RUNNING
THE MIT SCHEME
SYSTEM
There are two disks included with this book. These disks contain all of
the example programs in this book as well as the runtime portion of
the MIT Scheme development system (see Appendix B for locations on
the Internet where the full MIT Scheme system can be found, which
includes a native mode compiler and an Emacs-like editor).
It is easiest to set up MIT Scheme if it is installed in the directory
C:\SCHEME. Type the following commands in a DOS window:
C:
cd\
mkdir SCHEME
cd SCHEME
[PLACE THE FIRST FLOPPY IN YOUR DISK DRIVE NOW]
xcopy a:\*.* . /s
[PLACE THE SECOND FLOPPY IN YOUR DISK DRIVE NOW]
xcopy a:\*.* . /s
There are four ZIP format files that you need to decompress. You
will use the program PKUNZIP.EXE contained on the included disks
to decompress these files.
Type the following commands:
pkunzip -d bin. zip
pkunzip -d lib.zip
pkunzip -d runnoflo.zip
pkunzip -d help. zip
You will require WIN32S support to run MIT Scheme. Microsoft
Windows NT and Windows 95 have WIN32S support built-in. Microsoft
freely distributes a WIN32S kit which can be loaded on top of either
232 A. INSTALLING AND RUNNING THE MIT SCHEME SYSTEM
SCHEME
http://18.23.0.16/scheme-home.html
http://18.23.0.16/ftpdir/scheme-7.3/
http://www.cs.indiana.edu/scheme-repository/SRhome.html
CHESS
http://caissa.onenet.net/chess/
Go
ftp://bsdserver.ucsf.edu/Go
http://www.cwi.nl/jansteen/go/events/japan_news.html
http://www.mth.kcl.ac.uk/ mreiss/compgo.html
BIBLIOGRAPHY
Ada, 1 FORTRAN,l
allele, 33
and function, 11 garbage collection, 3
append, function, 8 gene, 28
genetic algorithms, 25, 28-43
begin special form, 13 Go, 183-190
breadth first search, 80-81 graph layout, 52-53