The vocabulary and grammar you use to write scripts is called TrainPlayer Programming Language, or TPL. It recognizes a given list of commands and keywords -- some related to trains, such as setting speed and direction, and some standard programming constructs, like loops, ifs, and branches. It allows you to create and substitute variables, and to separate frequently-used code into subroutines and procedures. It is rudimentary but reasonably complete.
This document gives an overview of the components of the language. It does not attempt to teach you programming, or even give you much more than a brief overview and a command reference. The main documentation is built into the program, and can be found on the Reference tab of Script Central -- the same table of commands and variables as shown below, but with more detail and examples under each topic.
Further reading matter can be found on the Scripting page of the TrainPlayer web site.
A script consists of a series of statements. Some are comments -- text provided by the script author to annotate or explain -- and are skipped over when the script is executed. Others are either executable statements, which cause actions to occur, or wait conditions, which cause the script to pause for some specified event or time period before proceeding. The text in the script is sometimes referred to as code, and the act of creating it is called coding.
An executable statement begins with a command -- one of the words or phrases recognized by the language, such as those listed below -- followed by arguments providing data needed by the command. An argument might consist of a single string or numeric value, or an expression formed by combining multiple values using arithmetic operators. In place of a literal string or numeric value, you can use the name of a variable, and it will be replaced by the corresponding value when the statement is executed.
Statements are normally executed one after another as they appear in the script. However, this behavior can be altered using flow commands. A loop is a block of statements which are executed repeatedly a given number of times. A branch is a jump to another place in the script, sometimes based on a decision made in an IF statement.
A call is a jump to a separate external block of statements; these execute in their entirety, and then control jumps back to the statement after the call. There are three variations on this. A subroutine is a block of script code stored in a text file; a call to the subroutine executes the file and then returns. A proc is a small routine stored between two tags within another script; the first time it is encountered, as its parent script executes, it is extracted and saved in memory under a speciried name; subsequently it can be called using this name. A function is a body of code which carries out a calculation and returns a result to be used by the calling script.
Whenever a train script or junction action is running, it is connected to a train. Commands in the script which do not specify otherwise are directed to this train. Train control commands can be used to redirect commands from the script-owning train to another one.
Variables are created and assigned using a Set statement, or the equivalent Let. This takes the name of a variable and an expression; the expression is evaluated and its value assigned to the variable. If the name is new, the variable is created, otherwise the existing variable is updated.
A script consists of a series of statements, usually one per line in a text file. Lines starting with * are comments, and are not executed. By default, comments starting with a single asterisk are echoed to the output window as the script runs; those starting with ** are not. Blank lines are ignored.
An executable statement consists of a command or wait condition name -- one of the words in the first column of the table below -- followed by any required arguments.
Multiple statements may be concatenated on a single line, using semicolon (;) as delimiter. As a special case, a wait statement may be followed immediately by a command on the same line.
A command is an instruction telling the program to take some action. There are two types: commands accessible from the program menus vs. those built into the language. The menu commands are described in the next section. The built-in commands are in these categories:
Most commands consist of a single verb followed by one or more arguments telling what to act on or what to do. In the table below, arguments are indicated by angle brackets -- you supply the value, not the brackets. Optional arguments are enclosed in braces; if omitted, these take on default values.
|speed 20||set speed of script owner to 20; start moving if stationary|
|echo "note to user!"||display message in output window|
|throw J35 1||throw switch at junction 35 to position 1|
|drive "Broadway Limited"||begin block of statements to be directed to another train|
A complete list of commands is given below. A better one is found in the Reference tab of Script Central.
When you select any command from a menu, you see a cascading series of names starting with a top-level menu. Write these names down one after another and you have a command you can use in a script. Menu names must be separated by spaces, multi-word names must be quoted. Examples:
|file open||bring up Open File dialog|
|file "open layout"||bring up Layout Chooser; note quotes required around two-word command|
|train "add car" boxcar||add car to selected train at insert position; note menu command applies to selected train, not script owner|
A wait condition tells the script to wait for a certain event or until a certain time before proceeding. There are only a few wait condition keywords, serving multiple purposes: AT a certain time on the clock, or when the first car of the train reaches a certain junction or station; AFTER an elapsed time, or when the last car passes the given location; or ON specified events.
|at J35 stop||when first car of train crosses junction 35, stop train, continue with script|
|after 0:0:02 speed 20||wait 2 seconds, then set speed to 20|
Script statements are normally executed one after another. Commands in the "flow control" category change this behavior, by jumping to other parts of the code based on some criterion. The following types of flow control are available:
|goto nextPart||jump to statement "nextPart:"|
|call sitandwait 10||call subroutine "sitandwait.txt" and pass one argument|
An IF statement takes the form
if (val1 comp-op val2)
code block 1
code block 2]
where val1 and val2 are string or numeric values, variables, or expressions, and comp-op is a comparison operator, one of:
= < > <> <= >=
The two values are compared using the given operator; if the result is TRUE, then code block 1 is executed; if FALSE, code block 1 is skipped, and control passes to the optional code block 2 or, if none, to the statement after the endif.
|let ncars = $train(train1, length)||create a variable and assign value from a system function|
|if (ncars < 10)
echo "short train"
|compare value to literal; if true, output this text|
echo "long train"
|if comparison is false, output alternate text instead|
A while statement defines a loop. It takes a form similar to IF:
while (val1 comp-op val2)
where the code block executes once every time the comparison expression evaluates to TRUE; if it is FALSE, the code block is skipped and control passes to the statement after the endwhile.
|let i = 1||create variable and initialize|
|while (i < 10)||compare variable to literal; if true, go to next statement|
| echo Line @i
let i = i + 1
|from here, jump back to start of loop|
|endwhile||when comparison is false, jump to here|
Use a CONTINUE statement within a loop to go to the next iteration, or a BREAK statement to jump out and terminate the loop.
Set/Let statements take the form
set var expr
let var = expr
where var is the name of a new or existing variable, and expr is an expression, one or more components joined by operators:
comp [op comp [...]]
A component is one of:
Op is an arithmetic operator. Between two numeric components, it can be one of these four:
+ - / *
Between strings, only the + operator is used, and means concatenation of strings.
let v = (2 + 6) / 12
let v2 = v + 99
set AllowYardMode 0
let s = "speed is " + $x_speed
Variables and Functions
A variable is a named value. You can make up a name, assign it a value, then use the name in place of the value in a script statement. This is the key to writing abstract code which can work on different input data.
To create a variable and/or assign it a value, you use a LET or SET statement. To represent it in a script statement, you normally* precede it with "@" -- a symbol you can read as "contents of," which identifies what follows as a variable name.
let CruisingSpeed = 55
* You don't need to precede a variable name with @ when you refer to it in a LET or SET statement. If you want to embed a variable contents into a larger string, use @ on both ends:
let s = "def"
echo The Alphabet: abc@s@ghi...
A system variable or function is a value supplied by the system. You call for a system value by giving a function name, often followed by a list of arguments in parentheses. An argument might be the id of an object, a keyword requesting a specific action or value, or an input value for a calculation or operation. Sometimes you don't get a value back, you just call the function to do some work, using a CALL statement.
System function names begin with "$". A list of them is given below, and also in the Reference tab of Script Central.
echo Today's date is $DATE
echo Name of active layout is $LAYOUT(Name)
let r = $RAND(1,100)
echo A random percentage -> @r
let storedData = $READ("C:\myfile.txt")
let outputData = storedData + "additional info"
call $WRITE("C:\myfile_new.txt", @outputData)
A function family is a set of related functions with the same name, and a list of arguments which usually includes the identifier of an object and/or a keyword representing a specific value. Several function families provide access to objects on the layout -- cars, trains, tracks, junctions, the layout itself -- while others work with general objects, like strings and files.
Some functions are "writable," meaning their values can be changed in LET or SET statements. Writable functions are indicated by "r/w" in the Reference listing in Script Central.
echo The load in box 44 is $CAR(x44, Loadname)
let newLoad = "Toy Tractors"
echo Changing load to @newLoad!
set $CAR(x44, Loadname) = newLoad
A local variable is one which is inaccessible outside of a certain limited region of code (or "scope"). Normally, when a variable is defined and assigned a value, it is "global" -- it can be referenced by any scripts or actions anywhere. But if it is named in a preceding LOCAL statement, then it is known only to the script (or subroutine or proc) where it is defined. Example:
* train script 1
let gv = "a global value"
let lv = "a local value"
* train script 2
echo global value was defined as: @gv
echo value local to script 1 is undefined here: @lv
An excellent discussion with detailed descriptions of each system function can be found on the web in TrainPlayer 6.1 - Extensions to the Trainplayer Programming Language (TPL).
A subroutine is a named body of code stored in a file outside a script. The code can be executed within the script by inserting a CALL statement including the name of the file.
To pass data into and out of the subroutine, one or more arguments may be included in the call. Arguments are numbered from left to right in the call, and are represented within the subroutine as placeholders starting with "%". For example, wherever the subroutine contains "%1", it will be replaced by the first argument in the call.
Here is a simple example of a subroutine which takes a single argument. It doesn't do anything particularly useful, but the point of making it into a subroutine is that it could be made fancier if needed.
|* subroutine sitandwait <secs>||comment at first line of subroutine tells how to call|
|echo sitting for %1 secs...||using passed arg in a string|
|after 0:0:%1||wait number of seconds given by arg 1, then return|
These lines become a subroutine if stored as a text file sitandwait.txt in the TP Scripts folder.
A proc is a type of subroutine which is not defined in an external file, but in a section of another script. Using proc and endproc statements within a script, you define a callable block of code and give it a name.
|proc sitandwait||defines proc and gives it a name|
|echo ...||content as above|
This becomes callable once the script containing it is run. Including it within the master script guarantees this.
If either of the above is available, a script can then say
call sitandwait 5
to cause 5 seconds of idleness.
The term "display string" (DS) means a lengthy string for conveying information to the user or to a file. Each of the following commands takes a ds as argument, and all process it in exactly the same way:
Quotes in TPL
Our general philosophy is: quotes are not required (except in certain situations listed below), but may be used if desired. Programmers used to formal languages tend to prefer quoted strings. Thus either of these is valid:
echo this is a string to be output
echo "this is a string to be output"
In a display string, variables are substituted whether or not the string is quoted. Examples:
echo the speed of the crossing train is $train($x_train,
echo "today is $date and it is already $time"
Quotes are optional around most function arguments and keywords. Thus both of these work:
echo the speed is $train(train66, speed)
echo the speed is $train("train66", "speed")
Where quotes are required:
1. Around a multi-word command, registry setting name, or filename -- that is, any of these which contains a space. In a menu, this applies only to a single level of a cascading menu. Examples:
file open "c:\my file.txt" <== filename with
file "revert to saved" <== menu command
set "Default Car Length" 400 <== reg setting name
3. Around a string containing a parsable character, where the meaning of that depends on the context. In a function call, an unquoted string must not contain the character ")", because that would terminate the call. In a comma-delimited list, a string must be quoted if it contains comma. Examples:
call $write(c:\myfile.txt, a string with ) paren)
call $write(c:\myfile.txt, "a string with ) paren") <== OK
4. Around a multi-line text block. This is the only way to define such a block, by enclosing it in double quotes and including carriage returns:
echo "this is a string
with multiple lines
and a blank line"
Note that if you want the string itself to include a quote, you must double it:
echo "string with embedded "" quote"
Table 1. TPL Command Reference
This table shows the command vocabulary as it stood at some point prior to release. For a more up-to-date listing, consult the Reference tab in Script Central. Contents of that tab can be exported for printing if desired.
|Goto <label>||jump to statement after given label|
|Autopause <secs>||turn on pauses of given duration during train movements|
|If (<expr> <comp-op> <expr>)||compare two expressions, branch based on result|
|Else||begin alternate block after IF|
|Elseif (<expr> <comp-op> <expr>)||evaluate alternate after IF|
|Endif||end IF block|
|While (<expr> <comp-op> <expr>)||compare two expressions, execute or skip next block based on result|
|Endwhile||end WHILE block|
|Break||break out of WHILE loop|
|Continue||continue to next iteration of WHILE loop|
|Exit||terminate running script|
|Throw <jxn> [<pos>]||throw switch to given position or next available|
|Rotate <ttbl> <jxn> [CCW]||rotate given turntable to junction, clockwise unless CCW added|
|Tablestop||stop turntable rotation|
|Sound [loop|stop] <spath>||play or stop sound in named wav file. If LOOP omitted play once.|
|Set <var> <expr>||set variable to value; equivalent to LET <var>=<expr>|
|Reset <var>||reset registry variable to stored value|
|Let <var>=<expr>||set given variable to value of expression; create variable if needed|
|Call <routine> <arglist>||call given subroutine or proc and pass any required arguments|
|Proc <routine>||begin definition of procedure of given name|
|Endproc||end procedure definition|
|Local <var[,var...]>||declare local variables|
|Return||return from subroutine or proc to calling script|
|Forward||set direction to forward|
|Reverse||set direction to reverse|
|Speed <mph>||set speed to given mph; starts train moving if stopped|
|Stop||decelerate to a stop|
|Uncouple <slot> OR <car> OR <car><car>||uncouple at given position, at given car, or between given cars|
|Horn [<msec>]||sound train horn for 3 sec or given time in millisec|
|Train Control Commands|
|Train <train>||select given train|
|Load [Toggle] [Car/Train/Cut] <ids> [<loadname>]||load given car(s) with given load or default|
|Unload [Toggle] [Car/Train/Cut] <ids>||unload given car(s)|
|Drive <train>||transfer control temporarily to given train|
|Enddrive||transfer control back to original train|
|Start <train> [<mph>]||start train script, or start train moving at 5 mph or given speed|
|User Interface Commands|
|Echo <string>||display given string in schedule window|
|Input <var> [<prompt>]||set user variable from input dialog, optionally using given prompt|
|Note [<string>]||display given string in popup window; hide window if no string|
|$CAR(label, ID)||numeric car id|
|$CAR(label, Loaded)||1 if car is loaded, otherwise 0|
|$CAR(label, Loadname)||name of load assigned to car, if any|
|$CAR(label, Label)||car label|
|$CAR(label, AAR)||car aar code|
|$CAR(label, RevEngine)||1 if car is marked as reverse engine, else 0|
|$CAR(label, Note)||user-applied note on car|
|$CAR(label, Track)||track number where car is currently located|
|$CAR(label, <any>)||custom property; set blank to remove|
|$CAR(label, Class)||general car class|
|$CAR(label, Type)||name of car type within collection|
|$CAR(label, Position)||trackno, dist to S end, 1=>headed S|
|$CAR(label, SortOrder)||user-applied integer value|
|$CAR(label, Train_ID)||id of owning train|
|$CAR(label, Car_ID)||id of car|
|$CAR(label, Train)||name of owning train|
|$CAR(label, Consist)||one-word description of train consist|
|$CAR(label, ExcludeOps)||X => exclude car from switchlists|
|$CAR(label, Load)||current load or 'unloadable' or 'empty'|
|$CAR(label, Location)||at or near track or station|
|$TRAIN(name, ID)||numeric train id|
|$TRAIN(name, NCars)||number of cars in the train|
|$TRAIN(name, Car)||label of car i, i between 0 and NCars-1|
|$TRAIN(name, Name)||train name, either user-assigned or Train<id>|
|$TRAIN(name, Speed)||current speed, same as returned by $SPEED|
|$TRAIN(name, Direction)||F = forward, R = reverse, T = toggle|
|$TRAIN(name, Length)||length in cars; returns same value as NCars|
|$TRAIN(name, Flip)||invert train on track|
|$TRAIN(name, <any>)||custom property; set blank to remove|
|$LAYOUT(NTrains)||number of trains on the layout|
|$LAYOUT(Train)||name of train i, i between 0 and NTrains|
|$TRACK(id, OccupiedBy)||return labels of cars on track|
|$SCENERY(id, Show)||show scenery object|
|$SCENERY(id, Hide)||hide scenery object|
|$SCENERY(id, Toggle)||toggle visibility of scenery object|
|$SCENERY(id, IsVisible)||1 if scenery object is showing, else 0|
|$STATION(id, OccupiedBy)||return labels of cars in station|
|$TURNTABLE(id, OccupiedBy)||return labels of cars on turntable|
|$TURNTABLE(id, Track)||id of bridge track|
|$TURNTABLE(id, AlignedTo)||id(s) of 1 or 2 locked external tracks|
|$STRING(string, Length)||length of string in characters|
|$STRING(string, Contains, argument)||returns 1 if string contains argument, 0 if not|
|$STRING(string, StartsWith, argument)||returns 1 if string starts with argument|
|$STRING(string, EndsWith, argument)||returns 1 if string ends with argument|
|$STRING(string, NextToken[, delims])||returns next substring, removes from string|
|$FILE(name, Open)||open named text file, return id or 0 if fail|
|$FILE(id, Close)||close file|
|$FILE(id, ReadAll)||read contents and return as string|
|$FILE(id, ReadLine)||read next line; return 'EOF' when done|
|$FILE(name, SaveVars)||write all user variables to named file|
|$FILE(name, ReadVars)||read all user variables from named file|
|$RAND(i1,i2)||random integer between i1 and i2; defaults false,100 if not specified|
|$SUBSTR(i1,n,s)||substring of s starting at i1, n chars long; if n = -1, go to end|
|$FINDSTR(sub,s)||return zero-based position of sub within s, or -1 if not found|
|$SETTING(name)||return named registry setting|
|$RRCLOCK(arg)||rrclock functions: get, set t, add t|
|$READ(f)||return contents of text file f as string|
|$WRITE(f,[a,]s)||write string s to file f, optionally append; 0 if fail|
|$STRLEN(s)||return length of s|
|$MSGBOX([type,]s)||show message s with ok/cancel or yes/no buttons; return 1=ok/yes 0=no|
|$VIEW(op,name)||get/set visibility of named window or feature; op=IsVisible,Show,Hide|
|$SWITCH(j)||return switch pos at j (0 or 1 standard), -1 if j not a switch|
|$SYSTEM(cmd)||execute system command in command box|
|$TIME||current time of day (h:m:s in 24-hour format)|
|$DATE||today's date (mm/dd/yyyy)|
|$TRAIN||name of selected train|
|$CAR||name of selected car|
|$SPEED||speed of train in MPH (KPH if metric settings in effect)|
|$KEY||numeric code of last key hit on keyboard|
|$X_TRAIN||name of crossing train -- train owning calling script|
|$X_SPEED||speed of crossing train|
|$X_CAR||label of crossing car|
|$DATADIR||TP application data directory path|
|AT <jxn>||lead car of train crosses junction|
|AT <(t j d)>||lead car of train crosses exact spot (dist d from jxn j on track t)|
|AT <h:m[:s]>||specified time is shown on layout clock; h:m required, secs optional|
|AT <station>||lead car of train enters named station|
|AFTER <jxn>||last car of train crosses junction|
|AFTER <(t j d)>||last car of train crosses exact spot|
|AFTER <h:m:s>||specified actual time has elapsed on wall clock|
|AFTER <station>||last car of train leaves station|
|ON STOP||train comes to a complete stop|
|ON COUPLE||train couples with another car|
|ON THROW <jxn>||specified switch is thrown by any means|
|ON TABLESTOP||turntable finishes rotating|
|ON KEY [<key>]||user presses any key or specified key|