NAME
Cual - Cuyo Animation Language
Cual is the main language used to describe the animations in cuyo.
Strictly speaking it’s the stuff between the << >> brackets in the
level description files (xxx.ld).
On the other hand this man page aims at being a complete description of
how to write levels for cuyo. But it’s still under construction. See
the file "example.ld" to get an idea of how the rest of the level
description works. There’s also a bit of example Cual code in
"example.ld". And of course, all the existing levels are examples.
Note that Cual is probably still very buggy. It has almost not been
tested. (The existing levels work, but that’s all.) So if strange
things happen and you’re sure it’s not your fault, tell me
(cuyo@karimmi.de).
HOW IT WORKS
The level description is organized in sections. There is a global
section and every level has its own section, which is a subsection of
the global section. It is common practice to place each level in a
separate file, which then basically starts by opening its section and
ends by closing it.
A section is defined by name = {contents}. name is the name of the new
section and contents contains the definitions that pertain to that
section. This is a sequence of definitions of the form name = stuff.
Here stuff can be {contents} as above, or it can be a single datum, or
it can be a comma-separated list of data. Inside such a list, datum *
number can be used as a shorthand for datum, ..., datum, i.e. a number-
fold repetition of datum. A datum can be an identifier, a string
(enclosed by ’"’), a word, or a number. In place of a number
<expression> can be used, where expression is an arbitrary expression
made up from literal numbers, previously defined numeric data, and the
operators +, -, *, / and %.
Definitions can also depend on versions. See section VERSIONING below.
Apart from definitions, a section can also contain cual definitions
(see below). These have to be enclosed in << and >>.
Each blob has its own (main) Cual procedure which does the drawing and
the animation stuff. The procedure only depends on the kind of the
blob, that is, it is the same for blobs of the same kind. However each
blob has its own instance of the variables.
In every game step, the procedure of each of the blobs is called once.
(There are 12.5 game steps per second.) It has to draw the blob each
time, even if nothing has changed. (However, there’s an internal
routine in cuyo which checks if the same is drawn as in the last step
and which then supresses the drawing.)
There may be other procedures associated to a kind of blob, which are
executed at special events, for example when a falling blob lands. In
contrast to the main procedure, these event handlers are not allowed to
draw anything. See section EVENT HANDLERS for a list of the existing
events.
The name of the main procedure of a blob (the one which draws the blob)
is the name of the kind of the blob. Normally, that name is the word
listed after pics= entry in the .ld file; but if that "word" contains a
dot, only the part before the dot makes up the name. (E. g. with
pics=redblob.xpm,greenblob.xpm, the names are "redblob" and
"greenblob".)
The name of an event handler procedure is the name of the kind,
followed by a dot, followed by the event name. (E.g. "redblob.land" for
the landing event of the redblob from above.)
[Explain the default procedures.]
LEVEL DATA
String valued data
name The name of the level. This appears in the list of levels as
well as in the level intro.
description
This is an optional further description of the level in its
intro.
author The name of the level author(s) for credit at the beginning of a
level.
Identifier valued data
bgpic Background picture (file name). If too small, placed at bottom.
Defaults to none.
toppic Appearance of the top border coming down (file name). Defaults
to none.
Number valued data
numexplode
The size that a group of blobs has to reach in order to explode.
This is only the level-wide default. Each kind can override
this. Whether the group does explode is also controlled by
behaviour. See section VARIABLES AND CONSTANTS for details.
toptime
Time the border takes to come down, in number of game steps.
Each game step lasts 80ms. The default value is 50 (i.e. one
pixel every four seconds).
topoverlap
Placement of toppic relative to the actual border. More
precisely, number of pixels the lower border of the picture is
below the actual border. Defaults to the height of the picture.
topstop
When the border comes down at the end of the level, number of
pixels it should stop before the bottom. Set this to the same
value as topoverlap if you want your toppic to be comletely
visible at the end. Defaults to 0.
chaingrass
If set to 1, chain reactions are neccessary to kill the grass.
Defaults to 0. More precisely, chaingrass only controls the
default for behaviour for grass blobs. See section VARIABLES
AND CONSTANTS for details.
mirror If set to 1, the level appears upside-down. Defaults to 0.
neighbours
Determines in which directions the blobs can connect to each
other in order to form groups. This is only the level-wide
default. Each kind can override this. See section VARIABLES AND
CONSTANTS for values. Defaults to neighbours_rect.
randomgreys
The time between two randomly appearing greys in game steps
(80ms). Use -1 for none at all, which is the default.
nogreyprob
The probability that a grey does not appear. See greyprob and
colourprob in section KIND DATA for details. The default is 0.
aiu_color, aiu_grass, aiu_grey, aiu_two_above,
aiu_monochromic_vertical, aiu_height
Parameters for the AI-Player utility function. Default
respectively to <10*(number of kinds)>, 20, 10, <aiu_color/2>,
<aiu_color>, and 10.
Colour valued data
(A colour is an RGB triple of numbers between 0 and 255)
bgcolor
The background colour. Defaults to white.
textcolor
Colour of any text. This includes the beginning-of-level
information, message()s, and score. Defaults to a certain shade
of dark grey.
topcolor
The colour of the top border comming down (where not determined
by toppic). Defaults to a certain shade of light grey.
Other data
startdist
Distribution of blobs at the beginning of the level. It is a
list of strings, the format of which is described in the section
STARTDIST.
pics, greypic, startpic, emptypic
Lists of kinds. These can be either file names referring to the
picture to be used, or declarations of kinds that have to be
defined later on. The different keywords (e.g. pics, emptypic)
define different defaults. In fact, only the first three may be
real lists, emptypic is limited to exactly one entry. In these
lists, it is advisable to use * whenever possible. Besides
being shorter to write, it also speeds up loading of the level
and cuts down memory usage. This is because cuyo does some
initializations only once for each entry with multiplier.
The intentions of these lists are normal blob kinds resp. grey
blob kind resp. grass blob kind resp. nothing-blob. However,
the only differences between pics, greypic and startpic are the
default values for behaviour, colourprob, goalprob, greyprob,
versions and distkey (see there). All of these can also be
overridden individually. Also, the default drawing code is
different. (The default drawing code for startpic does not draw
connections.)
kind Each kind can have its own section. See KIND DATA below for the
entries of that section.
KIND DATA
numexplode, neighbours
Defining these data in the section of a kind overrides the
level-wide value for the kind. See section LEVEL DATA for a
description of these data.
pics A list of file names of pictures to be used for this kind. The
nth entry can later be accessed in cual with pos=n-1.
colourprob
The probability that this kind appears as one of the two steered
falling blobs. More precisely, this is a nonnegative integer
weight. For determining the actual probability, the value is
divided by the sum of the colourprobs of all kinds. This sum
must be positive. The default is 1 for kinds declared with
pics= and 0 for all other kinds. The probability is also used
for + in startdist. For more details see section STARTDIST.
goalprob
This affects the semantics of * in startdist in the same way, as
colourprob does for +. The default is 1 for kinds declared with
startpic= and 0 for all other kinds.
greyprob
The probability that this kind appears as a grey blob. This is
similar to colourprob, but there is a difference: For greyprob,
nogreyprob is included in the sum, so that it might happen, that
no blob appears at all. There is a notable difference between a
positive nogreyprob and a positive greyprob in kind nothing,
when several lines of grey blobs appear: In the latter case,
empty blobs appear in the wall of greys, making holes. In the
former case, the wall is made less high. Usually this is
preferable. The default is 1 for kinds declared with greypic=
and 0 for all other kinds. The value also affects the semantics
of - in startdist. In this case, nogreyprob is not included in
the sum.
versions
At the creation of a blob, its version variable is initialized.
Usually, it is chosen at random from 0 to versions-1, but
startdist provides the possibility to specify it exactly. See
section STARTDIST for details. The default is 52 for kinds
declared with startpic and 1 for all other kinds.
distkey
An alphanumerical key, which is used in startdist to identify
this kind of blob. The default is A for kinds declared with
startpic and undefined for all other kinds. See section
STARTDIST for details.
CUAL DEFINITIONS
Inside << >>, variable and procedure definitions are expected.
procname = code ;
Defines a "procedure". The next section describes how code looks
like. Example:
redblob = {
schema16; 0*;
1; A,B,C; *;
};
var varname1 [= def1 [: reapply]], varname2 [= def2 [: reapply]], ... ;
Defines variables with default values. If no default is
specified, zero is used. See section VARIABLES AND CONSTANTS
about the meaning of the default value and the optional suffix :
reapply.
default varname1 = def1 [: reapply], varname2 = def2 [: reapply], ... ;
Changes the default for already defined variables. Again, the
suffix : reapply is optional. This is useful to give to a
single kind a different default for a variable than to the other
kinds. Also, the default of a system variable can be changed
this way.
CODE
A code fragment can be one of the following:
{ code; code; ...}
Executes one command after the other.
code, code, ...
This is useful for simple animations. Executes exactly one of
the commands: In the n-th call, the n-th command is executed.
After the last command, the first one is executed again.
However, if one of the commands is "busy" (see section
BUSIENESS), this one will be executed until it stops being busy,
and only after that, the next command will be executed.
procname
Executes the procedure procname, which has to be already
defined. The result is the same as if the code from procname
would have been inserted in that place.
&procname
Executes the procedure procname; however, every instance of such
a procname is the same. This concerns busieness and the state of
an animation sequence. (See sections BUSIENESS and AMPERSAND-
CALL.)
busy Does nothing except being busy. (See section BUSIENESS.)
varname = expr
Sets the variable. See section VARIABLES AND CONSTANTS for
details.
The same with +=, -=, *=, /=, %=, .+=, .-=.
Does what you would expect.
[ varname = expr ] code
Sets the variable varname to expr, executes code and then resets
the variable to the old value.
number A shortcut for file = number.
letter A shortcut for pos = number, where different letters mean
different numbers: A: 0, B: 1, ..., Z: 25, a: 26, ..., z: 51
* Draw the icon specified by the variables kind, file and pos.
May also draw only a part of the icon, if specified by the
variable qu (see section VARIABLES AND CONSTANTS).
*@(position)
Like *, but draws the icon at some other position. This drawing
is performed after all drawing by *. (See section VARIABLES AND
CONSTANTS for more details about @.)
@(position)*
Like *, but draws the icon at some other position. This drawing
is performed before all drawing by *. (See section VARIABLES
AND CONSTANTS for more details about @.)
if expr if-arrow if-code ;
if expr if-arrow if-code else [else-arrow] else-code ;
The arrows can be either "->" or "=>". If you use "->"
arrows, it does exactly what you would expect. If the
if-arrow is "=>", then once the expression gets true,
the if-code will be executed every subsequent time
(without testing the condition), as long as it is "busy".
For more details see section BUSIENESS. If the else-
arrow is "=>", then once the expression gets false, the
else-code will be executed every subsequent time as long
as it is busy. The else-arrow may only be omitted, if
the if-arrow is "->". Then the else-arrow also is "->".
(But this might change in the future.)
switch {
expr1 arrow1 code1 ;
expr2 arrow2 code2 ;
...
} The arrows can be either "->" or "=>". Does the same as:
if expr1 arrow1 code1
else if expr2 arrow2 code2
...
The last expr may be omitted to get an else part without
further condition.
bonus(expr)
The player gets expr bonus points.
message(String)
The string is displayed (blinking) on the screen. To be
used together with bonus(...). Example:
bonus(50);
message("You get 50 bonus points");
explode
Makes the blob explode. For the next 8 steps or so, the
blob is still what it was before, but the explosion is
drawn over the graphics. After that, it’s changed to a
nothing-blob.
sound(Filename)
Plays the given sound file.
You can also omit the code completely. Then, of course, it does
not do anything. This can be useful as part of ,-sequences.
There’s a shortcut for drawing: You may omit the ";" between a
number, a letter and the "*".
EXPRESSIONS
The only data type in cual is int. Bools are represented by 0
and 1, like in C. (And any number other than 0 is interpreted as
true, if a boolean is expected.)
Of course, variables, constants and numbers are expressions, and
you can use parentheses. There are the following operators
(listed here in order of increasing precedence):
|| Boolean or
&& Boolean and
==, !=, <, >, <=, >= Comparison
==.. A special comparison
! Boolean not
+, - Add, substract
: Special operator
*, /, % Multiply, divide, modulo
&, |, .+, .- Bitwise and, bitwise or, setting of bits (same as bitwise or), unsetting of bits
- Unary minus
. Testing of bits (a.b is the same as a&b != 0)
/ and % work mathematically correct and do not make funny
changes when the sign of the numerator changes. More
specifically, if b is positive, then a/b is the largest integer
n such that n*b<=a. If b is negative, then a/b is the largest
integer n such that n*b>=a. In both cases a%b is such, that
(a/b)*b+a%b = a. Examples:
13/5=2 13%5=3
-13/5=-3 -13%5=2
13/-5=-3 13/-5=-2
-13/-5=2 -13%-5=-3
The following are the special operators:
expr1 == expr2 .. expr3
Is true, if expr1 lies between expr2 and expr3. You may
also omit one of expr2 and expr3. (Then, it does the
same as <= resp. >=.) The precedence implies that
x==y==2..3 is the same as x==(y==2..3) and is neither
(x==y)==2..3 nor x==(y==2)..3. Note that this operator
might change in the future. (I plan to make something
like "expr in set" in Pascal.)
expr1 : expr2
Is true (that is, 1) with probability expr1/expr2
neighbour_pattern
neighbour_pattern is a sequence of six or eight
characters 0, 1 and ?. It is true if the sequence fits
to the neighbour sequence of the blob. The neighbour
sequence is a string of "0"s and "1"s with a "1" for each
neighbour of the same kind, starting above and going
clockwise. This way, you get a string of "0"s and "1"s
(six or eight, depending on wether this level is in hex
mode).
Example: 1???0??? is true iff the blob above this blob is
of the same kind and the blob below it is of different
kind.
For an empty blob the semantics is slightly different: If
in some direction there is no neighbour, because the
field ends there, the entry in the neighbour sequence is
1 nevertheless. So for an empty blob 1???0??? is true,
iff the blob above this blob does not exist or is empty
as well, and the blob below this blob exists and is not
empty.
If some blob changes its kind during a step, the
expression will still test the neighbours as they were at
the beginning of the step. (See the section VARIABLES
AND CONSTANTS for details.)
The following functions exist:
rnd(expr)
Returns a random value between 0 and expr-1
gcd(expr1, expr2)
Returns the greatest common divisor of expr1 and expr2
VARIABLES AND CONSTANTS
The following kinds of variables and constants exist:
— User defined variables (see section CUAL DEFINITIONS). At the
start of the level (or at the creation of the blob) the value
is the default value you provided. If you supplied the
default with : reapply, whenever a blob’s kind changes, the
value of the variable is also set to the default of the new
kind. There is a subtlety: This only happens if the new
value of kind is in fact different from the old one.
— System variables. These variables are always defined and have
special meanings, e.g. file and pos. Some of them are read-
only.
— User defined constants. These are defined in the main .ld
part, not in cual (not inside << >>).
— System constants. Some of them depend on properties of the
level, some are really constant.
Of each variable, there’s one instance in each blob. Normally,
you access the instance in your own blob, but with the following
syntax, you can access variables of other blops:
varname@@(x, y; side)
varname@@(x; side)
varname@@(; side)
varname@(dx, dy; side)
varname@(dx; side)
varname@()
If x and y are given, these are absolute coordinates in the grid
of blops, that is the variable is taken from the blob with
loc_x=x and loc_y=y (see under The system variables). If only x
is given, it specifies one of the two blobs that are currently
falling. If there is only one such blob left, because the other
one got stuck on some tower, the remaining blob’s coordinate is
0. Otherwise one of the two has coordinate 0, the other 1. The
value x is interpreted modulo 2.
In the @ variants, the coordinates are relative to the current
blob. The variant @@(; side) refers to the semiglobal blob, the
variant @() to the global blob (See section THE GLOBAL BLOB).
The extra part ; side is optional and specifies the side of the
game. This is only meaningful in two-player mode. side = <
specifies the left player, side = > the right player, side = =
the player to which the current blob pertains, and side = ! the
other player. @() and @@() can also be given as @ respectively
@@.
This can be done for both, reading and writing variables. It
also works for system variables (but not for constants).
In hex mode levels, for odd dx, dy should be a "half integer",
that is a number ending in ".5". This is the only place in Cual
where non-integers appear. Especially, ".5" is not allowed in
composite expressions. Therefore, also integer dy is always
allowed. If a half-integer is expected and an integer is given,
it is assumed to be rounded to above, that is 5 then represents
4.5 and -5 represents -5.5.
Caution: With mirror=1 the absolute and the relative coordinates
use different coordinate systems. Handle with extreme care.
Accessing foreign variables is not as easy as it might look at
first glance; it might easily introduce a dependence of the
internal order of execution of the blob codes. For this reason,
— reading variables with @ or @@ always returns the value of
the variable it had at the beginning of the current step,
that is, before any of the blob codes has been executed.
— when writing variables with @ or @@, the write operation will
only be executed at the end of the current step. (The write
operations are stored in a kind of queue.)
This is also true if a blob accesses its own variables with
@(0,0).
The operators +=, -=, etc. are also performed in the future if
the left hand side is an @-variable. (To be more precice, the
right hand side is calculated instantanousely.)
For illustration, look at the following six statements:
1) X += 1
2) X@(0, 0) += 1
3) X = X + 1
4) X = X@(0, 0) + 1
5) X@(0, 0) = X + 1
6) X@(0, 0) = X@(0, 0) + 1
Only 1) and 3) do the same; they simply increment X by 1.
Statement 4) sets X to one more than it was at the beginning of
the step. Statements 2), 5) and 6) cause the value of X to be
changed in the future (after the current step): X is set to one
more than:
2) the value of X just before the change (that is, X is
incremented in the future),
5) the current value of X,
6) the value of X at the beginning of the step.
Some more details
— Whenever you try to access a variable at a location which
doesn’t exist, you will get the default value. If default
values depend on the kind, the default pertaining to the blob
executing the code is used. This may change in the future.
— Changing a variable which doesn’t exist does nothing (and
does not result in an error).
[Add explanation of time slices; roughly:
@-access of variables in reality don’t access the value at
the beginning/end of the game step, but of the time slice.
The call of the main procedure of all blobs happens in the
same time slice, but each other kind of event has its own
time slice.]
The system variables
file Specifies the file number from which to take the icon
that is drawn by "*". This variable is reset to 0 before
the drawing procedure is executed.
pos Specifies the position in the file of the icon that is
drawn by "*". This variable is reset to 0 before the
drawing procedure is executed.
kind The kind of the blob. There are constants for the
possible values of this variable. If you change the
kind, you should be aware of three things:
— Expressions like "001???01" test the neighbour pattern
at the beginning of the current step. So the change of
the variable kind will not be reflected.
— In the current step, the program to draw the blob has
already been invoked (it might even be the program
which changed this variable); so in this step, the
blob will still look like one of the old kind.
However, if things are drawn after the kind has been
changed, icons from the new kind are taken.
— Defaults of the new kind that are declared with :
reapply are applied. This happens at the same time
that kind changed, but only if the new kind is
different from the old one.
version
Is assigned a hopefully distinctive value at the blob’s
creation. See versions in section KIND DATA for details.
qu Tells "*" which part of the icon to draw. It’s possible
to draw the whole icon, or only one of its quarters. If a
quarter is drawn, you may specify independently which of
the quarters to take and at which position to draw it.
Use the constants (see below). This variable is reset to
"draw all" before the drawing procedure is executed.
out1, out2
Set these Variables for debug output. The values will be
printed on top of the blob. These variable are reset to
"output nothing" before the drawing procedure is
executed. (In fact, "output nothing" is one special big
value.)
inhibit
Set this variable to a sum of the constants DIR_...; this
will inhibit that this blob connects into the given
directions. This is not for the graphics but for the
calculation of the connected components and the
explosions.
behaviour
This is a bit field. Refer to The Constants below for
the meaningful of its bits. The default is
calculate_size+explodes_on_size for normal blobs,
explodes_on_explosion+explodes_on_chainreaction for grey
blobs, 0 for the empty blob and
goalblob+explodes_on_explosion+explodes_on_chainreaction
or goalblob+explodes_on_chainreaction (depending on
whether chaingrass is set) for grass blobs.
falling_speed, falling_fast_speed
These variables are only used in the semiglobal blobs.
They define the vertical speed of the steered falling
blobs. The unit is pixels per game step. The defaults
are 6 and 32.
The system read-only variables
turn Is 1 resp. 2 if the blob is falling and just being turned
by the user and 0 otherwise. (1 in the first turning
step, 2 in the second one.) Be aware that if the user
presses the turn key fast several times, some of these
steps may be omitted. (Use the turn event if you want to
be sure that a program block is executed once for every
turn.)
connect
Contains internal data. Will be removed. Probably.
size The size of the component of the blob. (That is, how many
blobs are connected.)
loc_x, loc_y
The absolute coordinates of the blob. (0,0) = top left
corner
loc_xx, loc_yy
The absolute coordinates of the blob in pixels. This is
not always the same as loc_x*32 and loc_y*32,
particularly for the steered falling blobs.
loc_p The player of the blob (1 or 2)
falling
true, if the blob is falling. (Falling in the sense of
steered by the player. Grey blobs are not falling in that
sense.)
falling_fast
true, if the blob is falling fast, that is, the user
pressed the down key.
players
The number of players.
exploding
When the blob is exploding, the position in the explosion
animation (1 to 8); 0 else.
Currently, there is one exception: if the explosion has
been triggered by the explode command, then exploding
will have value 1 only after the current game step [more
precisely: time slice; fix that]. Reason: when reading
exploding@(x,y), we maybe don’t know yet that the other
blob calls explode.
The Constants:
Constants for behaviour:
goalblob
Set goalblob if this blob should act like grass: You will
have to get rid of it to win the level and making this
blob explode will give more points.
calculate_size
When this bit is set, size will be regularly updated to
the sum of weight in the connected component.
explodes_on_size
When this bit is set, a connected component explodes,
when it has size>=numexplode.
explodes_on_explosion, explodes_on_chainreaction
When these bits are set, the blob explodes whenever an
explosion, that was triggered by explodes_on_size,
happens in its neighbourhood. explodes_on_chainreaction
refers to those triggering explosions, that are the
second or later part of a chain reaction.
explodes_on_explosion refers to the other ones.
Constants for kind:
<name of kind of blob>
For each kind of blob, there’s one constant with the name
of that kind. Use it to check if a blob is of that kind
using kind@(x,y) == aKind or to change to that kind using
kind = aKind. See kind under The system variables for
the side-effects of setting kind.
Sometimes it is neccessary to perform arithmetic on
kinds, for example when several have been declared using
the * multiplier. The values of the constants are
succesive in the order, in which the kinds have been
declared. When a name is used several times, the first
use defines the value. Example:
startpic = apple, orange
pics = orange, pear, apple * 3, banana
greypic = pineapple
This initializes 2 kinds with the defaults for startpic,
6 kinds with the defaults for pics, and 1 kind with the
defaults for greypic. The value of the constant orange
is 1 more than that of apple, pear is 2 more than orange,
banana is 4 more than pear and pineapple is 1 more than
banana. We do not specify what these values actually
are.
This constant also exists for the empty kind, if one has
been declared using emptypic. In this case the value’s
relation to the other values is not specified at all.
global, semiglobal
Denote the kind of the global, respectively semiglobal,
blob.
nothing
Is the same as the constant for the empty kind. Is
provided, because sometimes, you don’t have an empty
kind, but you still need to test if a blob is empty.
outside
The value of kind if the coordinates are outside of the
game board.
Constants for neighbours:
neighbours_rect
A blob connects up, down, left, and right. This is the
default.
neighbours_diagonal
A blob connects diagonally.
neighbours_hex6
When used in the level-wide neighbours, this sets hex
mode. A blob connects up, down, left with a slight
upwards shift, left with a slight downwards shift, right
with a slight upwards shift, and right with a slight
downwards shift.
neighbours_hex4
When used in the level-wide neighbours, this sets hex
mode. A blob connects left with a slight upwards shift,
left with a slight downwards shift, right with a slight
upwards shift, and right with a slight downwards shift.
neighbours_knight
A blob connects in knight moves (Two forward and then one
sideways. Forward is one of up, down, left or right.
Sideways is perpendicular to forward. This makes a total
of eight directions.).
neighbours_eight
Combines neighbours_rect with neighbours_diagonal.
neighbours_3D
A more obscure mode created especially for 3d.ld. When
used in the level-wide neighbours, this sets hex mode. A
blob connects up, down, two (but not one) to the left,
and two to the right. In even columns it also connects
right with a slight upwards shift. In odd columns it
also connects left with a slight downwards shift.
neighbours_none
A blob does not connect at all.
Constants for qu:
Q_ALL Value for qu, which means "draw the complete picture".
Q_TL, Q_TR, Q_BL, Q_BR
Values for qu. "TL" means draw top-left quarter, etc.
(See the "*" command in the Code section.)
Q_SRC_DST
SRC and DST may be TL, TR, BL, BR. Take quarter SRC and
draw it at position DST
Miscellanious constants:
DIR_XX To be used with the variable inhibit to prevent the blob
connecting in the given directions. XX can be U, D, L, R
(horizontal and vertical); UL, UR, DL, DR (diagonal);
UUL, UUR, DDL, DDR, LLU, LLD, RRU, RRD (knight); F, B
(3d)
VERSIONING
Sometimes it is neccessary to define a level slightly
differently for different purposes. For example you might need
to decrease numexplode for the two-player version lest it
becomes unplayable. The difficulty settings from cuyo’s main
menu provide another exmaple.
This is done by qualifiing a definition with the versions it
should apply to. It is best explained by an example:
numexplode = 8
numexplode[2] = 6
numexplode[1,hard] = 10
This specifies, that normally numexplode should be 8. In two-
player mode it should be 6. In one-player hard mode it should be
10. Here the specifiers "2" for two-player mode, "1" for one-
player mode and "hard" for hard mode are used. Along with
"easy" for easy mode these are all the specifiers that cuyo uses
by itself.
Additionally, you can make up and use your own specifiers. In
order for them to take effect, though, you have to give cuyo
additional information about the current version. This is done
on the command line using the --version option. For example
cuyo --version=hard,de
Specifies both hard version (you can change that in the menu)
and version "de", which is not predefined (although many levels
use it to select German text).
There are several constraints to be observed:
— All versions of a definition must be made before the first
use of the thing defined. As sometimes it is not obvious
where the defined thing is used (for example startpic uses a
previous greypic by assigning successive numbers to kinds),
it is best to group all these versions into one block.
— A given version also applies to every more specialized
version, for which no definition is given. In the above
example, numexplode is set to 6 in two-player hard mode and
in two-player easy mode as well.
All resulting conflicts must be resolved. For example, if you
make a definition for [2] and one for [hard], you must also
make a definition for [2,hard] (or, equivalently, for
[hard,2]), because otherwise it would be ambiguous which of
the two former should apply in two-player hard mode.
— Cuyo knows, that easy and hard exclude each other.
Consequently, it is unneccessary (and indeed prohibited) to
give an [easy,hard] definition, even if both [easy] and
[hard] are given. The same holds for 1 and 2.
— Furthermore, cuyo knows, that 1 and 2 are exhaustive: There
is no mode which is neither single-player nor two-player.
(The human-versus-AI mode counts as two-player as far as cual
is concerned.) Therefore, if there are definitions for both,
it is unneccessary, (and again illegal) also to define a
version without any of both. For example, if [1,de] and
[2,de] are given, [de] must be omitted. Alternatively, you
could of course give [1,de] and [de] or [de] and [de,2].
BUSIENESS
(No, not Business ;-)
Busieness is a concept to make it easier to implement simple
animated sequences which are triggered by certain events. Each
code fragment has an internal state which tells if it is busy.
— Normal statements like assignments are never busy.
— A chain of commands separated by "," is busy as long as not
all of the commands have been executed.
— code1 ; code2 is busy as long as at least one of code1 and
code2 are busy.
Here’s an example of how to use busieness for an animation which
appears at random intervals:
switch {
1/100 => {B*, C*, D*, E*};
-> A*;
};
This code fragment normally draws the icon at position A (0).
But in each step, with a probability of 1/100, an animation
sequence consisting of icons B, C, D and E is started. With a
normal arrow ("->") after the "1/100", after the step in which B
has been drawn, the probability would be 99/100 that A is drawn
again. But with the double arrow, the switch statement won’t
switch back to A until the animation has terminated.
(Btw: It doesn’t matter if there’s a "->" or a "=>" before the
"A*"; A* isn’t busy anyway.)
THE GLOBAL BLOB
Apart from the normal blobs which you can see on screen, there’s
one global blob (for the whole game, not one for each player),
which, well, isn’t really a blob, but behaves a bit like it. It
has its own set of variables, and it can have a program which is
run once every step. To define such a global program, use
global=code. However, the global variables do exist even if you
don’t define global code. See section VARIABLES AND CONSTANTS on
how to access them. Note that the global blob is always
executed before any of the normal blobs.
There are also semiglobal blobs. There is one for each player.
These are programmed with semiglobal=code.
EVENT HANDLERS
The following events exist:
init Is called only once, when the blob gets into life, just
before the first time its main drawing routine is called.
turn Is called for falling blobs each time the user presses
the turn key.
land Is called when the steered blob lands.
changeside
Is called when a blob moves from one player to the other,
just after the blob has arrived at the new player.
connect
Is called when the connection of blobs is recalculated.
row_up1, row_up2
Is called when player 1 resp. 2 gets a row from the other
side.
row_down1, row_down2
Is called when player 1 resp. 2 gives a row to the other
side.
keyleft, keyright, keyturn, keyfall
Is called when the player presses the left, right, turn
or fall key. Is only called for the steered falling
blobs and the semiglobal blob, though.
THE LIFE OF A BLOB
Normal blobs come into life at the beginning of the game, or
they fall into life: either as colored blobs, steered by the
user, or as grey blobs. When a blob moves (by gravitiy or when
rows go from one player to another), it takes its variables with
it. When a blob explodes, it does not stop existing. Rather, it
transforms into an empty blob. That’s important for the
variables: The empty blob still has all the variables set to the
values they had before; only its kind is different. Empty blobs
are everywhere where there’s no other blob. (However, the
falling blobs steered by the user are in some sense "above"
everything else; there are empty blobs beneath them.)
The life of empty blobs is different from the one of normal
blobs. Empty blobs are not affected by gravity, and they often
start or stop existing. For example, when a single grey blob is
falling down, the empty blob below it stops existing when the
grey blob arrives and a new empty blob starts existing when the
grey blob moves on. There is only one situation in which empty
blobs move: When a row moves from one player to the other, and
everything moves up resp. down, the empty blobs move, too.
STARTDIST
The format of the startdist field is rather complicated. On the
plus side, this means that many things can be done with little
effort. We first describe the single-character format, which,
at the time of this writing, has sufficed for all needs. After
that, we describe the general format as an extension.
Every line of the startdist describes one row of blobs in the
level’s initial state. The lines are aligned to the bottom and
the topmost lines come first (normal reading order). Each line
must contain exactly 10 or exactly 20 characters. In a line of
length 20 the first 10 characters describe the left player, the
second 10 characters describe the right player. A line of length
10 describes both players. Hence, each character describes one
blob. The semantics are:
. An empty blop.
+, -, *
A blop chosen at random according to colourprob,
respectively greyprob, respectively goalprob. The value
of nogreyprob has no influence.
0..9, A..Z, a..z
These characters denote a specific kind. If the
character matches the distkey of some kinds, the first of
these is chosen. More generally, these characters are
ordered such that "9" comes before "A" and "Z" comes
before "a". In this order, the maximal distkey, which
does not come after the character, specifies the blob’s
kind. The difference between the character and the
distkey then specifies the blob’s version.
Example 1: In the special case, where the character
exactly matches a distkey, version is set to 0.
Example 2: Suppose kind apple has distkey = "A", kind
orange has distkey = "O" and no further distkeys exist.
Then the character "C" denotes an apple with version=2,
the character "N" denotes an apple with version=13, the
character "O" denotes an orange with version=0, the
character "S" denotes an orange with version=4, the
character "a" denotes an orange with version=12, and the
character "8" does not denote anything (and hence is
illegal).
In this way, startdist can reference 62 kind/version
combinations directly (and more at random). Because this might
at some time not be enough, the multichar extension has been
introduced. In this case, each blob is described by more than
one character. However, the number of characters per blob must
be the same for all keys. Hence, the lengths of startdist lines
then must be this number multiplied by 10 or by 20. Every
multicharacter combination starting with ".", "+", "-", or "*"
is treated as the corresponding character in single-character
format. All other character combinations are treated as numbers
in base 62 representation. Here, "A" to "Z" are digits with
decimal value 10 to 35, and "a" to "z" are digits with decimal
value 36 to 61. Leading spaces are allowed instead of zeroes
(however, the all-space string is forbidden). The maximal
distkey which (as a number) is not larger than the number given
in startdist, specifies the blob’s kind. The difference between
the startdist number and the distkey then specifies the blob’s
version. In the case of multichar distkeys, the default for
distkeys of kinds declared by startpic= is 10 in decimal.
For blops whose kinds are chosen at random (i.e. characters "+",
"-", "*" in single-character startdists), cuyo tries to make
these as different as possible. That means, by a certain
heuristic, cuyo minimizes the number of neighbouring blobs of
the same kind. "Neighbouring", of course, refers to the
neighbours entry. inhibit and the calculate_size bit of
behaviour have no effect (these are mutable during the lifetime
of blobs, while at the time of startdist processing, no blob’s
lifetime has started yet). So the only way to influence the
unneighbouring (if you really wish to do so), is by setting
neighbours appropriately. (Of course, this possibility is even
more limited, when you intend to set the calculate_size bit
during the blob’s lifetime.)
WHERE DO I PUT THE CUAL CODE?
Cual procedures and variables can be defined in different
sections of the .ld files:
— Outside of everything; that code is accessible from every
level coming after that definition.
— In the section of a level.
— In the section of a kind.
This basically does what you expect. However, there’s one thing
you might want to know: Even if you define a variable inside a
kind, every blob in that level will have that variable. The only
effect of defining the variable in the section of a kind is that
this kind is the only one which can access it.
AMPERSAND-CALL
To explain a bit what calling a procecure with an & means, here
two examples:
Example 1:
<<
myblob = {
...
switch {
myvar -> { 0A*; 1; A,B,C,D; *; 2A*};
-> { 0B*; 1; A,B,C,D; *; 2B*};
};
};
>>
Example 2:
<<
anim = {1; A,B,C,D; *};
myblob = {
...
switch {
myvar -> { 0A*; &anim; 2A*};
-> { 0B*; &anim; 2B*};
};
};
>>
The difference between these examples is what happens when myvar
changes. In example 1, the animation "A, B, C, D" will restart
at the beginning (because the two animations are different
ones); in example 2, the "same" animation is used in both cases,
so the animation will simply continue. (Removing the ampersands
from example 2 will turn the behaviour to the one of example 1.)
SEE ALSO
cuyo(6)
BUGS
Probably a lot. The following are just a few known ones:
There are several problems with busieness and that stuff. There
are several situations in which Cual doesn’t behave in the way I
would like, and in other situations I don’t know how Cual should
behave.
2008-2-13