In addition to recursive function definitions (see the previous section of this tutorial), Combinatorial Game Suite supports several scripting commands, including if/then/else statements and for loops. CGSuite's language is modeled after other common scripting languages; its syntax and structure should be recognizable to anyone familiar with, say, Maple or Visual Basic.
Like all other commands, scripts may be entered directly onto the worksheet. However, the entire script must be entered into a single worksheet field, and since scripts generally consist of long sequences of commands, this can be somewhat cumbersome. Alternatively, a script may be composed in an external text editor (such as GNU Emacs) and run in CGSuite by selecting "Run Script ..." from the File menu. The entire file containing the script is treated as if it were entered into a single worksheet field. You may choose to direct the script's output either to a new worksheet or to a text file.
As noted previously in this tutorial, multiple commands may be strung together in sequence using semicolons. For example:
g := 3; g := g + 2; g + 5
This would output 10
. Notice that output is generated only
for the last command in a sequence; it is suppressed for each intermediate
command. If the sequence ends with a semicolon, no output is generated at
all.
If you want to display output for intermediate commands, you can append >> out
to
the end of an expression. For example:
g := 3 >> out; g := g + 2 >> out; g + 5 >> out;
This would output 3
, 5
, and 10
, each
on a separate line.
The general form of the if-then-else statement is:
if condition then statements [elif condition then
statements] [else statements] fi
Here and throughout this section, an expression enclosed in brackets [ ] is
optional and may be omitted. statements can be any single command, or any sequence of commands separated by semicolons. Any number
(zero or more) of "elif condition then statements
" clauses may be
included. CGSuite will run the statement(s) following the first condition
that evaluates to true, and only those statements. If none of the
conditions is true, and an else clause is present, CGSuite will run
its statement(s). The following code tests how g
compares with 0
and displays a useful message:
if g < 0 then "negative" >> out;
elif g > 0 then "positive" >> out;
elif g == 0 then "zero" >> out;
else "fuzzy" >> out;
fi
The general form of the for statement is:
[for variable] [from expression] [to expression] [by
expression] [while condition] [where condition] do
statements od
As before, any bracketed expression is optional, and statements can be replaced by any command
or sequence of commands. Each of the expressions must be a canonical game (or
some more complicated expression that evaluates to a canonical game), and the
conditions must be boolean values (true
or false
).
CGSuite interprets the for statement as follows:
do
and od
.If any of from, to, or by are omitted, then the default
values are 1, infinity,
and 1, respectively. The default conditions are always true
.
The following example simply prints out all even integers between 2 and 10:
for n from 2 to 10 by 2 do
n >> out;
od
The same effect could be achieved using while:
n := 2;
while n <= 10 do
n >> out;
n := n + 2;
od
Finally, there is an alternate form of the for statement:
[for variable] [in expression] [while condition]
[where condition] do statements od
In this form, the in expression should evaluate to a list of objects, and
the statements(s) between do
and od
will be repeated once
for each element of the list. while and where have the same
meanings as before. The following example displays all good left
options in 4x4 Domineering together with their canonical forms:
for g in SensibleLeftOptions(DomineeringRectangle(4,4)) do
g >> out;
C(g) >> out;
od
You can use the break
command within a loop to terminate the
loop immediately, and continue
to continue the loop.
Either type of loop can be used in seq and tabulate expressions, for example:
seq(n.^ for n from 1 to 5) >> out;
tabulate(h, C(h) for h in LeftOptions(g)) >> out;
A procedure is a user-defined function. We met one type of procedure - recursive functions - in the previous section of this tutorial. Procedures can also be designed to execute a sequence of commands, rather than simply evaluate one expression. In such cases it's easiest to use the following alternate syntax for defining a procedure:
proc(variable-list) [local variable-list;] [option
remember;]
statements end
The following two examples define the same function f:
f(n_) := n + 1;
f := proc(n) n + 1 end;
There is a crucial difference between these two forms. Procedures
defined using f(n_)
will store their values in a cache as they are
computed, making recursive calculations vastly more efficient. Procedures
defined using proc(n)
will not cache values by default, but you can
override this behavior by inserting option remember;
immediately
before the body of the procedure. Contrast the following two definitions:
slowfib := proc(n)
if n == 0 or n == 1 then 1
else slowfib(n-1) + slowfib(n-2) fi
end;
fastfib := proc(n) option remember;
if n == 0 or n == 1 then 1
else fastfib(n-1) + fastfib(n-2) fi
end;
Both calculate fibonacci numbers, but fastfib
is substantially
more efficient (linear time instead of exponential).
Procedures can also take several parameters. Here's an example that computes the maximum of two integers:
max := proc(m,n)
if m <= n then n else m fi
end;
Procedures can take any object as parameters, not just games. Here's a more sophisticated example that takes a list of games (as described in the previous section) and returns the sum of all games in the list.
listsum := proc(L) local i, g;
g := 0;
for i from 1 to Length(L) do
g := g + L[i];
od;
g
end;
For example, typing listsum([3*,vv,^*])
would return 3v
.
Notice that in procedures involving multiple statements, the value returned by
the procedure is equal to the value of the last statement in the sequence (unless
that statement ends in a semicolon, in which case the procedure returns no
value). Also note the use of local i, g;
in the procedure
definition: This marks i
and g
as local variables for the procedure, so
that calls to listsum
will not affect the value of the global
i
and g
.
You can also use return
statements from within a procedure.
If return expression
is encountered at any point
during the procedure's execution, then it immediately terminates with value
equal to the expression.
A third "shorthand" notation for procedures is sometimes useful. The
following definitions of f
and max
, equivalent to
those given above, illustrate this notation:
f := n -> n + 1;
max := (m,n) -> if m <= n then n else m fi;
Procedures defined using ->
will never cache values,
so they should not be used for recursive calculations.
Recall that appending >> out
to an expression simply
outputs the value of the expression; for example:
"Hello" >> out;
Two similar constructions are useful. The following:
expression >> log;
inserts the value of expression into the kernel log. This is useful primarily when you want to track the progress of a calculation from within a script, without distorting your output file. Finally,
expression >> err;
causes the entire script to terminate immediately and display the value of
expression as an error message. This is useful for handling error
conditions within procedures. Here's an improved version of the max
procedure, which operates on any pair of games and displays an error message if
they are incomparable:
max := proc(g,h)
if g <= h then h
elif h <= g then g
else "The games are incomparable." >> err
fi
end;
The usual way to construct game positions in the Worksheet is to pass a list of strings to the appropriate method. For example:
Amazons("L...","R...")
While this is straightforward to enter directly, it's highly inconvenient for scripts that need to iterate over a class of game positions. An alternative construction is provided for this purpose: A game position can be represented by a two-dimensional array of one-character strings. (A two-dimensional array is just a list of lists.) Here are three ways to construct the same Amazons position:
Amazons("L...","R...")
Amazons(["L...","R..."])
Amazons([["L",".",".","."],["R",".",".","."]])
The third construction is extremely awkward to type directly, but it's quite
convenient for use in scripts. The following simple example illustrates
how it's used. It creates a table showing the canonical forms of all 3x3
Domineering positions with just one square blocked. Note the use of the
methods CreateTable
and AddTableRow
to construct and
populate a table with two columns.
table := CreateTable(2);
AddTableRow(table, "Position", "Value");
// Initialize the grid
for row from 1 to 3 do
for col from 1 to 3 do
grid[row][col] := ".";
od od;
for row from 1 to 3 do
for col from 1 to 3 do
grid[row][col] := "x"; // Block this square
g := Domineering(grid);
AddTableRow(table, g, C(g));
grid[row][col] := "."; // Now unblock this square
od od;
table
Notice how the script iterates over the desired positions using the same grid
for efficiency.
Several more sophisticated examples are included in the examples
directory of the cgsuite distribution. You can run them directly using the
"Run Script ..." command from the File menu.
1. Partizan End Nim. Partizan end nim is played with n
nim heaps arranged in linear order. Left, on his turn, may remove any
number of beans from the leftmost non-empty heap; right, from the rightmost.
We can represent partizan end nim positions as lists in cgsuite. For
example, Left's options from [2,3,1]
are to [1,3,1]
and [3,1]
; Right's only option is to [2,3]
.
Write a procedure pendnim
that takes a list as input and calculates
the value of the corresponding position in partizan end nim. (You might
find it easiest to use the list-manipulation methods Remove
and Clone
.)
Then construct a table showing the values of [m,n]
for 1 <= m,n <= 6
.