PART FIVE
CROPS
NEW CLASSES
Adding Entities
Create classes to represent Entity, Plant, Crop, Corn, and Potato
Corn and Potato extend Crop
Crops extends Plant
Plant extends Entity
All of these classes except for Corn and Potato should be abstract.
We won't use all of these layers immediately, but they'll allow us to expand our project later to add things like Trees which behave differently from Crops.
For now, just leave these classes as mostly empty stubs that extend the appropriate classes
PROJECT ORGANIZATION
Composition "has-a"
Game
World
Entity (ArrayList)
Cell (2D Array)
Terrain
Inheritance "is-a"
Terrain
Dirt
Grass
Water
Entity
Plant
Crop
Corn
Potato
IMPLEMENTING ENTITIES
Entity
Create protected variables called cell and color
Write a setCell(Cell c) method
Write a render(Graphics g) method that draws a circle to represent the crop based on the cell's location and the Entity's color
Corn and Potato
In the constructor, initialize color to yellow or brown respectively
Cell
Each cell should have a single entity stored as a variable
Write getEntity() and setEntity(Entity e), just as you did for terrain.
Remember that setEntity needs to both assign the entity in the Cell class, and tell that Entity to set its Cell back to this cell.
Write a boolean method called hasEntity() that checks if entity is null.
World
Declare and initialize an ArrayList<Entity> named entities
Write a method called addEntity(Entity e, int x, int y)
Adds the entity to the list of entities in world
Sets the entity in the appropriate cell
In the World constructor, hardcode in a sample corn and potato after reading in the map file
In the render() method, render all entities
CHECKPOINT
At this point, you should be able to see the image above, with at least one corn and potato hardcoded into the program
CHECKING VALID TERRAIN
Dirt Class
Add accessor methods isSoil() and isWet()
Entity Class
Not all entities can exist on every terrain. For example, crops can only grow on soil. Later on, we might make rules saying that trees can only be planted in Grass or plain Dirt.
Write an abstract method in Entity called isValid(Terrain t)
Implement this abstract method in the Crop class so that it returns true if the terrain is both Dirt and Soil, using the newly written isSoil method.
But wait... how can I access isSoil() from a generic Terrain object? We'll do an explicit cast. See code on the right!
We do two things here, and the order matters.
First, we check if the terrain is a Dirt. Then, we tell Java: "Hey, I promise this Terrain is actually a Dirt. So let's treat it as a an object of class Dirt."
This allows you to access Dirt-specific methods by explicitly casting it. Be careful - if you convert something improperly, you'll get a ClassCastException
World Class
Revisit the addEntity method. Write a conditional that checks if the target cell's terrain is valid for the new Entity.
CHECKPOINT
Try placing crops on invalid terrain locations, such as Water and Grass.
GROWTH
Entity
Write an abstract method called public void nextDay()
Plant
Create a variable called protected int daysGrown
Override the render(g) method from Entity. Call super.render() to make sure it still draws the image as normal, but add code to display the number of daysGrown in text over each crop circle.
Crop
Implement the nextDay() method
For a plant to grow, it needs to be on wet soil. Let's check that the current terrain meets those requirements. You'll need to use an explicit cast like we did in the last section.
World
In your World's next day method, loop through all of your entities and tell them to call their nextDay() methods.
Hint: Order matters here. You'll want to tell entities to update first, otherwise you'd dry out the soil before checking if they grow!
Notice that in this example, I chose to make a temporary variable rather than casting twice. This just makes it easier to read.
CHECKPOINT
Run your program and water crops and try ending the day a few times.
The crops counter should increase each time.
MATURITY
Plant
All plants can grow, and when a plant hits a certain number of days that it has grown, we'll say it has reached maturity.
In the plant class, we'll add an integer named maturity and an accessor method named isMature(). This method will return true if daysGrown is greater than or equal to maturity, and false otherwise.
Write an additional accessor called percentMaturity(). This method returns a float between 0 and 1 that represents how close to maturity the plant is. You can calculate this by dividing daysGrown by maturity. We'll use this in the next section!
Hint: Be sure to cast these values to a float to avoid integer division.Add code to the render method to show a special indication in the user interface of the crop is mature. In my example, I used a thick black circle.
Corn and Potato
Give each of these crops a starting maturity.
In my example, I used 7 days for corn and 4 days for potato.
For now, we can demonstrated maturity in the render() method by making a circle around the plant.
CHECKPOINT
Run your code... at this point, we' should now be able to see
Plants that grow each time you water them and start a new day
A visible counter of how many times a plant is grown
An indicator that a plant is fully mature