I'm creating a RTS game and one of the features is to construct differend kind of buildings. I'm finding a lot of repetition and I was thinking to extract it in helper method, but the problem is that every building is different object which inharits some propertyes from the main building class.
The building methods looks like this:
public static void buildDockyard(Base base) {
if (Validator.checkForBuilding(base, "Dockyard")) {
throw new IllegalStateException("Dockyard is already build");
}
Dockyard dockyard = new Dockyard("Dockyard");
int requiredPower = dockyard.requiredResource("power");
int requiredStardust = dockyard.requiredResource("stardust");
int requiredPopulation = dockyard.requiredResource("population");
Validator.checkResource(base, requiredPower, requiredStardust, requiredPopulation);
updateResourceAfterBuild(base, requiredPower, requiredStardust, requiredPopulation);
dockyard.setCompleteTime(dockyard.requiredResource("time"));
base.getBuildings().add(dockyard);
}
public static void buildHotel(Base base) {
if (Validator.checkForBuilding(base, "Space Hotel")) {
throw new IllegalStateException("Space Hotel is already build");
}
SpaceHotel spaceHotel = new SpaceHotel("Space Hotel");
int requiredPower = spaceHotel.requiredResource("power");
int requiredStardust = spaceHotel.requiredResource("stardust");
int requiredPopulation = spaceHotel.requiredResource("population");
Validator.checkResource(base, requiredPower, requiredStardust, requiredPopulation);
updateResourceAfterBuild(base, requiredPower, requiredStardust, requiredPopulation);
spaceHotel.setCompleteTime(spaceHotel.requiredResource("time"));
base.getBuildings().add(spaceHotel);
base.setCapacity(base.getCapacity() + spaceHotel.getCapacity());
}
I was thinking to refactor like this: The helper method
private static void construct(Building building, Base base) {
int requiredPower = building.requiredResource("power");
int requiredStardust = building.requiredResource("stardust");
int requiredPopulation = building.requiredResource("population");
Validator.checkResource(base, requiredPower, requiredStardust, requiredPopulation);
updateResourceAfterBuild(base, requiredPower, requiredStardust, requiredPopulation);
building.setCompleteTime(building.requiredResource("time"));
}
Aimed result
public static void buildDockyard(Base base) {
if (Validator.checkForBuilding(base, "Dockyard")) {
throw new IllegalStateException("Dockyard is already build");
}
Dockyard dockyard = new Dockyard("Dockyard");
construct(dockyar, base);
base.getBuildings().add(dockyard);
}
The problem is that each building has unique properties and resource requirements and the main Building class doesn't know about them, so I can't use it as a parameter in the helper method.
All of this is happening in a static helper class for the Base class.
How would you refactor this code ? Thank you in advance !
The best way to DRY in Java when making games is to have a clear understanding and terminology of your game. If you read any modern board game manual you will soon see that they will use exactly one word for one concept, like Turn
, Round
, Building
, Player
, Resource
. This allows to form a rough structure: A Building
costs a certain amount of Resource
. If a player hasn't enough of Resource
then tell him "We need more vespine gas.", etc. The clearer the picture, the DRY-er your Java and easier to create the necessary Classes for your code.
Parameters
If you end up with something like this:
public static void someFunction(Base base, Object param1, Object param2)
public static void someOtherFunc(Base base, Object paramA, Object paramB)
...
Then this is a strong hint that maybe both functions should be part of the Base
class.
Enums
If you have a limited set of values then Java Enums can be fantastic to represent them, e.g. your Resource system:
public enum Resource {
POWER, STARDUST, POPULATION
}
Now you don't have to remember if you called it "stardust", "Stardust" or if you even still have a Resource like "stardust". Instead you can use int requiredPower = building.requiredResource(Resource.POWER);
Polymorphism
Let's suppose we have two classes, Building
and StarHotel
, with StarHotel
being a specific kind of Building
. Having an abstract class Building
allows us to handle some general mechanics in a specific manner, like this:
public abstract class Building {
private ... cost;
private ... requirements;
private ...
// Std-Getter and Setter methods
public ... getCost() { return this.cost; }
}
EVERY Building has a cost, and requirements and other important variables. BUT we handled all the standard stuff of getting and setting these generic variables to a base class from which we now can extend other, more specific buildings. Thanks to the extends
keyword you can get the Cost of a StarHotel
Object without filling the StarHotel
class with repetitive Getters and Setters.
public class StarHotel extends Building {
// Getter, Setter inherited from Building class
}
Interfaces
Java Interfaces allow you to define Interfaces which define methods. In laymen terms: This is useful, because every Class that implements an Interface must implement the method, unless the interface provides the default
implementation.
public interface ResourceProvider {
void provideResourceFor(Base base); // A Resource Provider provides Resource for a base.
}
With this interface we have defined that if some Class implements ResourceProvider
it has to specify how and what resources to provide for some Base
object. Our interface does not care which Resource, which Base and even what provideResourceFor
could mean, but as long as something implements ResourceProvider
it has to provide the functionality.
Putting all together
Putting Enums, Interface and Polymorphism together, we can now create a StarHotel
class that extends Building
and implements ResourceProvider
, providing 8 Food units and 2 Happiness units to our Base.
public class StarHotel extends Building implements ResourceProvider
public void provideResourceFor(Base base) {
base.addResource(Resource.FOOD, 8);
base.addResource(Resource.HAPPINESS, 2);
}
}
That might be much to take in, but hopefully it will give you a good direction where to look further.