package com.drakulo.games.ais.core;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.drakulo.games.ais.core.audio.SoundHelper;
import com.drakulo.games.ais.core.building.Building;
import com.drakulo.games.ais.core.building.BuildingHelper;
import com.drakulo.games.ais.core.building.BuildingType;
import com.drakulo.games.ais.core.delayed.BuildingAction;
import com.drakulo.games.ais.core.delayed.HexagonMapAction;
import com.drakulo.games.ais.core.delayed.PreparationAction;
import com.drakulo.games.ais.core.delayed.ResearchAction;
import com.drakulo.games.ais.core.delayed.ColonyAction;
import com.drakulo.games.ais.ui.TileHelper;
/**
* <h1>The game engine</h1>
* <p>
* The game engine is the game's core. It handle all game logic. A call to the
* run method run will handle a loop of all game logic. Time between calls must
* be handled out of the engine.
* </p>
*
* @author Drakulo
*
*/
public final class GameEngine {
/** Time since last update to perform a call to the game engine each second */
private static int timeSinceGameUpdate = 0;
/**
* private constructor to force static use
*/
private GameEngine() {
// Nothing there
}
/**
* This method lunches the game logic. Called once per second. Handle heavy
* logic
*/
public static void run(int delta) {
// First, handle the colony pool
Colony colony = null;
do {
colony = GameData.popColonyFromPool();
if (colony != null) {
GameData.addColony(colony);
}
} while (colony != null);
// A call to the game logic must be done only once per
// second.
if (timeSinceGameUpdate >= 1000) {
timeSinceGameUpdate = 0;
List<Colony> colonies = GameData.getColonies();
// System.out.println("Colonies to update : " + colonies.size());
for (Colony c : colonies) {
// System.out.println("Update for colony : " + c.getName());
// Construction / update process
buildingProcess(c);
// Robots progress
robotProcess(c);
// Special actions process
specialActionProcess(c);
// Production process
productionProcess(c);
// Research progression
researchProcess(c);
// 3. Diplomacy TODO
// 4. Battles TODO
}
// Exploration actions
explorationActionsProcess();
} else {
// Wait for next second
timeSinceGameUpdate += delta;
}
}
/**
* This method handle the robot progress
*/
private static void explorationActionsProcess() {
List<HexagonMapAction> actions = GameData.getHexagonMapActions();
List<HexagonMapAction> actionsToRemove = new ArrayList<HexagonMapAction>();
for (HexagonMapAction action : actions) {
action.step();
if (action.isDone()) {
// The action is done so we have to remove it from the list.
actionsToRemove.add(action);
}
}
// All actions were processed. If there is some finished actions, we
// remove them from the action list
if (!actionsToRemove.isEmpty()) {
for (HexagonMapAction action : actionsToRemove) {
actions.remove(action);
action.runCallback();
}
}
}
/**
* This method handle the robot progress
*/
private static void specialActionProcess(Colony c) {
List<ColonyAction> actions = c.getSpecialActions();
List<ColonyAction> actionsToRemove = new ArrayList<ColonyAction>();
for (ColonyAction action : actions) {
action.step();
if (action.isDone()) {
// The action is done so we have to remove it from the list.
actionsToRemove.add(action);
}
}
// All actions were processed. If there is some finished actions, we
// remove them from the action list
if (!actionsToRemove.isEmpty()) {
for (ColonyAction action : actionsToRemove) {
actions.remove(action);
action.runCallback();
}
}
}
/**
* This method handle the robot progress
*/
private static void robotProcess(Colony c) {
List<PreparationAction> actions = c.getPreparationActions();
List<PreparationAction> actionsToRemove = new ArrayList<PreparationAction>();
for (PreparationAction action : actions) {
action.step();
if (action.isDone()) {
// The action is done so we have to remove it from the list.
actionsToRemove.add(action);
// The preparation is done. The tile have to be modified
TileHelper.setTile(action.getX(), action.getY(),
TileHelper.BUILDABLE_TILE, c);
c.releaseRobots(1);
}
}
// All actions were processed. If there is some finished actions, we
// remove them from the action list
if (!actionsToRemove.isEmpty()) {
for (PreparationAction action : actionsToRemove) {
actions.remove(action);
action.runCallback();
}
}
}
/**
* This method handle research steps
*/
private static void researchProcess(Colony c) {
ResearchAction research = c.getResearch();
// If the research is null, that's because the player didn't started a
// research. In that case, there is nothing to do.
if (research != null) {
research.step();
if (research.isDone()) {
// The research is done.
research.getTechnology().setOwned(true);
c.setResearch(null);
}
}
}
/**
* This method handles building construction and update
*/
private static void buildingProcess(Colony c) {
List<BuildingAction> actions = c.getBuildingActions();
List<BuildingAction> actionsToRemove = new ArrayList<BuildingAction>();
for (BuildingAction action : actions) {
// For each action, we make a step
action.step();
if (action.isDone()) {
// The action is done so we have to remove it from the list.
actionsToRemove.add(action);
// Robots associated to the construction / update have to be
// released
c.releaseRobots(action.getRobotsUsed());
// We have to update the building list in the game data.
Building building = action.getBuilding();
if (action.isUpgrade()) {
building.upgrade();// Uprade
building.setUpgrading(false); // The upgrading is done
} else {
// If the created building is unique, a flag must be set
if (BuildingType.RESEARCH_CENTER.equals(building.getType())) {
c.setResearchCenterBuilt(true);
}
building.setUnderConstruction(false);
// Creation
c.addBuilding(building);
}
SoundHelper.playSound(SoundHelper.BUILDING_END);
}
}
// All actions were processed. If there is some finished actions, we
// remove them from the action list
if (!actionsToRemove.isEmpty()) {
for (BuildingAction action : actionsToRemove) {
actions.remove(action);
action.runCallback();
}
}
}
/**
* Runs the production / consumption process
*/
private static void productionProcess(Colony c) {
// First we get the global modifiers : production - consumption
Map<Resource, BigDecimal> modifiers = analyzeModifiers(c);
if (hasNegativeModifiers(modifiers)) {
if (storeCanAbsorbLoss(c, modifiers)) {
// There is enough resources in store to absorb the production
// loss. Resource amount in store will decrease
applyModifiers(c, modifiers);
} else {
// There is not enough resources in store to absorb the loss. We
// have a problem : not all buildings can run but some may.
chooseAndRunBuildings();
}
} else {
// There is only production, we can simply apply it
applyModifiers(c, modifiers);
}
}
/**
* Analyses the buildings production and consumption and stock it in a map.
* Key : the resource. Value : the production modifier
*
* @return a map of modifiers (production - consumption)
*/
private static Map<Resource, BigDecimal> analyzeModifiers(Colony c) {
Resource[] resources = Resource.values();
Map<Resource, BigDecimal> analysis = new HashMap<Resource, BigDecimal>();
BigDecimal value;
for (Resource r : resources) {
analysis.put(r, BigDecimal.ZERO);
}
// For each building we will retreive production and consumption
List<Building> buildings = c.getBuildings();
if (!buildings.isEmpty()) {
int i = 0;
i++;
}
for (Building b : buildings) {
Map<Resource, BigDecimal> prod = BuildingHelper.getProductionOf(b);
Map<Resource, BigDecimal> cons = BuildingHelper.getConsumptionOf(b);
for (Resource r : resources) {
// Production
value = prod.get(r).divide(BigDecimal.valueOf(60));
if (value != null) {
analysis.put(r, analysis.get(r).add(value));
}
// Consumption
value = cons.get(r).divide(BigDecimal.valueOf(60));
if (value != null) {
analysis.put(r, analysis.get(r).subtract(value));
}
}
}
return analysis;
}
/**
* Tests if there is a negative value in the modifiers map. That's a simple
* search of a negative value.
*
* @param modifiers
* the modifiers
* @return true if there is a negative value
*/
private static boolean hasNegativeModifiers(
Map<Resource, BigDecimal> modifiers) {
Set<Resource> resources = modifiers.keySet();
BigDecimal value;
for (Resource r : resources) {
value = modifiers.get(r);
if (value != null && value.signum() == -1) {
return true;
}
}
return false;
}
/**
* This method takes modifiers and apply them to the store. The store has
* space limits according to buildings specs. If the previous store amount
* with the modifiers exeeds the max storable space, then the production
* will be capped to the max storable value.
*
* @param modifiers
* The modifiers map
*/
private static void applyModifiers(Colony c,
Map<Resource, BigDecimal> modifiers) {
Set<Resource> resources = modifiers.keySet();
Map<Resource, BigDecimal> storageSpace = getStoreSpace(c);
BigDecimal maxSpace;
BigDecimal amount;
BigDecimal currentStock;
for (Resource r : resources) {
maxSpace = storageSpace.get(r);
amount = modifiers.get(r);
currentStock = c.getResource(r);
if (NumberHelper.infEq(maxSpace, currentStock.add(amount))) {
// Tere is no more space in store for this resource, so we cap
// it to the max storable. However, if the command center is not
// built, it means that the player is currently colonizing the
// sector
if (c.isCommandCenterBuilt()) {
c.setResource(r, maxSpace);
} else {
// The command center is not built yet. Resources are not
// modified
}
} else {
// Enough space for the amount produced
c.updateResource(r, amount);
}
c.setCurrentModifier(r, amount);
}
}
/**
* Tests if there is enough resources in store to absorb the given modifiers
* loss
*
* @param c
* the colony
* @param modifiers
* the modifiers map
* @return true if there is enough resources in store to absorb the loss
*/
private static boolean storeCanAbsorbLoss(Colony c,
Map<Resource, BigDecimal> modifiers) {
Resource[] resources = Resource.values();
BigDecimal storeValue;
BigDecimal modifierValue;
for (Resource r : resources) {
storeValue = c.getResource(r);
modifierValue = modifiers.get(r);
if (NumberHelper.inf(storeValue, modifierValue)) {
// Store value is lesser than the modifier. Absorption is not
// possible
return false;
}
}
return true;
}
/**
* This method has to choose wich buildings will run and wich will not. The
* choices will be done according different paramaters
*/
private static void chooseAndRunBuildings() {
// The selection is made by
// TODO
}
/**
* Calculate the total amount of store space provided by the buildings
*
* @param colony
* The colony
* @return a map describing the store space available for each resource
*/
private static Map<Resource, BigDecimal> getStoreSpace(Colony c) {
Map<Resource, BigDecimal> storeSpace = new HashMap<Resource, BigDecimal>();
Resource[] resources = Resource.values();
// Initialize of storeSpace map
for (Resource r : resources) {
storeSpace.put(r, BigDecimal.ZERO);
}
BigDecimal space;
List<Building> buildings = c.getBuildings();
// For each building we'll check the space provided for each resource
for (Building b : buildings) {
for (Resource r : resources) {
space = b.getStoreSpaceFor(r);
storeSpace.put(r, storeSpace.get(r).add(space));
}
}
return storeSpace;
}
}