/**
* Copyright (C) 2013 Gundog Studios LLC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.godsandtowers.sprites;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import com.godsandtowers.core.PlayerStats;
import com.godsandtowers.core.grid.Grid;
import com.godsandtowers.core.grid.GridSquare;
import com.godsandtowers.messaging.LogicMessageProcessor;
import com.gundogstudios.modules.Modules;
import com.gundogstudios.util.FastMath;
public class AIPlayer extends Player {
private static final int LOST_LIFE_DELAY = 20000;
private static final int MOVE_DELAY = 500;
private boolean defends;
private boolean attacks;
private float aggressiveness;
private ArrayList<GridSquare> buildOrder;
private int moveDelay;
private float lastTurnsLife;
private long lostLifeDelay;
// this is ONLY for testing, it uses the arg2 of a message handler to store which game engine it is calling
// this is a complete hack and will be removed when use of googles message handling service is taken out of the code
private int gameID = 0;
public void setGameID(int id) {
this.gameID = id;
}
public AIPlayer() {
}
public AIPlayer(int id, PlayerStats playerStats, int races, Grid grid, boolean defends, boolean attacks) {
this(id, playerStats, races, grid, defends, attacks, null);
}
public AIPlayer(int id, PlayerStats playerStats, int races, Grid grid, boolean defends, boolean attacks,
ArrayList<GridSquare> buildOrder) {
super(id, playerStats, races, grid);
this.defends = defends;
this.attacks = attacks;
this.aggressiveness = FastMath.random() + .5f;
this.buildOrder = buildOrder;
if (defends && buildOrder == null) {
long start = System.currentTimeMillis();
this.buildOrder = super.grid.calculateBuildSquares();
Modules.LOG.info("AIPlayer", "TOOK " + (System.currentTimeMillis() - start) + " to calculate build order");
}
spendXP();
setCostUpgradeThreshold();
lastTurnsLife = super.getLife();
lostLifeDelay = 0;
}
private void spendXP() {
// TODO account for race experience
long xp = playerStats.getTotalXP();
xp -= purchase(playerStats.getBasePlayer(), xp / 2);
xp *= Races.ALL_RACES.length / 2; // # of races with the experience
xp -= purchase(playerStats.getBaseRace(), xp / 3);
Collection<BaseTower> towers = super.getBaseTowers();
if (defends) {
long spendXP = xp / towers.size() / (attacks ? 2 : 1);
for (BaseTower tower : towers) {
tower.setUnlocked(true);
xp -= purchase(tower, spendXP);
}
}
if (attacks) {
Collection<BaseCreature> creatures = super.getBaseCreatures();
long spendXP = xp / creatures.size();
for (BaseCreature creature : creatures) {
creature.setUnlocked(true);
purchase(creature, spendXP);
}
}
}
public void setCostUpgradeThreshold() {
float maxCost = 0;
for (BaseCreature creature : creatures.values()) {
float cost = creature.getCost();
if (cost > maxCost)
maxCost = cost;
}
super.costUpgradeThreshold = maxCost * COST_VS_INCOME_UPGRADE_FACTOR;
}
private long purchase(Upgradeable upgradable, long xp) {
long spentXP = 0;
int[] ids = upgradable.getUpgradeIDs();
long perID = xp / ids.length;
for (int id : ids) {
long cost = upgradable.getUpgradeCost(id);
long currXP = perID;
while (cost > 0 && currXP >= cost) {
currXP -= cost;
spentXP += cost;
upgradable.upgrade(id);
cost = upgradable.getUpgradeCost(id);
}
}
return spentXP;
}
@Override
public void nextTurn(int timePassed, int timeBetweenWaves) {
super.nextTurn(timePassed, timeBetweenWaves);
moveDelay -= timePassed;
if (moveDelay <= 0) {
if (calculateMoves(timePassed))
moveDelay += MOVE_DELAY;
else
moveDelay = 0;
}
}
public void increaseIncome(float amount) {
if (defends) {
super.increaseIncome(amount / 2);
}
}
public void addIncome(int currentWave) {
if (!defends) {
super.income += ((float) currentWave) / 10f;
}
super.addIncome(currentWave);
}
public boolean calculateMoves(int timePassed) {
if (defends && attacks) {
if (lastTurnsLife != super.getLife()) {
lostLifeDelay += LOST_LIFE_DELAY;
}
lastTurnsLife = super.getLife();
lostLifeDelay -= timePassed;
if (lostLifeDelay <= 0) {
lostLifeDelay = 0;
} else {
return buyOrUpgrade(timePassed);
}
float creaturePower = calculateCreaturePower();
float towerPower = calculateTowerPower() * aggressiveness;
// only do one thing a turn to make the computer more "human"
if (creaturePower > towerPower) {
return buyOrUpgrade(timePassed);
} else {
return buyCreature(timePassed);
}
} else if (attacks) {
return buyRandomCreature(timePassed);
} else if (defends) {
return buyOrUpgrade(timePassed);
} else {
return false;
}
}
private boolean buyOrUpgrade(long timePassed) {
float remainingGold = gold;
BaseTower[] sorted = super.getSortedBaseTowers();
boolean successful = buyTower(sorted, remainingGold);
if (!successful)
successful = upgradeTower(remainingGold);
return successful;
}
private float calculateCreaturePower() {
float creaturePower = 0f;
for (Creature creature : super.grid.getCreatures()) {
creaturePower += creature.getPower();
}
return creaturePower;
}
private float calculateTowerPower() {
float towerPower = 0f;
for (Tower tower : super.grid.getTowers()) {
towerPower += tower.getPower();
}
return towerPower;
}
private boolean upgradeTower(float remainingGold) {
Collection<Tower> towers = grid.getTowers();
int num = FastMath.floor(FastMath.random() * towers.size());
Iterator<Tower> iter = towers.iterator();
Tower oldTower = null;
while (num-- > 0)
oldTower = iter.next();
if (oldTower == null)
return false;
if (oldTower.getCost() <= remainingGold) {
Modules.MESSENGER.submit(LogicMessageProcessor.ID, LogicMessageProcessor.UPGRADE_TOWER, id,
oldTower.getX(), oldTower.getY(), gameID);
return true;
}
return false;
}
private boolean buyTower(BaseTower[] sorted, float remainingGold) {
for (GridSquare square : buildOrder) {
if (!square.canPlaceTower()) {
continue;
}
int num = FastMath.floor(FastMath.random() * sorted.length);
BaseTower tower = sorted[num];
if (tower.getCost() <= remainingGold) {
Modules.MESSENGER.submit(LogicMessageProcessor.ID, LogicMessageProcessor.BUY_TOWER, id,
tower.getName(), square.getX(), square.getY(), gameID);
return true;
}
return false;
}
return false;
}
private boolean buyRandomCreature(long timePassed) {
float lastGoldAmount = 0;
float remainingGold = gold;
BaseCreature[] creatures = super.getSortedBaseCreatures();
int count = creatures.length;
lastGoldAmount = remainingGold;
while (lastGoldAmount == remainingGold && count >= 0) {
int loc = FastMath.floor(FastMath.random() * creatures.length);
BaseCreature creature = creatures[loc];
if (remainingGold >= creature.getCost() && timePassed > 0) {
Modules.MESSENGER.submit(LogicMessageProcessor.ID, LogicMessageProcessor.BUY_CREATURE, id,
creature.getName(), gameID);
return true;
}
count--;
}
return false;
}
private boolean buyCreature(long timePassed) {
BaseCreature[] creatures = super.getSortedBaseCreatures();
for (int i = creatures.length - 1; i >= 0; i--) {
BaseCreature creature = creatures[i];
if (gold >= creature.getCost()) {
Modules.MESSENGER.submit(LogicMessageProcessor.ID, LogicMessageProcessor.BUY_CREATURE, id,
creature.getName(), gameID);
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
@Override
public void readExternal(ObjectInput input) throws IOException, ClassNotFoundException {
super.readExternal(input);
defends = input.readBoolean();
attacks = input.readBoolean();
aggressiveness = input.readFloat();
moveDelay = input.readInt();
buildOrder = (ArrayList<GridSquare>) input.readObject();
}
@Override
public void writeExternal(ObjectOutput output) throws IOException {
super.writeExternal(output);
output.writeBoolean(defends);
output.writeBoolean(attacks);
output.writeFloat(aggressiveness);
output.writeInt(moveDelay);
output.writeObject(buildOrder);
}
}