Guide to the TrainPlayer Programming Language


Part Four - A Worked Example

OVERVIEW

Hopper Loading Layout

Aim of the Project

About the Scripts

STEP BY STEP

Step 1 - Loading Shed Script

Step 2 - Master Script

Step 3 - Train Control Script

Step 4 - Task Analysis Script

Step 5 - Protect the Scripts

ADDITIONAL RESOURCES

Part 1 - The Scripting Tools

Part 2 - Commands and Functions

Part 3 - The Subroutines Library

Part 5 - Scripting for Advanced Ops

Hopper Loading Layout

Our Hopper Loading Demonstration uses just four scripts, a Master Script and three Junction Actions. This layout was first devised in 2014 and the best way to familiarize yourself with how it works is to open and run it from the Scripts and Puzzles\Scripting Examples folder in your Layout Chooser.

Back to Contents


Aim of the Project

"Old_King_Coal" needs to travel to the mine, reverse its consist onto the mine storage track, uncouple the caboose, load the empty hoppers by propelling them in reverse through the loading shed and reattach them to the caboose. As capacity on the loading track is limited the loading operation will be restricted to loading only two cars at a time. Once the train is loaded it can highball back onto the main line and head for home via a complete circuit of the track.

Back to Contents


About the Scripts

Each of the three Junction Action scripts serves a different purpose. The Action in the loading shed handles the loading of any empty hopper that is pushed or pulled through the shed. The Action on the bottom track ahead of the train applies a temporary, but full, Train Script to the train for controlling all its movements, and the third Action at the top of the image performs a check on the departing train, analysing its consist to ensure that it has been loaded properly and that its cars are in the right order.

The Master Script performs three separate tasks, it starts "Old_King_Coal" moving towards the mine, then it displays an introduction and instructions for the task, and finally it offers the user a choice between performing the task for themselves or watching a scripted demonstration.

These are all the right steps but they are not necessarily presented in the right order and our first step in preparing this layout was to write the Junction Action code needed for the loading shed because loading the hoppers is the key purpose of this layout. Once the loading shed is functioning properly the layout can be tested prior to preparing the additional scripts.

Back to Contents


Step 1 - Loading Shed Script

Before we examine the script used for loading the hoppers it is worth taking a look at the track arrangement inside the loading shed. To do this we select the Edit Track button on the Build toolbar and select the two short lengths of track within the shed. To assist in finding the junction locations we also select Show Numbers from the Tools Menu.

The track visibility on the two short lengths of track, tracks 1 and 2, is set to Hidden and the visibility of any cars on the hidden track is set to Cars invisible. This ensures that any car entering the shed for loading cannot be seen while it is inside the shed, but if the load status changes while it is out of sight the effect will be immediately visible when the car emerges from the shed.

To create our Junction Action script we right click on Junction 2 and select Action from the context menu. This opens the Junction Action Editor.

We set the Trigger combo at top right to When crossed, and the Direction combo to Any direction. We set the left hand combo to AAR code and the car type combo to HK which is the code used by the hoppers on this layout. This ensures that only cars with an AAR code of HK will execute this script and all other car types entering this shed will be ignored.

Now we can turn our attention to the script itself which is heavily commented to assist the novice to understand what is happening. The actual commands are restricted to just nine lines in the middle of the text.

   let train = $x_train
   let car = $x_car

   if ($train(@train,speed)<=4)
      if ($car(@car,loaded)="0")
         let loaded = @loaded + 1
         Load Car @car
         Echo @train loading @car, @loaded cars are loaded
      endif 
   endif

The first line of this script makes use of the System Function $x_train to extract the name of the train containing the car which triggered the script and store this name in the variable train for processing.

The second line uses the System Function $x_car to extract the car label (AAR code plus CarID number) and store this in the variable car for further processing.

The remainder of the script is a conditional statement to check that the speed of the train (whose name we have stored in the variable train) does not exceed 4 mph, AND the car which triggered the script, and whose label is stored in the variable car is not already loaded. Both of these conditions need to be true to execute the command to Load the car which triggered the script as it entered the loading shed and crossed Junction 2.

