An if command tests whether an expression is true or false for the purpose of selectively executing code. If the expression is true, execution will continue from the next line. If the expression is false, execution will continue from the line after the matching endif or else command. If no endif command is found by the end of the function, the function will terminate normally with an implicit return true. An if command must be of the format:
if LeftValue test RightValue [LeftValue Test RightValue]...
In an if command, each set of three parameters (two values and a test), is called an expression. Each expression is evaluated to equal either true or false. If more than one expression is supplied, the entire statement is considered be true if any one of the expressions is true — a logical OR. The ifall command has the same format as an if command except the entire statement is only considered to be true if all of the expressions are true — a logical AND.
The following table lists the possible tests that can be used with an if or ifall command when the left and right values are both either an integer, an integer constant, an object or the word random. For more information on constants and random see the chapter on Internals.
![]() |
Each object defined is assigned a unique number when the game is loaded. In a set, if or ifall command, an object's label is substituted for this number. Objects are numbered in the order they appear in the game file starting at one. |
| Test | Description |
|---|---|
| = or == | This tests if the left value is equal to the right value. |
| > | This tests if the left value is greater than the right value. |
| < | This tests if the left value is less than the right value. |
| >= or => | This tests if the left value is equal to or greater than the right value. |
| <= or =< | This tests if the left value is equal to or less than the right value. |
| != or <> | This tests if the left value is not equal to the right value. |
The following are the possible tests that can be used with an if command when the left and right values are both set to an object:
| Test | Description |
|---|---|
| grandof | This tests if the left object is the grand parent of the right object. |
| !grandof | This tests if the left object is not the grand parent of the right object. |
When an object is placed inside another object, which is then placed inside yet another object, the last object is said to be the grand parent of the first. This is the case no matter how many intermediate objects there are.
The following are the possible tests that can be used with an if command when the left value is a location and the right value is an object:
| Test | Description |
|---|---|
| locationof | This tests if the left value is the location of the right object (the location reached by walking the right object's parent chain up to the first LOCATION). |
| !locationof | This tests if the left value is not the location of the right object. |
The word locationof is also valid as a value-expression keyword on the right-hand side of a set:
set dl_index = locationof noun1
The difference between grandof and locationof is that, for example, if a key is put on a keyring, which is put in a box, which is put into a bag, and the bag is in a room called "beach", locationof key would resolve to the beach, but grandof key would resolve to the bag.
The following are the possible tests that can be used with an if command when the left value is an object and the right value is an attribute. For more information see the chapter on Attributes.
| Test | Description |
|---|---|
| has | This tests if the object has the specified attribute set |
| hasnt | This tests if the object hasn't the specified attribute set |
The following are the possible tests that can be used with an if command when the left value is an object and the right value is one of the words *here, *held, *present or *anywhere. These words have the same meanings when used in an if statement as in a grammar statement. For more information see the section on Grammar Statements.
| Test | Description |
|---|---|
| is | This tests if the object is in the specified scope. |
| isnt | This tests if the object isnt in the specified scope. |
To help clarify, here are some examples of the various types of if commands:
if beach locationof bucket if sand grandof bucket if TOTAL_MOVES >= 42 if glove has WORN if guard isnt *present : id_card has WORN if noun1 = sword : noun1 = knife if sword(parent) = field
It is possible to nest if statements. Nesting involves placing a second if command before the matching endif command of a first. The end result of this is that the code between the second if command and its matching endif command will only be executed if both statements are true — a logical AND.
The endall command can be used to exit all nested conditional blocks at once by immediately resetting the nesting level to zero. This is useful when deeply nested if statements all need to be exited at a certain point without requiring a matching endif for each level. For example:
if condition1
if condition2
if condition3
write "All three conditions are true.^"
endall
In the above code, the endall command exits all three if blocks at once, removing the need for three separate endif commands.
An ifstring command is used to compare two strings of text. If the string of text is to contain any spaces, it must be enclosed in double quotes. An ifstring command must use the following format:
ifstring text test String [text Test String]...
Below is a list of the possible tests:
| Test | Description |
|---|---|
| == or = | This tests if the first string equals the second string |
| != or <> | This tests if the first string doesn't equal the second string |
| contains | This tests if the first string contains the second string |
| !contains | This tests if the first string doesn't contain the second string |
| ==C or =C | This tests if the first string equals the second string including case |
| !=C or <>C | This tests if the first string doesn't equal the second string including case |
| containsC | This tests if the first string contains the second string including case |
| !containsC | This tests if the first string doesn't contain the second string including case |
| beginswith | This tests if the first string begins with the second string |
| !beginswith | This tests if the first string doesn't begin with the second string |
Below are some examples of the ifstring command being used.
ifstring string_arg[0] contains "help" ifstring command[0] == "take"
The ifexecute command works in a similar manner to the call command (see the chapter on Functions of more information.) If the function specified after the ifexecute command exists and does not return false, executing will continue from the line after the ifexecute command. If the function being called does not exist or returns false executing will continue from after the matching endif or else command.
This command is normally only used by library code to test if the game author has provided some specific associated function and to perform some sort of default action if not. For example:
ifexecute "here.look" # ASSOCIATED FUNCTION PROVIDED, DO NOTHING else # NO ASSOCIATED FUNCTION, PERFORM DEFAULT execute "+look" endif
If the ifexecute command is true, in other words, a look function associated with the player's current location exists and does not return false, the block of code below it will be executed. As the associated look function has already performed any required action, this block contains only a comment. If this function did not exist or returned false the function +look would be executed.
The code following an else command is executed only if the matching if command was false. If the matching if command was true, execution will continue from the line after the matching endif command. For example:
if mulder has DEAD write "Clamminess on your lips.^" else write "He looks quite surprised to say the least.^" endif
Nested if commands may also make use of the else command. The fragment below sketches a hand-rolled "take all visible" routine that walks every child of the player's current location, applying the same weight and liquid checks the library's +take function uses:
loop
if noun3(parent) = here
if noun3(mass) < heavy
if noun3 hasnt LIQUID
if noun3(mass) <= player(quantity)
move noun3 to player
ensure noun3 has TOUCHED
write "You take " noun3{the} .^
else
write "You are carrying too much to take "
write noun3{the} .^
endif
else
write noun3{The} " run" noun3{s} " through "
write "your fingers.^"
endif
endif
endif
endloop
In the above code, the block of code after the first else command is executed if the matching if command above it:
if noun3(mass) <= player(quantity)
is false. This line of code will not get executed, however, if the line:
if noun3 hasnt LIQUID
is false as execution will have already jumped to after the second else command.
The loop command is used to iterate through all the objects (and locations) defined in the game. The loop command takes a single argument being the integer variable to use as a pointer to the current object during iteration. If a loop command is executed with no parameters, noun3 is used as the default variable. When the loop command is executed, iteration starts with the variable being set to 1 (the first object or location in the game file). When the matching endloop command is executed the variable is incremented to point to the next object or location and execution continues from the first line after the original loop command. The loop will end when the endloop command is executed with the iteration variable already pointing to the last object or location. When the loop ends, execution continues from the first line after the endloop command. For example, the following code will output the short description of each object and location as a list:
{+print_objects
loop
write noun3{List} ^
endloop
}
As another example, this function will output all the children of the object passed as an argument to it, using the variable POINTER as the iteration variable:
integer POINTER
{+print_children
write "Children of " arg[0]{the} ":^"
loop POINTER
if POINTER(parent) = arg[0]
write " " POINTER{the} ^
endif
endloop
}
It is not legal to nest loops unless the inner loop is within a function that is called from within the outer loop. To return out of a loop early you can either return from the currently executing function or set the iteration variable to point to the last object or location manually. For example, to return out of a loop after the fifth object, use the constant objects like this:
loop
write noun3{The} ^
if noun3 = 5
# SET THE CONTAINER noun3 TO POINT TO THE
# LAST OBJECT OR LOCATION SO THAT THE
# LOOP WILL STOP ITERATING
set noun3 = objects
endif
endloop
The index of the last object or location (which is also the number of object and locations in the game) is stored in the constant objects.
The select command is similar to the loop command except that it only iterates over a subset of game's objects. This subset is defined by passing a parameter to the select command. This parameter can be one of the following three things:
It is possible to prefix any of the above three criterion with a shriek (!) to indicate that the objects to be selected should not meet the specified criterion.
Although the loop command can be used to produce any list of objects that the select command can, the select command is significantly faster as the bulk of the filtering is done by compiled code. Even if the selected objects are filtered with further if statements as in a regular loop, the number of objects returned by the select command is usually a significantly smaller number than the total number of objects in the game, so the remaining processing is relatively quick. Below is an example of the loop above using the select command:
integer POINTER
{+print_children
write "Children of " arg[0]{the} ":^"
select arg[0] POINTER
write " " POINTER{the} ^
endselect
}
If the desired set of objects cannot be expressed with a single criterion, further if statements are placed within the loop such as this code from verbs.library:
{+contains_liquid?
# CHECK IF THE SPECIFIED OBJECT CONTAINS ANY LIQUIDS
select LIQUID CHILD
if CHILD(parent) = arg[0]
return true
endif
endselect
return false
}
Like the loop command, if a container is not specified (CHILD in the example above), noun3 is used by default.
![]() |
Be careful not to use the default iteration container noun3 more than once when using nested select loops. As it is a global variable the loops will interfere with each other if it is shared. |
| Scope | Description |
|---|---|
| *held | All objects held by the player. |
| *here | All objects in the current location. |
| *present | All objects either in the current location or currently held. |
| *anywhere | All objects in the game (use loop command instead). |
![]() |
As only a single parameter can be used with a select command it is more efficient to use the criterion that selects the smallest set of the object when if statements are required to filter the list further. For example, if you wanted a list of all the objects that are being carried by the player that don't have a mass of scenery, the it would be more efficient to use the select command to find the objects that are children of the player and then if statements to check their mass. This is because there will generally be fewer objects in the game that are currently being carried by the player than objects with a mass that isn't set to scenery. If the select command has been used to find all the objects that aren't scenery, the if statement to check if each object is also carried by the player would need to be executed many more times. |
A repeat... until loop allows you to repeat a section of code until a specified condition is true. The expression or expressions to be tested should follow the until statement, using the same syntax as an if statement.
The following is an example of a repeat... until loop:
set INDEX = 10 repeat write "DON'T PANIC! " set INDEX - 1 until INDEX = 0
The untilall command works in the same way as until except that when multiple expressions are supplied, the loop only exits when all of the expressions are true — a logical AND. This is equivalent to the relationship between if and ifall. For example:
repeat set counter + 1 untilall counter = 10 : status = 1
![]() |
loop...endloop loops and repeat...until loops cannot be nested within a single function. It is legal, however, to place a second loop within a function that is called from within the first loop. For example:
{+print_objects
set INDEX = 10
loop noun3
write noun3{the} ^
execute "+print_children"
endloop
}
{+print_children
loop noun4
if noun3 grandof noun4
write " " noun4{the} ^
endif
endloop
}
Note that it is legal, however, to nest a repeat loop inside an object loop or vice versa. |
As a repeat loop will always happen at least once, if there is any chance that a loop should happen zero times, a while loop must be used. A while loop performs its test first, then executes the code following the expression if it evaluates to true. On reaching an endwhile command it will return to the matching while command and re-evaluate the expression. When the expression eventually evaluates to false, execution will continue from the line after the endwhile command. For example, the following function will output the value of the passed arguments:
{+arg_values
set INDEX = 0
while INDEX != @arg
write "Argument" INDEX " = " arg[INDEX] ".^"
set INDEX + 1
endwhile
}
The whileall command works in the same way as while except that when multiple expressions are supplied, the loop continues only if all of the expressions are true — a logical AND. This is equivalent to the relationship between if and ifall. For example:
whileall counter < 10 : status = 1 set counter + 1 endwhile
JACL has two additional loop constructs that walk an external data source row-by-row rather than iterating over the in-memory object table. They share a syntax and only differ in how each row is supplied to the body.
An iterate...enditerate loop reads each row of a CSV file in turn. The body of the loop is executed once per row with the special integer array field[] populated with the comma-separated values from that row and the integer field_count set to how many fields were on it. The first argument is the CSV filename; an optional second argument skip_header causes the loop to discard the first row before iterating.
iterate "german_words.csv" skip_header write "Row " field[0] " says " field[1] ^ enditerate
An update...endupdate loop is the writable counterpart. The body runs for each row of the named CSV file, with the same field[] and field_count bindings, but anything the body writes back into field[] is persisted to the file when the matching endupdate is reached. This is the canonical way to mutate CSV-backed state from inside JACL.
The break command terminates either of these loops in the same way it does an endwhile or endloop; see the next section. For a full discussion of CSV iteration, including escaping conventions and the field[] contract, see the CSV chapter.
The break command is used to immediately exit the current loop. When a break command is executed, execution continues from the line after the end of the loop (endwhile, until, endloop, endselect, enditerate, endupdate). For example:
while counter < 100
if counter = 50
break
endif
set counter + 1
endwhile
In the above code, if counter reaches 50, the break command will exit the while loop immediately and execution will continue from after the endwhile command.
return or return true
The command return will stop execution of the current function and return to the next line after the corresponding execute command in the calling function. If the function containing the return command was called internally by the JACL interpreter, processing will continue, eventually returning to the command prompt for the player's next move.
return false
The command return false will stop execution of the current function just like a return command. When it returns, the JACL interpreter will behave as though the function it returned from did not exist, and was therefore not executed at all. This will, in the case of calls originating from grammar statements, cause the default action to occur.
The following is a demonstration of the use of return false:
object bond: james bond
{ask_about_bomb
if bomb(parent) = limbo
return false ;The function +ask_about will now be
; executed as though this function did
; not exist.
endif
write "~I'm glad you asked...~^"
}
This is a common technique in JACL games. Once the player has typed a command that refers to an object, the interpreter will attempt to execute the appropriate function that is associated with that object. For example, if the player was to type ask bond about the bomb, the interpreter would attempt to execute the function ask_about_bomb that is associated with the object bond (the function above). If this function does not exist, the function +ask_about will be called instead. When a return false command executed, the interpreter will behave as though the local function containing the return false does not exist. In essence, this allows a function to override the default result of a verb under some conditions and accept it under others.