Hi guys! I'm going to show you a Fox and Rabbit Simulator made in BlueJ.
We're going to use these classes
We're going to use these classes
- Simulator
- SimulatorView
- Location
- Field
- FieldStats
- Counter
- Randomizer
- Rabbit
- Fox
Here is the source code of those classes:
Simulator
import java.util.Random; import java.util.List; import java.util.ArrayList; import java.util.Iterator; import java.awt.Color; /** * A simple predator-prey simulator, based on a rectangular field * containing rabbits and foxes. * * @author Elkana Hans Widersen * @version 1.0 */ public class Simulator { /** * Constants representing configuration information for the simulation. */ private static final int def_width = 50; // The default width for the grid. private static final int def_depth = 50; // The default depth of the grid. private static final double foxProbability = 0.02; // The probability that a fox will be created in any given grid position. private static final double rabbitProbability = 0.08; // The probability that a rabbit will be created in any given grid position. /** * Lists of animals in the field. * Separate lists are kept for ease of iteration. */ private List<Rabbit> rabbits; private List<Fox> foxes; private Field field; // The current state of the field. private int step; // The current step of the simulation. private SimulatorView view; // A graphical view of the simulation. /** * Construct a simulation field with default size. */ public Simulator() { this(def_depth, def_width); } /** * Create a simulation field with the given size. * @param depth Depth of the field. Must be greater than zero. * @param width Width of the field. Must be greater than zero. */ public Simulator(int depth, int width) { if(width <= 0 || depth <= 0) { System.out.println("The dimensions must be greater than zero."); System.out.println("Using default values."); depth = def_depth; width = def_width; } rabbits = new ArrayList<Rabbit>(); foxes = new ArrayList<Fox>(); field = new Field(depth, width); // Create a view of the state of each location in the field. view = new SimulatorView(depth, width); view.setColor(Rabbit.class, Color.orange); view.setColor(Fox.class, Color.blue); // Setup a valid starting point. reset(); } /** * Run the simulation from its current state for a reasonably long period, * e.g. 500 steps. */ public void runLongSimulation() { simulate(500); } /** * Run the simulation from its current state for the given number of steps. * Stop before the given number of steps if it ceases to be viable. * @param numSteps The number of steps to run for. */ public void simulate(int numSteps) { for(int step = 1; step <= numSteps && view.isViable(field); step++) { simulateOneStep(); } } /** * Run the simulation from its current state for a single step. * Iterate over the whole field updating the state of each * fox and rabbit. */ public void simulateOneStep() { step++; // Provide space for newborn rabbits. List<Rabbit> newRabbits = new ArrayList<Rabbit>(); // Let all rabbits act. for(Iterator<Rabbit> it = rabbits.iterator(); it.hasNext(); ) { Rabbit rabbit = it.next(); rabbit.run(newRabbits); if(!rabbit.isAlive()) it.remove(); } // Provide space for newborn foxes. List<Fox> newFoxes = new ArrayList<Fox>(); // Let all foxes act. for(Iterator<Fox> it = foxes.iterator(); it.hasNext(); ) { Fox fox = it.next(); fox.hunt(newFoxes); if(!fox.isAlive()) it.remove(); } // Add the newly born foxes and rabbits to the main lists. rabbits.addAll(newRabbits); foxes.addAll(newFoxes); view.showStatus(step, field); } /** * Reset the simulation to a starting position. */ public void reset() { step = 0; rabbits.clear(); foxes.clear(); populate(); // Show the starting state in the view. view.showStatus(step, field); } /** * Randomly populate the field with foxes and rabbits. */ private void populate() { Random rand = Randomizer.getRandom(); field.clear(); for(int row = 0; row < field.getDepth(); row++) { for(int col = 0; col < field.getWidth(); col++) { if(rand.nextDouble() <= foxProbability) { Location location = new Location(row, col); Fox fox = new Fox(true, field, location); foxes.add(fox); } else if(rand.nextDouble() <= rabbitProbability) { Location location = new Location(row, col); Rabbit rabbit = new Rabbit(true, field, location); rabbits.add(rabbit); } // else leave the location empty. } } } }
SimulatorView
import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.util.LinkedHashMap; import java.util.Map; /** * A graphical view of the simulation grid. * The view displays a colored rectangle for each location * representing its contents. It uses a default background color. * Colors for each type of species can be defined using the * setColor method. * * @author Elkana Hans Widersen * @version 1.0 */ public class SimulatorView extends JFrame { private static final Color emptyColor = Color.white; // Colors used for empty locations. private static final Color unknownColor = Color.gray; // Color used for objects that have no defined color. private final String stepField = "Step: "; private final String populationField = "Population: "; private JLabel stepLabel, population; private FieldView fieldView; private Map<Class, Color> colors; // A map for storing colors for participants in the simulation private FieldStats stats; // A statistics object computing and storing simulation information /** * Create a view of the given width and height. * @param height The simulation's height. * @param width The simulation's width. */ public SimulatorView(int height, int width) { stats = new FieldStats(); colors = new LinkedHashMap<Class, Color>(); setTitle("Fox and Rabbit Simulation"); stepLabel = new JLabel(stepField, JLabel.CENTER); population = new JLabel(populationField, JLabel.CENTER); setLocation(100, 50); fieldView = new FieldView(height, width); Container contents = getContentPane(); contents.add(stepLabel, BorderLayout.NORTH); contents.add(fieldView, BorderLayout.CENTER); contents.add(population, BorderLayout.SOUTH); pack(); setVisible(true); } /** * Define a color to be used for a given class of animal. * @param animalClass The animal's Class object. * @param color The color to be used for the given class. */ public void setColor(Class animalClass, Color color) { colors.put(animalClass, color); } /** * @return The color to be used for a given class of animal. */ private Color getColor(Class animalClass) { Color col = colors.get(animalClass); if(col == null) { // no color defined for this class return unknownColor; } else return col; } /** * Show the current status of the field. * @param step Which iteration step it is. * @param field The field whose status is to be displayed. */ public void showStatus(int step, Field field) { if(!isVisible()) setVisible(true); stepLabel.setText(stepField + step); stats.reset(); fieldView.preparePaint(); for(int row = 0; row < field.getDepth(); row++) { for(int col = 0; col < field.getWidth(); col++) { Object animal = field.getObjectAt(row, col); if(animal != null) { stats.incrementCount(animal.getClass()); fieldView.drawMark(col, row, getColor(animal.getClass())); } else fieldView.drawMark(col, row, emptyColor); } } stats.countFinished(); population.setText(populationField + stats.getPopulationDetails(field)); fieldView.repaint(); } /** * Determine whether the simulation should continue to run. * @return true If there is more than one species alive. */ public boolean isViable(Field field) { return stats.isViable(field); } /** * Provide a graphical view of a rectangular field. This is * a nested class (a class defined inside a class) which * defines a custom component for the user interface. This * component displays the field. * This is rather advanced GUI stuff - you can ignore this * for your project if you like. */ private class FieldView extends JPanel { private final int GRID_VIEW_SCALING_FACTOR = 6; private int gridWidth, gridHeight; private int xScale, yScale; Dimension size; private Graphics g; private Image fieldImage; /** * Create a new FieldView component. */ public FieldView(int height, int width) { gridHeight = height; gridWidth = width; size = new Dimension(0, 0); } /** * Tell the GUI manager how big we would like to be. */ public Dimension getPreferredSize() { return new Dimension(gridWidth * GRID_VIEW_SCALING_FACTOR, gridHeight * GRID_VIEW_SCALING_FACTOR); } /** * Prepare for a new round of painting. Since the component * may be resized, compute the scaling factor again. */ public void preparePaint() { if(!size.equals(getSize())) { // if the size has changed... size = getSize(); fieldImage = fieldView.createImage(size.width, size.height); g = fieldImage.getGraphics(); xScale = size.width / gridWidth; if(xScale < 1) xScale = GRID_VIEW_SCALING_FACTOR; yScale = size.height / gridHeight; if(yScale < 1) yScale = GRID_VIEW_SCALING_FACTOR; } } /** * Paint on grid location on this field in a given color. */ public void drawMark(int x, int y, Color color) { g.setColor(color); g.fillRect(x * xScale, y * yScale, xScale-1, yScale-1); } /** * The field view component needs to be redisplayed. Copy the * internal image to screen. */ public void paintComponent(Graphics g) { if(fieldImage != null) { Dimension currentSize = getSize(); if(size.equals(currentSize)) g.drawImage(fieldImage, 0, 0, null); else { // Rescale the previous image. g.drawImage(fieldImage, 0, 0, currentSize.width, currentSize.height, null); } } } } }
Location
/** * Represent a location in a rectangular grid. * * @author Elkana Hans Widersen * @version 1.0 */ public class Location { // Row and column positions. private int row; private int col; /** * Represent a row and column. * @param row The row. * @param col The column. */ public Location(int row, int col) { this.row = row; this.col = col; } /** * Implement content equality. */ public boolean equals(Object obj) { if(obj instanceof Location) { Location other = (Location) obj; return row == other.getRow() && col == other.getCol(); } else return false; } /** * Return a string of the form row,column * @return A string representation of the location. */ public String toString() { return row + "," + col; } /** * Use the top 16 bits for the row value and the bottom for * the column. Except for very big grids, this should give a * unique hash code for each (row, col) pair. * @return A hashcode for the location. */ public int hashCode() { return (row << 16) + col; } /** * @return The row. */ public int getRow() { return row; } /** * @return The column. */ public int getCol() { return col; } }
Field
import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Random; /** * Represent a rectangular grid of field positions. * Each position is able to store a single animal. * * @author Elkana Hans Widersen * @version 1.0 */ public class Field { private static final Random rand = Randomizer.getRandom(); // A random number generator for providing random locations. private int depth, width; // The depth and width of the field. private Object[][] field; // Storage for the animals. /** * Represent a field of the given dimensions. * @param depth The depth of the field. * @param width The width of the field. */ public Field(int depth, int width) { this.depth = depth; this.width = width; field = new Object[depth][width]; } /** * Empty the field. */ public void clear() { for(int row = 0; row < depth; row++) { for(int col = 0; col < width; col++) field[row][col] = null; } } /** * Clear the given location. * @param location The location to clear. */ public void clear(Location location) { field[location.getRow()][location.getCol()] = null; } /** * Place an animal at the given location. * If there is already an animal at the location it will * be lost. * @param animal The animal to be placed. * @param row Row coordinate of the location. * @param col Column coordinate of the location. */ public void place(Object animal, int row, int col) { place(animal, new Location(row, col)); } /** * Place an animal at the given location. * If there is already an animal at the location it will * be lost. * @param animal The animal to be placed. * @param location Where to place the animal. */ public void place(Object animal, Location location) { field[location.getRow()][location.getCol()] = animal; } /** * Return the animal at the given location, if any. * @param location Where in the field. * @return The animal at the given location, or null if there is none. */ public Object getObjectAt(Location location) { return getObjectAt(location.getRow(), location.getCol()); } /** * Return the animal at the given location, if any. * @param row The desired row. * @param col The desired column. * @return The animal at the given location, or null if there is none. */ public Object getObjectAt(int row, int col) { return field[row][col]; } /** * Generate a random location that is adjacent to the * given location, or is the same location. * The returned location will be within the valid bounds * of the field. * @param location The location from which to generate an adjacency. * @return A valid location within the grid area. */ public Location randomAdjacentLocation(Location location) { List<Location> adjacent = adjacentLocations(location); return adjacent.get(0); } /** * Get a shuffled list of the free adjacent locations. * @param location Get locations adjacent to this. * @return A list of free adjacent locations. */ public List<Location> getFreeAdjacentLocations(Location location) { List<Location> free = new LinkedList<Location>(); List<Location> adjacent = adjacentLocations(location); for(Location next : adjacent) { if(getObjectAt(next) == null) free.add(next); } return free; } /** * Try to find a free location that is adjacent to the * given location. If there is none, return null. * The returned location will be within the valid bounds * of the field. * @param location The location from which to generate an adjacency. * @return A valid location within the grid area. */ public Location freeAdjacentLocation(Location location) { // The available free ones. List<Location> free = getFreeAdjacentLocations(location); if(free.size() > 0) return free.get(0); else return null; } /** * Return a shuffled list of locations adjacent to the given one. * The list will not include the location itself. * All locations will lie within the grid. * @param location The location from which to generate adjacencies. * @return A list of locations adjacent to that given. */ public List<Location> adjacentLocations(Location location) { assert location != null : "Null location passed to adjacentLocations"; // The list of locations to be returned. List<Location> locations = new LinkedList<Location>(); if(location != null) { int row = location.getRow(); int col = location.getCol(); for(int roffset = -1; roffset <= 1; roffset++) { int nextRow = row + roffset; if(nextRow >= 0 && nextRow < depth) { for(int coffset = -1; coffset <= 1; coffset++) { int nextCol = col + coffset; // Exclude invalid locations and the original location. if(nextCol >= 0 && nextCol < width && (roffset != 0 || coffset != 0)) { locations.add(new Location(nextRow, nextCol)); } } } } /** * Shuffle the list. Several other methods rely on the list * being in a random order. */ Collections.shuffle(locations, rand); } return locations; } /** * Return the depth of the field. * @return The depth of the field. */ public int getDepth() { return depth; } /** * Return the width of the field. * @return The width of the field. */ public int getWidth() { return width; } }
FieldStats
import java.awt.Color; import java.util.HashMap; /** * This class collects and provides some statistical data on the state * of a field. It is flexible: it will create and maintain a counter * for any class of object that is found within the field. * * @author Elkana Hans Widersen * @version 1.0 */ public class FieldStats { private HashMap<Class, Counter> counters; // Counters for each type of entity (fox, rabbit, etc.) in the simulation. private boolean countsValid; // Whether the counters are currently up to date. /** * Construct a FieldStats object. */ public FieldStats() { /** * Set up a collection for counters for each type of animal that * we might find. */ counters = new HashMap<Class, Counter>(); countsValid = true; } /** * Get details of what is in the field. * @return A string describing what is in the field. */ public String getPopulationDetails(Field field) { StringBuffer buffer = new StringBuffer(); if(!countsValid) generateCounts(field); for(Class key : counters.keySet()) { Counter info = counters.get(key); buffer.append(info.getName()); buffer.append(": "); buffer.append(info.getCount()); buffer.append(' '); } return buffer.toString(); } /** * Invalidate the current set of statistics; reset all * counts to zero. */ public void reset() { countsValid = false; for(Class key : counters.keySet()) { Counter count = counters.get(key); count.reset(); } } /** * Increment the count for one class of animal. * @param animalClass The class of animal to increment. */ public void incrementCount(Class animalClass) { Counter count = counters.get(animalClass); if(count == null) { /** * We do not have a counter for this species yet. * Create one. */ count = new Counter(animalClass.getName()); counters.put(animalClass, count); } count.increment(); } /** * Indicate that an animal count has been completed. */ public void countFinished() { countsValid = true; } /** * Determine whether the simulation is still viable. * I.e., should it continue to run. * @return true If there is more than one species alive. */ public boolean isViable(Field field) { // How many counts are non-zero. int nonZero = 0; if(!countsValid) generateCounts(field); for(Class key : counters.keySet()) { Counter info = counters.get(key); if(info.getCount() > 0) nonZero++; } return nonZero > 1; } /** * Generate counts of the number of foxes and rabbits. * These are not kept up to date as foxes and rabbits * are placed in the field, but only when a request * is made for the information. * @param field The field to generate the stats for. */ private void generateCounts(Field field) { reset(); for(int row = 0; row < field.getDepth(); row++) { for(int col = 0; col < field.getWidth(); col++) { Object animal = field.getObjectAt(row, col); if(animal != null) incrementCount(animal.getClass()); } } countsValid = true; } }
Counter
import java.awt.Color; /** * Provide a counter for a participant in the simulation. * This includes an identifying string and a count of how * many participants of this type currently exist within * the simulation. * * @author Elkana Hans Widersen * @version 1.0 */ public class Counter { private String name; // A name for this type of simulation participant. private int count; // How many of this type exist in the simulation. /** * Provide a name for one of the simulation types. * @param name A name, e.g. "Fox". */ public Counter(String name) { this.name = name; count = 0; } /** * @return The short description of this type. */ public String getName() { return name; } /** * @return The current count for this type. */ public int getCount() { return count; } /** * Increment the current count by one. */ public void increment() { count++; } /** * Reset the current count to zero. */ public void reset() { count = 0; } }
Randomizer
import java.util.Random; /** * Provide control over the randomization of the simulation. * * @author Elkana Hans Widersen * @version 1.0 */ public class Randomizer { private static final int seed = 1111; // The default seed for control of randomization. private static final Random rand = new Random(seed); // A shared Random object, if required. private static final boolean useShared = true; // Determine whether a shared random generator is to be provided. /** * Constructor for objects of class Randomizer */ public Randomizer() { } /** * Provide a random generator. * @return A random object. */ public static Random getRandom() { if(useShared) return rand; else return new Random(); } /** * Reset the randomization. * This will have no effect if randomization is not through * a shared Random generator. */ public static void reset() { if(useShared) rand.setSeed(seed); } }
Rabbit
import java.util.List; import java.util.Random; /** * A simple model of a rabbit. * Rabbits age, move, breed, and die. * * @author Elkana Hans Widersen * @version 1.0 */ public class Rabbit { // Characteristics shared by all rabbits (static fields). private static final int breedAge = 5; // The age at which a rabbit can start to breed. private static final int maxAge = 40; // The age to which a rabbit can live. private static final double breedProbability = 0.15; // The likelihood of a rabbit breeding. private static final int maxBreedSize = 4; // The maximum number of births. private static final Random rand = Randomizer.getRandom(); // A shared random number generator to control breeding. // Individual characteristics (instance fields). private int age; // The rabbit's age. private boolean alive; // Whether the rabbit is alive or not. private Location location; // The rabbit's position. private Field field; // The field occupied. /** * Create a new rabbit. A rabbit may be created with age * zero (a new born) or with a random age. * * @param randomAge If true, the rabbit will have a random age. * @param field The field currently occupied. * @param location The location within the field. */ public Rabbit(boolean randomAge, Field field, Location location) { age = 0; alive = true; this.field = field; setLocation(location); if(randomAge) age = rand.nextInt(maxAge); } /** * This is what the rabbit does most of the time - it runs * around. Sometimes it will breed or die of old age. * @param newRabbits A list to add newly born rabbits to. */ public void run(List<Rabbit> newRabbits) { incrementAge(); if(alive) { giveBirth(newRabbits); // Try to move into a free location. Location newLocation = field.freeAdjacentLocation(location); if(newLocation != null) setLocation(newLocation); else { // Overcrowding. setDead(); } } } /** * Check whether the rabbit is alive or not. * @return true if the rabbit is still alive. */ public boolean isAlive() { return alive; } /** * Indicate that the rabbit is no longer alive. * It is removed from the field. */ public void setDead() { alive = false; if(location != null) { field.clear(location); location = null; field = null; } } /** * Return the rabbit's location. * @return The rabbit's location. */ public Location getLocation() { return location; } /** * Place the rabbit at the new location in the given field. * @param newLocation The rabbit's new location. */ private void setLocation(Location newLocation) { if(location != null) field.clear(location); location = newLocation; field.place(this, newLocation); } /** * Increase the age. * This could result in the rabbit's death. */ private void incrementAge() { age++; if(age > maxAge) setDead(); } /** * Check whether or not this rabbit is to give birth at this step. * New births will be made into free adjacent locations. * @param newRabbits A list to add newly born rabbits to. */ private void giveBirth(List<Rabbit> newRabbits) { // New rabbits are born into adjacent locations. // Get a list of adjacent free locations. List<Location> free = field.getFreeAdjacentLocations(location); int births = breed(); for(int b = 0; b < births && free.size() > 0; b++) { Location loc = free.remove(0); Rabbit young = new Rabbit(false, field, loc); newRabbits.add(young); } } /** * Generate a number representing the number of births, * if it can breed. * @return The number of births (may be zero). */ private int breed() { int births = 0; if(canBreed() && rand.nextDouble() <= breedProbability) { births = rand.nextInt(maxBreedSize) + 1; } return births; } /** * A rabbit can breed if it has reached the breeding age. * @return true if the rabbit can breed, false otherwise. */ private boolean canBreed() { return age >= breedAge; } }
Fox
import java.util.List; import java.util.Iterator; import java.util.Random; /** * A simple model of a fox. * Foxes age, move, eat rabbits, and die. * * @author Elkana Hans Widersen * @version 1.0 */ public class Fox { // Characteristics shared by all foxes (static fields). private static final int breedAge = 10; // The age at which a fox can start to breed. private static final int maxAge = 150; // The age to which a fox can live. private static final double breedProbability = 0.35; // The likelihood of a fox breeding. private static final int maxBreedSize = 5; // The maximum number of births. /** * The food value of a single rabbit. In effect, this is the * number of steps a fox can go before it has to eat again. */ private static final int rabbitFoodValue = 7; private static final Random rand = Randomizer.getRandom(); // A shared random number generator to control breeding. // Individual characteristics (instance fields). private int age; // The fox's age. private boolean alive; // Whether the fox is alive or not. private Location location; // The fox's position. private Field field; // The field occupied. private int foodLevel; // The fox's food level, which is increased by eating rabbits. /** * Create a fox. A fox can be created as a new born (age zero * and not hungry) or with a random age and food level. * * @param randomAge If true, the fox will have random age and hunger level. * @param field The field currently occupied. * @param location The location within the field. */ public Fox(boolean randomAge, Field field, Location location) { age = 0; alive = true; this.field = field; setLocation(location); if(randomAge) { age = rand.nextInt(maxAge); foodLevel = rand.nextInt(rabbitFoodValue); } else { // leave age at 0 foodLevel = rabbitFoodValue; } } /** * This is what the fox does most of the time: it hunts for * rabbits. In the process, it might breed, die of hunger, * or die of old age. * @param field The field currently occupied. * @param newFoxes A list to add newly born foxes to. */ public void hunt(List<Fox> newFoxes) { incrementAge(); incrementHunger(); if(alive) { giveBirth(newFoxes); // Move towards a source of food if found. Location newLocation = findFood(location); if(newLocation == null) { // No food found - try to move to a free location. newLocation = field.freeAdjacentLocation(location); } // See if it was possible to move. if(newLocation != null) setLocation(newLocation); else { // Overcrowding. setDead(); } } } /** * Check whether the fox is alive or not. * @return True if the fox is still alive. */ public boolean isAlive() { return alive; } /** * Return the fox's location. * @return The fox's location. */ public Location getLocation() { return location; } /** * Place the fox at the new location in the given field. * @param newLocation The fox's new location. */ private void setLocation(Location newLocation) { if(location != null) field.clear(location); location = newLocation; field.place(this, newLocation); } /** * Increase the age. This could result in the fox's death. */ private void incrementAge() { age++; if(age > maxAge) setDead(); } /** * Make this fox more hungry. This could result in the fox's death. */ private void incrementHunger() { foodLevel--; if(foodLevel <= 0) setDead(); } /** * Tell the fox to look for rabbits adjacent to its current location. * Only the first live rabbit is eaten. * @param location Where in the field it is located. * @return Where food was found, or null if it wasn't. */ private Location findFood(Location location) { List<Location> adjacent = field.adjacentLocations(location); Iterator<Location> it = adjacent.iterator(); while(it.hasNext()) { Location where = it.next(); Object animal = field.getObjectAt(where); if(animal instanceof Rabbit) { Rabbit rabbit = (Rabbit) animal; if(rabbit.isAlive()) { rabbit.setDead(); foodLevel = rabbitFoodValue; // Remove the dead rabbit from the field. return where; } } } return null; } /** * Check whether or not this fox is to give birth at this step. * New births will be made into free adjacent locations. * @param newFoxes A list to add newly born foxes to. */ private void giveBirth(List<Fox> newFoxes) { /** * New foxes are born into adjacent locations. * Get a list of adjacent free locations. */ List<Location> free = field.getFreeAdjacentLocations(location); int births = breed(); for(int b = 0; b < births && free.size() > 0; b++) { Location loc = free.remove(0); Fox young = new Fox(false, field, loc); newFoxes.add(young); } } /** * Generate a number representing the number of births, * if it can breed. * @return The number of births (may be zero). */ private int breed() { int births = 0; if(canBreed() && rand.nextDouble() <= breedProbability) births = rand.nextInt(maxBreedSize) + 1; return births; } /** * A fox can breed if it has reached the breeding age. */ private boolean canBreed() { return age >= breedAge; } /** * Indicate that the fox is no longer alive. * It is removed from the field. */ private void setDead() { alive = false; if(location != null) { field.clear(location); location = null; field = null; } } }
Thank you and see you next time!
Komentar
Posting Komentar