In addition to applying a load to the car, the script will also increment the loaded variable by one. This variable is initiated in the Master Script with a zero value and is used to enable us to know exactly how many of our hoppers have been loaded, and identify when the loading task has been completed.

In Step 2 we will take a look at the Master Script.

Back to Contents


Step 2 - Master Script

The Master Script runs automatically each time a scripted layout is loaded - unless it has been deliberately disabled on the context menu of the Scripts tab of Script Central. The first block of code in this Master Script is used to echo a message to the status bar and Schedule Window to the effect that the Master Script is being started.

The second block of code is used to initialize two variables which will be needed later by the other scripts. The Mode variable which will record the user's choice as to whether he wants to operate the layout manually, or whether he would prefer to watch an automated demonstration. The loaded variable is set to 0 and will be used by the Loading Shed script to keep track of the number of hoppers that have been loaded.

Our third block of code was added in 2021 to make use of two new features that were added to TrainPlayer with the introduction of version 8. These are the introduction of the AfterJxnDist System Variable which specifies how far in RRU units a train will travel beyond a specified junction when an AFTER <Jxn> command is used - to ensure the switch is not obstructed if it needs to be thrown. The other change makes use of a new option 2 for the System Variable "Ops Mode"to just Stop the train if a derailment occurs. Previously the options were restricted to 0 for Crash and scatter the wreckage, or 1 for Bounce and start traveling in the opposite direction.

Our final block of code in this first section of the Master Script is a Drive block to place a few lines of code on the engine of Old_King_Coal to start him out on his trip towards the mine. He will receive further instructions on route from a Junction Action script located at Jxn 13 (See Step 3 - Train Control Script below).

The next task performed by the Master Script (see below) is to put up a Note to explain to the user exactly what he is expected to do with this layout. To protect this note from accidental closure we modify another System Variable and set the value of NoteDownOnEsc to 0. If we were to lose this note before the user has read it then he wouldn't know what to do next - so we lock him into following our instructions.

The Note itself is presented in the form of a multiline display string enclosed in quotes. The command is Note and the note itself will display all the text following the open quotation mark right through to the closing quote which is several lines further down in the script.

The image on the right shows how this note is displayed, the size and position were first set up during testing and this data was then automatically saved with the layout.

If we were writing this script today we would make use of the NoteWndRect System Variable to define the exact top,left,bottom,right co-ordinates for the required Note window. This system variable was not available to us in 2014 when this script was written.

After displaying the Note Window, the Master Script waits in a little loop for the train to Stop just prior to the trailing junction leading into the mine area. This line of code is in effect a customized Wait Condition. It assigns a value "no" to the variable ready and waits for this value to change to "yes". It would wait for ever if it were not for the fact that our Train Control Script will change this value automatically to "yes" as soon as our train stops moving to wait for further instructions.

Once the train stops the script can continue, which it does by waiting for the user to press the F1 key - as per the instructions in the Note window.

The final part of our Master Script starts after the train stops and the user presses F1 to close the Note window.

The next command to be executed closes down the Note window. A multiple line display string is allocated to the variable choice and a message box is opened within a conditional statement to put this question to the user.

$msgbox(yn,@choice) is a System Function which returns 1 if the user clicks the "Yes" button or 0 if he clicks the "No" button. Our script reads ths response and if the user opted to complete the task himself by clicking Yes the Master Script allocates the value "manual" to the variable Mode, if he clicks No the Mode variable is set to "auto". These values will be processed by the "Train Control Script" which we will turn to in Step 3.

Back to Contents


Step 3 - Train Control Script

In the previous step we used a Drive block in our Master Script to start our train moving towards the mine at 30 mph. However we didn't include any other instructions for the train within the Drive block and we opted not to provide "Old_King_Coal" with a "Train Script".

This is largely a matter of personal preference, had we used a Train Script we would have needed to rewind the script before starting it because Train Scripts require all trains to be in exactly the right position for the script to start. Had we written the whole script within the drive block the Master Script may have become a little unwieldy and difficult to follow. So we opted to just start the train rolling and issue it with further instructions through another Junction Action script.

