# You may have to edit this file to delete header lines produced by # mailers or news systems from this file (all lines before these shell # comments you are currently reading). # Shell archive made by dwjones on Thu Apr 13 21:58:14 CDT 2017 # To install this software on a UNIX system: # 1) create a directory (e.g. with the shell command mkdir stuff) # 2) change to that directory (e.g. with the command cd stuff), # 3) direct the remainder of this text to sh (e.g. sh < fileHoldingThisText). # This will make sh create files in the new directory; it will do # nothing else (if you're paranoid, you should scan the following text # to verify this before you follow these directions). Then read README # in the new directory for additional instructions. cat > README <<\xxxxxxxxxx A discrete event simulation of a road network, written in Java Contents: classes -- a list of all the java class-description files in this directory Makefile -- documents the interfile dependencies in this directory *.java -- see classes and Makefile for more information testRoadNetwork -- an example input file to test the simulation To compile the code, use either: javac @classes or make To test the simulation java RoadNetwork testRoadNetwork | more xxxxxxxxxx cat > classes <<\xxxxxxxxxx Errors.java PRNG.java ScanSupport.java Simulation.java Road.java Intersection.java Traversable.java NoStop.java StopLight.java Sink.java Source.java Vehicle.java RoadNetwork.java xxxxxxxxxx cat > Makefile <<\xxxxxxxxxx # Makefile documents relationships between parts of RoadNetwork application # author Douglas Jones # version 2017-04-10 # main program RoadNetwork.class: RoadNetwork.java ScanSupport.class Errors.class Simulation.class PRNG.class Road.class Intersection.class javac RoadNetwork.java # utilities ScanSupport.class: ScanSupport.java Errors.class javac ScanSupport.java Errors.class: Errors.java javac Errors.java Simulation.class: Simulation.java javac Simulation.java PRNG.class: PRNG.java javac PRNG.java # simulation components Road.class: Road.java Simulation.class Errors.class ScanSupport.class javac Road.java Intersection.class: Intersection.java Simulation.class Errors.class ScanSupport.class PRNG.class NoStop.class StopLight.class Source.class Sink.class Vehicle.class javac Intersection.java Traversable.class: Traversable.java Errors.class ScanSupport.class # in the above, we had to omit dependency on Intersection.class in # order to avoide creating a circular dependency relationship javac Traversable.java NoStop.class: NoStop.java Traversable.class Errors.class ScanSupport.class javac NoStop.java StopLight.class: StopLight.java Traversable.class Errors.class ScanSupport.class javac StopLight.java Vehicle.class: Vehicle.java ScanSupport.class PRNG.class javac Vehicle.java # make utilities clean: rm -f *.class rm -f *.html xxxxxxxxxx cat > Errors.java <<\xxxxxxxxxx /* Errors.java -- error reporting support package */ /** Utility package for error handling. * * General purpose error reporting package for command-line applications. * It allows reporting fatal errors and warnings to the user. * * @author Douglas Jones * @version 2017-04-05 * this code is ripped from RoadNetwork.java version 2017-03-31. */ public class Errors { private Errors(){}; // you may never instantiate this class private static int count = 0; // warning count, really public read only /** Provide public read only access to the count of warnings. * @return the count of the non-fatal warnings */ public static int count() { return count; } /** Warn of non fatal errors with a message on system.err * @param message the string to output as an error message. */ public static void warn( String message ) { System.err.println( "Warning: " + message ); count = count + 1; } /** Report fatal errors with a message on system.err * and then exit the application. * @param message the string to output as an error message. */ public static void fatal( String message ) { System.err.println( "Fatal error: " + message ); System.exit( -1 ); } } xxxxxxxxxx cat > Intersection.java <<\xxxxxxxxxx /* Intersection.java -- abstract definition of an intersection */ import java.util.LinkedList; import java.util.Scanner; /** Intersections are linked by one-way roads. * There are many subclasses of Intersection. The parent class, * defined here, establishes a shared framework used by all subclasses. * Only subclasses of Intersection may be instantiated. * * @author Douglas Jones * @version 2017-04-10 * This code is ripped from RoadNetwork.java version 2017-03-31. * * @see Road * @see Traversable * @see NoStop * @see StopLight * @see Source * @see Sink */ abstract class Intersection { /** List of Road objects leading from this intersection. * Ideally this would be public read-append only, but we use * getter and setter methods to achieve this. * @see outgoingSize * @see outgoingGet * @see addOutgoing */ protected final LinkedList outgoing = new LinkedList (); /** Getter method for outgoing road list. * Vehicles use this to "read their road map" to navigate. * @return the size of the list. * @see outgoing */ public int outgoingSize() { return outgoing.size(); } /** getter method for outgoing road list. * Vehicles use this to "read their road map" to navigate. * @return a selected element of the list. * @see outgoing */ public Road outgoingGet( int element ) { return outgoing.get( element ); } /** setter method to inform this intersection what roads leave it. * @param r a road that leaves this intersection. * @see outgoing */ public void addOutgoing( Road r ) { outgoing.add( r ); } /** List of Road objects leading to this intersection. * Ideally this would be public append only, but we use * a setter methods to achieve this. * Subclasses of Road need access to this so they can * associate one queue with each incoming road. * @see addIncoming */ protected final LinkedList incoming = new LinkedList (); /** setter method, how the intersection learns what roads enter it. * @param r a road that enters this intersection * @returns index of the incoming road */ public int addIncoming( Road r ) { incoming.add( r ); return incoming.size() - 1; } /** The name of the intersection. * We wish we could declare it to be final, but it is set by the * subclass constructors. */ public String name; /** factory method to scans and processes one intersection * definition. * @param sc Scanner from which the definition is read * @return either a new Intersection * or null if error. */ public static Intersection newIntersection( Scanner sc ) { String myName = ScanSupport.nextName( sc ); if ((myName == null) || ("".equals( myName ))) { Errors.warn( "Intersection has no name" ); sc.nextLine(); return null; } if (RoadNetwork.findIntersection( myName ) != null) { Errors.warn( "Intersection '" + myName + "' redefined." ); sc.nextLine(); return null; } String kind = ScanSupport.nextName( sc ); if ((kind == null) || ("".equals( kind ))) { // default NoStop return new NoStop( sc, myName ); } else if ("stoplight".equals( kind )) { // stoplight return new StopLight( sc, myName ); } else if ("source".equals( kind )) { // source of vehicles return new Source( sc, myName ); } else if ("sink".equals( kind )) { // sink consumes vehicles return new Sink( sc, myName ); } else { // error Errors.warn( "Intersection '" + myName + "' '" + kind + "' not an intersection kind" ); sc.nextLine(); } return null; } /** Check this Intersection to see if it meets global sanity * constraints. Errors are reported through calls to * Errors.warn. * Currently, the only sanity constraints checked here involve making * sure that the intersection is connected by roads. * Subclasses of intersection may define their own sanity checks. * @see Errors.warn */ public void check() { if (incoming.isEmpty()) { Errors.warn( this.toString() + ": no incoming roads" ); } if (outgoing.isEmpty()) { Errors.warn( this.toString() + ": no outgoing roads" ); } } /** Get the textual representation of this intersection. * Subclasses are likely to augment this with more content. * @return the textual representation of this intersection. */ public String toString() { return( "intersection " + name ); } /********** simulation code for this intersection **********/ /** Simulation method for arrival at this intersection. * Every subclass must define this method. * @param time The time at which the vehicle arrives. * @param v The vehicle that arrives. * @param v The arrival direction, important only for intersections * that maintain one incoming queue per incoming road. */ public abstract void arrivalEvent( float time, Vehicle v, int dir ); /** Simulation method for departure from this intersection. * Every subclass must define this method. * @param time the time at which the vehicle departs. * @param v the vehicle that departs. */ public abstract void departureEvent( float time, Vehicle v ); } xxxxxxxxxx cat > NoStop.java <<\xxxxxxxxxx /* NoStop.java -- implements uncontrolled intersections */ import java.util.LinkedList; import java.util.Scanner; /** Uncontrolled Intersections, cars pass through first-come first-served. * Cars wait only if the intersection is currently blocked by another car. * @author Douglas Jones * @version 2017-04-12 * this code is ripped from RoadNetwork.java version 2017-03-31, * with minor changes to improve the documentation. * @see Traversable * @see Intersection */ public class NoStop extends Traversable { /** Scan and processes one uncontrolled Intersection. * @param sc Scanner from which the description is read * @param myName the value to be put in the name * field of the new Intersection. */ public NoStop( Scanner sc, String myName ) { // now keyword for intersection type was scanned name = myName; getTraversalTime( sc ); ScanSupport.lineEnd( sc, () -> "Intersection '" + name + "'" ); } /** Get this uncontrolled intersection's name in a form like the input * @return the textual representation of the intersection */ public String toString() { return( super.toString() + " " + traversalTime ); } /********** simulation code for this intersection **********/ // control over vehicles waiting to get through the intersection private boolean occupied = false; // the intersection is initially open private final LinkedList waiting = new LinkedList (); /** Simulation method for arrival at this uncontrolled intersection. * @param time the time at which the vehicle arrives. * @param v the vehicle that arrives. * @param dir arrival direction. */ public void arrivalEvent( float time, Vehicle v, int dir ) { if (occupied) { // make this vehicle wait waiting.add( v ); } else { // let this vehicle continue onward Simulation.schedule( //Bug: actual traversal could involve vehicle time + traversalTime, (float t)-> this.departureEvent( t, v ) ); occupied = true; } } /** Simulation method for departure from this intersection. * @param time the time at which the vehicle departs. * @param v the vehicle that departs. */ public void departureEvent( float time, Vehicle v ) { // first, make vehicle v arrive at an outgoing road now Simulation.schedule( time, (float t)->v.selectRoad( this ).arrivalEvent( t, v ) ); System.out.println( "At " + time + " vehicle " + v + " left " + name ); // second, see if someone else can enter this intersection if (waiting.isEmpty()) { // if someone arrives later, they won't wait occupied = false; } else { // get a waiting vehicle and let it continue Vehicle v1 = waiting.remove(); Simulation.schedule( //Bug: actual traversal could involve vehicle time + traversalTime, (float t)-> this.departureEvent( t, v1 ) ); } } } xxxxxxxxxx cat > PRNG.java <<\xxxxxxxxxx /* PRNG.java -- pseudo-random number generator support */ import java.util.Random; /** Pseudo-random number generator support. * * Java's utility class Random allows users to create unlimited numbers * of pseudo-random number streams that are, unfortunately strongly * correlated. Therefore, this class gives us a single stream from * which all random numbers needed by the application are drawn. * * @author Douglas Jones * @version 2017-03-31 * this code is ripped from RoadNetwork.java version 2017-03-31. */ public class PRNG { private static final Random rand = new Random(); //Bug: seeding on the above is dumb /** Draw a single integer from the pseudo-random stream. * @param cieling the least integer greater than the output. * @return an integer from zero (inclusive) to cieling (exclusive). */ public static int nextInt( int cieling ) { return rand.nextInt( cieling ); } //Notice: other distributions should be computed from rand here } xxxxxxxxxx cat > Road.java <<\xxxxxxxxxx /* Road.java -- simulation of one road */ import java.util.Scanner; /** Roads link intersections. * @author Douglas Jones * @version 2017-04-10 * This code is ripped from RoadNetwork.java version 2017-03-31. * * @see Intersection * @see Simulation */ public class Road { private final float travelTime; // how long to travel down road private final Intersection destination; // where road goes, or null private final Intersection source; // source of road, or null private int dir; // direction of this road // Road name is the source-destination names /** initializer scans and processes one road definition * @param sc the Scanner from which the Road description is read */ public Road( Scanner sc ) { String srcName = ScanSupport.nextName( sc ); String dstName = ScanSupport.nextName( sc ); // if there are no next names on this line, these are "" // therefore, the findIntersection calls below will fail source = RoadNetwork.findIntersection( srcName ); if (source == null) { Errors.warn( "Road '" + srcName + "' '" + dstName + "' source undefined." ); } destination = RoadNetwork.findIntersection( dstName ); if (destination == null) { Errors.warn( "Road '" + srcName + "' '" + dstName + "' destination undefined." ); } travelTime = ScanSupport.nextFloat( sc ); ScanSupport.lineEnd( sc, () -> "Road '" + srcName + "' '" + dstName + "'" ); // do sanity checks on fields, at this point, toString is legal if (travelTime < 0.0f) Errors.warn( this.toString() + ": has a negative travel time?" ); if (travelTime != travelTime) Errors.warn( // odd condition above! True when travelTime is NaN this.toString() + ": no travel time given." ); // let the source and destination know about this road if (destination != null) dir = destination.addIncoming( this ); if (source != null) source.addOutgoing( this ); } /** check this road to see if it meets global sanity constraints */ public void check() { // nothing to check. } /** output this road in a format like that used for input */ public String toString() { String srcName; String dstName; if (source == null) { srcName = "???"; } else { srcName = source.name; } if (destination == null) { dstName = "???"; } else { dstName = destination.name; } return( "road " + srcName + " " + dstName + " " + travelTime ); } /********** simulation code for this road **********/ /** simulation method for arrival at this road * @param time the time at which the vehicle arrives * @param v the vehicle that arrives */ public void arrivalEvent( float time, Vehicle v ) { // schedule arrival event at the next intersection Simulation.schedule( time + travelTime, (float t) -> departureEvent( t, v ) ); } /** simulation method for departure from this road * @param time the time at which the vehicle departs * @param v the vehicle that departs */ public void departureEvent( float time, Vehicle v ) { // schedule arrival event at the next intersection Simulation.schedule( time, (float t) -> destination.arrivalEvent( t, v, dir ) ); // could just call destination.arrivalEvent( time, v, dir ); } } xxxxxxxxxx cat > RoadNetwork.java <<\xxxxxxxxxx /* RoadNetwork.java -- the main class for a road network simulation */ import java.util.LinkedList; import java.io.File; import java.io.FileNotFoundException; import java.util.Scanner; /** The main class that controls the reading, writing and simulation of a * road network. * @author Douglas Jones * @version 2017-04-12 * this code is ripped from RoadNetwork.java version 2017-03-31 * with minor changes to the comments. * @see Road * @see Intersection * @see Simulation * @see ScanSupport * @see Errors * @see main */ public class RoadNetwork { /** The list of all roads in the road network. */ private static LinkedList roads = new LinkedList (); /** The list of all intersections in the road network. */ private static LinkedList inters = new LinkedList (); /** Utility method to look up an intersection by name. * Class Road needs this to find the intersections * linked by a road. * Class Intersection needs this to find if the new * intersection has a duplicate name. * * @param s the name of the intersection. * @return the Intersection with that name * or null if there is no such object */ public static Intersection findIntersection( String s ) { // Bug: this uses a linear search. for ( Intersection i: inters ) { if (i.name.equals( s )) return i; } return null; } /** read in a road network and build the lists * roads and inters. * @param sc the scanner from which input text is read. */ private static void initializeNetwork( Scanner sc ) { while (sc.hasNext()) { // until we hit the end of the file String command = ScanSupport.nextName( sc ); if (("intersection".equals( command )) || ("i".equals( command )) ) { Intersection i = Intersection.newIntersection(sc); if (i != null) inters.add( i ); } else if (("road".equals( command )) || ("r".equals( command )) ) { roads.add( new Road( sc ) ); } else if ("".equals( command )) { // blank line // line holding -- ends up here! ScanSupport.lineEnd( sc, () -> "Line" ); } else { Errors.warn( "Command '" + command + "' is not road or intersection" ); sc.nextLine(); // skip the rest of the error } } } /** Check the sanity of the network by calling the check * method of each component. Sanity checking outputs error messages * through class Errors. */ private static void checkNetwork() { for ( Intersection i: inters ) { i.check(); } for ( Road r: roads ) { r.check(); } } /** Write out a road network * by calling the toString method of each component. */ private static void writeNetwork() { for ( Intersection i: inters ) { System.out.println( i.toString() ); } for ( Road r: roads ) { System.out.println( r.toString() ); } } /** main program that reads a road network and then, * if the network does not contain errors, simulates it, and * if it does contain errors, just writes it out. * @param args the command line arguments. */ public static void main( String[] args ) { // verify that the argument exists. if (args.length < 1) { Errors.fatal( "Missing file name on command line" ); } else if (args.length > 1) { Errors.fatal( "Unexpected command line args" ); } else try { initializeNetwork( new Scanner( new File( args[0] ) ) ); checkNetwork(); if (Errors.count() > 0) { writeNetwork(); } else { Simulation.run(); } } catch (FileNotFoundException e) { Errors.fatal( "Could not read '" + args[0] + "'" ); } } } xxxxxxxxxx cat > ScanSupport.java <<\xxxxxxxxxx /* ScanSupport.java -- package of support methods for scanning */ import java.util.Scanner; import java.util.regex.Pattern; /** Support methods for scanning input files. * @author Douglas Jones * @version 2017-04-05 * This is ripped from RoadNetwork.java version 2017-03-31 with a change: * nextFloat method is added based on the homework and the comments are * somewhat improved. * @see Errors */ public class ScanSupport { /** Pattern for recognizing identifers */ public static final Pattern name // letter followed by alphanumeric = Pattern.compile( "[a-zA-Z][a-zA-Z0-9_]*|" ); /** Pattern for recognizing floating point numbers */ public static final Pattern numb // Digits.Digits or .Digits or nothing = Pattern.compile( "[0-9]+\\.?[0-9]*|\\.[0-9]+|" ); /** Pattern for recognzing whitespace excluding newlines */ public static final Pattern whitespace = Pattern.compile( "[ \t]*" ); /** Get next name without skipping to next line (unlike sc.Next()). * @param sc the scanner from which end of line is scanned. * @return the name, if there was one, or an empty string. */ public static String nextName( Scanner sc ) { sc.skip( whitespace ); // the following is weird code, it skips the name // and then returns the string that matched what was skipped sc.skip( name ); return sc.match().group(); } /** Get next float without skipping lines (unlike sc.nextFloat()). * @param sc the scanner from which end of line is scanned. * @return the name, if there was one, or NaN if not. */ public static Float nextFloat( Scanner sc ) { sc.skip( whitespace ); // the following is weird code, it skips the name // and then returns the string that matched what was skipped sc.skip( numb ); String f = sc.match().group(); // now convert what we can or return NaN if (!"".equals( f )) { return Float.parseFloat( f ); } else { return Float.NaN; } } /** Class used only for deferred evaluation of lambda expressions * passed to lineEnd. */ public interface EndMessage { /** Method to compute the error message text * @return the text the error message */ public abstract String myString(); } /** Advance to next line and complain if there is junk at the line end; * call this when all useful content has been consumed from the line * it skips optional line-end comments and complains about anything * it finds while advancing to the next line. * @see Errors * @see EndMessage * @param sc the scanner from which end of line is scanned. * @param message will be evaluated only when there is an error; * it is typically passed as a lambda expression, for example, * {@code ScanSupport.lineEnd( sc, () -> "this " + x + " that" );} */ public static void lineEnd( Scanner sc, EndMessage message ) { sc.skip( whitespace ); String lineEnd = sc.nextLine(); if ( (!lineEnd.equals( "" )) && (!lineEnd.startsWith( "--" )) ) { Errors.warn( "" + message.myString() + " followed unexpected by '" + lineEnd + "'" ); } } } xxxxxxxxxx cat > Simulation.java <<\xxxxxxxxxx /* Simulation.java -- discrete event simulation framework */ import java.util.PriorityQueue; /** Discrete event simulation support framework * @author Douglas Jones * @version 2017-04-10 * this code is ripped from RoadNetwork.java version 2017-03-31 */ class Simulation { /** Interface allowing actions to be passed as lambda expressions. */ public interface Action { void trigger( float time ); } /** Events are the core of the control structure of the simulation. */ private static class Event { /** Each event has a time */ float time; /** Each event has an action */ Action act; /** Construct and initialize a new Event. * @param t the time of the event. * @param a the act to be triggered then. */ Event( float t, Action a ) { time = t; act = a; }; /** Trigger the event's act at the indicated * time. */ void trigger() { act.trigger( time ); } } /** Events are queued for {@code run} retrieve in chronological order. */ private static PriorityQueue eventSet = new PriorityQueue ( (Event e1, Event e2)->Float.compare( e1.time, e2.time ) ); /** Users call schedule to schedule one action at some time, * usually a later time but possibly the current time. * * @param time specifies when the event should occur. * @param act specifies what to do at that time. * Typically, {@code act} is a lambda expression, * so a call to schedule could look like this: * * {@code * Simulation.schedule( t, (float t)->object.method( params, t ) ) * } */ public static void schedule( float time, Action act ) { eventSet.add( new Event( time, act ) ); } /** the main program should build the model, * this inolves scheduling some initial events * and then, just once, it should call {@code run}. */ public static void run() { while (!eventSet.isEmpty()) { Event e = eventSet.remove(); e.trigger(); } } } xxxxxxxxxx cat > Sink.java <<\xxxxxxxxxx /* Sink.java -- sink intersections consume vehicles from the road network */ import java.util.Scanner; /** Sink Intersections destroy vehicdles and could collect statistics. * @author Douglas Jones * @version 2017-04-12 * ripped from RoadNetwork.java version 2017-03-31 * with minor changes to improve comments. * @see Intersection */ public class Sink extends Intersection { // attributes of the sink intersection? None! /** Initializer scans and processes one sink intersection. * @param sc {@code Scanner} from which to read the description. * @param myName the value to be put in the {@code name} field. */ public Sink( Scanner sc, String myName ) { // the name and keyword for the Source were already scanned name = myName; ScanSupport.lineEnd( sc, () -> this.toString() ); } /** Output this {@code Intersection} in a format like that used for * input. * @return the textual representation of this object */ public String toString() { return( super.toString() + " sink" ); } /** Check this Sink to see if it meets global sanity constraints. * This overrides the default check because it requires that there * be incoming roads but forbids any outgoing roads. */ public void check() { if (incoming.isEmpty()) { Errors.warn( this.toString() + ": no incoming roads" ); } if (!outgoing.isEmpty()) { Errors.warn( this.toString() + ": no outgoing roads allowed" ); } } /********** simulation code for this intersection **********/ /** Simulate the arrival of a vehicle at this sink intersection. * @param time the time at which the vehicle arrives. * @param v the vehicle that arrives. * @param dir arrival direction. */ public void arrivalEvent( float time, Vehicle v, int dir ) { //nothing happens unless we add code to gather statistics System.out.println( "At " + time + " crushed vehicle " + v + " at " + name ); } /** Simulate the departure of a vehicle from this intersection. * This never happens but we must provide a method. * @param time the time at which the vehicle departs. * @param v the vehicle that departs. */ public void departureEvent( float time, Vehicle v ) { //Bug: No vehicles ever depart from a sink, thow something. } } xxxxxxxxxx cat > Source.java <<\xxxxxxxxxx /* Source.java -- source intersections inject traffic into the road network */ import java.util.Scanner; /** Source Intersections create vehicles. * Each source intersection periodically launches new vehicles into the * road network. * * @author Douglas Jones * @version 2017-04-12 * this code is ripped from RoadNetwork.java version 2017-03-31, * with minor changes to improve comments. * @see Intersection */ public class Source extends Intersection { /** the interval between successive arriving vehicles */ final float arrivalTime; /** Scan and processes one source Intersection description. * @param sc {@code Scanner} from which the description is read. * @param myName the value to be put in the {@code name} field. */ public Source( Scanner sc, String myName ) { // now keyword for Source was scanned name = myName; arrivalTime = ScanSupport.nextFloat( sc ); if (arrivalTime != arrivalTime) { // it is NaN Errors.warn( this.toString() + "' stoplight, no interarrival time given." ); } else if (arrivalTime < 0.0f) { Errors.warn( this.toString() + "' has a negative interarrival time?" ); } ScanSupport.lineEnd( sc, () -> this.toString() ); //launch this source vehicle generation process Simulation.schedule( arrivalTime, (float t)->this.departureEvent( t, new Vehicle() ) ); } /** Get the intersection description in a form like that used for input. * @return the textual representation of this intersection. */ public String toString() { return( super.toString() + " source " + arrivalTime ); } /** Check this to see if it meets global sanity constraints. * This overrides the default {@code check} method. * In this case, no incoming roads are permitted * but there must be at least one outgoing road. */ public void check() { if (!incoming.isEmpty()) { Errors.warn( this.toString() + ": no incoming roads allowed" ); } if (outgoing.isEmpty()) { Errors.warn( this.toString() + ": no outgoing roads" ); } } /********** simulation code for this intersection **********/ /** Simulate the arrival of a vehicle at this intersection. * Our framework requires this, but no vehicles ever arrive at sources. * @param time the time at which the vehicle arrives. * @param v the vehicle that arrives. * @param dir the arrival direction. */ public void arrivalEvent( float time, Vehicle v, int dir ) { //Bug: No vehicles ever arrive at a source, thow something. } /** Simulate the departure of a vehicle from this intersection * @param time the time at which the vehicle departs * @param v the vehicle that departs */ public void departureEvent( float time, Vehicle v ) { // first, make vehicle v arrive at an outgoing road now Simulation.schedule( time, (float t)->v.selectRoad( this ).arrivalEvent( t, v ) ); // we could have done v.selectRoad( ).arrivalEvent( ) System.out.println( "At " + time + " create vehicle " + v + " at " + name ); // second, schedule the next departure from the source Simulation.schedule( time + arrivalTime, //Bug: the above suggests a really stupid arrival model (float t)->this.departureEvent( t, new Vehicle() ) ); } } xxxxxxxxxx cat > StopLight.java <<\xxxxxxxxxx /* StopLight.java -- traversable intersections controlled by stoplights */ import java.util.LinkedList; import java.io.File; import java.io.FileNotFoundException; import java.util.Scanner; /** StopLight protected Intersections, cars pass through on green lights * so long as the interseciton is clear. If the light is red, cars * wait until the light changes and the intersection clears. Only one * incoming road sees a green light at a time. * Each stoplight has a logical process that periodically changes the state * of the light. * * @author Douglas Jones * @version 2017-03-31 * This code is ripped from RoadNetwork.java version 2017-03-31, * with minor changes to improve the comments and a small bug fix: * the old version let a vechicles into the intersection without marking it * as occupied when the light changed. * @see Intersection * @see Traversable */ public class StopLight extends Traversable { /** how long is the light green in each direction. */ private final float greenTime; /** how long is the light yellow in each direction. */ private final float yellowTime; /** Scan and initialize one stoplight Intersection. * @param sc Scanner from which the description for this is read. * @param myName the value to be put in the {@code name} field. */ public StopLight( Scanner sc, String myName ) { // now keyword for StopLight was scanned name = myName; getTraversalTime( sc ); greenTime = ScanSupport.nextFloat( sc ); if (greenTime != greenTime) { // it is NaN Errors.warn( "Intersection '" + name + "' stoplight, no green time given." ); } else if (greenTime < 0.0f) { Errors.warn( "Intersection '" + name + "' Stoplight '" + greenTime + "' has a negative green time?" ); } yellowTime = ScanSupport.nextFloat( sc ); if (yellowTime != yellowTime) { // it is NaN Errors.warn( "Intersection '" + name + "' stoplight, no yellow time given." ); } else if (yellowTime < 0.0f) { Errors.warn( "Intersection '" + name + "' Stoplight '" + greenTime + "' '" + yellowTime + "' has a negative yellow time?" ); } ScanSupport.lineEnd( sc, () -> "Intersection '" + name + "' stoplight '" + greenTime + "' '" + yellowTime ); // start the logical process for this stoplight Simulation.schedule( greenTime + yellowTime, //Bug: the above is simplistic, isn't yellow different? (float t) -> lightChangeEvent( t ) ); } /** Get a description of this in a format like that used for input. * @return the textual representation of this intersection. */ public String toString() { return( super.toString() + " stoplight " + traversalTime + " " + greenTime + " " + yellowTime ); } /********** simulation code for this stoplight **********/ /** The green light direction. */ private int lightDir = 0; /** Is this intersection currently occupied? */ private boolean occupied = false; /** Queues of waiting vehicles, one for each incoming road indexed * by road number. */ private LinkedList waiting[]; /** The size of the set of incoming roads. * {@code incoming.size()} could be used, and this would be final * except that Java does not permit final here because of the way * it is initialized in {@code check}. */ private int incomingSize; /** Check this stop light to see if it's sane and finish initialization. * This uses the default check and then does more initialization. */ public void check() { super.check(); // do the regular checks // finish the initialization incomingSize = incoming.size(); // create the queues, one per incoming road // the code should read // waiting = new LinkedList [incomingSize]; // unfortunately, that isn't allowed, so we fake it as follows: class LinkedListVehicle extends LinkedList {} waiting = new LinkedListVehicle [incomingSize]; for (int i = 0; i < incomingSize; i++) { waiting[i] = new LinkedListVehicle (); } } /** Simulate a vehicle arriving at this stoplight. * @param time the time at which the vehicle arrives. * @param v the vehicle that arrives. * @param dir the arrival direction. */ public void arrivalEvent( float time, Vehicle v, int dir ) { if ((lightDir != dir) || (occupied)) { // make this vehicle wait waiting[dir].add( v ); } else { // let this vehicle continue onward Simulation.schedule( //Bug: actual traversal could involve vehicle time + traversalTime, (float t)-> this.departureEvent( t, v ) ); occupied = true; } } /** Simulate a vehicle departing this stoplight. * @param time the time at which the vehicle departs. * @param v the vehicle that departs. */ public void departureEvent( float time, Vehicle v ) { // first, make vehicle v arrive at an outgoing road now Simulation.schedule( time, (float t)->v.selectRoad( this ).arrivalEvent( t, v ) ); System.out.println( "At " + time + " vehicle " + v + " left " + name ); // second, see if someone else can enter this intersection if (waiting[lightDir].isEmpty()) { // if someone arrives later, they won't wait occupied = false; } else { // get a waiting vehicle and let it continue Vehicle v1 = waiting[lightDir].remove(); Simulation.schedule( //Bug: actual traversal could involve vehicle time + traversalTime, (float t)-> this.departureEvent( t, v1 ) ); } } /** Simulate a change of state of this stoplight. * @param time the time at which the light changes. */ private void lightChangeEvent( float time ) { // change the light lightDir = lightDir + 1; if (lightDir >= incomingSize) lightDir = 0; // see if light change lets a car into the intersection if ((!waiting[lightDir].isEmpty()) && (!occupied)) { // get a waiting vehicle and let it continue Vehicle v = waiting[lightDir].remove(); Simulation.schedule( //Bug: actual traversal could involve vehicle time + traversalTime, (float t)-> this.departureEvent( t, v ) ); occupied = true; } // schedule the next change Simulation.schedule( time + greenTime + yellowTime, //Bug: the above is simplistic, isn't yellow different? (float t) -> lightChangeEvent( t ) ); } } xxxxxxxxxx cat > Traversable.java <<\xxxxxxxxxx /* Traversable.java -- behavior shared by all traversable intersections */ import java.util.Scanner; /** all Intersections that are traversable share this common behavior. * @author Douglas Jones * @version 2017-04-12 * This version of the code is ripped from RoadNetwork.java version 2017-04-31 * @see Intersection * @see NoStop * @see StopLight */ public abstract class Traversable extends Intersection { /** traversable intersections are characterized by how long it takes * to cross the intersection */ protected float traversalTime; /** Scan the traversal time of a traversable intersection. * @param sc the input from which the time is scanned. */ protected void getTraversalTime( Scanner sc ) { traversalTime = ScanSupport.nextFloat( sc ); if (traversalTime != traversalTime) { /* it is NaN */ Errors.warn( "Intersection '" + name + "' no traversal time given." ); } else if (traversalTime < 0.0f) { Errors.warn( "Intersection '" + name + "' '" + traversalTime + "' has a negative traversal time?" ); } } } xxxxxxxxxx cat > Vehicle.java <<\xxxxxxxxxx /* Vehicle.java -- implements what little behavior vehicles exhibit */ import java.util.LinkedList; import java.io.File; import java.io.FileNotFoundException; import java.util.Scanner; /** Simulated vehicles. * @author Douglas Jones * @version 2017-03-31 * code ripped from RoadNetwork.java version 2017-03-31, * with small improvements to comments. * @see PRNG */ public class Vehicle { // Bug: Vehicles could have many more attributes, such as a plan or // an intended destination. /** Make a navigation decision. * @param i The {@code Intersection} at which the decision is made. * @return The {@code Road} to take away from that intersection. */ public Road selectRoad( Intersection i ) { //Bug: we use the most stupid algorithm, random decision return i.outgoingGet( PRNG.nextInt( i.outgoingSize() ) ); } } xxxxxxxxxx cat > testRoadNetwork <<\xxxxxxxxxx intersection A 0.05 intersection B stoplight 0.05 1 4 intersection C source 12 intersection D sink road A B 1.5 road A B 1.8 road B A 0.5 road C A 0.015 road A D 0.024 xxxxxxxxxx