Control Structures

From BISim Wiki
Jump to: navigation, search

Control Structures are statements which are used to control execution flow in the scripts. They are sequences of scripting code which help to control complex procedures. You can use control structures to define code which is only executed under certain conditions or repeated for a couple of times.

Note for advanced readers: The scripting language control structures are normal scripting commands, with no special handling compared to other commands. This is different from most imperative programming languages (like C), where control statements are implemented in the core language grammar. The "controlling" done by them is implemented by accepting code as an argument. The complex control structures like "while ... do ..." are implemented using helper types, like While Type.


Contents

Requirements

To fully understand this article you should read the following articles:

Conditional Structures

Conditional structures help to define code which is only executed under certain circumstances.

if-Statement

The if-statement defines code that is only executed if a certain condition is met:

if (CONDITION) then {
    STATEMENT;
    ...
};

Example:

if (damage car > 0) then {
    hint "damaged"
};

In this example, the condition is that the car damage is greater than zero.


Alternative Code (else)

Via the else clause it is possible to define code which is executed when the condition is not true.

if (CONDITION) then {
    STATEMENT_TRUE;
    ...
} else {
    STATEMENT_FALSE;
    ...
};

The second sequence of statements (STATEMENT_FALSE) are executed when CONDITION is false.

if (damage car > 0) then {
    hint "damaged"
} else {
    hint "intact"
};


Alternative Code (elseif)

Via the elseif clause (available since V3.7) it is possible to define multiple conditional code sections which are only executed when the respective condition is true.

if (getDammage player == 0) then {
  hint "no damage";
} elseif (getDammage player < 0.3) then {
  hint "damage is less than 0.3"; 
} elseif (getDammage player < 1) then {
  hint "player is not dead yet";
} else {
  hint "player is dead";
};

Conditional assignments

If...then structures can also be used to assign conditional values to variables:

_state = if (alive player) then {true} else {false}; // _state contains true if player is alive
_state = if (alive player) then {"alive"} else {"dead"}; // _state contains "alive" if player is alive


Nested if-Statements

Since the if-statement is itself a statement, you can also create nested if-statements.

Example:

if (alive player) then {
    if (someAmmo player) then {
        hint "The player is alive and has ammo!";
    } else {
        hint "The player is out of ammo!";
    };
} else {
    hint "The player is dead!";
};


switch-Statement

To execute specific code, depending on that value contained in a variable (or returned by a statement), multiple nested if blocks could be used:

if (_color == "blue") then {
    hint "What a nice color";
} else {
    if (_color == "red") then {
        hint "Don't you get aggressive?";
    }
};

The more values there are to compare, the longer and harder to read the code will get.
To make this process easier to code and to read, the switch-statement was introduced:

switch (VARIABLE) do {

    case VALUE1: {
        STATEMENT1;
        ...
    };

    case VALUE2: {
        STATEMENT2;
        ...
    };

    ...
};

The structure compares VARIABLE against all given values (VALUE1, VALUE2, ...). If any of the values matches, the corresponding block of statements is executed.

Example:

switch (_color) do {

    case "blue": {
        hint "What a nice color";
    };

    case "red": {
        hint "Don't you get aggressive?";
    };
};

default-Block

To catch values that are not explicitly defined in one of the case definitions, a default block can be used (Note that there is no colon after the default tag!):

switch (VARIABLE) do {

    case VALUE1: {
        STATEMENT;
        ...
    };

    case VALUE2: {
        STATEMENT;
        ...
    };

    default {
        STATEMENT;
        ...
    };
};

Variable assignments

Switch can be used to assign different values to a variable:

_color = switch (side player) do {
  case west: {"ColorGreen"}; 
  case east: {"ColorRed"}; 
};


Loops

Loops are used to execute the same code block for a specific or infinite number of times.

waitUntil-Loop

This loop repeats the same code block as long as a given boolean condition is false.

waitUntil {
    STATEMENT;
    ...
    CONDITION
};

Note that condition is practically return value of the code block. The loop processes as follow:

  1. Execution of all nested statements. Go on to 2.
  2. CONDITION is evaluated. If it is false, go back to 1., else quit loop.

If CONDITION is always true, the loop quits after single iteration of STATEMENT.

Because the test of the waitUntil expression takes place after execution of the loop, a waitUntil loop executes one or more times. A waitUntil loop can be used only inside scheduled code, and since VBS2 NG it will be interrupted every 3.0ms to let other scripts run. It will resume on the next simulation step.

Example:

_counter = 0;

waitUntil {
    _counter = _counter + 1;
    _counter < 10
};


while-Loop

This loop repeats the same code block as long as a given boolean condition is true.

while {CONDITION} do {
    STATEMENT;
    ...
};

Note the curled braces for the condition (as opposed to round parentheses for if statements). The loop processes as follow:

  1. CONDITION is evaluated. If it is true, go on to 2., else skip the block and go on with the code following the loop.
  2. Execution of all nested statements. Go back to 1.

If CONDITION is false from the start, the statements within the block of the loop will never be executed.

Because the test of the while expression takes place before each execution of the loop, a while loop executes zero or more times. This differs from the for loop, which executes one or more times. Since VBS2 NG; a while loop inside a spawned or execVMd thread will be interrupted every 3.0ms to let other scripts run. It will resume on the next simulation step. Also, a while loop will terminate without warning after 10,000 iterations if started from within a non-scheduled environment.