This script is positioned on Junction 13 which is a point the train has to cross shortly after starting out on its run to join the main line.

When a train triggers a Junction Action script, the script is added to the train as a temporary Train Script and actioned immediately. If the train is already running a script that script is suspended and will not resume until the temporary Train Script has completed.

So once the engine of "Old_KIng_Coal" has passed Junction 13 it is once again under script control, albeit with a temporary Train Script that will disappear once it reaches its end. You can check this out by clicking the "Edit Script" button on the Script toolbar - lo and behold "Old_King_Coal" is running a Train Script that has been loaded from the Junction Action which we stored on Junction 13.

If we compare the first few lines of this temporary Train Script with the Junction Action script we placed on Junction 13 we will see that the commands to be executed are identical. Our Junction Action is set to trigger when crossed in any direction by any train. These are the default settings for triggering a Junction Action. We could have changed "Any Train" to "Train" and selected "Old_King_Coal" in the middle combo but as there is only one powered train on this layout the defaults were considered acceptable. Next we will analyse the script.

The loaded variable was set to 0 in the Master Script when we first loaded our layout, we only want to run this script if this is still the case; because if any of the cars have already been loaded this can't be the first time this train has passed this junction. If any loaded cars are detected the script control jumps to the "end" label and no further action is taken.

If there are no loaded cars on the layout the train is required to visit the mine for loading so the script continues. The next command is to stop at Junction 6 just ahead of the trailing spur into the mine.

Once the train stops the variable ready is set to "yes" and the variable mode is set to "none" which allows the Master Script to accept the F1 key to close the initial opening Note. Closing the Note authorizes the Master Script to display the message box which asks the user if he wants to load the cars himself, or watch the demo. If the user opts to do the job himself the mode variable is set to "manual", if he declines it is set to "auto".

This Train Control script then checks the "mode" variable continuously using a custom wait condition which holds up the script until the mode variable is no longer equal to "none". When the value of mode changes we check to see if it has been changed to "Manual" and if so we jump to the end of the script because the user has opted not to watch the demonstration.

NOTE: If we were writing this script today we would probably have used "EXIT" rather than "GOTO END" but that option was not available to us in 2014 when this script was written.

As the mode variable does not contain "none" and does not contain "Manual" then it must contain "auto" (the only other possible option) so our Train Control Script now continues with instructions for our demonstration of loading the train.

** If we are here then the operator has elected for the demonstration
   echo You have elected to watch the automatic demonstration

** Set switches for train to enter the station
** When writing a script you can find the switch numbers using Tools>Show Numbers
** Hovering over the switch with the mouse will display its current setting, usually 0 or 1
   throw 35 1
   throw 24 1

*  Pull forward until clear past the junction which leads to the mine
   forward
   speed 20
   after 35
   after 0:0:01 stop

*  Set route into mine to set down caboose
   throw 35 0
   throw 55 0

*  Move caboose to mine spur
   reverse
   speed 10
   at 51 speed 4
   at (52 50 15) stop
   let slot = 10;   ** uncoupling pin position +2

** Commence loop to load two cars each time (repeats three times to load six cars)
   while (loaded<6)

*  Move empty cars to loading tippler
   let slot = @slot - 2;   ** uncoupling pin position
   forward
   uncouple @slot
   speed 15
   after (40 55 25)
   stop
   throw 55 1
   reverse
   speed 10
   at 43 speed 4
*  Loading cars under tippler
   at (49 48 50)
   stop

*  Add the loaded hoppers to the train
   forward
   speed 15
   after (40 55 20)
   stop
   throw 55 0
   reverse
   speed 4
   on couple stop

** End of of loop for loading two cars each time
   endwhile

*  Haul loaded train back onto mainline
   forward
   echo Train moving out with loaded hoppers
   horn
   speed 15
   after 38
   speed 30
   after (26 35 40)
   throw 35 1

*  Wait for report after one circuit of the track

** End of automatic demo script
   end:

