package simulator; /* * Attraction.java * Created on 2005/1/3 * */ import java.util.LinkedList; import java.util.ListIterator; import java.io.*; import java.util.Formatter; /** * Attraction - This is the base class for all types of attractions. It * contains the basic variables needed for statistics and ride implementation. * However, ride specific information such as when to dispatch cars and when to * add/remove cars are left for the implementing sub-classes. * @author Ping * */ public abstract class Attraction implements ClockedThing, Cloneable { public static String ATTRACTION_TYPE = "Default"; /* * This internal class represents the cars in the attraction. Each car * holds the customers that are on the car. * */ protected class Car implements ClockedThing { // this stores the status of the car private CarStatus carStatus; // This stores how many minutes this car has spent in the current // activity (either in the ride, waiting for passengers, or // loading). private int minutesSpent; // this stores the actual customers on the car private Customer[] customers; private int numRiders; public Car() { carStatus = CarStatus.CAR_WAITING; customers = new Customer[carCapacity]; numRiders = 0; } /** * This is a copy constructor, it will make a deep copy of everything * @param copy */ public Car(Car copy) { this.carStatus = copy.carStatus; this.minutesSpent = copy.minutesSpent; this.numRiders = copy.numRiders; customers = new Customer[copy.customers.length]; try { for(int i = 0; i < numRiders; i++) { // only copy over if there are actually riders customers[i] = (Customer)copy.customers[i].clone(); } } catch(CloneNotSupportedException e) { e.printStackTrace(); } } /* * This function returns true if the car is empty */ public boolean isCarEmpty() { return numRiders == 0; } /* * This function returns true if the car is full */ public boolean isCarFull() { return numRiders == customers.length; } /* * This function returns true if the car is ready to be dispatched */ public boolean isCarReady() { return carStatus == CarStatus.CAR_READY; } /* * This function returns true if the car is waiting to be loaded */ public boolean isCarWaiting() { return carStatus == CarStatus.CAR_WAITING; } /* * This function returns true if the car is on the ride */ public boolean isCarOnRide() { return carStatus == CarStatus.CAR_ON_RIDE; } public boolean isCarDone() { return carStatus == CarStatus.CAR_DONE; } /* * This function returns true if the car is loading */ public boolean isCarLoading() { return carStatus == CarStatus.CAR_LOADING; } /* * This function returns the number of riders on the car */ public int getNumRiders() { return numRiders; } /* * This function gets the number of minutes spent on the current activity */ public int getMinutesSpent() { return minutesSpent; } /* * This function loads the array of customers onto the car * and sets the car to the loading status. */ public void loadCustomers(Customer[] boardingCustomers) { if(boardingCustomers.length > customers.length) { throw new IllegalArgumentException("Unable to load more customers than" + " the maximum number of riders allowed per car."); } for(int i = 0; i < boardingCustomers.length; i++) { customers[i] = boardingCustomers[i]; if(!isSimulation) { customers[i].boardRide(Attraction.this); } } // update the number of riders currNumOfRiders += boardingCustomers.length; totalRiders += boardingCustomers.length; if(currNumOfRiders > maxNumOfRiders) { maxNumOfRiders = currNumOfRiders; } numRiders = boardingCustomers.length; // if there are no load time for this ride, then go straight to // ready state if(loadingTime == 0) { carStatus = CarStatus.CAR_READY; } else { carStatus = CarStatus.CAR_LOADING; } minutesSpent = 0; } /* * This function unloads all the customers on the ride and * sends them to the exit */ public void unloadCustomers() { for(int i = 0; i < numRiders; i++) { if(!isSimulation) { customers[i].exitRide(Attraction.this); } customers[i] = null; } // update the number of riders currNumOfRiders -= numRiders; numRiders = 0; carStatus = CarStatus.CAR_WAITING; // reset the time minutesSpent = 0; } public void dispatchCar() { // only cars that are loaded and ready can be dispatched if(carStatus == CarStatus.CAR_READY) { carStatus = CarStatus.CAR_ON_RIDE; minutesSpent = 0; } } /* * This function updates the car's status */ public boolean onClockTick() { minutesSpent++; // only cars on the ride already or those being loaded need to // be updated. if(carStatus == CarStatus.CAR_ON_RIDE && minutesSpent >= rideLength) { // the ride has finished, unload everyone carStatus = CarStatus.CAR_DONE; minutesSpent = 0; } else if(carStatus == CarStatus.CAR_LOADING && minutesSpent >= loadingTime) { // The car has been loaded and can be dispatched carStatus = CarStatus.CAR_READY; minutesSpent = 0; } return true; } } // Enumeration to represent the state of the ride protected enum AttractionStatus {OPEN, CLOSED} // This enum specifies the status of the car. Note that it cannot be // declared inside the Car class since it is an inner class. // CAR_WAITING is when the car is done and waiting to be loaded // CAR_ON_RIDE is when the car has been dispatched and is on the ride // CAR_LOADING is when we are loading the car with passengers // CAR_READ is when the car has been loaded and is ready to be dispatched // CAR_DONE is when the car just finished the ride protected enum CarStatus {CAR_WAITING, CAR_ON_RIDE, CAR_LOADING, CAR_READY, CAR_DONE} protected AttractionStatus attractionStatus; // local variables for storing general information about an attraction protected String name; protected Location entrance; protected Location exit; //protected int numOfCars = 0; protected int maxNumOfCarsPossible; protected int carCapacity; protected int loadingTime; protected int rideLength; // we use a linked list to implement a queue of the riders in line protected LinkedList waitingLine; // This list stores the entering time of each customer so that we can // determine the waiting time of each customer protected LinkedList waitingTime; // This link list represents the cars that are active in the attraction protected LinkedList cars; // Variables for storing the statistics // The number of ticks since the ride openned protected int numOfTicks; // the max number of riders in this attraction (actually riding it) protected int maxNumOfRiders; // current number of riders protected int currNumOfRiders; // the max length of the waiting line protected int maxLineLength; // the max/min number of cars in operation protected int maxNumOfCars; protected int minNumOfCars; // the total number of people who have been on this attraction protected int totalRiders; // These values are used to compute the averages // these values should be updated properly on each clock tick in // order for the averages to be correct protected int totalRidersForAllTicks; protected int totalCarsForAllTicks; protected int totalLineLengthForAllTicks; protected int totalWaitingTime; // this flag is to denote whether or not the attraction is in "simulation" // mode. If it is in simulation mode, it will not make calls to // inform customers of anything protected boolean isSimulation = false; protected int estimatedWaitTime; protected int timeSinceEstimate; protected final static int TIME_UNTIL_NEW_ESTIMATES = 10; // This variable stores whether or not the ride should be processed // on any given clock tick. The ride should not be processed // if the ride is closed and there are no body in line/on the ride. protected boolean needProcessing; /** * This is the constructor for the attraction class. Note that this is protected so * that outside classes cannot make an instance of this class. It simply initializes * some of the variables. * * @param attractionName * @param entrance * @param exit * @param initialNumOfCars * @param maxNumOfCarsPossible * @param carCapacity * @param rideTime * @param loadingTime */ protected Attraction(String attractionName, Location entrance, Location exit, int initialNumOfCars, int maxNumOfCarsPossible, int carCapacity, int rideTime, int loadingTime) { // Note that some initialization we use the "this" some we don't. We only need // to use "this" if there could be confusion in the name (if two variables have the same // name, the variable with "this" is the variable belonging to the class while // the one without is passed in from the parameter). name = attractionName; this.entrance = entrance; this.exit = exit; this.maxNumOfCarsPossible = maxNumOfCarsPossible; this.carCapacity = carCapacity; rideLength = rideTime; this.loadingTime = loadingTime; attractionStatus = AttractionStatus.OPEN; waitingLine = new LinkedList(); waitingTime = new LinkedList(); cars = new LinkedList(); // load up the initial cars for(int i = 0; i < initialNumOfCars; i++) { cars.add(new Car()); } //maxNumOfCars = initialNumOfCars; isSimulation = false; } /* * This function pulls the appropriate number of customers out of the * waiting line and calculates their waiting time. It then returns * an array of the customers that was removed and ready to be put on the * ride. */ protected Customer[] extractCustomers() { // lets find out how many customers we can actually pull out of the // waiting line int size = Math.min(waitingLine.size(), carCapacity); Customer[] riders = new Customer[size]; // pull the customers out of the waiting line for(int i = 0; i < size; i++) { riders[i] = waitingLine.remove(); int timeEntered = waitingTime.remove(); // calculate the rider's waiting time totalWaitingTime += numOfTicks - timeEntered; } return riders; } /** * This function returns true if a car will be available during this * clock tick. This includes cars that are waiting or cars that are done. * @return */ protected boolean hasCarAvailable() { for(Car c : cars) { if(c.isCarDone() || c.isCarWaiting()) { // found a car that will be available return true; } } return false; } /** * This function removes the first empty car from the list of cars */ protected void removeCar() { // find a car thats waiting so it can be removed // don't want to remove a car that is in the middle of the ride. ListIterator iterator = cars.listIterator(); while(iterator.hasNext()) { Car c = iterator.next(); if(c.isCarEmpty()) { // remove the first empty car we find iterator.remove(); break; } } } /** * */ protected void updateNumOfCars() { if(shouldAddCar()) { //System.out.println("Adding 1 car"); cars.addFirst(new Car()); if(cars.size() > maxNumOfCars) { maxNumOfCars = cars.size(); } } else if(shouldRemoveCar()) { //System.out.println("Removing 1 car"); removeCar(); if(cars.size() < minNumOfCars) { minNumOfCars = cars.size(); } } } /** * This function will return true if the ride should add a car to * the ride. This is ride type specific. * @return True if a car should be added, false otherwise */ abstract protected boolean shouldAddCar(); /** * This function will return true if the ride should remove a car from * the ride. This is ride type specific. * @return True if a car should be removed, false otherwise. */ abstract protected boolean shouldRemoveCar(); /** * This function returns the name of attraction * @return String containing the name of the attraction */ public String getName() { return name; } /** * This function will determine if there is anyone in the ride * (actually riding or in line). * * @return boolean - True if there is no one in line or no one * in the ride, false otherwise. */ public boolean isRideEmpty() { return currNumOfRiders == 0 && waitingLine.size() == 0; } /** * This function returns the length of the ride * @return int representing how long the ride takes (in minutes) */ public int getRideLength() { return rideLength; } /** * This function returns the loading time of the ride * @return int representing how long it takes to load the cars * on the ride (in minutes) */ public int getLoadingTime() { return loadingTime; } /** * This function returns the location of the ride's entrance * @return Returns the entrance. */ public Location getEntrance() { return entrance; } /** * This function returns the location of the ride's exit * @return Returns the exit. */ public Location getExit() { return exit; } /** * This function returns the number of people waiting in line (currently) * for this attraction * @return int representing the number of people in line */ public int getLineLength() { return waitingLine.size(); } /** * This returns the maximum line length since the attraction openned * @return Returns the maxLineLength. */ public int getMaxLineLength() { return maxLineLength; } /** * This returns the maximum number of cars in operation since the * attraction openned * @return Returns the maxNumOfCars. */ public int getMaxNumOfCars() { return maxNumOfCars; } /** * This function gets the minimum number of cars used since the ride * openned * @return Returns the minNumOfCars. */ public int getMinNumOfCars() { return minNumOfCars; } /** * This returns the maximum number of riders on the attraction since * the attraction openned * @return Returns the maxNumOfRiders. */ public int getMaxNumOfRiders() { return maxNumOfRiders; } /** * This returns the total number of riders who have been on this attraction * since the attraction openned * @return Returns the totalRiders. */ public int getTotalRiders() { return totalRiders; } /** * This function returns the average number of riders/minute (clock tick) * @return double representing the avg number of riders */ public double getAvgNumOfRiders() { return ((double)totalRidersForAllTicks) / numOfTicks; } /** * This function returns the average line length/minute (clock tick) * @return double representing the avg line length */ public double getAvgLineLength() { return ((double)totalLineLengthForAllTicks) / numOfTicks; } /** * This function returns the average number of riders/minute (clock tick) * @return double representing the avg number of riders */ public double getAvgNumOfCars() { return ((double)totalCarsForAllTicks) / numOfTicks; } /** * This function returns the average waiting time for this attraction * @return double representing the average waiting time */ public double getAvgWaitingTime() { if(totalRiders == 0) return 0; else return ((double)totalWaitingTime) / totalRiders; } /** * This function sets the simulation mode of the attraction. * If it should be in simulation mode (no logging, no information to the * customer, etc), then the mode should be true. * @param mode - true if it should be in simulation mode (no logging, as if nothing * is actually happening), false otherwise. */ public void setSimulationMode(boolean mode) { isSimulation = mode; } /** * This function puts the customer in line for this attraction * @param customer - The customer to put in line * @return boolean true if the customer was able to get in line, false otherwise */ public boolean enqueue(Customer customer) { boolean success = false; if(attractionStatus == AttractionStatus.OPEN) { waitingLine.addLast(customer); // store the current tick that the customer entered waitingTime.addLast(numOfTicks); if(waitingLine.size() > maxLineLength) maxLineLength = waitingLine.size(); success = true; } return success; } /** * This function should be called when the attraction first opens * */ public void openRide() { attractionStatus = AttractionStatus.OPEN; // initialize the variables numOfTicks = 0; maxNumOfRiders = 0; currNumOfRiders = 0; maxLineLength = 0; maxNumOfCars = 0; totalRiders = 0; totalRidersForAllTicks = 0; totalCarsForAllTicks = 0; totalLineLengthForAllTicks = 0; totalWaitingTime = 0; minNumOfCars = maxNumOfCars = cars.size(); timeSinceEstimate = TIME_UNTIL_NEW_ESTIMATES; needProcessing = true; } /** * This function should be called when the park or the ride closes * */ public void closeRide() { attractionStatus = AttractionStatus.CLOSED; } /** * This function simply updates the various statistics based on the clock tick * * The attraction sub-classes must remove customers from the waiting queue and put * them on rides (if there are space available) or remove customer from rides * (if they are done) during each clock tick. * * @return boolean - this function will return true when the ride is closed * and that there are no more people inline or on the ride. * */ public boolean onClockTick() { if(needProcessing) { if(!isSimulation) { // tell all the customers in line that a minute has passed for(Customer c : waitingLine) { c.onClockTick(); } // now tell everyone on the ride that a tick has gone by for(Car c : cars) { for(int i = 0; i < c.numRiders; i++) { c.customers[i].onClockTick(); } } } // updates local variables numOfTicks++; totalRidersForAllTicks += currNumOfRiders; totalCarsForAllTicks += cars.size(); totalLineLengthForAllTicks += waitingLine.size(); timeSinceEstimate++; needProcessing = attractionStatus == AttractionStatus.OPEN || !isRideEmpty(); } return needProcessing; } public Object clone() throws CloneNotSupportedException { Attraction clone = (Attraction)super.clone(); clone.cars = new LinkedList(); // makes a deep copy for(Car c : this.cars) { // Note the syntax. This is to create an instance of car // that belongs to the instance of Attraction (specifically, // the clone instance). This is so that the Attraction.this // refers to the right Attraction instance clone.cars.add(clone.new Car(c)); } clone.waitingLine = new LinkedList(); for(Customer cust : this.waitingLine) { clone.waitingLine.add((Customer) cust.clone()); } clone.waitingTime = new LinkedList(); for(Integer i : this.waitingTime) { clone.waitingTime.add(new Integer(i)); } return clone; } /** * This function prints out various stats about the attraction. * These stats include the number of riders, their wait times, * and the number of cars used. * */ public void printStats() { System.out.println("---------------------------------------------"); System.out.println("Ride: " + name); System.out.println("\nTotal number of riders: " + totalRiders); System.out.printf("Average number of riders per minute: %.2f%n", getAvgNumOfRiders()); System.out.printf("Average wait time per rider: %.2f%n", getAvgWaitingTime()); System.out.println("\nNumber of cars:"); System.out.printf("%7d maximum %n%7d minimum %n%10.2f average%n", maxNumOfCars, minNumOfCars, getAvgNumOfCars()); System.out.println("\nLine length:"); System.out.printf("%7d maximum %n%10.2f average%n%n", maxLineLength, getAvgLineLength()); System.out.println("---------------------------------------------"); } /** * Writes the statistics of the attraction to the given file. * * @param outfile */ public void writeStatsToFile(FileWriter outfile) { Formatter formatter = new Formatter(outfile); formatter.format("---------------------------------------------%n"); formatter.format("Ride: " + name); formatter.format("%n%nTotal number of riders: " + totalRiders); formatter.format("%nAverage number of riders per minute: %.2f", getAvgNumOfRiders()); formatter.format("%nAverage wait time per rider: %.2f", getAvgWaitingTime()); formatter.format("%n%nNumber of cars:%n"); formatter.format("%7d maximum %n%7d minimum %n%10.2f average%n", maxNumOfCars, minNumOfCars, getAvgNumOfCars()); formatter.format("%nLine length:%n"); formatter.format("%7d maximum %n%10.2f average%n%n", maxLineLength, getAvgLineLength()); formatter.format("---------------------------------------------%n"); } /** * This function returns the estimated waiting time for this ride. * It does not have to be 100% accurate but should present a rough * estimate as to when someone will get on the ride (assuming he/she * enters the waiting line right now). * * @return int representing the expected number of minutes someone will wait */ abstract public int getEstimatedWaitTime(); }