Flow Control¶
BREAK
¶
Breaks out of a loop:
SET X TO 1.
UNTIL 0 {
SET X TO X + 1.
IF X > 10 { BREAK. } // Exits the loop when
// X is greater than 10
}
IF
/ ELSE
¶
Checks if the expression supplied returns true. If it does, IF
executes the following command block. Can also have an optional ELSE
to execute when the IF
condition is not true. ELSE
can have another IF
after it, to make a chain of IF
/ELSE
conditions:
SET X TO 1.
IF X = 1 { PRINT "X equals one.". } // Prints "X equals one."
IF X > 10 { PRINT "X is greater than ten.". } // Does nothing
// IF-ELSE structure:
IF X > 10 { PRINT "X is large". } ELSE { PRINT "X is small". }
// An if-else ladder:
IF X = 0 {
PRINT "zero".
} ELSE IF X < 0 {
PRINT "negative".
} ELSE {
PRINT "positive".
}
Note
The period (.
) is optional after the end of a set of curly braces like so:
// both of these lines are fine
IF TRUE { PRINT "Hello". }
IF TRUE { PRINT "Hello". }.
In the case where you are using the ELSE
keyword, you must not end the previous IF
body with a period, as that terminates the IF
command and causes the ELSE
keyword to be without a matching IF
:
// works:
IF X > 10 { PRINT "Large". } ELSE { PRINT "Small". }.
// syntax error - ELSE without IF.
IF X > 10 { PRINT "Large". }. ELSE { PRINT "Small". }.
LOCK
¶
Locks an identifier to an expression. Each time the identifier is used in an expression, its value will be re-calculated on the fly:
SET X TO 1.
LOCK Y TO X + 2.
PRINT Y. // Outputs 3
SET X TO 4.
PRINT Y. // Outputs 6
LOCK follows the same scoping rules as the SET command. If the variable name used already exists in local scope, then the lock command creates a lock function that only lasts as long as the current scope and then becomes unreachable after that. If the variable name used does not exist in local scope, then LOCK will create it as a global variable, unless @NOLAZYGLOBAL is set to off, in which case it will be an error.
Note that a LOCK expression is extremely similar to a user function. Every time you read the value of the “variable”, it executes the expression again.
Note
If a LOCK
expression is used with a flight control such as THROTTLE
or STEERING
, then it will get continually evaluated in the background each physics tick.
UNLOCK
¶
Releases a lock on a variable. See LOCK
:
UNLOCK X. // Releases a lock on variable X
UNLOCK ALL. // Releases ALL locks
UNTIL
loop¶
Performs a loop until a certain condition is met:
SET X to 1.
UNTIL X > 10 { // Prints the numbers 1-10
PRINT X.
SET X to X + 1.
}
Note
If you are writing an UNTIL
loop that looks much like the
example above, consider the possibility of writing it as a
FROM loop instead.
Note that if you are creating a loop in which you are watching a physical value that you expect to change each iteration, it’s vital that you insert a small WAIT at the bottom of the loop like so:
SET PREV_TIME to TIME:SECONDS.
SET PREV_VEL to SHIP:VELOCITY.
SET ACCEL to V(9999,9999,9999).
PRINT "Waiting for accellerations to stop.".
UNTIL ACCEL:MAG < 0.5 {
SET ACCEL TO (SHIP:VELOCITY - PREV_VEL) / (TIME:SECONDS - PREV_TIME).
SET PREV_TIME to TIME:SECONDS.
SET PREV_VEL to SHIP:VELOCITY.
WAIT 0.001. // This line is Vitally Important.
}
The full explanation why is in the CPU hardware description page.
FOR
loop¶
Loops over a list collection, letting you access one element at a time. Syntax:
FOR variable1 IN variable2 { use variable1 here. }
Where:
- variable1 is a variable to hold each element one at a time.
- variable2 is a LIST variable to iterate over.
Example:
PRINT "Counting flamed out engines:".
SET numOUT to 0.
LIST ENGINES IN MyList.
FOR eng IN MyList {
IF ENG:FLAMEOUT {
set numOUT to numOUT + 1.
}
}
PRINT "There are " + numOut + "Flamed out engines.".
Note
If you are an experienced programmer looking for something more like the for-loop from C, with its 3-part clauses of init, check, and increment in the header, see the FROM loop description. The kerboscript ‘for’ loop is more like a ‘foreach’ loop from other modern languages like C#.
FROM
loop¶
Identical to the UNTIL loop, except that it also contains an explicit initializer and incrementer section in the header.
Syntax:¶
FROM
{ one or more statements }UNTIL
Boolean_expressionSTEP
{ one or more statements }DO
one statement or a block of statements inside braces ‘{}’
Quick Example:
print "Countdown initiated:".
FROM {local x is 10.} UNTIL x = 0 STEP {set x to x-1.} DO {
print "T -" + x.
}
Note
If you are an experienced programmer, you can think of the FROM
loop as just being Kerboscript’s version of the generic 3-part
for-loop for( int x=10; x > 0; --x ) {...}
that first appeared
in C and is now so common to many programming languages, except
that its Boolean check uses the reverse of that logic because it’s
based on UNTIL loops instead of WHILE loops.
What the parts mean¶
FROM
{ one or more statements }- Perform these statements at the beginning before starting the first
pass through the loop. They may contain local declarations of new
variables. If they do, then the variables will be local to the body
of the loop and won’t be visible outside the loop. In this case the
braces
{
and}
are mandatory even when there is only one statement present. To create a a null FROM clause, give it an empty set of braces.
- Perform these statements at the beginning before starting the first
pass through the loop. They may contain local declarations of new
variables. If they do, then the variables will be local to the body
of the loop and won’t be visible outside the loop. In this case the
braces
UNTIL
expression- Exactly like the UNTIL loop. The loop will run this
expression at the start of each pass through the loop body, and if
it’s true, it will abort and stop running the loop. It checks before
the initial first pass of the loop as well, so it’s possible for the
check to prevent the loop body from even executing once. Braces
{
..``}`` are not used here because this is not technically a complete statement. It is just an expression that evaluates to a value.
- Exactly like the UNTIL loop. The loop will run this
expression at the start of each pass through the loop body, and if
it’s true, it will abort and stop running the loop. It checks before
the initial first pass of the loop as well, so it’s possible for the
check to prevent the loop body from even executing once. Braces
STEP
{ one or more statements }- Perform these statements at the bottom of each loop pass. The purpose
is typically to increment or decrement the variable you declared in
your
FROM
clause to get it ready for the next loop pass. In this case the braces{
and}
are mandatory even when there is only one statement present. To create a null FROM clause, give it an empty set of braces.
- Perform these statements at the bottom of each loop pass. The purpose
is typically to increment or decrement the variable you declared in
your
DO
one statement or a block of statements inside braxes{
..``}``:- This is where the loop body gets put. Much like with the UNTIL and FOR loops, these braces are not mandatory when there is only exactly one statement in the body, but are a very good idea to have anyway.
Why some braces are mandatory¶
Some braces are mandatory (for the FROM
and STEP
clauses) even
when there is only one statement inside them, because the period that
ends a single statement would look like it’s terminating the entire
FROM loop if it was open and bare. Wrapping it inside braces makes it
more visually obvious that it’s not the end of the FROM loop.
Why DO
is mandatory¶
Other loop types don’t require a keyword to begin the loop body. You
can just start in with the opening left-brace {
. The reason the
additional DO
keyword exists in the FROM loop is because otherwise
you’d have two back-to-back brace sections (The end of the STEP
clause would abut against the start of the loop body) without any
punctuation between them, and that would look too much like it was
starting a brand new thing from scratch.
Other formatting examples¶
// prints a count from 1 to 10:
FROM {local x is 1.} UNTIL x > 10 STEP {set x to x+1.} DO { print x.}
// Entire header in one line, body indented:
// --------------------------------------------
FROM {local x is 1.} UNTIL x > 10 STEP {set x to x+1.} DO {
print x.
}
// Each header part on its own line, body indented:
// --------------------------------------------
FROM {local x is 1.}
UNTIL x > 10
STEP {set x to x+1.}
DO {
print x.
}
// Fully exploded out: Each header part on its own line,
// each clause indented separately:
// --------------------------------------------
FROM
{
local x is 1. // x will count upward from 1.
local y is 10. // while y is counting downward from 10.
}
UNTIL
x > 10 or y = 0
STEP
{
set x to x+1.
set y to y-1.
}
DO
{
print "x is " + x + ", y is " + y.
}
// ETC.
Any such combination of indenting styles, or mix and match of them, is understood by the compiler. The compiler ignores the spacing and indenting. It is recommended that you pick just two of them and stick with them - one compact one to use for short headers, and one longer exploded one to use for more wordy headers when you have to split it up across lines.
The literal meaning of FROM
¶
If you have a FROM
loop, it ends up being exactly identical to an
UNTIL loop written as follows:
If we assume that AAAA, BBBB, CCCC, and DDDD are placeholders referring to the actual script syntax, then in the generic case, the following is how all FROM loops work:
FROM LOOP:
FROM { AAAA } UNTIL BBBB STEP { CCCC } DO { DDDD }
Is exactly the same as doing this:
{ // start a brace to keep the scope of AAAA local to the loop.
AAAA
UNTIL BBBB {
DDDD
CCCC
}
} // end a brace to throw away the local scope of AAAA
An example of why the FROM loop is useful¶
Given that the FROM
loop is really just an alternate way to write a
certain format of UNTIL loop, you might ask why bother having it.
The reason is that in the long run it makes your script easier to
edit and maintain. It makes things more self-contained and cut-and-pasteable:
Above, in the documentation for UNTIL loops, this example was given:
SET X to 1.
UNTIL X > 10 { // Prints the numbers 1-10
PRINT X.
SET X to X + 1.
}
The same example, expressed as a FROM
loop is this:
FROM {SET X to 1.} UNTIL X > 10 {SET X to X + 1.} DO {
PRINT X.
}
Kerboscript FROM
loop provides a way to place those sections in the
loop header so they are declared up front and let people see the layout
of how the loop iterates, leaving the body to just contain the statements
to be done for that iteration.
If you are editing your script and need to cut a loop section and move it elsewhere, the FROM loop makes it more visually obvious how to cut that loop and move it. It makes the important parts of the loop be self contained in the header, so you don’t leave the initializer behind when moving the loop.
WAIT
¶
Halts execution for a specified amount of time, or until a specific set of criteria are met. Note that running a WAIT UNTIL
statement can hang the machine forever if the criteria are never met. Examples:
WAIT 6.2. // Wait 6.2 seconds
WAIT UNTIL X > 40. // Wait until X is greater than 40
WAIT UNTIL APOAPSIS > 150000. // You can see where this is going
Note that any WAIT
statement, no matter what the actual expression is, will always result in a wait time that lasts at least one physics tick.
Note
The WAIT command only causes mainline code to be suspended. Trigger code such as WHEN, ON, LOCK STEERING, and LOCK THROTTLE, will continue executing while your program is sitting still on the WAIT command.
WHEN
/ THEN
¶
Executes a command when a certain criteria are met. Unlike WAIT
, WHEN
does not halt execution. It starts a check in the background that will keep actively looking for the trigger condition while the rest of the code continues. When it triggers, the body after the THEN
will execute exactly once, after which the trigger is removed unless the PRESERVE
is used, in which case the trigger is not removed.
The body of a THEN
or an ON
statement interrupts the normal flow of a kOS program. When the event that triggers the body happens, the main kOS program is paused until the body of the THEN
completes.
Warning
With the advent of local variable scoping in kOS version 0.17 and above, it’s important to note that the variables used within the expression of a WHEN or an ON statement should be GLOBAL variables or the results are unpredictable. If local variables were used, the results could change depending on where you are within the execution at the time.
Warning
Do not make the body of a WHEN
/THEN
take a long time to execute. If you attempt to run code that lasts too long in the body of your WHEN
/THEN
statement, it will cause an error. Avoid looping during WHEN
/THEN
if you can. For details on how to deal with this, see the tutorial on design patterns.
Note
Changed in version 0.12: IMPORTANT BREAKING CHANGE: In previous versions of kOS, the body of a WHEN
/THEN
would execute simultaneously in the background with the rest of the main program. This behavior has changed as of version 0.12 of kOS, as described above, and scripts that used to rely on this behavior will not work with version 0.12 of kOS
Example:
WHEN BCount < 99 THEN PRINT BCount + " bottles of beer on the wall”.
// Watch in the background for when the altitude is high enough.
// Once it is, then turn on the solar panels and action group 1
WHEN altitude > 70000 THEN {
PRINT "ACTIVATING PANELS AND AG 1.".
PANELS ON.
AG1 ON.
}
A WHEN
/THEN
trigger is removed when the program that created it exits, even if it has not occurred yet. The PRESERVE
can be used inside the THEN
clause of a WHEN
statement. If you are going to make extensive use of WHEN
/THEN
triggers, it’s important to understand more details of how they work in the kOS CPU.
ON
¶
The ON
command is almost identical to the WHEN
/THEN
command. ON
sets up a trigger in the background that will run the selected command exactly once when the boolean variable changes state from true to false or from false to true. This command is best used to listen for action group activations.
Just like with the WHEN
/THEN
command, the PRESERVE
command can be used inside the code block to cause the trigger to remain active and not go away.
Warning
With the advent of local variable scoping in kOS version 0.17 and above, it’s important to note that the variables used within the expression of a WHEN or an ON statement should be GLOBAL variables or the results are unpredictable. If local variables were used, the results could change depending on where you are within the execution at the time.
How does it differ from WHEN
/THEN
? The WHEN
/THEN
triggers are executed whenever the conditional expression becomes true. ON
triggers are executed whenever the boolean variable changes state either from false to true or from true to false.
The body of an ON
statement can be a list of commands inside curly braces, just like for WHEN
/THEN
. Also just like with WHEN
/THEN
, the body of the ON
interrupts all of KSP while it runs, so it should be designed to be a short and finish quickly without getting stuck in a long loop:
ON AG3 {
PRINT "Action Group 3 Activated!”.
}
ON SAS PRINT "SAS system has been toggled”.
ON AG1 {
PRINT "Action Group 1 activated.".
PRESERVE.
}
Warning
DO NOT make the body of an ON
statement take a long time to execute. If you attempt to run code that lasts too long in the body of your ON
statement, it will cause an error. For general help on how to deal with this, see the tutorial on design patterns.
Avoid looping during ON
code blocks if you can. If you are going to make extensive use of ON
triggers, it’s important to understand more details of how they work in the kOS CPU.
PRESERVE
¶
PRESERVE
is a command keyword that is only valid inside of WHEN
/THEN
and ON
code blocks.
When a WHEN
/THEN
or ON
condition is triggered, the default behavior is to execute the code block body exactly once and only once, and then the trigger condition is removed and the trigger will never occur again.
To alter this, execute the PRESERVE
command anywhere within the body of the code being executed and it tells the kOS computer to keep the trigger condition active. When it finishes executing the code block of the trigger, if PRESERVE
has happened anywhere within that run of the block of code, it will not remove the trigger. Instead it will allow it to re-trigger, possibly as soon as the very next tick. If the PRESERVE
keyword is executed again and again each time the trigger occurs, the trigger could remain active indefinitely.
The following example sets up a continuous background check to keep looking for if there’s no fuel in the current stage, and if there is, then it activates the next stage, but no more often than once every half second. Once more than NUMSTAGES
have happened, it allows the check to stop executing but it keeps the check alive until that happens:
SET NUMSTAGES TO 5.
SET COOLDOWN_START TO 0.
WHEN (TIME:SECONDS > COOLDOWN_START + 0.5) AND STAGE:LIQUIDFUEL = 0 {
SET COOLDOWN_START TO TIME:SECONDS.
STAGE.
SET NUMSTAGES TO NUMSTAGES - 1.
IF NUMSTAGES > 0 {
PRESERVE.
}
}
// Continue to the rest of the code
DECLARE FUNCTION
¶
Covered in more depth elsewhere in the documentation,
the DECLARE FUNCTION
statement creates a user-defined function that
you can then call elsewhere in the code.
Boolean Operators¶
All conditional statements, like IF
, can make use of boolean operators. The order of operations is as follows:
=
<
>
<=
>=
<>
AND
OR
NOT
Boolean is a type that can be stored in a variable and used that way as well. The constants True
and False
(case insensitive) may be used as values for boolean variables. If a number is used as if it was a Boolean variable, it will be interpreted in the standard way (zero means false, anything else means true):
IF X = 1 AND Y > 4 { PRINT "Both conditions are true". }
IF X = 1 OR Y > 4 { PRINT "At least one condition is true". }
IF NOT (X = 1 or Y > 4) { PRINT "Neither condition is true". }
IF X <> 1 { PRINT "X is not 1". }
SET MYCHECK TO NOT (X = 1 or Y > 4).
IF MYCHECK { PRINT "mycheck is true." }
LOCK CONTINUOUSCHECK TO X < 0.
WHEN CONTINUOUSCHECK THEN { PRINT "X has just become negative.". }
IF True { PRINT "This statement happens unconditionally." }
IF False { PRINT "This statement never happens." }
IF 1 { PRINT "This statement happens unconditionally." }
IF 0 { PRINT "This statement never happens." }
IF count { PRINT "count isn't zero.". }