package game.controller; import game.libraries.general.*; import game.model.*; import game.model.mapsquares.*; import game.model.military.*; import game.view.output.*; import java.util.*; /** * Iterate on all squares or armies, look if two armies are in the same square. * Then, depending on who controls them and their orders, execute orders (fight, * spy, etc.). Also test if there is a city so that it can be taken... *
* For the moment, EncounterManager emits Strings through notifyObservers * which can be used as an output for the player, playtester, debugger... */ public class EncounterManager extends Observable implements Observer { public EncounterManager() { } public void newTick() { for (Iterator iterator = Coordinator.mapIterator(); iterator.hasNext();) { manageSquare((MapSquare)iterator.next()); } } public void manageSquare(MapSquare square) { int numberOfCivilizations = square.getNumberOfCivilizations(); if (numberOfCivilizations < 2) { return; } ArmyData.Encounter encounter = square.getEncounter(); if (encounter == null) { square.setControllingCiv(); } else { CombatReports.addEvent(square, encounter.tf0); CombatReports.addEvent(square, encounter.tf1); meet(encounter, square); CombatReports.addEvent(square, encounter.tf0); CombatReports.addEvent(square, encounter.tf1); } } private Civilization meet(ArmyData.Encounter encounter, MapSquare square) { // Check if either army is of the civ owning the square Army firstArmy = encounter.tf0.getTempTaskForce(); Army secondArmy = encounter.tf1.getTempTaskForce(); Civilization civ1 = firstArmy.getCiv(); Civilization civ2 = secondArmy.getCiv(); // Here some attempt at diplomacy, like declaring war Civilization civ3 = square.getControllingCiv(); // Now wage war: Take all units. Collection defenders = firstArmy.listUnits(); Collection attackers = secondArmy.listUnits(); // Terrain bonus = -1 if terrain owned by defender, +1 by attacker // (not possible yet), 0 if neutral. int terrainBonus = 0; if (civ1.equals(civ3)) // note civ3 can be null { defenders = secondArmy.listUnits(); attackers = firstArmy.listUnits(); terrainBonus = -1; } else if (civ2.equals(civ3)) terrainBonus = -1; Output.combat.println("---------------------------------"); Output.combat.print(" -- Beginning of combat --"); Output.combat.println("---------------------------------"); // SCOUTING phase: float scoutOutput = scout(attackers,defenders,square,terrainBonus); Output.combat.print("Scout output phase: " + scoutOutput); // MANOEUVER phase: float manoeuvreOutput = manoeuvre(attackers,defenders,square,scoutOutput); Output.combat.println("Manoeuver output phase: " + manoeuvreOutput); Output.combat.println(); //notifyObservers("End of manoeuvre: " + status(attackers, defenders)); // COMBAT phase: // If neither, check if either army is garrison/fortified // If not, simultaneous attacks. // compute odds for attacker and defender: // add naval power if defender is better (landings), but only on first round // avoid dividing by 0: float attackerPower = 0.01f; float defenderPower = 0.01f; Iterator attackerIterator = attackers.iterator(); Iterator defendersArmies = defenders.iterator(); // Land-sea not taken into account yet // Fire bonus is >0 for attackers, <0 for defenders: float fireBonus = (float) ((scoutOutput >0) ? Math.sqrt(scoutOutput) : - Math.sqrt(-scoutOutput) ); fireBonus += manoeuvreOutput; Output.combat.println("Beginning of combat: "); int NFight = 0; Civilization fightWinner = null; for (;NFight < 1;NFight++) { fightWinner = fight(attackers,defenders,fireBonus,true); } Output.combat.println("---------------------------------"); Output.combat.print(" -- End of combat --"); Output.combat.println("---------------------------------"); firstArmy.tidy(); secondArmy.tidy(); if (null != fightWinner) square.setControllingCiv(fightWinner); Output.log.printline(); Output.log.println("Outcome:"); Output.log.println(firstArmy); Output.log.println("and:"); Output.log.println(secondArmy); Output.log.println(); return fightWinner; } /** * This combat involves two groups of Armies. It could be simpler to change * the Collection into Armies (Task Forces) and let Army manage the code * @arg attackers Attacker armies. * @arg defenders Defender armies. * @arg fireBonus Bonus to firepower coming from previous phases. > 0 for * attacker, < 0 in favour of defender. * @arg goOnAfterFirstRound If true, the fight will go on till all armies * either die or retreat. IF false, the fight ends after one round. */ private Civilization fight(Collection attackers, Collection defenders, float fireBonus, boolean goOnAfterFirstRound) { if (attackers.isEmpty() || defenders.isEmpty()) { return null; } return assault(attackers,defenders,fireBonus,goOnAfterFirstRound); } /** * Assault according to Paul's model. * Returns winner civilization. */ private Civilization assault(Collection attackers, Collection defenders, float fireBonus, boolean goOnAfterFirstRound) { // Grouping of units: // Two cases, either I have a collection of elements (case of skirmish // combat) or collection of Armies with no isolated Unit (Units class) //Make sure we have only elements in the Collections (Element class) // List all armies involved: Output.combat.println("Converting to elements"); ArrayList elements1 = new ArrayList(); ArrayList elements2 = new ArrayList(); Iterator toTestElements = attackers.iterator(); Object anArmy = null; if (toTestElements.hasNext()) anArmy = toTestElements.next(); if (anArmy instanceof Element) { // Elements case (skirmish/scout) Iterator it1 = attackers.iterator(); while (it1.hasNext()) { Army next = (Army)it1.next(); Iterator it = next.listElementsForOrder(null); while (it.hasNext()) elements1.add(it.next()); } it1 = defenders.iterator(); while (it1.hasNext()) { Army next = (Army)it1.next(); Iterator it = next.listElementsForOrder(null); while (it.hasNext()) elements2.add(it.next()); } } // We can try with elements only in order not to suffer artefacts from // the order in which TF is built. else { // Case of units: Divide all TFs into Units instances: Iterator it1 = attackers.iterator(); while (it1.hasNext()) { Army next = (Army)it1.next(); elements1.addAll(next.listElements()); } it1 = defenders.iterator(); while (it1.hasNext()) { Army next = (Army)it1.next(); elements2.addAll(next.listElements()); } } //First place units in matched groupings. BattleMatching match = new BattleMatching(elements1,elements2); match.addObserver(this); //Divide fire power match.fireSupport(); //Range? // Range not yet taken into account. Always start hand-to-hand. // Resolution cycles do {} while (match.combat(fireBonus) && goOnAfterFirstRound) ; // Notify only if main fight, not scout/skirmish if (goOnAfterFirstRound) notifyObservers("Fight won by " + match.winner()); match.tidyTempTaskForces(); Iterator toTidy = attackers.iterator(); while (toTidy.hasNext()) { Army next = (Army)toTidy.next(); next.tidy(); } toTidy = defenders.iterator(); while (toTidy.hasNext()) { Army next = (Army)toTidy.next(); next.tidy(); } return match.winner(); } /** * Returns higher number if army1 has a better result than army2. * TerrainBonus is -1 if terrain is owned by defender, 1 by attacker (?) * The result is a %age. If > 0, it is advantage for army1, * if < 0, it is advantage for army2. */ private float scout(Collection army1,Collection army2, MapSquare square,int terrainBonus) { if (army1.isEmpty() || army2.isEmpty()) { Output.combat.println("Scout but no armies here"); return 0f; } ScoutOrder order = ScoutOrder.ORDER; // Give order - find scouting units: int number1 = 0; int number2 = 0; Iterator iterate = army1.iterator(); Iterator scouts = null; ArrayList tmp = new ArrayList(); ArrayList tmp2 = new ArrayList(); while (iterate.hasNext()) { Army next = (Army)iterate.next(); number1 += next.getNumberOfElements(); scouts = next.listElementsForOrder(order); while (scouts.hasNext()) tmp.add(scouts.next()); } iterate = army2.iterator(); while (iterate.hasNext()) { Army next = (Army)iterate.next(); number2 += next.getNumberOfElements(); scouts = next.listElementsForOrder(order); while (scouts.hasNext()) tmp2.add(scouts.next()); } // Now we know all scouts: Compute the scouting strengths. float scoutStrength1 = computeScoutStrength(tmp) + number1 + number2/2; float scoutStrength2 = computeScoutStrength(tmp2) + number2 + number1/2; // Apply diminishing returns: sqrt scoutStrength1 = (float)Math.sqrt(scoutStrength1); scoutStrength2 = (float)Math.sqrt(scoutStrength2); float ownership = 15f; if (terrainBonus > 0) scoutStrength1 += ownership; else if (terrainBonus < 0) scoutStrength2 += ownership; return (scoutStrength1 - scoutStrength2); } /** * Computes the scouting strength of scout units: mobility + health. */ private float computeScoutStrength(Collection scouts) { float returnValue = 0; Iterator elements = scouts.iterator(); while (elements.hasNext()) { Army next = (Army)elements.next(); returnValue += (next.getMobility() + next.getHealth()); // Later on, add tech and experience and comm tech factors } return returnValue; } /** * Manoeuvre phase. Returns positive result if attackers outmanoeuvre * defenders. */ private float manoeuvre(Collection army1,Collection army2,MapSquare square,float scoutOutput) { if (army1.isEmpty() || army2.isEmpty()) { Output.combat.println("Manoeuvre but no armies here"); return 0f; } SkirmishOrder order = SkirmishOrder.ORDER; // Give order - find scouting units: int number1 = 0; int number2 = 0; Iterator iterate = army1.iterator(); Iterator scouts = null; ArrayList tmp = new ArrayList(); ArrayList tmp2 = new ArrayList(); while (iterate.hasNext()) { Army next = (Army)iterate.next(); number1 += next.getNumberOfElements(); scouts = next.listElementsForOrder(order); while (scouts.hasNext()) tmp.add(scouts.next()); } iterate = army2.iterator(); while (iterate.hasNext()) { Army next = (Army)iterate.next(); number2 += next.getNumberOfElements(); scouts = next.listElementsForOrder(order); while (scouts.hasNext()) tmp2.add(scouts.next()); } // fights between skirmishers happen here // Note that scoutOutput is not square-rooted here. int roundsOfFight = numberOfManoeuvreRounds(); //Output.combat.println("Skirmishing between " + tmp.size() + " and " + tmp2.size() + " elements. " + roundsOfFight + " rounds of fight"); // Not sure anymore that I can use as is. int i = 1; for (; i <= roundsOfFight; i++) { fight(tmp,tmp2,(scoutOutput/100),false); // Benefits like fortification by engineers should go here. } // Next we compute manoeuvreoutput; // Terrain not taken into account yet float cover1 = (float)Math.sqrt(manoeuvreValue(tmp,army1,scoutOutput)); float cover2 = (float)Math.sqrt(manoeuvreValue(tmp2,army2,-scoutOutput)); float result = (cover1 - cover2); //Output.combat.println("Manoeuvre output = " + result); return result; } // Externalize computation of number of rounds of fight in manoeuvre phase. // Will be easier to change the compute method if needed, but less efficient // than including it in the same loop as skirmish fights above private int numberOfManoeuvreRounds() { // 1 round, 30% ends, then 40 % etc: int result = 1; float chance = 0.3f; float increment = 0.1f; while (Rand.nextFloat() > chance) { result++; chance += increment; } return result; } /** * Returns manoeuvering bonus for one army with skirmishers left and * wholeArmy units, with scoutOutput the scout phase output changed so * that if > 0 it is helpful for this particular army. */ private float manoeuvreValue(Collection skirmishers,Collection wholeArmy,float scoutOutput) { float result = 0f; Iterator skirmishIterator = skirmishers.iterator(); while (skirmishIterator.hasNext()) { Army next = (Army)skirmishIterator.next(); result += next.getMobility()/2; // - terrain factor } Iterator armies = wholeArmy.iterator(); while (armies.hasNext()) { Army next = (Army)armies.next(); result += 2 * next.getNumberOfElements(); } // result = (float)Math.sqrt(result); seems Krenske's example and // formula don't place sqrt at the same place result *= (1+scoutOutput/100); // * generalship power numberRounds return result; } /** * Notifies observers AND displays data on game.view.IO */ public void notifyObservers(Object data) { setChanged(); super.notifyObservers(data); Output.combat.print(data.toString()); } public void update(Observable observed, Object message) { notifyObservers(message); } private void status(Collection list1, Collection list2) { Output.combat.println("Status:"); Output.combat.print("First army:", list1); Output.combat.print("Second army:", list2); Output.combat.println(); } }