/*******************************************************************************
* Copyright (c) 2016 - 2017
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*******************************************************************************/
package jsettlers.ai.army;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import jsettlers.ai.highlevel.AiStatistics;
import jsettlers.common.buildings.EBuildingType;
import jsettlers.common.material.EMaterialType;
import jsettlers.common.movable.EMovableType;
import jsettlers.common.movable.ESoldierType;
import jsettlers.common.position.ShortPoint2D;
import jsettlers.graphics.action.SetMaterialProductionAction.EMaterialProductionType;
import jsettlers.input.tasks.MoveToGuiTask;
import jsettlers.input.tasks.SetMaterialProductionGuiTask;
import jsettlers.input.tasks.UpgradeSoldiersGuiTask;
import jsettlers.logic.map.grid.movable.MovableGrid;
import jsettlers.logic.player.Player;
import jsettlers.network.client.interfaces.ITaskScheduler;
/**
* This general is named winner because his attacks and defence should be very hard for human enemies. This should be realized by creating locally
* superiority. (You can kill 200 bowmen with just 100 bowmen if you fight 100 vs 20 in loops. This general should lay the focus on some swordsmen to
* occupy own towers, 20 spearmen to defeat rushes and the rest only bowmen because in mass this is the strongest military unit. It upgrades bowmen
* first because this is the main unit and the 20 defeating spearmen defeats with lv1 as well. This general should store bows until level3 is reached
* to get as many level3 bowmen as posibble. TODO: store bows until level3 is reached TODO: group soldiers in direction of enemy groups to defeat them
* TODO: group soldiers in direction of enemy groups to attack them
*
* @author codingberlin
*/
public class ConfigurableGeneral implements ArmyGeneral {
private static final byte MIN_ATTACKER_COUNT = 20;
private static final byte MIN_SWORDSMEN_COUNT = 10;
private static final byte MIN_PIKEMEN_COUNT = 20;
private static final int BOWMEN_COUNT_OF_KILLING_INFANTRY = 300;
private static final EBuildingType[] MIN_BUILDING_REQUIREMENTS_FOR_ATTACK =
{EBuildingType.COALMINE, EBuildingType.IRONMINE, EBuildingType.IRONMELT, EBuildingType.WEAPONSMITH, EBuildingType.BARRACK};
private final AiStatistics aiStatistics;
private final Player player;
private final ITaskScheduler taskScheduler;
private final MovableGrid movableGrid;
private float attackerCountFactor;
public ConfigurableGeneral(AiStatistics aiStatistics, Player player, MovableGrid movableGrid, ITaskScheduler taskScheduler, float
attackerCountFactor) {
this.aiStatistics = aiStatistics;
this.player = player;
this.taskScheduler = taskScheduler;
this.movableGrid = movableGrid;
this.attackerCountFactor = attackerCountFactor;
}
@Override
public void commandTroops(Set<Integer> soldiersWithOrders) {
Situation situation = calculateSituation(player.playerId);
if (aiStatistics.getEnemiesInTownOf(player.playerId).size() > 0) {
defend(situation, soldiersWithOrders);
} else if (enemiesAreAlive()) {
byte weakestEnemyId = getWeakestEnemy();
Situation enemySituation = calculateSituation(weakestEnemyId);
boolean infantryWouldDie = wouldInfantryDie(enemySituation);
if (attackIsPossible(situation, enemySituation, infantryWouldDie)) {
attack(situation, infantryWouldDie, soldiersWithOrders);
}
}
}
private boolean attackIsPossible(Situation situation, Situation enemySituation, boolean infantryWouldDie) {
for (EBuildingType requiredType : MIN_BUILDING_REQUIREMENTS_FOR_ATTACK) {
if (aiStatistics.getNumberOfBuildingTypeForPlayer(requiredType, player.playerId) < 1) {
return false;
}
}
float combatStrength = 1F; //TODO: use when storages for gold are working: player.getCombatStrengthInformation().getCombatStrength(false);
float effectiveAttackerCount;
if (infantryWouldDie) {
effectiveAttackerCount = situation.bowmenPositions.size() * combatStrength;
} else {
effectiveAttackerCount = situation.soldiersCount() * combatStrength;
}
return effectiveAttackerCount >= MIN_ATTACKER_COUNT && effectiveAttackerCount * attackerCountFactor > enemySituation.soldiersCount();
}
private boolean wouldInfantryDie(Situation enemySituation) {
return enemySituation.bowmenPositions.size() > BOWMEN_COUNT_OF_KILLING_INFANTRY;
}
private boolean enemiesAreAlive() {
for (byte enemyId : aiStatistics.getEnemiesOf(player.playerId)) {
if (aiStatistics.isAlive(enemyId)) {
return true;
}
}
return false;
}
@Override
public void levyUnits() {
if (!upgradeSoldiers(ESoldierType.BOWMAN))
if (!upgradeSoldiers(ESoldierType.PIKEMAN))
upgradeSoldiers(ESoldierType.SWORDSMAN);
int missingSwordsmenCount = Math.max(0, MIN_SWORDSMEN_COUNT
- aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.SWORDSMAN_L1, player.playerId).size()
- aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.SWORDSMAN_L2, player.playerId).size()
- aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.SWORDSMAN_L3, player.playerId).size());
int missingSpearmenCount = Math.max(0, MIN_PIKEMEN_COUNT
- aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.PIKEMAN_L1, player.playerId).size()
- aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.PIKEMAN_L2, player.playerId).size()
- aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.PIKEMAN_L3, player.playerId).size());
int bowmenCount = aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.BOWMAN_L1, player.playerId).size()
+ aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.BOWMAN_L2, player.playerId).size()
+ aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.BOWMAN_L3, player.playerId).size();
if (missingSwordsmenCount > 0) {
setNumberOfFutureProducedMaterial(player.playerId, EMaterialType.SWORD, missingSwordsmenCount);
setNumberOfFutureProducedMaterial(player.playerId, EMaterialType.SPEAR, 0);
setNumberOfFutureProducedMaterial(player.playerId, EMaterialType.BOW, 0);
setRatioOfMaterial(player.playerId, EMaterialType.SWORD, 0F);
setRatioOfMaterial(player.playerId, EMaterialType.SPEAR, 1F);
setRatioOfMaterial(player.playerId, EMaterialType.BOW, 0F);
} else if (missingSpearmenCount > 0) {
setNumberOfFutureProducedMaterial(player.playerId, EMaterialType.SWORD, 0);
setNumberOfFutureProducedMaterial(player.playerId, EMaterialType.SPEAR, missingSpearmenCount);
setNumberOfFutureProducedMaterial(player.playerId, EMaterialType.BOW, 0);
setRatioOfMaterial(player.playerId, EMaterialType.SWORD, 0F);
setRatioOfMaterial(player.playerId, EMaterialType.SPEAR, 0.3F);
setRatioOfMaterial(player.playerId, EMaterialType.BOW, 1F);
} else if (bowmenCount * player.getCombatStrengthInformation().getCombatStrength(false) < BOWMEN_COUNT_OF_KILLING_INFANTRY){
setNumberOfFutureProducedMaterial(player.playerId, EMaterialType.SWORD, 0);
setNumberOfFutureProducedMaterial(player.playerId, EMaterialType.SPEAR, 0);
setNumberOfFutureProducedMaterial(player.playerId, EMaterialType.BOW, 0);
setRatioOfMaterial(player.playerId, EMaterialType.SWORD, 0F);
setRatioOfMaterial(player.playerId, EMaterialType.SPEAR, 0.3F);
setRatioOfMaterial(player.playerId, EMaterialType.BOW, 1F);
} else {
setNumberOfFutureProducedMaterial(player.playerId, EMaterialType.SWORD, 0);
setNumberOfFutureProducedMaterial(player.playerId, EMaterialType.SPEAR, 0);
setNumberOfFutureProducedMaterial(player.playerId, EMaterialType.BOW, 0);
setRatioOfMaterial(player.playerId, EMaterialType.SWORD, 0F);
setRatioOfMaterial(player.playerId, EMaterialType.SPEAR, 0F);
setRatioOfMaterial(player.playerId, EMaterialType.BOW, 1F);
}
}
private void setNumberOfFutureProducedMaterial(byte playerId, EMaterialType materialType, int numberToProduce) {
if (aiStatistics.getMaterialProduction(playerId).getAbsoluteProductionRequest(materialType) != numberToProduce) {
taskScheduler.scheduleTask(new SetMaterialProductionGuiTask(playerId, aiStatistics.getPositionOfPartition(playerId), materialType,
EMaterialProductionType.SET_PRODUCTION, numberToProduce));
}
}
private void setRatioOfMaterial(byte playerId, EMaterialType materialType, float ratio) {
if (aiStatistics.getMaterialProduction(playerId).getRelativeProductionRequest(materialType) != ratio) {
taskScheduler.scheduleTask(new SetMaterialProductionGuiTask(playerId, aiStatistics.getPositionOfPartition(playerId), materialType,
EMaterialProductionType.SET_RATIO, ratio));
}
}
private boolean upgradeSoldiers(ESoldierType type) {
if (player.getMannaInformation().isUpgradePossible(type)) {
taskScheduler.scheduleTask(new UpgradeSoldiersGuiTask(player.playerId, type));
return true;
}
return false;
}
private void defend(Situation situation, Set<Integer> soldiersWithOrders) {
List<ShortPoint2D> allMyTroops = new Vector<ShortPoint2D>();
allMyTroops.addAll(situation.bowmenPositions);
allMyTroops.addAll(situation.pikemenPositions);
allMyTroops.addAll(situation.swordsmenPositions);
sendTroopsTo(allMyTroops, aiStatistics.getEnemiesInTownOf(player.playerId).iterator().next(), soldiersWithOrders);
}
private void attack(Situation situation, boolean infantryWouldDie, Set<Integer> soldiersWithOrders) {
byte enemyId = getWeakestEnemy();
ShortPoint2D targetDoor = getTargetEnemyDoorToAttack(enemyId);
if (infantryWouldDie) {
sendTroopsTo(situation.bowmenPositions, targetDoor, soldiersWithOrders);
} else {
List<ShortPoint2D> soldiers = new ArrayList<>(situation.bowmenPositions.size() + situation.pikemenPositions.size() + situation
.swordsmenPositions.size());
soldiers.addAll(situation.bowmenPositions);
soldiers.addAll(situation.pikemenPositions);
soldiers.addAll(situation.swordsmenPositions);
sendTroopsTo(soldiers, targetDoor, soldiersWithOrders);
}
}
private byte getWeakestEnemy() {
byte weakestEnemyId = 0;
int minAmountOfEnemyId = Integer.MAX_VALUE;
for (byte enemyId : aiStatistics.getEnemiesOf(player.playerId)) {
if (aiStatistics.isAlive(enemyId)) {
int amountOfEnemyTroops = aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.BOWMAN_L1, enemyId).size();
amountOfEnemyTroops += aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.BOWMAN_L2, enemyId).size();
amountOfEnemyTroops += aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.BOWMAN_L3, enemyId).size();
amountOfEnemyTroops += aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.PIKEMAN_L1, enemyId).size();
amountOfEnemyTroops += aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.PIKEMAN_L2, enemyId).size();
amountOfEnemyTroops += aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.PIKEMAN_L3, enemyId).size();
amountOfEnemyTroops += aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.SWORDSMAN_L1, enemyId).size();
amountOfEnemyTroops += aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.SWORDSMAN_L2, enemyId).size();
amountOfEnemyTroops += aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.SWORDSMAN_L3, enemyId).size();
if (amountOfEnemyTroops < minAmountOfEnemyId) {
minAmountOfEnemyId = amountOfEnemyTroops;
weakestEnemyId = enemyId;
}
}
}
return weakestEnemyId;
}
private void sendTroopsTo(List<ShortPoint2D> attackerPositions, ShortPoint2D target, Set<Integer> soldiersWithOrders) {
List<Integer> attackerIds = new Vector<Integer>();
for (ShortPoint2D attackerPosition : attackerPositions) {
int movableId = movableGrid.getMovableAt(attackerPosition.x, attackerPosition.y).getID();
if (!soldiersWithOrders.contains(movableId)) {
attackerIds.add(movableId);
}
}
taskScheduler.scheduleTask(new MoveToGuiTask(player.playerId, target, attackerIds));
}
private ShortPoint2D getTargetEnemyDoorToAttack(byte enemyToAttackId) {
List<ShortPoint2D> myMilitaryBuildings = aiStatistics.getBuildingPositionsOfTypesForPlayer(EBuildingType.getMilitaryBuildings(),
player.playerId);
ShortPoint2D myBaseAveragePoint = aiStatistics.calculateAveragePointFromList(myMilitaryBuildings);
List<ShortPoint2D> enemyMilitaryBuildings = aiStatistics.getBuildingPositionsOfTypesForPlayer(EBuildingType.getMilitaryBuildings(),
enemyToAttackId);
return aiStatistics.getBuildingAt(AiStatistics.detectNearestPointFromList(myBaseAveragePoint, enemyMilitaryBuildings)).getDoor();
}
private Situation calculateSituation(byte playerId) {
Situation situation = new Situation();
situation.swordsmenPositions.addAll(aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.SWORDSMAN_L1, playerId));
situation.swordsmenPositions.addAll(aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.SWORDSMAN_L2, playerId));
situation.swordsmenPositions.addAll(aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.SWORDSMAN_L3, playerId));
situation.bowmenPositions.addAll(aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.BOWMAN_L1, playerId));
situation.bowmenPositions.addAll(aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.BOWMAN_L2, playerId));
situation.bowmenPositions.addAll(aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.BOWMAN_L3, playerId));
situation.pikemenPositions.addAll(aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.PIKEMAN_L1, playerId));
situation.pikemenPositions.addAll(aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.PIKEMAN_L2, playerId));
situation.pikemenPositions.addAll(aiStatistics.getMovablePositionsByTypeForPlayer(EMovableType.PIKEMAN_L3, playerId));
return situation;
}
private static class Situation {
private final List<ShortPoint2D> swordsmenPositions = new Vector<ShortPoint2D>();
private final List<ShortPoint2D> bowmenPositions = new Vector<ShortPoint2D>();
private final List<ShortPoint2D> pikemenPositions = new Vector<ShortPoint2D>();
public int soldiersCount() {
return swordsmenPositions.size() + bowmenPositions.size() + pikemenPositions.size();
}
}
@Override
public String toString() {
return this.getClass().getName();
}
}