The report referred to above is prepared by the Task Analysis Script which is described in Step 4.

Back to Contents


Step 4 - Task Analysis Script

The final Junction Action on our Hopper Loading demonstration layout checks that all six hoppers have been loaded and the departing train is in the correct order. The script is attached to Junction 6 which is the same junction where the empty train stopped prior to commencing the loading operation. It is triggered by "Any Last Car" crossing the junction in an East to West (right to left) direction.

A transcript of the full script is shown below, it works by testing the length of the train for all nine cars, ensuring the engine is the lead car and the caboose is the last car. It also tests that all six hoppers have been loaded. A multiple line display variable is built for the final report and presented in the form of a note indicating success or failure when the train has completed its first circuit of the track.

The first block of code in this script tests the loaded variable to ensure that at least some cars have been loaded. If none are loaded the script terminates without a report. This is because the first time the train crosses this junction will be when the empty train first arrives at the mine for loading. We do not want the script to run until it detects the departing train after the loading operations have been undertaken.

** Junction Action to test if task has been completed.
** Triggered by any last car travelling East to West.

** Test the load variable and ignore this script on the first pass if all cars are still empty.
   if (@loaded=0)
      goto end
   endif

** Identify the train name for use in checks
   let train = $x_train

** Initialize a variable to record a pass or fail
   let result = "Pass";   ** default value

** Check the composition of train and record the result for building a closing report
   echo checking composition of train

** Check train length
   if ($train(@train, Ncars)=9)
      let line1 = "-- This train is complete, all cars are present."
   else
      let line1 = "-- Whoops, we seem to have left a car behind somewhere."
      let result = "Fail"
   endif
** Check to ensure the engine is the lead car
   if ($train(@train,car 0)="ES1")
      let line2 = "-- Engine is correctly positioned at front of train."
   else
      let line2 = "-- Whoops, the engine should be the lead car."
      let result = "Fail"
   endif
** Check to ensure the caboose is the last car
   if ($train(@train,car 8)="N4")
      let line3 = "-- Caboose is correctly positioned at rear of train."
   else
      let line3 = "-- Whoops, the caboose should be the last car."
      let result = "Fail"
   endif
** Check that all six hoppers were loaded
   if (@loaded=6)
      let line4 = "-- All six hoppers have been loaded."
   else
      let line4 = "-- Whoops, not all of the hoppers were loaded."
      let result = "Fail"
   endif
     
** Get second line for report.
   if (@result = "Pass")
   let line0 = "Congratulations you have completed this task successfully"
   else
   let line0 ="On this occasion you failed to complete the task correctly, better luck next time"
   endif

** Build and display the closing report
   let report = "TASK IS COMPLETE

@line0

Report on composition of train
@line1
@line2
@line3
@line4

If you would like to view the TPL code for controlling this layout:
-- Click the SC button on the Script Toolbar if you wish to examine the scripts.
-- Remember to close the Script Central dialogue before continuing.

To repeat the task or rerun the demo, select Revert to Saved from the File Menu

If you want to quit you need not stop the train before closing the layout.
When you close this layout please take care not to overwrite the original file.

I hope this example will encourage you to introduce TPL code to your own layouts.

                                                       Richard Fletcher    July 2014"

*  Display message to show completion of demo
   Note @report
   on stop
** Blank Note to hide the Note window
   Note

** Label to signify end of script
   end:

Back to Contents


Step 5 - Protect the Scripts

Once you are satisfied that the scripting of your layout is complete, you can use the "Read Only" flag to protect your Scripts from accidental damage by enthusiastic users.

Right click on your layout and select "Layout Properties" from the context menu. Go to the Advanced Tab and put a checkmark in the box which states "Open for Read Only", then Save your file giving it the default name you require.

When this file is reloaded it will be set for "Read Only" so that if a user accidentally clicks "Yes" when prompted to Save on closing the file, this will not overwrite the original scripted scenario. Instead it will offer an incremental file name in the "Save As" dialogue.

Back to Contents


Richard Fletcher for TrainPlayer - December 2024