For a game I am playing, OGame, I am trying to calculate the fastest strategy to reach a certain state in the game, relevant parts of the game for this question are:
- You can build different types of buildings.
- Buildings take time to be built.
- In particular the Metal Mine, Crystal Mine and Deuterium Synthesizer buildings grant respectively Metal, Crystal and Deuterium resources every second.
- Buildings can be upgraded.
- At higher level the mines (Metal/Crystal/Deuterium) produce more resources, but also cost more to get upgraded.
- There is a very small base Metal and Crystal production if you do not have any buildings.
- You start the game with 500 Metal and 500 Crystal.
- Metal Mine, Crystal Mine and Deuterium Synthesizer cost energy to operate, the Solar Plant building provides that energy. If your consumption is higher than your production, then your mines are less efficient.
Please note that this is a rough prototype, but by all means do write critique on it as creating rough prototypes as good as possible is also a valuable skill.
My main concern with this program is the performance, if I query it to calculate the fastest strategy for for example Metal Mine 1, or 2, or 3 or 4 then it is fast, but for Metal Mine 5 it takes forever. The problem lies in RAM usage as for Metal Mine 5 it takes at least 6 GB of RAM and this is obviously a big issue. On the other hand, I am restricted to doing a Brute-Force search as I cannot think of any good heuristic which would let me allow a more efficient algorithm.
public class OGamePlannerMain {
public void run() {
ServerSettings serverSettings = new ServerSettings(1, 1);
PlayerSnapshot initialPlayerSnapshot = new PlayerSnapshot(serverSettings);
EnumMap<Resource, Double> initialResources = new EnumMap<>(Resource.class);
initialResources.put(METAL, 500d);
initialResources.put(CRYSTAL, 500d);
initialPlayerSnapshot.initializeResources(initialResources);
//TODO currently it checks for metal mine level, later check for Small Cargo 1
Predicate<PlayerSnapshot> successPredicate = playerSnapshot -> (playerSnapshot.getBuildingLevel(METAL_MINE) == 5);
Planner planner = new Planner(initialPlayerSnapshot, successPredicate);
PlayerSnapshot earliestPlayerSnapshot = planner.plan();
System.out.println("Time elapsed: " + earliestPlayerSnapshot.getTime() + " seconds");
System.out.println("---");
for (Resource resource : Resource.values()) {
System.out.println(resource + ": " + earliestPlayerSnapshot.getResourceAmount(resource));
}
System.out.println("---");
earliestPlayerSnapshot.getPerformedActions().forEach(System.out::println);
}
public static void main(String[] args) {
new OGamePlannerMain().run();
}
}
public class Planner {
private final PlayerSnapshot initialPlayerSnapshot;
private Predicate<PlayerSnapshot> successPredicate;
public Planner(PlayerSnapshot initialPlayerSnapshot, Predicate<PlayerSnapshot> successPredicate) {
this.initialPlayerSnapshot = initialPlayerSnapshot;
this.successPredicate = successPredicate;
}
public PlayerSnapshot plan() {
PlayerSnapshot earliestMatchingPlayerSnapshot = null;
if (successPredicate.test(initialPlayerSnapshot)) {
return initialPlayerSnapshot;
}
PriorityQueue<PlayerSnapshot> queue = new PriorityQueue<>(Comparator.comparingLong(PlayerSnapshot::getTime));
queue.add(initialPlayerSnapshot);
while (!queue.isEmpty()) {
PlayerSnapshot playerSnapshot = queue.poll();
if (earliestMatchingPlayerSnapshot != null && playerSnapshot.getTime() >= earliestMatchingPlayerSnapshot.getTime()) {
continue;
}
for (Action action : playerSnapshot.generateActions()) {
if (action.isAllowed(playerSnapshot)) {
PlayerSnapshot resultingPlayerSnapshot = action.performAction(playerSnapshot);
if (successPredicate.test(resultingPlayerSnapshot)) {
if (earliestMatchingPlayerSnapshot == null) {
earliestMatchingPlayerSnapshot = resultingPlayerSnapshot;
} else {
if (resultingPlayerSnapshot.getTime() < earliestMatchingPlayerSnapshot.getTime()) {
earliestMatchingPlayerSnapshot = resultingPlayerSnapshot;
}
}
} else {
queue.add(resultingPlayerSnapshot);
}
}
}
}
return earliestMatchingPlayerSnapshot;
}
}
public class PlayerSnapshot {
private final ServerSettings serverSettings;
private final List<Action> performedActions = new ArrayList<>();
private long time = 0;
private final EnumMap<Resource, Double> resources = new EnumMap<>(Resource.class);
private final EnumMap<Building, Integer> buildings = new EnumMap<>(Building.class);
private final EnumMap<Research, Integer> researches = new EnumMap<>(Research.class);
private final EnumMap<Ship, Integer> ships = new EnumMap<>(Ship.class);
private Building buildingInProgress = null;
public PlayerSnapshot(ServerSettings serverSettings) {
this.serverSettings = serverSettings;
for (Resource resource : Resource.values()) {
resources.put(resource, 0d);
}
for (Building building : Building.values()) {
buildings.put(building, 0);
}
for (Research research : Research.values()) {
researches.put(research, 0);
}
for (Ship ship : Ship.values()) {
ships.put(ship, 0);
}
}
public ServerSettings getServerSettings() {
return serverSettings;
}
public List<Action> getPerformedActions() {
return performedActions;
}
public long getTime() {
return time;
}
public double getResourceAmount(Resource resource) {
return resources.getOrDefault(resource, 0d);
}
public int getBuildingLevel(Building building) {
return buildings.getOrDefault(building, 0);
}
public int getResearchLevel(Research research) {
return researches.getOrDefault(research, 0);
}
public int getShipAmount(Ship ship) {
return ships.getOrDefault(ship, 0);
}
public void initializeResources(Map<Resource, Double> resources) {
this.resources.putAll(resources);
}
public void initializeBuildings(Map<Building, Integer> buildings) {
this.buildings.putAll(buildings);
}
public void initializeResearches(Map<Research, Integer> researches) {
this.researches.putAll(researches);
}
public void initializeShips(Map<Ship, Integer> ships) {
this.ships.putAll(ships);
}
public List<Action> generateActions() {
List<Action> actions = new ArrayList<>();
addBuildingActions(actions);
//TODO add actions for other things too
return actions;
}
private void addBuildingActions(List<Action> actions) {
if (buildingInProgress == null) {
buildings.forEach((building, level) -> {
if (building.satisfiesRequirements(this)) {
ActionCost upgradeCost = building.getUpgradeCost(this);
if (satisfiesResourcesCost(upgradeCost)) {
actions.add(new StartUpgradeBuildingAction(building));
}
else {
actions.add(new WaitForBuildingAction(building));
}
}
});
}
else {
//TODO generate all possible actions for that building (including DM usage)
Action finishBuildingAction = new FinishUpgradeBuildingAction(buildingInProgress);
if (finishBuildingAction.isAllowed(this)) {
actions.add(finishBuildingAction);
}
}
}
public PlayerSnapshot copyForNewAction(Action performedAction) {
PlayerSnapshot playerSnapshot = new PlayerSnapshot(serverSettings);
playerSnapshot.performedActions.addAll(performedActions);
playerSnapshot.performedActions.add(performedAction);
playerSnapshot.time = time; //TODO maybe related ActionCost to Action and add it at this point?
playerSnapshot.resources.putAll(resources);
playerSnapshot.buildings.putAll(buildings);
playerSnapshot.researches.putAll(researches);
playerSnapshot.ships.putAll(ships);
return playerSnapshot;
}
public boolean satisfiesResourcesCost(ActionCost actionCost) {
if ((int)Math.floor(getResourceAmount(METAL)) < actionCost.getMetal()) {
return false;
}
if ((int)Math.floor(getResourceAmount(CRYSTAL)) < actionCost.getCrystal()) {
return false;
}
if ((int)Math.floor(getResourceAmount(DEUTERIUM)) < actionCost.getDeuterium()) {
return false;
}
if ((int)Math.floor(getResourceAmount(DARK_MATTER)) < actionCost.getDarkMatter()) {
return false;
}
return true;
}
private void addCost(ActionCost actionCost) {
addTimeCost(actionCost);
addResourcesCost(actionCost);
}
private void addTimeCost(ActionCost actionCost) {
time += actionCost.getTime();
double metalProduction = METAL_MINE.getHourlyResourceProduction(this) / 3600d;
double crystalProduction = CRYSTAL_MINE.getHourlyResourceProduction(this) / 3600d;
double deuteriumProduction = DEUTERIUM_SYNTHESIZER.getHourlyResourceProduction(this) / 3600d;
//TODO create better system to add resources
resources.merge(METAL, metalProduction * actionCost.getTime(), (amount, production) -> amount + production);
resources.merge(CRYSTAL, crystalProduction * actionCost.getTime(), (amount, production) -> amount + production);
resources.merge(DEUTERIUM, deuteriumProduction * actionCost.getTime(), (amount, production) -> amount + production);
}
private void addResourcesCost(ActionCost actionCost) {
resources.merge(METAL, actionCost.getMetal() * 1d, (amount, cost) -> amount - cost);
resources.merge(CRYSTAL, actionCost.getCrystal() * 1d, (amount, cost) -> amount - cost);
resources.merge(DEUTERIUM, actionCost.getDeuterium() * 1d, (amount, cost) -> amount - cost);
resources.merge(DARK_MATTER, actionCost.getDarkMatter() * 1d, (amount, cost) -> amount - cost);
}
public void wait(ActionCost actionCost) {
addTimeCost(actionCost);
}
public void startUpgradeBuilding(Building building) {
addResourcesCost(building.getUpgradeCost(this));
buildingInProgress = building;
}
public void finishUpgradeBuilding(Building building) {
addTimeCost(building.getUpgradeCost(this));
buildings.merge(building, 1, (currentLevel, newLevels) -> currentLevel + newLevels);
buildingInProgress = null;
}
public boolean isCurrentlyUpgradingBuilding(Building building) {
return (buildingInProgress == building);
}
}
public class ServerSettings {
private final int economySpeed;
private final int fleetSpeed;
public ServerSettings(int economySpeed, int fleetSpeed) {
this.economySpeed = economySpeed;
this.fleetSpeed = fleetSpeed;
}
public int getEconomySpeed() {
return economySpeed;
}
public int getFleetSpeed() {
return fleetSpeed;
}
}
public enum Building implements GameObject {
METAL_MINE {
@Override
public ActionCost getUpgradeCost(PlayerSnapshot playerSnapshot) {
int currentLevel = playerSnapshot.getBuildingLevel(this);
int metalCost = (int)Math.floor(60 * Math.pow(1.5d, currentLevel));
int crystalCost = (int)Math.floor(15 * Math.pow(1.5d, currentLevel));
long time = calculateTime(metalCost, crystalCost, playerSnapshot);
return new ActionCost(time, metalCost, crystalCost, 0, 0);
}
@Override
public int getEnergyCost(PlayerSnapshot playerSnapshot) {
int currentLevel = playerSnapshot.getBuildingLevel(this);
return (int)Math.ceil(10 * currentLevel * Math.pow(1.1d, currentLevel));
}
@Override
public double getHourlyResourceProduction(PlayerSnapshot playerSnapshot) {
int metalMineLevel = playerSnapshot.getBuildingLevel(METAL_MINE);
int economySpeed = playerSnapshot.getServerSettings().getEconomySpeed();
return (30d + (30d * metalMineLevel * Math.pow(1.1d, metalMineLevel) * calculateEnergyModifier(playerSnapshot))) * economySpeed;
}
},
CRYSTAL_MINE {
@Override
public ActionCost getUpgradeCost(PlayerSnapshot playerSnapshot) {
int currentLevel = playerSnapshot.getBuildingLevel(this);
int metalCost = (int)Math.floor(48 * Math.pow(1.6d, currentLevel));
int crystalCost = (int)Math.floor(24 * Math.pow(1.6d, currentLevel));
long time = calculateTime(metalCost, crystalCost, playerSnapshot);
return new ActionCost(time, metalCost, crystalCost, 0, 0);
}
@Override
public int getEnergyCost(PlayerSnapshot playerSnapshot) {
int currentLevel = playerSnapshot.getBuildingLevel(this);
return (int)Math.ceil(10 * currentLevel * Math.pow(1.1d, currentLevel));
}
@Override
public double getHourlyResourceProduction(PlayerSnapshot playerSnapshot) {
int crystalMineLevel = playerSnapshot.getBuildingLevel(CRYSTAL_MINE);
int economySpeed = playerSnapshot.getServerSettings().getEconomySpeed();
return (15d + (20d * crystalMineLevel * Math.pow(1.1d, crystalMineLevel) * calculateEnergyModifier(playerSnapshot))) * economySpeed;
}
},
DEUTERIUM_SYNTHESIZER {
@Override
public ActionCost getUpgradeCost(PlayerSnapshot playerSnapshot) {
int currentLevel = playerSnapshot.getBuildingLevel(this);
int metalCost = (int)Math.floor(225 * Math.pow(1.5d, currentLevel));
int crystalCost = (int)Math.floor(75 * Math.pow(1.5d, currentLevel));
long time = calculateTime(metalCost, crystalCost, playerSnapshot);
return new ActionCost(time, metalCost, crystalCost, 0, 0);
}
@Override
public int getEnergyCost(PlayerSnapshot playerSnapshot) {
int currentLevel = playerSnapshot.getBuildingLevel(this);
return (int)Math.ceil(20 * currentLevel * Math.pow(1.1d, currentLevel));
}
@Override
public double getHourlyResourceProduction(PlayerSnapshot playerSnapshot) {
return 0d; //TODO implement this later, depends on planet temperature too
}
},
SOLAR_PLANT {
@Override
public ActionCost getUpgradeCost(PlayerSnapshot playerSnapshot) {
int currentLevel = playerSnapshot.getBuildingLevel(this);
int metalCost = (int)Math.floor(75 * Math.pow(1.5d, currentLevel));
int crystalCost = (int)Math.floor(30 * Math.pow(1.5d, currentLevel));
long time = calculateTime(metalCost, crystalCost, playerSnapshot);
return new ActionCost(time, metalCost, crystalCost, 0, 0);
}
@Override
public int getEnergyCost(PlayerSnapshot playerSnapshot) {
int currentLevel = playerSnapshot.getBuildingLevel(this);
return -(int)Math.ceil(20 * currentLevel * Math.pow(1.1d, currentLevel));
}
@Override
public double getHourlyResourceProduction(PlayerSnapshot playerSnapshot) {
return 0d;
}
},
ROBOTICS_FACTORY {
@Override
public ActionCost getUpgradeCost(PlayerSnapshot playerSnapshot) {
int currentLevel = playerSnapshot.getBuildingLevel(this);
int metalCost = (int)Math.floor(400 * Math.pow(2d, currentLevel));
int crystalCost = (int)Math.floor(120 * Math.pow(2d, currentLevel));
int deuteriumCost = (int)Math.floor(200 * Math.pow(2d, currentLevel));
long time = calculateTime(metalCost, crystalCost, playerSnapshot);
return new ActionCost(time, metalCost, crystalCost, deuteriumCost, 0);
}
@Override
public int getEnergyCost(PlayerSnapshot playerSnapshot) {
return 0;
}
@Override
public double getHourlyResourceProduction(PlayerSnapshot playerSnapshot) {
return 0d;
}
},
SHIPYARD(new Requirement(ROBOTICS_FACTORY, 2)) {
@Override
public ActionCost getUpgradeCost(PlayerSnapshot playerSnapshot) {
int currentLevel = playerSnapshot.getBuildingLevel(this);
int metalCost = (int)Math.floor(400 * Math.pow(2d, currentLevel));
int crystalCost = (int)Math.floor(200 * Math.pow(2d, currentLevel));
int deuteriumCost = (int)Math.floor(100 * Math.pow(2d, currentLevel));
long time = calculateTime(metalCost, crystalCost, playerSnapshot);
return new ActionCost(time, metalCost, crystalCost, deuteriumCost, 0);
}
@Override
public int getEnergyCost(PlayerSnapshot playerSnapshot) {
return 0;
}
@Override
public double getHourlyResourceProduction(PlayerSnapshot playerSnapshot) {
return 0d;
}
},
RESEARCH_LAB {
@Override
public ActionCost getUpgradeCost(PlayerSnapshot playerSnapshot) {
int currentLevel = playerSnapshot.getBuildingLevel(this);
int metalCost = (int)Math.floor(200 * Math.pow(2d, currentLevel));
int crystalCost = (int)Math.floor(400 * Math.pow(2d, currentLevel));
int deuteriumCost = (int)Math.floor(200 * Math.pow(2d, currentLevel));
long time = calculateTime(metalCost, crystalCost, playerSnapshot);
return new ActionCost(time, metalCost, crystalCost, deuteriumCost, 0);
}
@Override
public int getEnergyCost(PlayerSnapshot playerSnapshot) {
return 0;
}
@Override
public double getHourlyResourceProduction(PlayerSnapshot playerSnapshot) {
return 0d;
}
};
private Requirement[] requirements;
Building(Requirement... requirements) {
this.requirements = requirements;
}
@Override
public Requirement[] getRequirements() {
return requirements;
}
public abstract ActionCost getUpgradeCost(PlayerSnapshot playerSnapshot);
public abstract int getEnergyCost(PlayerSnapshot playerSnapshot);
public abstract double getHourlyResourceProduction(PlayerSnapshot playerSnapshot);
public ActionCost calculateWaitCost(PlayerSnapshot playerSnapshot) {
ActionCost upgradeCost = getUpgradeCost(playerSnapshot);
double metalWaitHours = (upgradeCost.getMetal() - playerSnapshot.getResourceAmount(METAL)) / METAL_MINE.getHourlyResourceProduction(playerSnapshot);
double crystalWaitHours = (upgradeCost.getCrystal() - playerSnapshot.getResourceAmount(CRYSTAL)) / CRYSTAL_MINE.getHourlyResourceProduction(playerSnapshot);
double deuteriumWaitHours = (upgradeCost.getDeuterium() - playerSnapshot.getResourceAmount(DEUTERIUM)) / DEUTERIUM_SYNTHESIZER.getHourlyResourceProduction(playerSnapshot);
double minimumWaitHours = 0d;
if (upgradeCost.getMetal() > 0) {
minimumWaitHours = Math.max(minimumWaitHours, metalWaitHours);
}
if (upgradeCost.getCrystal() > 0) {
minimumWaitHours = Math.max(minimumWaitHours, crystalWaitHours);
}
if (upgradeCost.getDeuterium() > 0) {
minimumWaitHours = Math.max(minimumWaitHours, deuteriumWaitHours);
}
long minimumWaitSeconds = (long)Math.ceil(minimumWaitHours * 3600d);
return new ActionCost(minimumWaitSeconds, 0, 0, 0, 0);
}
private static double calculateEnergyModifier(PlayerSnapshot playerSnapshot) {
int metalEnergyCost = METAL_MINE.getEnergyCost(playerSnapshot);
int crystalEnergyCost = CRYSTAL_MINE.getEnergyCost(playerSnapshot);
int deuteriumEnergyCost = DEUTERIUM_SYNTHESIZER.getEnergyCost(playerSnapshot);
int solarPlantEnergyCost = SOLAR_PLANT.getEnergyCost(playerSnapshot);
if (solarPlantEnergyCost == 0) {
return 0d;
}
else {
double energyModifier = (metalEnergyCost + crystalEnergyCost + deuteriumEnergyCost) / (-solarPlantEnergyCost * 1d);
return Math.min(Math.max(energyModifier, 0d), 1d);
}
}
public static long calculateTime(int metalCost, int crystalCost, PlayerSnapshot playerSnapshot) {
int roboticsFactoryLevel = playerSnapshot.getBuildingLevel(ROBOTICS_FACTORY);
int economySpeed = playerSnapshot.getServerSettings().getEconomySpeed();
int naniteFactoryLevel = 0; //TODO add this
double timeInHours = (metalCost + crystalCost) / (2500d * (1 + roboticsFactoryLevel) * economySpeed * Math.pow(2d, naniteFactoryLevel));
return (long)Math.ceil(timeInHours * 3600d);
}
}
public class ActionCost {
private final long time;
private final int metal;
private final int crystal;
private final int deuterium;
private final int darkMatter;
public ActionCost(long time, int metal, int crystal, int deuterium, int darkMatter) {
this.time = time;
this.metal = metal;
this.crystal = crystal;
this.deuterium = deuterium;
this.darkMatter = darkMatter;
}
public long getTime() {
return time;
}
public int getMetal() {
return metal;
}
public int getCrystal() {
return crystal;
}
public int getDeuterium() {
return deuterium;
}
public int getDarkMatter() {
return darkMatter;
}
}
public interface Action {
boolean isAllowed(PlayerSnapshot playerSnapshot);
PlayerSnapshot performAction(PlayerSnapshot playerSnapshot);
}
public class StartUpgradeBuildingAction implements Action {
private final Building building;
public StartUpgradeBuildingAction(Building building) {
this.building = building;
}
@Override
public boolean isAllowed(PlayerSnapshot playerSnapshot) {
for (Requirement requirement : building.getRequirements()) {
if (!requirement.isSatisfied(playerSnapshot)) {
return false;
}
}
//TODO save cost here?
ActionCost actionCost = building.getUpgradeCost(playerSnapshot);
return playerSnapshot.satisfiesResourcesCost(actionCost);
}
@Override
public PlayerSnapshot performAction(PlayerSnapshot playerSnapshot) {
PlayerSnapshot newPlayerSnapshot = playerSnapshot.copyForNewAction(this);
newPlayerSnapshot.startUpgradeBuilding(building);
return newPlayerSnapshot;
}
@Override
public String toString() {
return "StartUpgradeBuildingAction(" + building + ")";
}
}
public class FinishUpgradeBuildingAction implements Action {
private final Building building;
public FinishUpgradeBuildingAction(Building building) {
this.building = building;
}
@Override
public boolean isAllowed(PlayerSnapshot playerSnapshot) {
return playerSnapshot.isCurrentlyUpgradingBuilding(building);
}
@Override
public PlayerSnapshot performAction(PlayerSnapshot playerSnapshot) {
PlayerSnapshot newPlayerSnapshot = playerSnapshot.copyForNewAction(this);
newPlayerSnapshot.finishUpgradeBuilding(building);
return newPlayerSnapshot;
}
@Override
public String toString() {
return "FinishUpgradeBuildingAction(" + building + ")";
}
}
public class WaitForBuildingAction implements Action {
private final Building building;
public WaitForBuildingAction(Building building) {
this.building = building;
}
@Override
public boolean isAllowed(PlayerSnapshot playerSnapshot) {
double metalHourlyProduction = METAL_MINE.getHourlyResourceProduction(playerSnapshot);
double crystalHourlyProduction = CRYSTAL_MINE.getHourlyResourceProduction(playerSnapshot);
double deuteriumHourlyProduction = DEUTERIUM_SYNTHESIZER.getHourlyResourceProduction(playerSnapshot);
ActionCost upgradeCost = building.getUpgradeCost(playerSnapshot);
double metalWaitHours = (playerSnapshot.getResourceAmount(METAL) + upgradeCost.getMetal()) / metalHourlyProduction;
double crystalWaitHours = (playerSnapshot.getResourceAmount(CRYSTAL) + upgradeCost.getCrystal()) / crystalHourlyProduction;
double deuteriumWaitHours = (playerSnapshot.getResourceAmount(DEUTERIUM) + upgradeCost.getDeuterium()) / deuteriumHourlyProduction;
if (Double.isInfinite(metalWaitHours) || Double.isInfinite(crystalWaitHours) || Double.isInfinite(deuteriumWaitHours)) {
return false;
}
return true;
}
@Override
public PlayerSnapshot performAction(PlayerSnapshot playerSnapshot) {
PlayerSnapshot newPlayerSnapshot = playerSnapshot.copyForNewAction(this);
newPlayerSnapshot.wait(building.calculateWaitCost(playerSnapshot));
return newPlayerSnapshot;
}
@Override
public String toString() {
return "WaitForBuildingAction(" + building + ")";
}
}
public class Requirement {
private final GameObject gameObject;
private final int level;
public Requirement(GameObject gameObject, int level) {
this.gameObject = gameObject;
this.level = level;
}
public boolean isSatisfied(PlayerSnapshot playerSnapshot) {
if (gameObject instanceof Building) {
int snapshotLevel = playerSnapshot.getBuildingLevel((Building)gameObject);
return (snapshotLevel >= level);
}
throw new UnsupportedOperationException();
}
}
public interface GameObject {
Requirement[] getRequirements();
default boolean satisfiesRequirements(PlayerSnapshot playerSnapshot) {
for (Requirement requirement : getRequirements()) {
if (!requirement.isSatisfied(playerSnapshot)) {
return false;
}
}
return true;
}
}
public enum Research implements GameObject {
ENERGY_TECHNOLOGY(new Requirement(RESEARCH_LAB, 1)),
COMBUSTION_DRIVE(new Requirement(ENERGY_TECHNOLOGY, 1));
private Requirement[] requirements;
Research(Requirement... requirements) {
this.requirements = requirements;
}
@Override
public Requirement[] getRequirements() {
return requirements;
}
}
public enum Ship implements GameObject {
SMALL_CARGO(new Requirement(SHIPYARD, 2), new Requirement(COMBUSTION_DRIVE, 1));
private Requirement[] requirements;
Ship(Requirement... requirements) {
this.requirements = requirements;
}
@Override
public Requirement[] getRequirements() {
return requirements;
}
}
The code is not intended to work with the Research
and Ship
classes just yet, I have only included those classes such that the code is complete.
The code is also available in my GitHub project.