This page contains feedback for the Zillions of Games development group on improvements that might be made for a possible Zillions of Games 2.0 product. Authors are Joao Neto and Adrian King.
This page was formatted by David Howe.
João's feedback
(adrian@uranos.xidak.com)
Foreshadowing
In my programming career, I've had occasion to design a handful of
special-purpose microlanguages, though they were generally not as
ambitious as ZRF. It is my experience that as soon as you unleash such
a thing on the world, users will immediately want to push the
microlanguage far beyond its intended boundaries. If you want to
satisfy the users, you almost always wind up creating something close to
a full-fledged general-purpose programming language. We might call this
the Law of Microlanguages: "Bloat or die". This will presumably be
ZRF's fate as well, and from Jeff and Mark's point of view, probably the
best they can hope for (if they want to keep ZRFers reasonably happy) is
managed bloat rather than no bloat.
What Zillions Has Now in the Way of Programming Facilities
In thinking about what I want from Zillions, I've found it useful to try
to inventory what Zillions already has. The Zillions documentation has
not always made this easy, but here's my understanding of what's there.
One thing to be clear on is that computation doesn't take place in
Zillions the way it would in a normal programming language. Each game
state that Zillions examines needs to have its own copy of the values of
all variables that are persistent from state to state, so that there is
not a unique copy of so-called "global" variables. Zillions has to deal
with a large number of parallel universes, each cloned from a preceding
universe, and each with its own copy of the state of every persistent
variable.
One problem with the ZRF documentation is the lack of consistency in
terminology, which is very confusing. The blocks in which instructions
can occur (e.g., the arguments to "moves") don't seem to have a single
name; they seem to be called, among other things, "move generation
block", "move block", and "<move-def>". I'll call them "action blocks";
they're the places where the allowable actions in the game are computed.
The entire block headed by "moves", which can contain many action
blocks, I'll call a "moves block". Note that a "drops" block is also
considered to be an action block.
What Zillions has now:
Data Types Zillions Needs
Unquestionably, a lot of games could be described a lot more easily if
Zillions had integer variables and expressions. You can simulate
arithmetic with boolean logic by means of computations like:
but this is exhausting if a and b can take on are more than a tiny range
of values.
Some kinds of computations would probably be easier if there were a
higher-level data structure such as sets, arrays, or lists that could
contain elements of primitive data types (and perhaps contain set,
array, or list elements). This would be the kind of thing you would
return from the "set generators" (e.g., found-pieces) David Howe
described in his message to me. However, this kind of data structure
might be more work to implement, and is probably lower priority for ZRF
programmers than the ability to work more generally with the primitive
data types. Below, I'll refer to this kind of data structure as "list",
although some other structure might be implemented instead, if any is
implemented.
Data Types of Which the Programmer Should Be Able to Declare Variables
A ZRF program should be able to declare (at least implicitly, by setting
the variable) a variable of any of the types ZRF recognizes, except
possibly string. Ideally there would be a way to have variables of
type boolean, position, direction, piece, player, movetype, integer, and
list.
With the ability to have a variable of a given type you'll want the
ability to have expressions of those types, meaning you need operators
that take those types as arguments. For every type you want a way to
compare 2 values for equality or inequality (yielding a boolean result),
and the ability to set a variable to a value of its type. Other
possible operators:
Possible list operators:
Times When Computations Should Be Able to Be Performed
Arbitrary computation (by which I mean doing anything except changing
the state of the board; that should be allowed only in action blocks)
should be able to be performed at any of the following times:
When computation is performed at the start of a turn, the resulting
variable values are saved; those values are then restored at the start
of the moves block for each piece (since a moves block can change the
variables further). Similarly, when computation is performed at the
start of a moves block, values are saved and then restored at the start
of each action block.
Temporal Durations and Scopes of Variables Zillions Should Support
Of course, allowing arbitrary computation at various points is not
useful if Zillions immediately forgets the results. That means some
additional durations and scopes need to be allowed for variables.
A persistent variable's value at the start of a turn is the same value
it had at the time of the "add" for the last move made.
Desirable Control Structures
A "case", in addition to the current "if" and "while", would be
syntactically convenient, although I don't know that it would have much
advantage over a series of ifs.
Example:
"foreach" might offer more benefits in terms of concision, especially in
conjunction with list variables (the first argument to foreach could
presumably be a list expression).
(foreach x (north south east west up down)
(check-for-adjacent-paralyzer x)
)
Out-of-line procedure calls (rather than macro expansions) might allow
some of the more complicated games to fit in memory. A fairly
complicated game like the 4-dimensional Chesseract requires specifying
moves in so many different directions that it's easy to generate
enormous macro expansions. Presumably these would be slower to call
than a macro; the programmer would have to use them judiciously.
Desirable Actions
These are miscellaneous things that are hard to do right now.
These operations could only be allowed in an action block, and generally
take effect only when the following "add" occurs:
These actions should, if possible, take place in the order in which they
were specified in the moves block; otherwise, the programmer may get
very confused about what happens when. Instructions that set persistent
variables should also take place in the same order, so that if I do
these operations in this order:
I wind up with piece z in the off-board pool, its variable v set to x,
and y's variable v still has the same value it had while y was in the
off-board pool.
These actions have to do with modifying the turn order:
More Stuff
The limit of 1000 distinct symbols is easily exceeded for very large
boards, which may have more than 1000 positions even if the game is
not particularly complex. It would be helpful to increase this.
Debugging
ZRF programmers are adventurous but not infallible. Adding a
sophisticated debugger to Zillions would be swell but probably not worth
the effort. However, it sure would be nice to have the debugger of last
resort, namely some kind of print statement, e.g.:
(print "The " current-piece " is now on " current-position "\")
When this statement was reached, it could display its output to the
"Moves Played" window:
The Knight is now on e6
Used incautiously, this could result in a prodigious amount of output;
used judiciously, it could allow ZRFers who have lost their way to see
where they have gone wrong.
How Zillions Treats the Off-Board Pool
I'd think it would be advantageous to make the off-board pool more
explicitly available to the player, as well as to the ZRF programmer.
If the pool could be controlled well enough to be used in Shogi, then
you'd want a display of the current contents of the pool, so that you'd
know what pieces you have in hand. I'd suggest that the pool should be
displayed in another subwindow of the Zillions window (although the
display should probably be suppressed if the game is known not to make
use of the pool).
The pool is sort of like a special "position" that, unlike all other
positions, is capable of containing an indefinite number of pieces of
different types and owners.
How Zillions Displays Possible Moves
When you select a piece, Zillions puts green circles on the places it
thinks the piece can go. If the piece you select cannot move itself,
but can cause something to happen on some other position (e.g.,
capturing a piece by shooting), Zillions doesn't always let you "move"
the piece (at least not if you program the move in the most
straightforward way).
I would have thought that Zillions would use a different algorithm to
determine how to mark the possible "destination" squares. I'd imagine
that any position (including the pool) whose content could change by
the move of a piece would be marked as a possible destination for that
piece; if you dragged the piece to that position, and more than one
move could affect that position, you'd get a menu to choose which move
you wanted.
This means that sometimes the starting square of the piece would also
be marked, e.g., in cases where the piece can promote in place. In such
cases you'd also want to mark the starting square as a possible
destination, so that picking up the piece and dropping it on the same
square could select a move that affects the starting square.
As an exception, you probably wouldn't want to mark the starting square
as a possible destination if it wound up empty and the piece that was
moving wound up on another square (this is the way most kinds of chess
pieces move). In this case, it would probably be more useful to mark
only the square where the piece wound up.
Another possibility is to include in the ZRF language an operator that
says "after adding this move, when the human player selects the current
piece, display symbol x on square y (and actually make the move if the
player drags the piece to square y)". That would certainly cover any
cases where the game designer's desires didn't match Zillions's default
behavior.
As a detail, you probably don't want to allow a ZRF file to generate
moves whose results differ only by variable settings. For example, if
you generate 2 moves, one of which moves the Flarg at a1 to b2 and sets
the Flarg's attribute cool? to false, and the other of which moves the
Flarg from a1 to b2 and sets its attribute cool? to true, Zillions
should probably consider this an error rather than giving you a menu
that tries to describe how the variable settings changed in the 2
different moves. Variables, in this view, should be visible to the
programmer but not the player. Similarly, Zillions should flag an error
if a move is generated whose sole effect is to change the value of
variables without altering the kind, owner, or number of piece(s) on
any position (counting the pool as a special position).
And in Conclusion May I Say
It may sound as if I'm being critical of ZRF, but dang, I wish I'd
thought of it.
Adrian's feedback
Feedback from João Pedro Neto
*** Proposals for ZRFs (version 1.1)
Here are a bunch of suggestions for an update of the ZRF language.
Let's shoot in all directions, in order to hit something! :-)
*****************************************
* comments for blocks, like the /* */ comments in C
*****************************************
* (last-player? player)
* (actual-player? player)
* (next-player? player)
true if the last/actual/next player is <player>
*****************************************
* (piece-of-player? player [position|direction])
true if the piece is of <player>
*****************************************
* <any-piece> declaration for win/loss-conditions
*****************************************
* Real global flags for the entire game (boolean and
position data type)
e.g., (set-global var_name bool_value)
(set-global var_name position-name)
with a place in the (game ) block for initialization
e.g.
(game ...
... (set-global var_name bool_value) ...
)
This is ideal for many things, e.g, for programming KO rules
(you can save the tabu square to compare later).
Also, with position variables, there can be instructions like,
(send-piece position1 position2)
to send a piece from one position to another, in a more
direct way.
Just a side thought: It's a known result of Theory of Computation
that if you have a programming language with iteration, conditional
and global memory, you have Turing Equivalence (that is, you can
make everything that is computable), at least up to the available
memory.
I'm not sure if the ZoG memory system satisfies the requirements
for Turing Equivalence. This may mean that certain board games
cannot be implemented, even with hard and/or unelegant work.
And that's nasty, because it's impossible to have a procedure
to decide if a game (defined by an algorithm) is programmable
or not.
*****************************************
* The option of a piece moved by other player, to be able to use
the player's symmetries only in that move turn, Nice to handle
neutral pieces that act differently depending of who is moving
them (eg. the Holes in Amoeba)
e.g.,
(piece ...
(user-symmetry true) ...
)
*****************************************
* Multiple Mark/Back mechanism, using a Stack and/or a Queue
*****************************************
* ForEach instruction, a loop process for iterate elements in a set
e.g.,
(foreach (<square> in <some-zone>)
; stuff using <square>
)
With this instruction, there may have some set generators,
for e.g.,
- (found-pieces (<player> <piece>) (...) ... )
- (found-pieces (<player> any-piece) )
- (found-pieces (any-player any-piece) )
...
returns the squares where these pieces are
- (found-pieces (condition) )
returns the squares where the pieces with condition true are
(e.g., (found-pieces (attacked?) ), get all attacked pieces)
- (found-marked-squares)
returns all marked squares
- (found-attribute <attr-name> <value> )
returns the squares where the pieces that have <attr-name>
with value <value>
*****************************************
* A CASE statement. This may be useful, if ZRF starts handling
non-boolean variables and functions.
e.g.,
(case (actual-player)
( (White) (...stuff...) )
( (Black) (...stuff...) )
( (Red) (...stuff...) )
) ;endcase
*****************************************
* Integer types with aritmetic (+,-,*,/,trunc,round) and
relacional (>,<,=,<> ...) operators.
Also, local and global integer variables would be nice.
* Random Numbers,
e.g., rand(n) gives a number between 0 and n-1
with normal distribution
Possibly, ZoG doesn't have integer types because
of performance reasons (At least it is not real aritmetic :-).
*****************************************
A last note: we know very well that it is easier to propose
things than to make them! I want to thank ZoG team for such
a great game!!!
And thank you very much for reaching EOF :)
Joao Pedro Neto (vascog@uol.com.br | jpn@di.fc.ul.pt)
Feedback from Adrian King
if a-is-1 and b-is-1 then set sum-is-2 true
else if a-is-1 and b-is-2 then set sum-is-3 true
else if a-is-2 and b-is-1 then set sum-is-3 true
else if a-is-2 and b-is-2 then set sum-is-4 true...
and, not, or: boolean operators, already provided
(owner <position/direction>):
returns <player>; owner of piece at
position
last-player, current-player, next-player:
return <player> of previous, current,
following turn
current-position: return the current <position> (applicable
only in an moves block)
current-piece: <piece> whose move is being calculated
current-movetype: <movetype> being calculated
(+/-/*///... <integer> <integer> ...):
return <integer>; the usual operators
(</>/<=/>= <integer> <integer>):
return <boolean>; integer comparisons
(append <list> <value>)
(push <list> <value>):
push is same as append, but thinking of
the list as a stack; return value
pushed/appended
(prepend <list> <value>):
like append, but add to start of list
(pop <list>): remove and return last item of list
(remove-head <list>):
remove and return first item of list
(list-item <list> <integer>):
return <integer>th item of list
(delete-list-item <list> <integer>...)
delete specified items in list
(insert-list-item <list> <integer> <value>)
insert specified items in list
(case (owner $1)
( (White) ... )
( (Black) ... )
...
)
Example:
Example:
(define-proc check-for-adjacent-paralyzer
; set paralyzer-adjacent? if enemy Paralyzer is adjacent in
; direction $1
(if (on-board $1)
... (set-flag paralyzer-adjacent? ...) ...
)
)