/* * ManifestDestiny.java * * Created on July 22, 2001, 9:06 PM */ import java.util.*; import java.io.*; /** * This is the driver class which creates and runs the island simulator */ public class ManifestDestiny { // Input stream delimiter public static final String DELIMITER = " "; public static final String START = "START"; public static final String END = "END"; // Vector of islands read in from the input stream private Vector islands; // Number of rounds to play, which is read in as input private int turns; /* Creates new ManifestDestiny */ public ManifestDestiny() { // Call the super classe constructor super(); // Initialize the simulator init(); } // Initialize the island object and tribes vector protected void init() { // Fill the island object with the data from the input file islands = getIslands(); } // This method reads in the data from the input file and fills the island's // matrix with the data. protected Vector getIslands() { int rows = 0; int columns = 0; int rounds; String line = null; BufferedReader input = null; StringBuffer data = null; Island island; Vector islands = new Vector(); StringTokenizer st = null; String token = null; try { // Create the input reader input = new BufferedReader( new InputStreamReader( System.in ) ); // Read the first line of data which should be the start string line = input.readLine(); st = new StringTokenizer( line ); token = st.nextToken(); while( ( token != null ) && ( token.equals( START ) ) ) { // Read the number of rounds rounds = Integer.parseInt( st.nextToken() ); // Read the first line of data line = input.readLine(); // Calculate the number of columns columns = ( new StringTokenizer( line, DELIMITER ).countTokens() / 2 ); // Create the StringBuffer which is going to hold the data data = new StringBuffer(); // Loop through the data to count the number or rows and to build the // data string. rows = 0; while ( ( line != null ) && ( !line.equals( END ) ) ) { rows++; data.append( line + DELIMITER ); line = input.readLine(); } if ( line.equals( END ) ) { // Create the island object island = new Island( rounds, rows, columns ); // Populate the island with the data island.populate( data ); // Add the island to the islands vector islands.add( island ); } else { throw new IOException( "Missing " + END ); } // Read in the next line of input line = input.readLine(); if ( ( line != null ) && ( !token.equals( "" ) ) ) { st = new StringTokenizer( line ); token = st.nextToken(); } else { token = null; } } } catch ( NumberFormatException nfe ) { // There was non-numeric data for the food/population column of input System.out.println( "Invalid data in the input. Non-numeric perhaps" ); System.out.println( nfe.getMessage() ); System.exit( 0 ); } catch ( NoSuchElementException nsee ) { // There was some data missing. Perhaps some of the rows of data // had less input than others. System.out.println( "Invalid data in the input. Not enough data." ); System.out.println( nsee.getMessage() ); System.exit( 0 ); } catch ( IOException ioe ) { // There was some sort of i/o error reading the input. System.out.println( "Error reading the input." ); System.out.println( ioe.getMessage() ); System.exit( 0 ); } finally { // Make sure the inputstream is closed if ( input != null ) { try { input.close(); } catch( IOException ioe ) { // Do nothing } } } return islands; } // This method starts the simulator. It calls the nextRound method once for // each round. The number of rounds was read in from the input file private void start() { Enumeration islandEnum = islands.elements(); Island island; // Loop through each of the sets of island data that was read in. while( islandEnum.hasMoreElements() ) { // Get the island island = (Island)islandEnum.nextElement(); // Start that islands simulation island.start(); // Print the results of that island island.printResults(); } } /** * This is the main method which is the starting point for the driver class. */ public static void main (String args[]) { // Create the manifest destiny simulator ManifestDestiny simulator = new ManifestDestiny(); // Start the simulator simulator.start(); } } /**************************************************************************** * Interface for which the Tribe class implements * Defines constants and methods for which the Tribe class must implement ****************************************************************************/ interface TribeInterface { float ATTACK_PERCENTAGE = .1F; float DEFEND_PERCENTAGE = .5F; float TRAVEL_PERCENTAGE = .1F; float STARVE_PERCENTAGE = .05F; float PROCREATION_PERCENTAGE = .33F; public void attack( Island island ); public void defend( Island island, int casualties ); public void eat( Island island ); public boolean isAlive(); public boolean isFightingTime( Island island, Position position ); public boolean isNearFood( Island island ); public void move( Island island ); } /********************************************************************* * This class represents a tribe. A instance is created for each * tribe on the island. ********************************************************************/ class Tribe implements TribeInterface { private int ID; private int population; private Position position; private Position lastPosition; private Vector traveledTo; private String timeOfDeath; // public constructor which initializes the tribes member variables public Tribe( int id, int size, Position startPoint ) { ID = id; population = size; position = startPoint; traveledTo = new Vector(); lastPosition = startPoint; timeOfDeath = ""; } // Create a new Tribe object public Tribe( String ID, int size, int row, int column ) { this( Integer.parseInt( ID ), size, row, column ); } // Create a new Tribe object public Tribe( int ID, int size, int row, int column ) { this( ID, size, new Position( row, column ) ); } // This method is called when attacking another tribe to reduce the number // of peoplein by some percentage due to not eating while attacking public void attack( Island island ) { // Reduce the population by the number of people who die in the attack die( island, population * TribeInterface.ATTACK_PERCENTAGE ); } // This method is called when defending against an attack. The tribes // population is reduced by some percentage of the population of the other // tribe which is passed in as an int. public void defend( Island island, int numAttackers ) { // Kill some percentage of the size of the attacking tribe die( island, numAttackers * TribeInterface.DEFEND_PERCENTAGE ); } // This method rounds the number of casualties up to the nearest whole number // and then calls the other die method. public void die( Island island, float casualties ) { die( island, (int)Math.ceil( casualties ) ); } // This method reduces the population by the number of casualties entered public void die( Island island, int casualties ) { // Reduce the population population -= casualties; // If the population is less than one if ( population < 1 ) { // Set the population to zero setPopulation( 0 ); // Set the time of death to the current round number setTimeOfDeath( island.getRound() ); // Get the island entry where the tribe died IslandEntry entry = island.getEntry( getPosition() ); // Set the descriptor to a field without wheat entry.setDescriptor( IslandInterface.FIELD_WO_WHEAT ); // Set the hasTribe flag to false entry.setHasTribe( false ); } } /************************************************************************* * This method is called when it is time for a tribe to eat. The tribe * consumes 1 unit of wheat for every person in the tribe. It starts by * eating food to the north and going clockwise until everyone has eaten. * If there isn't enough food for everyone, then some percentage of the * people who don't eat will die. The tribe will produce offspring equal * to some percentage of the people who do eat. ************************************************************************/ public void eat( Island island ) { int population = getPopulation(); int peopleToFeed = population; int food; String[] directions = { IslandInterface.NORTH, IslandInterface.EAST, IslandInterface.SOUTH, IslandInterface.WEST }; // Loop through the directions, starting with north for ( int i = 0; i < directions.length; i++ ) { if ( island.isValidEntry( getPosition(), directions[i] ) ) { // Get the island entry IslandEntry position = island.getEntry( getPosition(), directions[i] ); if ( position.getFood() > 0 ) { // Get the amount of wheat food = position.getFood(); // If there is enough food for everyone if ( food >= peopleToFeed ) { // Reduce the amount of food by the number of people to eat position.reduceFood( peopleToFeed ); // Set the number of people left to feed to zero peopleToFeed = 0; // Exit the loop break; } // If there isn't enough food else { // Reduce the number of people to feed by the amount of food peopleToFeed = peopleToFeed - food; // Set the amount of wheat to zero for this island entry position.zeroOutFood(); } } } } // Some of the people left to feed will starve starve( island, peopleToFeed ); // Some of the people who did eat will multiply procreate( population - peopleToFeed ); } // Returns the id of the tribe public int getID() { return ID; } /************************************************************************* * This methoc returns the Position object representing the next position * that the tribe should travel to. It takes an Island object as input ************************************************************************/ private Position getNextPosition( Island island ) { final int xcoord = getPosition().getXCoord(); final int ycoord = getPosition().getYCoord(); int lowestPoints = Integer.MAX_VALUE; int points = 0; Position nextPosition = new Position( xcoord, ycoord ); // These arrays are used for the coordinates of the 4 different cardinal directions. int[] xcoords = { xcoord, xcoord + 1, xcoord, xcoord - 1 }; int[] ycoords = { ycoord - 1, ycoord, ycoord + 1, ycoord }; for ( int i = 0; i < xcoords.length; i++ ) { if ( ( island.isValidEntry( xcoords[i], ycoords[i] ) ) && ( island.getEntry( xcoords[i], ycoords[i] ).isNavigable() ) ) { // Get the points for the current location points = getPoints( xcoords[i], ycoords[i], lastPosition ); // Compare it to the lowest point number found so far if ( points < lowestPoints ) { lowestPoints = points; nextPosition.setCoordinates( xcoords[i], ycoords[i] ); } } } return nextPosition; } // Returns the number of points for the island entry represented by the x and y // coordinates passed in. 1 point is added every time the island entry represented // by the x and y coordinates has already been traveled to. The points // are doubled if the coordinates match that of the last position. private int getPoints( int xcoord, int ycoord, Position lastPosition ) { int visits = 0; Position position; Enumeration iterator = traveledTo.elements(); // Loop through all of the entries in the traveled to enumeration while ( iterator.hasMoreElements() ) { // Get the next position in the enumeration position = (Position)iterator.nextElement(); // Check to see if the coordinates match the coordinates passed in if ( ( position.getXCoord() == xcoord ) && ( position.getYCoord() == ycoord ) ) { visits++; } } // Double the points if these coordinates match those of the last position if ( ( lastPosition.getXCoord() == xcoord ) && ( lastPosition.getYCoord() == ycoord ) ) { visits *= 2; } return visits; } // Returns the current position of the tribe. public Position getPosition() { return position; } // Returns the population of the tribe. public int getPopulation() { return population; } // Returns the round number in which this tribe died public String getTimeOfDeath() { return timeOfDeath; } // Returns a boolean indicating whether or not this tribe still has any // surviving members public boolean isAlive() { return ( population > 0 ); } /*********************************************************************** * This method is called whenever the tribe is looking for someone to fight. * It starts by looking north and then keeps looking clockwise. * It takes an Island object and Position object as input and returns true if * there is a nearby tribe to fight. The Position object passed in is set * to the coordinates of the tribe to attack if a tribe is found. ***********************************************************************/ public boolean isFightingTime( Island island, Position opponent ) { boolean fightingTime = false; int xcoord = position.getXCoord(); int ycoord = position.getYCoord(); // These arrays are used for the coordinates of the 4 different cardinal directions. int[] xcoords = { xcoord, xcoord + 1, xcoord, xcoord - 1 }; int[] ycoords = { ycoord - 1, ycoord, ycoord + 1, ycoord }; for ( int x = 0; x < xcoords.length; x++ ) { if ( ( island.isValidEntry( xcoords[x], ycoords[x] ) ) && ( island.getEntry( xcoords[x], ycoords[x] ).hasTribe() ) ) { opponent.setCoordinates( xcoords[x], ycoords[x] ); fightingTime = true; break; } } return fightingTime; } /************************************************************************ * This method is called to check the surrounding squares and look for a * field with wheat. Returns a boolean indicating whether or not food * was found. ***********************************************************************/ public boolean isNearFood( Island island ) { boolean isNearFood = false; int xcoord = position.getXCoord(); int ycoord = position.getYCoord(); // These arrays are used for the coordinates of the 4 different cardinal directions. int[] xcoords = { xcoord, xcoord + 1, xcoord, xcoord - 1 }; int[] ycoords = { ycoord - 1, ycoord, ycoord + 1, ycoord }; for ( int x = 0; x < xcoords.length; x++ ) { if ( ( island.isValidEntry( xcoords[x], ycoords[x] ) ) && ( island.getEntry( xcoords[x], ycoords[x] ).hasFood() ) ) { isNearFood = true; break; } } return isNearFood; } /********************************************************************** * This method is called whenever it is time for a tribe to move on. * It retrieves the next position and then sets all the proper variables * to indicate the new position * It takes an Island object as input **********************************************************************/ public void move( Island island ) { // Retrieve the new location Position nextPosition = getNextPosition( island ); // Add the current location to the traveledTo Vector traveledTo.add( getPosition() ); // Set the lastPosition to the current position lastPosition = getPosition(); // Set the current location to a be a field with no wheat island.getEntry( getPosition() ).setDescriptor( IslandInterface.FIELD_WO_WHEAT ); // Set the hasTribe flag on the current position to false island.getEntry( getPosition() ).setHasTribe( false ); // Set the hasTribe flag on the new position to true island.getEntry( nextPosition ).setHasTribe( true ); // Set the tribe number variable on the new position island.getEntry( nextPosition ).setTribeNumber( getID() ); // Set the position to the new position setPosition( nextPosition ); // kill some percentage due to traveling die( island, population * TribeInterface.TRAVEL_PERCENTAGE ); } // Increases the population of the tribe by the integer amount passed in. public void procreate( int peopleWhoAte ) { population += (int)Math.ceil( peopleWhoAte * TribeInterface.PROCREATION_PERCENTAGE ); } // Sets the population to the integer passed in. public void setPopulation( int pop ) { population = pop; } // Sets the position to the position passed in. public void setPosition( Position newPosition ) { position = newPosition; } // Sets the round number that this tribe died in public void setTimeOfDeath( int round ) { timeOfDeath = new Integer( round ).toString(); } // Kills 5% of the people who didn't eat public void starve( Island island, int peopleWhoDidntEat ) { die( island, peopleWhoDidntEat * TribeInterface.STARVE_PERCENTAGE ); } } /**************************************************************************** * Interface which the Island Class implements. * It defines constants and methods which the Island class must implement. ****************************************************************************/ interface IslandInterface { String FIELD_W_WHEAT = "w"; String FIELD_WO_WHEAT = "."; String NORTH = "north"; String SOUTH = "south"; String EAST = "east"; String WEST = "west"; public void populate( StringBuffer data ); } /***************************************************************************** * This class represents the island. It contains a matrix of IslandEntry objects * and methods for accessing and analyzing the different island entries. *****************************************************************************/ class Island implements IslandInterface { private int width; private int height; private int numRounds; private IslandEntry[][] matrix; public int round; private Vector tribes; // public constructor which sets the size of the matrix public Island( int rounds, int numRows, int numCols ) { width = numCols; height = numRows; matrix = new IslandEntry[width][height]; round = 0; numRounds = rounds; tribes = new Vector(); } // Returns the IslandEntry with the given position public IslandEntry getEntry( int xcoord, int ycoord ) { return matrix[xcoord][ycoord]; } // Returns the IslandEntry with the given position public IslandEntry getEntry( Position position ) { return getEntry( position.getXCoord(), position.getYCoord() ); } // Returns the IslandEntry object which is one square away from the position // passed in the direction that was passed in. public IslandEntry getEntry( Position position, String direction ) { if ( direction.equals( IslandInterface.NORTH ) ) { return getEntry( position.getXCoord(), position.getYCoord() - 1 ); } else if ( direction.equals( IslandInterface.EAST ) ) { return getEntry( position.getXCoord() + 1, position.getYCoord() ); } else if ( direction.equals( IslandInterface.SOUTH ) ) { return getEntry( position.getXCoord(), position.getYCoord() + 1 ); } else { return getEntry( position.getXCoord() - 1, position.getYCoord() ); } } // Returns the current round number public int getRound() { return round; } // This method searches through the matrix and finds all the // positions of the island that has a tribe on it and returns a Vector with // all of these tribes public void extractTribes() { IslandEntry islandEntry = null; Tribe tribe = null; // Loop through every entry on the island looking for tribes for ( int x = 0; x < getWidth(); x++ ) { for ( int y = 0; y < getHeight(); y++ ) { // Get the island entry at coordinates (x,y) islandEntry = getEntry( x, y ); // Check to see if there is a tribe at that position if ( islandEntry.hasTribe() ) { // Create a new tribe tribe = new Tribe( islandEntry.getDescriptor(), islandEntry.getNumber(), x, y ); // Put the new tribe in the vector // Make sure the vector is big enough first if ( tribes.size() <= tribe.getID() ) { tribes.setSize( tribe.getID() + 1 ); } tribes.set( tribe.getID(), tribe ); } } } } // Returns the width of the island, which is the number of columns in the matrix public int getWidth() { return width; } // Returns the height of the island, which is the number of rows in the matrix public int getHeight() { return height; } // Returns boolean indicating whether or not the position entered is a valid // set of coordinates for the island. public boolean isValidEntry( int xcoord, int ycoord ) { return ( ( xcoord >= 0 ) && ( xcoord < getWidth() ) && ( ycoord >= 0 ) && ( ycoord < getHeight() ) ); } // Returns boolean indicating whether or not the position entered is a valid // set of coordinates for the island. public boolean isValidEntry( Position position, String direction ) { if ( direction.equals( IslandInterface.NORTH ) ) { return isValidEntry( position.getXCoord(), position.getYCoord() - 1 ); } else if ( direction.equals( IslandInterface.EAST ) ) { return isValidEntry( position.getXCoord() + 1, position.getYCoord() ); } else if ( direction.equals( IslandInterface.SOUTH ) ) { return isValidEntry( position.getXCoord(), position.getYCoord() + 1 ); } else if ( direction.equals( IslandInterface.WEST ) ) { return isValidEntry( position.getXCoord() - 1, position.getYCoord() ); } else { return false; } } // This is the method that runs exactly one round of the island simulation protected void nextRound() { Tribe tribe = null; Position position = new Position(); // Loop through all of the tribes in the tribes Vector for ( int x = 0; x < tribes.size(); x++ ) { // Get the tribe at position x in the Vector tribe = (Tribe)tribes.get( x ); // Check to see if the tribe has any surviving members if ( tribe.isAlive() ) { // Check for a neighboring tribe to fight if ( tribe.isFightingTime( this, position ) ) { // Extract the tribe that is being attacked from the tribes Vector Tribe defendingTribe = (Tribe)tribes.elementAt( getEntry( position ).getTribeNumber() ); // Call the defend method of the tribe which is being attacked, // passing in the population of the attacking tribe. defendingTribe.defend( this, tribe.getPopulation() ); // Call the attack method of the attacking tribe tribe.attack( this ); } // Else look for something to eat else if ( tribe.isNearFood( this ) ) { // Eat the food tribe.eat( this ); } // If there is no fighting or eating then it is time to move else { // Move to a new position tribe.move( this ); } } } } // Fills the island with the data that is passed in public void populate( StringBuffer data ) throws NoSuchElementException, NumberFormatException { int number = 0; String descriptor = null; // Tokenize the input string StringTokenizer st = new StringTokenizer( data.toString() ); // For every row in the island for ( int row = 0; row < getHeight(); row++ ) { // For every pair of tokens in the line of data for ( int col = 0; col < getWidth(); col++ ) { // Set the descriptor to the first token descriptor = st.nextToken(); // Set the number to the second token number = Integer.parseInt( st.nextToken() ); // Set that spot in the island matrix to a new IslandEntry object matrix[col][row] = new IslandEntry( descriptor, number ); } } extractTribes(); } // This method prints the results of the simulator public void printResults() { Enumeration tribeEnum = tribes.elements(); Tribe tribe; // Loop through all of the tribes while ( tribeEnum.hasMoreElements() ) { tribe = (Tribe)tribeEnum.nextElement(); // Add 1 to the coordinates since java arrays are zero based. System.out.println( tribe.getID() + " " + tribe.getPopulation() + " (" + tribe.getPosition().getXCoord() + "," + tribe.getPosition().getYCoord() + ") " + tribe.getTimeOfDeath() ); } System.out.println(); } // This method starts the simulation for this island. It calls the nextRound // method once for each round. public void start() { // while the round number is less than the number of rounds while( round < numRounds ) { // Increment the round number round++; // Start the next round nextRound(); } } } /***************************************************************************** * Abstract class which contains member variables and methods which a generic * IslandEntry should have. *****************************************************************************/ abstract class BasicIslandEntry { protected String descriptor; protected int number; // Returns the value of the descriptor field public String getDescriptor() { return descriptor; } // Returns the number field which is initially used as the population of the // tribe occupying this island entry. public int getNumber() { return number; } // Returns whether or not this particulary island entry is navigable by a tribe abstract public boolean isNavigable(); // Utility method used to determine if the String passed in represents an integer public boolean isNumeric( String string ) { try { Integer number = new Integer( string ); } catch( NumberFormatException nfe ) { return false; } return true; } // Set the descriptor field public void setDescriptor( String desc ) { descriptor = desc; } } /**************************************************************************** * This class is used to represent an individual island square. The island * object contains a matrix of IslandEntry objects ****************************************************************************/ class IslandEntry extends BasicIslandEntry { private int tribeNo; private int food; private boolean hasTribe; // public constructor which sets the member variables public IslandEntry( String desc, int num ) { // If the description is numeric, then this IslandEntry must have a tribe. if ( isNumeric( desc ) ) { hasTribe = true; tribeNo = Integer.parseInt( desc ); number = num; } else { food = num; } descriptor = desc; } // Returns the amount of food that this island entry has public int getFood() { return food; } // Returns the tribe number that is currently occupying this island entry public int getTribeNumber() { return tribeNo; } // Returns a boolean indicating whether or not this island entry has any wheat public boolean hasFood() { return ( descriptor.equals( IslandInterface.FIELD_W_WHEAT ) && ( food > 0 ) ); } // Returns whether or not this island entry currently as a tribe on it public boolean hasTribe() { return hasTribe; } // Returns whether or not this island entry is navigable by a tribe public boolean isNavigable() { return ( descriptor.equals( IslandInterface.FIELD_WO_WHEAT ) || ( descriptor.equals( IslandInterface.FIELD_W_WHEAT ) ) ); } // Reduces the food by the integer amount passed in public void reduceFood( int amountEaten ) { food -= amountEaten; } // Sets the hasTribe flag to the boolean value passed in public void setHasTribe( boolean flag ) { hasTribe = flag; } // Sets the tribe number to the integer passed in public void setTribeNumber( int number ) { tribeNo = number; } // Sets the food to zero and sets the descriptor to a field without wheat public void zeroOutFood() { food = 0; descriptor = IslandInterface.FIELD_WO_WHEAT; } } /****************************************************************************** * This class is used to keep track of positions on the island. It contains 2 * member variables, an x coordinate and a y coordinate. ******************************************************************************/ class Position { int xcoord; int ycoord; // Create a new Position object public Position() { this( 0, 0 ); } // Create a new Position object public Position( int x, int y ) { xcoord = x; ycoord = y; } // get the x coordinate public int getXCoord() { return xcoord; } // get the y coordinate public int getYCoord() { return ycoord; } // set both coordinates public void setCoordinates( int x, int y ) { xcoord = x; ycoord = y; } // Set the x coordinate public void setXCoord( int x ) { xcoord = x; } // Set the y coordinate public void setYCoord( int y ) { ycoord = y; } }