Assignment 1: Trainspotting

In this assignment you will write a program to control two trains. The purpose is to write the program so that the trains move relatively independently of each other, but without colliding.

Instead of a real railway, we have a simulator, where one can control the speeds of trains and change rail switches. The simulator gives a signal when a train passes sensors suitably placed on the tracks. It is part of the assignment to decide where sensors should be placed to give sufficient information for your program. The map without sensors looks as follows.

Placeholder for the map, please enable images

Choice of programming language.

In this assignment you must use Java.

Important

In order to make sure you are using the right version of Java and train simulator, do not forget to run

$ setup_course tda381

on Chalmers Linux machines.


Assignment

Your assignment is to write a program that controls the trains. The trains should run simultaneously between the two stations (note that each station has two tracks). It is important that the trains are able to move at the same time, and be as independent from each other as possible, without crashing. At the stations the trains should stop and then wait 1 to 2 seconds. The trains should run forever between the stations.

In the beginning there are no sensors on the tracks. You must find where it is suitable to place sensors so that the control program gets the necessary information.

The program that you deliver should work as follows. Each train should be controlled by an independent 'train program', which runs in its own Java thread. Train programs cannot communicate with outside world by anything else than acquiring and releasing semaphores. In particular, no shared variables should be read or changed by the train programs. Also, both trains must use the same 'train program', and it is not allowed to make the behaviour of a train depend on its train id. For example, the track that a train chooses should not depend on its train id.

Below is a list of more detailed requirements.

Requirements

  • Command line parameters. Your program must take three optional parameters from the command line that represent the speed for each train, and the speed of the simulation. For instance, the line
    $ 2 "tsim --speed 20 Lab1.map" "java Lab1 10 5 20"
    will launch the simulation at speed 20 with the top train at speed 10 and the bottom train at speed 5.
    If the first or second parameter is missing, then you may pick any speed in the range [1,maxspeed], and if the third parameter is missing, you must default to 100 as this is the default for the simulator as well.
  • Accounting for speed at stations. The trains must wait 1-2 seconds at each station after coming to a full stop.
    Use the following formula (time in ms): time_at_station + 2 * simulation_speed_parameter * |train_speed|
  • Maximum train speed. You need to find out the maximum train speed maxspeed that is usable with your particular solution. This must be at least 15.
  • No minimal speed. There should be no assumption about minimal train speed. All speeds in the range [1,maxspeed] will be tried.
  • No excessive semaphores. Solutions that use 10 or more semaphores will be rejected.
  • No Polling. Your solutions must not use busy-waiting-loops or their close relative, polling.
  • Dynamic behaviour. You must not statically encode how the trains choose track at the switches (for example: train 1 always takes the upper path in the middle, while train 2 always takes the lower; same for stations). You must let the trains have one track as default (for example: the track with shortest distance). You must select the other track if the default track is already occupied by the other train.
    Note that you should not try to find a solution which works for any track layout or any number of trains, as this will be too complicated for all of us! It is enough to solve the problem for the given track layout.
  • No Map Modifications. You may not alter the map in any way, other than by adding sensors.
  • No randomization. You may not use randomization in any way that affects the simulation, as this makes testing more difficult.
  • Two trains – two threads. When the system is running there should be only two processes (threads): one for each train. This is in addition to the main thread and the thread that handles communication with the simulator. This latter thread is created in the interface code we provide you with.
  • Two trains – one implementation. The solution must use the same implementation for both trains. The only thing separating the processes of the different trains is the train id, starting position, and speed. The fact that the two trains start in different positions means that it is permitted (and necessary) to have different initial behaviour for the two processes, but after that the behaviour of the processes must not be dependent on which train it is acting for.
  • Use binary semaphores. You must use the provided binary semaphore for mutual exclusion. You will have the opportunity to use other synchronisation mechanisms in the other assignments.
  • Trains mind their own business. Trains should operate independently. A train should not make plans/decisions for the other train. Further, a train should not make plans/decisions for itself based on the information (speed, position, direction) of the other one.
  • Good Train Flow The parallel section in the middle of the map must allow a fast train to overtake a slow train going in the same direction.
  • Documentation. In addition to working code you need to provide a convincing argument why your solution is correct. You must document your solution with an ASCII text file. Give a high-level description of your program and then go into detail about all pieces of code that are essential for correctness. The documentation should include everything needed for easy understanding of the solution. In particular we demand that your documentation contains discussion on:
    • Placement of the sensors
    • Choice of critical sections
    • Maximum train speed and the reason for it
    • How you tested your solution
  • Code quality. Reasonable code quality is expected; don't complain if your assignment is rejected because we think your program is ugly (magic numbers, poor use of types, cut-and-paste coding style) – even if it works.
  • Submission. When you submit your solution, you must provide the following files:
    • Lab1.java (your program to control the trains)
    • Lab1.map (which indicates the placement of your sensors)
    • documentation.txt (ASCII file containing your documentation)

