/************************************************************************************
MRailSim - a model railway simulation program - http://mrailsim.sourceforge.net/
Copyright (C) 2004,2007 Bernd Arnold
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
************************************************************************************/
package net.sf.mrailsim.trains;
import java.awt.Graphics;
import java.util.ArrayList;
import net.sf.mrailsim.rails.Node;
import net.sf.mrailsim.rails.Track;
import net.sf.mrailsim.rails.TrackPosition;
import net.sf.mrailsim.shared.Logger;
import net.sf.mrailsim.signals.MainSignal;
import net.sf.mrailsim.signals.PreSignal;
public class Train {
/**
* The unique id of this train
*/
private final long id;
/**
* The head and the tail of this train are ported to the TrainTermination class.
* @see TrainTermination
*/
private final TrainTermination head;
private final TrainTermination tail;
/**
* Length of this train
*/
private final int length;
/**
* How far this train has traveled since last reset to 0. It is
* increased each time this train is moved.
*/
// TODO: what if an overflow occurs?
private long distanceTravelled = 0;
/**
* Reference to the train group this train belongs to. Is <code>null</code>,
* if this train is not a member of any train group.
* @see TrainGroup
*/
private TrainGroup trainGroup;
/**
* A list of tracks this train resides. It is updated each time the
* train terminations change the track.
*/
private ArrayList<Track> tracksOccupied;
private DrivingControl drivingControl;
/**
* Class constructor; initializes this train.
* @param id the unique id of this train
* @param length the length of this train
*/
public Train( long id, int length ) {
this.id = id;
this.length = length;
head = new TrainTermination( this );
tail = new TrainTermination( this );
tracksOccupied = new ArrayList<Track>();
}
// TODO: Fill class
/**
* Returns the id of this train.
* @return the train id
*/
public long getId() {
return id;
}
/**
* Returns the train group this train is a member of.
* @return the train group or <code>null</none>, if none assigned
*/
public TrainGroup getTrainGroup() {
return trainGroup;
}
/**
* Sets a new train group this train is a member of. Sets also the field
* drivingControl to the train group's driving control.
*
* @param trainGroup the train group this train is a member of
* @see #drivingControl
* @see DrivingControl
*/
public void setTrainGroup( TrainGroup trainGroup ) {
this.trainGroup = trainGroup;
drivingControl = trainGroup.getDrivingControl();
}
/**
* Returns the length of this train.
* @return the length of this train
*/
public int getLength() {
return length;
}
/**
* Returns the train termination that is responsible for the head of this train.
* @return the train termination for the head
*/
public TrainTermination getHead() {
return head;
}
/**
* Returns the train termination that is responsible for the tail of this train.
* @return the train termination for the tail
*/
public TrainTermination getTail() {
return tail;
}
/**
* Moves the train the given amount. Depending on the driving direction this
* method moves the train forward or backward.
* @param distance the distance the train should be moved, must be greater
* or equal 0
*/
void move( int distance ) {
if ( distance <= 0 ) {
return;
}
try {
if ( getDirection() == DrivingControl.Direction.FORWARD ) {
head.moveAhead( distance );
tail.moveBack( distance );
distanceTravelled += distance;
} else if ( getDirection() == DrivingControl.Direction.BACKWARD ) {
tail.moveAhead( distance );
head.moveBack( distance );
distanceTravelled += distance;
}
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
}
}
public void setPositions( TrackPosition trackPositionHead, TrackPosition trackPositionTail ) {
tracksOccupied.add( trackPositionHead.getTrack() );
// TODO: if different track, also add!
// tracksOccupied.add( trackPositionTail.getTrack() );
head.setPosition( trackPositionHead );
tail.setPosition( trackPositionTail );
}
/**
* Returns the distance this train has already traveled.
* @return the distance
*/
public long getDistanceTravelled() {
return distanceTravelled;
}
/**
* Returns the direction in which this train will go when moved. Since
* the direction is not controlled by an individual train, the driving control
* is questioned for the direction.
* @return the direction this train will travel
* @see DrivingControl.Direction
*/
public DrivingControl.Direction getDirection() {
return drivingControl.getDirection();
}
/**
* Is called to inform this train that the track has changed by a train termination.
* So either a new track was entered or an already entered track was left now.
* This train informs the new or old track that the train has entered or left.
* @param termination the reporting train termination, <code>head</code> or <code>tail</code>
* @param oldTrack the old track the termination was on
* @param newTrack the new track the termination is on now
* @param node the involved node
* @see TrainTermination
*/
void trackChanged( TrainTermination termination, Track oldTrack, Track newTrack, Node node ) {
if ( getDirection() == DrivingControl.Direction.FORWARD ) {
if ( termination == head ) {
tracksOccupied.add( newTrack );
newTrack.trainEnters( this, node );
newTrackEntered( newTrack, node );
}
if ( termination == tail ) {
tracksOccupied.remove( oldTrack );
oldTrack.trainLeaves( this );
}
}
if ( getDirection() == DrivingControl.Direction.BACKWARD ) {
if ( termination == head ) {
tracksOccupied.remove( oldTrack );
oldTrack.trainLeaves( this );
}
if ( termination == tail ) {
tracksOccupied.add( newTrack );
newTrack.trainEnters( this, node );
newTrackEntered( newTrack, node );
}
}
//TODO: inform traingroup, too
//TODO: Really necessary?
}
private void newTrackEntered( Track newTrack, Node node ) {
// TODO Look more tracks ahead!
Track nextTrack = null;
Node nextNode = null;
try {
nextNode = newTrack.getOutgoingNode( node );
nextTrack = nextNode.getOtherTrack( newTrack );
} catch (Exception e) {
// TODO Handle right
e.printStackTrace();
System.exit( 2 );
}
if ( nextTrack == null ) {
// TODO: Handle better
System.err.println( "nextTrack is null in newTrackEntered(...), train #" + id + " track #" + newTrack.getId() + "." );
System.exit( 2 );
}
if ( nextNode == null ) {
// TODO: Handle better
System.err.println( "nextNode is null in newTrackEntered(...), train #" + id + " track #" + newTrack.getId() + "." );
System.exit( 2 );
}
// TODO: Only check for signals when train is the first one in train group or the last one!
// TODO: move code to method checkForSignalsInSight(...) ?
for ( PreSignal signal : nextTrack.getPreSignals() ) {
if ( signal.isBoundToNode( nextNode ) ) {
Logger.log( Logger.Component.TRAIN, Logger.Level.INFORMATION, "Train #" + id + " sees pre signal #" +
signal.getId() + " showing " + signal.getDisplay() + "." );
drivingControl.preSignalAhead( signal.getDisplay(), signal.getDistanceToMainSignal() );
}
}
for ( MainSignal signal : nextTrack.getMainSignals() ) {
if ( signal.isBoundToNode( nextNode ) ) {
Logger.log( Logger.Component.TRAIN, Logger.Level.INFORMATION, "Train #" + id + " sees main signal #" +
signal.getId() + " showing " + signal.getDisplay() + "." );
drivingControl.mainSignalAhead( signal.getDisplay() );
}
}
}
/**
* Returns how many tracks are occupied by this train.
* @return The number of occupied tracks
*/
public Object getTrackOccupiedCount() {
return tracksOccupied.size();
}
/**
* Draws this train into a graphics context.
* @param g the graphics context
*/
public void draw(Graphics g) {
// TODO: The lines after return will only draw a circle...
return;
// Coordinates coord1 = head.getPosition().getNode().getCoordinates();
// Coordinates coord2 = tail.getPosition().getNode().getCoordinates();
//
// if ( coord1 != null && coord2 != null ) {
// g.setColor( Color.BLUE );
//
// int x = ( coord1.getX() + coord2.getX() ) / 2;
// int y = ( coord1.getY() + coord2.getY() ) / 2;
//
// g.fillOval( x, y, 15, 15 );
// }
}
void setDrivingControl( DrivingControl drivingControl ) {
this.drivingControl = drivingControl;
}
public DrivingControl getDrivingControl() {
return drivingControl;
}
public String toString() {
TrackPosition pos = null;
String out = String.format( "Train: %d, Train group: %d\n",
getId(), getTrainGroup().getId() );
pos = getHead().getPosition();
out += String.format( "Current position of head: track: %d, node: %d, away: %d\n" ,
pos.getTrack().getId(),
pos.getNode().getId(), pos.getDistance()
);
pos = getTail().getPosition();
out += String.format( "Current position of tail: track: %d, node: %d, away: %d\n",
pos.getTrack().getId(),
pos.getNode().getId(), pos.getDistance()
);
out += String.format( "Length: %d\n", getLength() );
out += String.format( "Driving direction: %s\n", ( getDirection() == DrivingControl.Direction.FORWARD ? "forward" : ( getDirection() == DrivingControl.Direction.BACKWARD ? "backward" : "undefined" ) ) );
out += String.format( "Driving mode: %s\n", drivingControl.getDrivingMode() );
out += String.format( "Total distance travelled: %d\n", getDistanceTravelled() );
out += String.format( "Tracks occupied: %d\n", getTrackOccupiedCount() );
out += String.format( "\nSpeed: %d\n", getTrainGroup().getCurrentSpeed() );
return out;
}
}