Flow Control

The IF, IFALL, ENDIF and ENDALL Commands

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.
Information 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.

The IFSTRING Command

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

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 ELSE Command

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 and ENDLOOP Commands

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 and ENDSELECT Commands

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:

  1. an object that all the selected objects must be a direct child of
  2. an attribute that all the selected objects must have, or
  3. a scope that all the selected objects must be in

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.
Warning 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.
The following scopes can be used with the select command:
Scope Description
*heldAll objects held by the player.
*hereAll objects in the current location.
*presentAll objects either in the current location or currently held.
*anywhereAll objects in the game (use loop command instead).
Information 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.

The REPEAT, UNTIL and UNTILALL Commands

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
Warning 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.

The WHILE, WHILEALL and ENDWHILE Commands

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

The ITERATE, ENDITERATE, UPDATE and ENDUPDATE Commands

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

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.

The RETURN 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.

Back to Contents