Train Simulator

The simulator is called tsim and with this program you can even modify the train map. You can run tsim as follows:

tsim
Make a new, empty map, of default size.
tsim filename
Read in an old map from file filename
tsim --speed 50 filename
Read in an old map from file filename, and also set the simulation speed to 50. This value is the number of milliseconds that tsim waits between updates of it's state (i.e moving the trains). 100 ms is the default, so 50 makes the simulation go at double speed.
tsim --priority 15 --speed 20 filename
For extreme simulation speeds you may need to also set the priority of the simulator process, since otherwise it might move the trains without your controller program getting time to receive sensor events. The priority value is the nice value.
tsim --help
See all options of the simulator.

tsim as a map editor

You need to get your own copy of the map in order to be able to add the sensors. The map you will be working on is that shown above.

The map file is found in the downloads section. Download it to your working directory (we strongly recommend that you create a subdirectory lab1 for your work with this assignment).

Then you can modify the map by writing tsim Lab1.map. Figure 2 below shows how to use tsim. You should only select the sensor button in the tools window (the small T-like symbol) and then use the mouse to place sensors on the track.

Note that you can see where switches are placed by pointing at them and looking at the coordinates given in the tools window. This information is useful when your program is to interact with the simulator. Do not forget to save your changes.

Tsim placeholder, please enable images

tsim as a simulator

You may start tsim and interact with it using commands on standard input. Try for example the commands

SetSpeed 1 20
SetSwitch 17 7 RightSwitch

and see what happens. (The second command is necessary for the train to survive the first switch which initially is set in the wrong direction). The tsim language interfaces handle the communication with tsim and let you change the speed of the trains and the state of the switches in a more convenient way.

Here are some things to note about the simulator:

  • When you give a new speed for a train, it takes a while for the train to increase or decrease from its old to its new speed. This means that the train's braking distance puts limits on how fast they could drive.
  • The trains can not turn around, but can be given a negative speed to travel backwards.
  • You are not able to give a speed which implies that a train changes direction. To change direction the train must first be stopped (by giving it speed 0). There is no signal from the simulator to tell you when the train has stopped, so you will need to wait a suitable time before changing direction.
    This is approximately 2 * simulation_speed_parameter * |train_speed|
  • You must be careful when you change the switches. If a train comes to a switch which is in the wrong position, then the train will derail. See Figure 3 below.
Placeholder for Switch, please enable images

How your program communicates with the simulator

The simulator and your program run as separate OS processes. They use pipes to communicate. We will have two-way communication, so we will need two pipes. See Figure 4 below.

Placeholder for Communication, please enable images

You can join together two programs in the above way with the special command 2. This command starts the two commands which are given as arguments, and joins the output (stdout) of one to the input (stdin) of the other.

$ 2 "tsim Lab1.map" "java Lab1 10 5"

where Lab1 is the class containing your solution.

Note on debugging

Since both the standard I/O channels are in use, if you want to write something to the screen (for example, to help with debugging), you must use System.err.println(String s).


Program/Simulator interaction

All commands between the your program and the simulator are sent line-by-line as text strings, as you saw above. To aid you in your assignment we provide you with a Java library (TSim) that implements the protocol tsim is using. You must use this Java library. The package provides you with an interface to control the behaviour of the trains and switches. The package is available in the downloads section below.

The Java TSim contains a collection of classes to be used when communicating with the simulator from Java. You can view the JavaDoc documentation of the all interface classes here.

Usage

You cannot create instances of TSimInterface using its constructor, since it is private. Instead you use the static method getInstance() that creates an instance of the class if that has not already been done (singleton pattern).

TSimInterface tsi = TSimInterface.getInstance();

Interesting classes

  • TSimInterface is the class used to control the trains. It contains methods to control the speed of the train and to switch the positions of the switches. All these methods throw a CommandException if some tsim-related error occurs. In addition there is a method to turn on debugging, which displays the communication between your program and tsim.
    Read the Javadoc documentation of this class carefully!
  • SensorEvent represents the event of a train passing over a sensor. Every time a train passes over a sensor first an ACTIVE event is created followed by an INACTIVE event when the train leaves the sensor. This class contains the status of the sensor, the position of the sensor and the identity of the train causing the event.
  • CommandException represents a failure of one of the methods of TSimInterface. Information on the error is stored in the exception and can be acquired by the getMessage method. You should never catch an exception leaving the exception handler empty! For your own and our best always print the error message contained in the exception object. Otherwise, your program will be much harder to debug.
    try {
      ...
    }
    catch (CommandException e) {
      System.err.println(e.getMessage());
    
      /* If a command failed then something bad has
         happened and we probably don't want to
         continue executing */
    
      System.exit(1);
    }

Downloads

To start you need to download these two files:

  • A small example that starts one of the trains to get you started
  • The map to place your sensors on

If you are running your assignment on Chalmers machines and use the setup_course tda381 command you do not need to download any more files.

If you wish to work on this assignment on your own computer, then you can download the files needed using the links below. Note that we do not have any experience trying to make the simulator work on windows, it will probably be quite difficult and we do not provide any help for that, but it is known to work on Mac and Linux. However, should you succeed, we would be happy to hear about it. In this case, you will also need these files:

  • The Java TSim source, containing the TSim interface
  • The simulator source code. To install it, run
    $ ./configure
    $ make
    $ make install
    The default installation path is /usr/local. If you want to install to some other location instead run ./configure --prefix=/your/location. For more information, see the INSTALL file.
  • The source of the 2 command. Compile it by writing
    $ gcc -o 2 2.c

To unzip and untar the packages use for example (or read the man page of tar and gzip):

$ gzip -dc filename_goes_here.tar.gz | tar xvf -

Tips and Tricks

  • Which parts of track are critical resources?
  • There are two common ways of writing the train programs. One is that the train remembers in which place on the track it is and performs its actions based on that (stateful solution). The other is that the train decides what to do based on the coordinates of the sensor it steps on (stateless solution). In the stateless solution, you might still have to remember some things, for example which semaphors you are holding.
  • Do not try to solve the general problem. Create a solution that works for this particular exercise. For example, it is OK to assume that there will be only 2 trains on the map. If you are interested in solving the general problem, write a short description of how to solve it in the documentation file.
  • If you have to lock two critical sections at the same time, this probably indicates that your train flow will be poor. Perhaps you need a little bit more sensors?
  • It is not necessary for trains to stop exactly at the end of the track at a station (before changing direction).
  • Do not overoptimise modularity. The assignment is simple enough to have the solution in one file.
  • For debugging, it is helpful to print out statements about the state of the trains and semaphores every time it is changed. See the section on the train simulator for implementation details
  • For testing, run multiple instances of the simulator (at a high simulation speed), with different train speeds for a long time. e.g Test your trains with the first going very fast and the second going very slow, and vice versa. Don't set the simulation speed too high (--speed switch should be about 15 or higher), since the trains might not get commands from your program on time. Use the --priority switch when running the simulation fast, as outlined earlier.
  • Note that get_sensor (TId) blocks until the given train (TId) next passes a sensor.
  • In previous courses, the best solutions had usually less than 300 lines of code. If your solution is much bigger than that then you are probably making it too complicated. Try making it simpler. It is very important that your code is understandable.
  • There is no need to have more than a single try-catch in the entire program. See here.

Note!

It was stated above but may be stated again: Reasonable code quality is expected. You are programmers – programming should be an art to you. The quality of your code will be vital for its reuse and maintenance.

Last modified: Monday, 21-Jan-2013 16:14:33 CET
COMPUTER SCIENCE AND ENGINEERING - Chalmers University of Technology and Göteborg University
SE-412 96 Göteborg, Sweden - Tel: +46 (0)31- 772 1000