This below code implements a simulation of an ocean(as 2d-array of cells) which currently have Shark, fish or be Empty. User can run this simulation, as shown below:
> java Simtext 25 30 1
With this command, SimText will animate a 25x30 ocean array with a starveTime
of 1 unit.
/* point.java */
package Project1;
/**
* The Point class defines a location (x,y) in the Ocean.
*
* @author mohet01
*
*/
class Point {
/**
* Here top-left of Windows screen is considered as origin.
* x is an x-coordinate of a location in an ocean
* y is an y-coordinate of a location in an Ocean
*/
private int x;
private int y;
/**
* Constructor creates a Point object below co-ordinates
* @param x
* is an x-coordinate of a Critter location in an Ocean
* @param y
* is an y-coordinate of a Critter location in an Ocean
*/
public Point(int x, int y){
this.x = x;
this.y = y;
}
/**
* This method returns the x-coordinate of Critter in an ocean
* @return
* x-coordinate of a Critter location in an Ocean.
*/
public int getX(){
return this.x;
}
/**
* This method returns the y-coordinate of Critter in an ocean
* @return
* y-coordinate of a Critter location in an Ocean.
*/
public int getY(){
return this.y;
}
}
/* Critter.java */
package Project1;
/**
* The abstract class Critter defines a base class for any creature
* that can exist at a specific location in the ocean.
* @author mohet01
*
*/
abstract class Critter {
/**
* Below data member defines a location of a Critter in an Ocean
*/
Point location;
public Critter(int x, int y){
location = new Point(x,y);
}
public Point getLocation(){
return location;
}
/**
* This method computes the behavior of the Critter in the Ocean.
* Computes new value of location property of Critter.
* No operation is performed as this is a base class.
*/
public abstract Critter update(Ocean currentTimeStepSea);
}
/* Empty.java */
package Project1;
/**
* The Empty class defines itself as an entity as it has some meaning/significance
* being empty in an Ocean. Check update() method for more meaning.
* @author mohet01
*
*/
class Empty extends Critter{
/**
* Constructor will create a new location which is Empty
* @param x
* is the x-coordinate of location which is Empty.
* @param y
* is the y-coordinate of location which is empty.
*/
public Empty(int x, int y){
super(x,y);
}
/**
* This method updates the Empty cell based on the behavior of it's
* nearest neighborhood
* @param currentTimeStepSea
* Ocean in the current time step
* @param nextTimeStepSea
* Ocean that has to look like in next time Step
*
*/
@Override
public Critter update(Ocean currentTimeStepSea) {
int neighborFishCount = 0;
int neighborSharkCount = 0;
//Check all the 8 neighbors of an Empty cell to count sharks and Fish
neighborSharkCount = Utility.countSharkAsNeighbor(this, currentTimeStepSea);
neighborFishCount = Utility.countFishAsNeighbor(this,currentTimeStepSea);
//Update Empty Cell for next time step.
if((neighborFishCount >= 2) && (neighborSharkCount <=1)){
/*
* 7) If a cell is empty, at least two of its neighbors are fish, and at most one
* of its neighbors is a shark, then a new fish is born in that cell.
*
*/
return new Fish(this.getLocation().getX(),this.getLocation().getY());
}else if((neighborFishCount >= 2) && (neighborSharkCount >= 2)){
/*
* 8) If a cell is empty, at least two of its neighbors are fish, and at least two
* of its neighbors are sharks, then a new shark is born in that cell. (The new
* shark is well-fed at birth, even though it hasn’t eaten a fish yet.)
*
*/
return new Shark(this.getLocation().getX(),this.getLocation().getY(),0);
}
else{
/*
* 6) If a cell is empty, and fewer than two of its neighbors are fish, then the
* cell remains empty.
*/
return this;
}
}
}
/* Fish.java */
package Project1;
/**
* The Fish class defines the behavior of a Fish in an Ocean
* @author mohet01
*
*/
class Fish extends Critter{
/**
* Constructor will create a new location for Fish
* @param x
* is the x-coordinate of location(which can be EMPTY) of Fish
* @param y
* is the y-coordinate of location(which can be EMPTY) of Fish
*/
public Fish(int x, int y){
super(x,y);
}
/**
* This method updates the Fish cell based on the behavior of it's
* nearest neighborhood
* @param currentTimeStepSea
* Ocean in the current time step
* @param nextTimeStepSea
* Ocean that has to look like in next time Step
*
*/
@Override
public Critter update(Ocean currentTimeStepSea){
int neighborSharkCount=0;
neighborSharkCount = Utility.countSharkAsNeighbor(this, currentTimeStepSea);
//Updating fish cell for current & next time step
if(neighborSharkCount ==1){
/*
* 4) If a cell contains a fish, and one of its neighbors is a shark, then the
* fish is eaten by a shark, and therefore disappears.
*
*/
return new Empty(this.getLocation().getX(),this.getLocation().getY());
}
else if(neighborSharkCount > 1){
/*
* 5) If a cell contains a fish, and two or more of its neighbors are sharks, then
* a new shark is born in that cell. Sharks are well-fed at birth; _after_ they
* are born, they can survive an additional starveTime time steps without eating.
*
*/
return new Shark(this.getLocation().getX(),this.getLocation().getY(),0);
}
else {
/*
* condition is (neighborSharkCount < 1)
* 3) If a cell contains a fish, and all of its neighbors are either empty or are
* other fish, then the fish stays where it is.
*/
return this;
}
}
}
/* Ocean.java */
package Project1;
/**
* The Ocean class defines an object that models an ocean full of sharks and
* fish.
* @author mohet01
*
*/
class Ocean {
/**
* Define any variables associated with an Ocean object here. These
* variables MUST be private.
*
*/
//width of an Ocean
private int width;
//height of an Ocean
private int height;
/*
* I preferred, 2d array of references to Critter objects
* rather than List. Reasons(correct me),
* 1) To display an array of ocean, it adds more logic in paint() method.
* 2) Checking 8 nearest neighbors of each Critter looks inefficient,
* For example: for an ocean of SEEFE
* FEEFE a 2x2 ocean, If i maintain
* a list of Critter for this 2x2 ocean, i need to traverse
* S->E->E->F->E->F to get my first nearest neighbor of Shark,
* In contrast, With 2d array, I would just use modulo operation as
* mentioned in update() method. Let us see what happens!!!
*
*/
private Critter[][] oceanMatrix;
/**
* Constructor that creates an empty ocean with below dimension
*
* @param width
* is the width of the ocean.
* @param height
* is the height of the ocean.
*
*/
public Ocean(int width, int height){
this.oceanMatrix = new Critter[height][width];
this.width = width;
this.height = height;
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
oceanMatrix[row][col] = new Empty(row,col);
}
}
}
/**
* This method adds Critter in an ocean.
* @param object
* is the Critter object to be added in Ocean.
*/
public void addCritter(Critter object){
Point p = object.getLocation();
int x = p.getX();
int y = p.getY();
/*
* I understand that, location property make sense to be be moved
* to corresponding Critter<type> class as it's property, which i did, But
* also captured location property of a Critter Object in Ocean class(with
* above 3 lines of code) which is redundant and not relevant, But 2d array
* is more efficient than list, for checking neighbor in update() method.
* Are we Breaking SRS????
* So, Instead of List am using 2d array. Let us see what happens!!!
*/
oceanMatrix[x][y] = object;
}
/**
* This method returns either Critter Object reference
*
* @param x
* is the x-coordinate of the cell whose contents are queried.
* @param y
* is the y-coordinate of the cell whose contents are queried.
*/
public Critter cellContents(int x, int y) {
return oceanMatrix[x][y];
}
/**
* getWidth() returns the width of an ocean Object.
*
* @return
* the width of the ocean.
*
*/
public int getWidth() {
return this.width;
}
/**
* getHeight() returns the height of an Ocean object.
*
* @return
* the height of the Ocean.
*/
public int getHeight() {
return this.height;
}
/**
* timeStep() performs a simulation time step as described in README.
*
* @return
* an ocean representing the elapse of one time Step.
*/
public Ocean timeStep() {
Ocean nextTimeStepSea = new Ocean(width, height);
for (int row = 0; row < this.height; row++) {
for (int col = 0; col < this.width; col++) {
Critter creature = this.cellContents(row, col);
nextTimeStepSea.addCritter(creature.update(this));
}
}
return nextTimeStepSea;
}
}
/* Shark.java */
package Project1;
/**
* The Shark class defines behavior of a Shark in an Ocean.
* @author mohet01
*
*/
class Shark extends Critter{
/**
* Below data member is the number of simulation time steps that a Shark
* can live through without eating.
*/
static int starveTime;
/**
* Below data member specifies the hunger of each shark you add to the
* ocean.
*/
private int hungerLevel;
/**
* Constructor will create a new location for Shark
* @param x
* is the x-coordinate of location(which can be EMPTY) of Shark
* @param y
* is the y-coordinate of location(which can be EMPTY) of Shark
*/
public Shark(int x, int y, int hungerLevel){
super(x,y);
//Sharks are well-fed at birth
this.hungerLevel = hungerLevel;
}
/*
* This method provides the starvation time of Shark creature
*/
public static int getStarvationTime(){
return starveTime;
}
/**
* isSharkStarving() checks the hunger level of shark, if reached to starveTime level
* @param x
* is the x-coordinate of the cell whose contents are queried.
* @param y
* is the y-coordinate of the cell whose contents are queried.
* @return the boolean value
*/
private boolean isSharkStarving(){
return (this.hungerLevel == (starveTime+1));
}
/**
* This method updates the shark cell based on the behavior of it's
* nearest neighborhood
* @param currentTimeStepSea
* Ocean in the current time step
* @param nextTimeStepSea
* Ocean that has to look like in next time Step
*
*/
@Override
public Critter update(Ocean currentTimeStepSea){
boolean gotTheFish = false;
//Check all the 8 neighbors of a Shark Cell for fish
gotTheFish = Utility.checkFishAsNeighbor(this, currentTimeStepSea);
//Updating Shark Cell
if(gotTheFish){
/*
* 1) If a cell contains a shark, and any of its neighbors is a fish, then the
* shark eats during the time step, and it remains in the cell at the end of the
* time step. (We may have multiple sharks sharing the same fish. This is fine;
* they all get enough to eat.)
*/
return this; //return currentTimeStep Shark
}else{
/*
* 2) If a cell contains a shark, and none of its neighbors is a fish, it gets
* hungrier during the time step. If this time step is the (starveTime + 1)th
* time step the shark has gone through without eating, then the shark dies
* (disappears). Otherwise, it remains in the cell.
*
*/
this.hungerLevel++;
if(isSharkStarving()){
return new Empty(this.getLocation().getX(), this.getLocation().getY());
}
else{
return this; //return currentTimeStep Shark
}
}
}
}
/* Utility.java */
package Project1;
/**
* The Utility class provides some utility functions which are used
* by multiple classes like Fish, Shark etc...
* @author mohet01
*
*/
final class Utility {
/**
* Don't let anyone instantiate this class.
*/
private Utility() {}
/**
* This method checks the existence of at-least one fish as nearest
* neighbor surrounding Creature's cell
* @param sea
* is the ocean of currentTimeStep
* @return
* returns true on at-least one fish existence otherwise false
*
*/
public static boolean checkFishAsNeighbor(Critter creature, Ocean sea){
//get Creatures location
Point p = creature.getLocation();
int x = p.getX();
int y = p.getY();
int row,col;
for(int i = x-1;i <= x+1; i++){
for(int j = y-1; j <= y+1; j++){
/*
* Problem statement says(as per link):
* http://www.cs.berkeley.edu/~jrs/61bf06/hw/pj1/readme
* You can also refer to locations such as (4, 0) or (-4, 3),
* which are both the same as (0, 0) in a 4x3 ocean.
* so modulo is being performed for given i & j
*/
row = mod(i, sea.getHeight());
col = mod(i, sea.getWidth());
if(sea.cellContents(row, col) instanceof Fish)
return true;
}
}
return false;
}
/**
* This method counts number of Shark as nearest neighbor surrounding
* Creature's cell
* @param sea
* is the ocean of currentTimeStep
* @return
* returns number of Shark surrounding creature
*
*/
public static int countSharkAsNeighbor(Critter creature, Ocean sea){
int neighborSharkCount = 0;
//get Creatures location
Point p = creature.getLocation();
int x = p.getX();
int y = p.getY();
int row,col;
for(int i = x-1;i <= x+1; i++){
for(int j = y-1; j <= y+1; j++){
/*
* Problem statement says(as per link):
* http://www.cs.berkeley.edu/~jrs/61bf06/hw/pj1/readme
* You can also refer to locations such as (4, 0) or (-4, 3),
* which are both the same as (0, 0) in a 4x3 ocean.
* so modulo is being performed for given i & j
*/
row = mod(i, sea.getHeight());
col = mod(i, sea.getWidth());
if(sea.cellContents(row, col) instanceof Shark)
neighborSharkCount++;
}
}
return neighborSharkCount;
}
/**
* This method counts number of Fish as nearest neighbor surrounding
* Creature's cell
* @param sea
* is the ocean of currentTimeStep
* @return
* returns number of Fish surrounding creature
*
*/
public static int countFishAsNeighbor(Critter creature, Ocean sea){
int neighborFishCount = 0;
//get Creatures location
Point p = creature.getLocation();
int x = p.getX();
int y = p.getY();
int row,col;
for(int i = x-1;i <= x+1; i++){
for(int j = y-1; j <= y+1; j++){
/*
* Problem statement says(as per link):
* http://www.cs.berkeley.edu/~jrs/61bf06/hw/pj1/readme
* You can also refer to locations such as (4, 0) or (-4, 3),
* which are both the same as (0, 0) in a 4x3 ocean.
* so modulo is being performed for given i & j
*/
row = mod(i, sea.getHeight());
col = mod(i, sea.getWidth());
//if((sea.cellContents(row, col).getClass().getName()).equals(Fish.class))
if(sea.cellContents(row, col) instanceof Fish)
neighborFishCount++;
}
}
return neighborFishCount;
}
/**
* This method performs the modulo operation using euclidean divison
*
* @param n
* is the numerator
* @param d
* is the denominator
* @return
* Remainder
*/
private static int mod(int n, int d) {
if (n >= 0)
return n % d;
else
return d + ~(~n % d);
}
}
package Project1;
import java.util.Random;
/* SimText.java */
/* DO NOT CHANGE THIS FILE (except as noted). */
/* (You may wish to make temporary or insert println() statements */
/* while testing your code. when you're finished testing and debugging, */
/* though, make sure your code works with the original version of this file. */
/**
* The SimText class is a program that runs and animates a simulation of Sharks
* and Fish.
*
* The SimText program takes up to four parameters. The first two specify the
* width and height of the ocean. The third parameter specifies the value of
* starveTime. For example, if you run
*
* java SimText 25 25 1
*
* then SimText will animate a 25x25 ocean with a starveTime of 1 unit. If you run
* "java SimText" with no parameters, by default SimText will animate a 50x25
* ocean with a starveTime of 3. With some choices of parameters, the ocean
* quickly dies out; with others, it teems forever.
* @author mohet01
*
*/
public final class SimText {
/**
* Don't let anyone instantiate this class.
*/
private SimText(){}
/**
* Default parameters. (You may change these if you wish.)
*
*/
// Default ocean width
private static int width = 50;
// Default ocean height
private static int height = 25;
// Default shark starvation time
private static int starveTime = 3;
//Default Shark hunger Level
private static int hungerLevel = 0;
/**
* The {@code MAGIC_NUMBER} is a prime number that is used in LCG
* expressions to select random location in an Ocean.
*/
private static final int MAGIC_NUMBER = 78887;
private static final int LOC_MULTIPLE_OF_NUMBER = 8;
/**
* paint() prints an Ocean.
*/
public static void paint(Ocean sea) {
if (sea != null) {
int width = sea.getWidth();
int height = sea.getHeight();
// Draw the ocean
for (int x = 0; x < width + 2; x++) {
System.out.print("-");
}
System.out.println();
for (int row = 0; row < height; row++) {
System.out.print("|");
for (int col = 0; col < width; col++) {
Critter creature = sea.cellContents(row, col);
System.out.print(creature.getClass().getName().substring(9, 10));
}
System.out.println("|");
}
for (int x = 0; x < width + 2; x++) {
System.out.print("-");
}
System.out.println();
}
}
/**
* main() reads the parameters and performs the simulation and animation.
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
Ocean sea;
/**
* Read the input parameters.
*/
if (args.length > 0) {
try {
width = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
System.out
.println("First argument to SimText is not an number");
}
}
if (args.length > 1) {
try {
height = Integer.parseInt(args[1]);
} catch (NumberFormatException e) {
System.out
.println("Second argument to SimText is not an number");
}
}
if (args.length > 2) {
try {
starveTime = Integer.parseInt(args[2]);
} catch (NumberFormatException e) {
System.out
.println("Third argument to SimText is not an number");
}
}
Shark.starveTime = starveTime;
/**
* Create the initial ocean.
*/
sea = new Ocean(width, height);
/**
* Visit each cell (in a roundabout order); randomly place either Fish,
* or Shark or Empty Object in each location.
*
*/
// Create a "Random" object with seed 0
Random random = new Random(0);
int x = 0;
int y = 0;
for (int row = 0; row < height; row++) {
x = (x + MAGIC_NUMBER) % height;
if ((x & LOC_MULTIPLE_OF_NUMBER) == 0) {
for (int col = 0; col < width; col++) {
y = (y + MAGIC_NUMBER) % width;
if ((y & LOC_MULTIPLE_OF_NUMBER) == 0) {
// Between -2147483648 and 2147483647
int r = random.nextInt();
if (r < 0) {
sea.addCritter(new Fish(x,y));
} else if (r > 1500000000) {
sea.addCritter(new Shark(x,y,hungerLevel));
}
}
}
}
else{
sea.addCritter(new Empty(x,y));
}
}
/**
* Perform time steps forever.
*
*/
// Loop forever
while (true) {
paint(sea);
// For fun, you might wish to change the delay in the next line.
Thread.sleep(100); // Wait ten seconds (10000 milliseconds)
sea = sea.timeStep(); // Simulate a timestep
}
}/* end main() */
}
My main goal for this implementation is to understand OOP programming principles Abstraction
and Encapsulation
With the given 8 rules of Simulation in Part I of this link mentioned below,
The ocean is rectangular, but the edges are connected together in a topological donut or torus. This means that the top (North) and bottom (South) edges are considered adjacent, so if you start at the top edge and go up, you'll be at the bottom edge (just like in the video game Asteroids). Similarly, the East and West edges are connected (just like in Pac Man).
Note that the origin is in the upper left; the x-coordinate increases as you move right, and the y-coordinate increases as you go down. (This conforms to Java's graphics commands, though you won't need to use them directly in this project.) You can also refer to locations such as (4, 0) or (-4, 3), which are both the same as (0, 0) in a 4x3 ocean. (More generally, the coordinates in an ixj ocean are taken modulo i for the x-coordinate, which is horizontal, and modulo j for the y-coordinate, which is vertical.) Any pair of integers will give you a valid position in the grid by "wrapping around" at the edges.
(Hint: programming will be a lot easier if you write helper functions that do the wrapping around for you, and use them in all your methods, so you don't have to think about it again.)
There are two kinds of entities in this ocean: sharks and fish. The sharks and fish breed, eat, and die in the ocean. Each cell of the grid can be occupied by a single shark or fish, or it can be empty.
Part I: Simulating Sharks and Fish
An ocean is described by its size and the initial placement of sharks and fish in the ocean. It is also described by a parameter called the "starveTime" for a shark. This is the number of simulation timesteps that a shark can live through without eating.
The simulation proceeds in timesteps. A "timestep" is a transition from one ocean to the next. (Don't confuse timesteps with oceans; every timestep starts with one ocean and ends with another.) The rules for how the ocean looks at the end of a timestep depend only on the occupants of the cells at the beginning of the timestep. Therefore, to obtain correct behavior, you will often be working with two copies of the ocean simultaneously; one representing the ocean at the beginning of the timestep, and the other representing the ocean at the end of the timestep. (If you are foolish enough to try to implement a timestep using just a single Ocean object, you will modify the values of cells whose old values are still needed to compute the new values for other cells, and thus you will compute the wrong answer.)
The contents of any particular cell at the end of a timestep depend only on the contents of that cell and its eight neighbors at the beginning of the timestep. The "neighbors" are the eight adjacent cells: the cells immediately to the north, south, east, and west, as well as the four diagonal neighbors. Here are the rules:
If a cell contains a shark, and any of its neighbors is a fish, then the shark eats during the timestep, and it remains in the cell at the end of the timestep. (We may have multiple sharks sharing the same fish. This is fine; they all get enough to eat.)
If a cell contains a shark, and none of its neighbors is a fish, it gets hungrier during the timestep. If this timestep is the (starveTime + 1)th timestep the shark has gone through without eating, then the shark dies (disappears). Otherwise, it remains in the cell. An example demonstrating this rule appears below.
If a cell contains a fish, and all of its neighbors are either empty or are other fish, then the fish stays where it is.
If a cell contains a fish, and one of its neighbors is a shark, then the fish is eaten by a shark, and therefore disappears.
If a cell contains a fish, and two or more of its neighbors are sharks, then a new shark is born in that cell. Sharks are well-fed at birth; after they are born, they can survive an additional starveTime timesteps without eating. (But they will die at the end of starveTime + 1 consecutive timesteps without eating.)
If a cell is empty, and fewer than two of its neighbors are fish, then the cell remains empty.
If a cell is empty, at least two of its neighbors are fish, and at most one of its neighbors is a shark, then a new fish is born in that cell.
If a cell is empty, at least two of its neighbors are fish, and at least two of its neighbors are sharks, then a new shark is born in that cell. (The new shark is well-fed at birth, even though it hasn't eaten a fish yet.
My question:
Does this implementation breaks the rule of proper abstraction
and encapsulation
, if it runs for long run?
Note: Am new to Java programming, and learning as per the guidance in course. This course does not encourage to use existing java packages.
starveTime
member variable is set inShark
class is valid? Do you think am setting this this variable inSimText
class beforeShark
class is loaded. Somehow, i feel this is wrong approach. i did not assignstarveTime
property to everyShark
object because it is common value. \$\endgroup\$