Example:

_counter = 0;

while {_counter < 10} do {
    _counter = _counter + 1;
};

for-Loop

The for-loop repeats the same code block for a specific number of times.

All for loops whether spawned or execVMd or called are blocking and atomic in execution. They will not terminate or be interrupted after a certain time nor after a number of fixed iterations (unless there is a float overflow or other unexpected failure of the conditional statement). This differs from while loops, and has the potential to freeze up VBS.

for [{BEGIN}, {CONDITION}, {STEP}] do {
    STATEMENT;
    ...
};

The loop processes as follows:

  1. BEGIN is executed
  2. CONDITION is evaluated. If it is true, go on to 3., else skip the block and go on with the code following the loop.
  3. The statements in the code block are executed
  4. STEP is executed, go on to 2.

If CONDITION is false from the beginning on, the code block will never be executed.

Example:

// a loop repeating 10 times, displaying the numbers 0 to 9.
for [{_i=0}, {_i<10}, {_i=_i+1}] do {
    player globalChat str (i);
};

Description:

  1. The variable _i is set to 0
  2. The condition _i<10 is evaluated, which is true until _i surpasses 9.
  3. The code player globalChat _i; is executed
  4. The variable _i is incremented by 1, back to step 2.

for-from-to-Loop

There exists an alternate syntax of the for-loop, which simplifies the last example a bit, and improves the performance of the loop as well.

for "VARNAME" from STARTVALUE to ENDVALUE do {
    STATEMENT;
    ...
};

The loop processes as follows:

  1. A variable with the name VARNAME is initialized with STARTVALUE
  2. If VARNAME is not equal to ENDVALUE, the code block is executed
    1. If ENDVALUE is greater than STARTVALUE, the variable VARNAME is incremented by 1
    2. If ENDVALUE is less than STARTVALUE, the variable VARNAME is decremented by 1
  3. Go back to step 2

The following example is semantically equal to the last example.

Example:

// a loop repeating 10 times
for "_i" from 0 to 9 do {
    player globalChat format["%1",_i];
};

Description:

  1. _i is set to 0
  2. player globalChat 0 is executed
  3. _i is incremented by 1 => _i is now 1
  4. Back to step 2

for-from-to-Loop with custom step

The default step to increment the variable in for-from-to-Loops is 1. You can set a custom step though using this syntax:

for "VARNAME" from STARTVALUE to ENDVALUE step STEP do {
    STATEMENT;
    ...
};

The rest is equal to the above section.

Example:

// a loop repeating 5 times
for "_i" from 0 to 9 step 2 do {
    player globalChat format["%1",_i];
};

Description:

  1. _i is set to 0
  2. player globalChat 0 is executed
  3. _i is incremented by 2 => _i is now 2
  4. Back to step 2

forEach-Loop

To step through every element of an array, the forEach command can be used:

{
    STATEMENT;
    ...
} forEach ARRAY;

The code block is executed exactly (count ARRAY) times.

You may use the special variable _x within the code block, which always references to the current item of the array. Another special variable inside forEach loop is _forEachIndex accessible within the code block, representing current item's array index.

Example:

_array = [unit1, unit2, unit3];
{
    _x setDamage 1;
} forEach _array;

Description:

  1. In the first loop, the statement unit1 setDamage 1; is executed
  2. In the second loop, the statement unit2 setDamage 1; is executed
  3. In the third loop, the statement unit3 setDamage 1; is executed

While it is possible to nest 'forEach' loops, if the '_x' value of an outer loop is needed within an inner one, it has to be assigned to a local variable:

{
  _unit = _x;
  {
     player sidechat format["Unit '%1', weapon: '%2'",_unit,_x]
  } forEach (weapons _x);
} forEach allUnits;

NOTE: Each iteration (also when it consists of multiple commands) will be executed within one frame, so be aware of large and/or complex code blocks!

count-Loop

It is possible to use count command in place of forEach, featuring minor performance improvement over forEach at the cost of lack of _forEachIndex variable.

{
    STATEMENT;
    ...
    EXPRESSION
} count ARRAY;

The code block is executed exactly (count ARRAY) times.

You may use the special variable _x within the code block, which always references to the current item of the array. The value returned by command is count of return values of each blocks equal to true. It is mandatory for code block to return defined boolean value - true or false.

Example:

_array = [unit1, unit2, unit3];
{
    _x setDamage 1;
    false
} count _array;

Description:

  1. In the first loop, the statement unit1 setDamage 1; is executed
  2. In the second loop, the statement unit2 setDamage 1; is executed
  3. In the third loop, the statement unit3 setDamage 1; is executed

While it is possible to nest 'count' loops, if the '_x' value of an outer loop is needed within an inner one, it has to be assigned to a local variable:

{
  _unit = _x;
  {
     player sidechat format["Unit '%1', weapon: '%2'",_unit,_x];
     false
  } count (weapons _x);
} count allUnits;

NOTE: Each iteration (also when it consists of multiple commands) will be executed within one frame, so be aware of large and/or complex code blocks!


Return Values

Control structures (with exception of count) always return the last expression evaluated within the structure. Note that there must not be a semicolon (;) after this value, otherwise Nothing is returned.

example:

_ret = if (myCondition) then {myValueA} else {myValueB};

=> returns myValueA or myValueB


See also

Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox