/**
* 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.core;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import com.godsandtowers.core.commands.AutoBuyCreatureCommand;
import com.godsandtowers.core.commands.BuildAllTowersCommand;
import com.godsandtowers.core.commands.BuildTowerCommand;
import com.godsandtowers.core.commands.BuyCreatureCommand;
import com.godsandtowers.core.commands.BuyTowerCommand;
import com.godsandtowers.core.commands.CancelAutoBuyCreatureCommand;
import com.godsandtowers.core.commands.CancelBuildTowersCommand;
import com.godsandtowers.core.commands.ExecuteSpecialCommand;
import com.godsandtowers.core.commands.GridTouchCommand;
import com.godsandtowers.core.commands.MaxUpgradeTowerCommand;
import com.godsandtowers.core.commands.SellTowerCommand;
import com.godsandtowers.core.commands.UpgradeTowerCommand;
import com.godsandtowers.core.networking.HostNetworkManager;
import com.godsandtowers.messaging.ApplicationMessageProcessor;
import com.godsandtowers.messaging.GLMessageProcessor;
import com.godsandtowers.messaging.ViewMessageProcessor;
import com.godsandtowers.sprites.Creature;
import com.godsandtowers.sprites.MovingProjectile;
import com.godsandtowers.sprites.Player;
import com.godsandtowers.sprites.Projectile;
import com.godsandtowers.sprites.Tower;
import com.godsandtowers.util.Constants;
import com.godsandtowers.util.TDWPreferences;
import com.gundogstudios.gl.Sprite;
import com.gundogstudios.modules.Modules;
public class HostGameEngine implements GameEngine {
private static final String TAG = "HostGameEngine";
private static final int REFRESH_RATE = 500;// ms
private LinkedBlockingQueue<Runnable> commands;
private GridTouchListener[] gridTouchListener;
private Player[] players;
private boolean loading;
private boolean paused;
private boolean quit;
private boolean exited;
private boolean started;
private boolean finished;
private long overSlept;
private long lastUpdate;
private int timeSinceLastRefresh;
private GameInfo gameInfo;
private HostNetworkManager networkManager;
private ExecutorService executor;
public HostGameEngine(GameInfo gameInfo, HostNetworkManager networkManager) {
if (networkManager != null) {
this.networkManager = networkManager;
executor = Executors.newSingleThreadExecutor();
executor.execute(networkManager);
}
this.players = gameInfo.getPlayers();
this.gameInfo = gameInfo;
this.gridTouchListener = new GridTouchListener[players.length];
for (int i = 0; i < players.length; i++) {
gridTouchListener[i] = new GridTouchListener(players[i]);
}
loading = true;
paused = true;
quit = false;
started = false;
exited = false;
finished = false;
overSlept = 0;
lastUpdate = 0;
timeSinceLastRefresh = 0;
commands = new LinkedBlockingQueue<Runnable>();
}
public void upgradeTower(int player, float x, float y) {
commands.add(new UpgradeTowerCommand(gameInfo, players[player], x, y));
gridTouchListener[player].cancelUpgrading();
}
public void sellTower(int player, float x, float y) {
commands.add(new SellTowerCommand(gameInfo, players[player], x, y));
}
public void buyTower(int player, String name, float x, float y) {
commands.add(new BuyTowerCommand(gameInfo, players[player], name, x, y));
}
public void buyCreature(int player, String name) {
commands.add(new BuyCreatureCommand(gameInfo, players[player], players[(player + 1) % players.length], name));
}
public void executeSpecial(int player, String name) {
commands.add(new ExecuteSpecialCommand(players[player], players[(player + 1) % players.length], name));
}
public void buildAllTowers(int player) {
commands.add(new BuildAllTowersCommand(gridTouchListener[player]));
}
public void buildTower(int player, String name) {
commands.add(new BuildTowerCommand(gridTouchListener[player], name));
}
public void cancelBuildTowers(int player) {
commands.add(new CancelBuildTowersCommand(gridTouchListener[player]));
}
public void gridTouchCommand(int player, int col, int row) {
commands.add(new GridTouchCommand(gridTouchListener[player], col, row));
}
public void autoBuyCreature(int player, String name) {
commands.add(new AutoBuyCreatureCommand(players[player], name));
}
public void maxUpgradeTower(int player, float x, float y) {
commands.add(new MaxUpgradeTowerCommand(gameInfo, players[player], x, y));
gridTouchListener[player].cancelUpgrading();
}
public void cancelAutoBuyCreature(int player) {
commands.add(new CancelAutoBuyCreatureCommand(players[player]));
}
public void launchUpgradeView(int player, Tower tower) {
if (player == gameInfo.getLocalPlayerID()) {
Modules.MESSENGER.submit(ViewMessageProcessor.ID, ViewMessageProcessor.UPGRADE_TOWER, tower);
} else {
networkManager.sendUpgradeView(player, tower);
}
}
public void processResults(int winnerID) {
throw new RuntimeException("ProcessResults called on HostGameEngine, someone is hacking the code");
}
@Override
public void run() {
try {
while (!quit) {
long start = System.currentTimeMillis();
started = true;
if (!paused && !loading) {
nextTurn();
} else {
synchronized (this) {
this.notifyAll();
}
try {
Thread.sleep(REFRESH_RATE);
} catch (InterruptedException e) {
Modules.LOG.error(TAG, "interrupted during sleeping: " + e.toString());
}
}
timeSinceLastRefresh += System.currentTimeMillis() - start;
if (timeSinceLastRefresh >= REFRESH_RATE) {
Modules.MESSENGER.submit(ViewMessageProcessor.ID, ViewMessageProcessor.REFRESH);
timeSinceLastRefresh = 0;
}
}
if (!Constants.BALANCING)
Modules.LOG.info(TAG, "thread stopping cleanly");
} catch (Exception e) {
e.printStackTrace();
Modules.LOG.error(TAG, e.toString());
Modules.MESSENGER.submit(ApplicationMessageProcessor.ID, ApplicationMessageProcessor.GAME_ERROR, e);
} finally {
if (!Constants.BALANCING)
Modules.LOG.info(TAG, "EXITING");
synchronized (this) {
exited = true;
this.notifyAll();
}
if (networkManager != null) {
if (finished)
networkManager.sendGameResults(gameInfo);
networkManager.shutDown();
executor.shutdown();
try {
executor.awaitTermination(5000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdownNow();
}
if (finished) {
if (!Constants.BALANCING)
Modules.MESSENGER.submit(ApplicationMessageProcessor.ID,
ApplicationMessageProcessor.GAME_COMPLETED, gameInfo);
}
}
}
private void nextTurn() throws InterruptedException {
long start = System.currentTimeMillis();
int timePassed = (int) (start - lastUpdate);
if ((timePassed < 0 || timePassed > 1000) || Constants.BALANCING)
timePassed = 50;
lastUpdate = start;
boolean addIncome = gameInfo.update(timePassed);
boolean opponentsAlive = false;
boolean playerAlive = false;
for (int i = 0; i < players.length; i++) {
Player player = players[i];
int id = player.getID();
if (gameInfo.getGameType() == GameInfo.DEFENSE && gameInfo.getCurrentWave() >= gameInfo.getMaxWaves()) {
if (id != gameInfo.getLocalPlayerID()) {
opponentsAlive = true;
continue;
} else {
if (player.getGrid().getCreatures().size() == 0) {
finished = true;
quit = true;
gameInfo.setWon(true);
break;
}
}
}
String autoBuyCreatureName = player.getAutoBuyCreatureName();
if (autoBuyCreatureName != null && player.timeToAutoBuy(timePassed)) {
new BuyCreatureCommand(gameInfo, players[id], players[(id + 1) % players.length], autoBuyCreatureName)
.run();
}
if (addIncome)
player.addIncome(gameInfo.getCurrentWave());
player.nextTurn(timePassed, gameInfo.getTimeBetweenWaves());
ArrayList<Sprite> removeDead = player.updateDyingSprites(timePassed);
for (Sprite sprite : removeDead) {
Modules.MESSENGER.submit(GLMessageProcessor.ID, GLMessageProcessor.REMOVE_SPRITE, id, sprite);
}
ArrayList<Creature> creaturesReachedFinished = player.moveCreatures(timePassed);
for (Creature creature : creaturesReachedFinished) {
Modules.MESSENGER.submit(GLMessageProcessor.ID, GLMessageProcessor.REMOVE_SPRITE, id, creature);
}
if (player.getLife() <= 0)
continue;
if (i != gameInfo.getLocalPlayerID())
opponentsAlive = true;
else
playerAlive = true;
ArrayList<Projectile> newProjectiles = new ArrayList<Projectile>();
ArrayList<Creature> creaturesKilled = player.attackCreatures(timePassed, newProjectiles);
for (Projectile projectile : newProjectiles) {
Modules.MESSENGER.submit(GLMessageProcessor.ID, GLMessageProcessor.ADD_SPRITE, id, projectile);
}
gameInfo.creaturesKilled(id, creaturesKilled);
ArrayList<MovingProjectile> uselessProjectiles = new ArrayList<MovingProjectile>();
creaturesKilled = player.moveProjectiles(timePassed, uselessProjectiles);
for (MovingProjectile projectile : uselessProjectiles) {
Modules.MESSENGER.submit(GLMessageProcessor.ID, GLMessageProcessor.REMOVE_SPRITE, id, projectile);
}
gameInfo.creaturesKilled(id, creaturesKilled);
ArrayList<Tower> towersDestroyed = player.attackTowers(timePassed);
id = (id > 0) ? id - 1 : players.length - 1;
gameInfo.towersDestroyed(id, towersDestroyed);
}
if (!opponentsAlive) {
finished = true;
quit = true;
gameInfo.setWon(true);
} else if (!playerAlive) {
finished = true;
quit = true;
gameInfo.setWon(false);
}
if (networkManager != null) {
if (System.currentTimeMillis() - networkManager.getTimeSinceLastHeartbeat() > DISCONNECT_TIMEOUT) {
finished = true;
quit = true;
gameInfo.setWon(false);
gameInfo.setDisconnected(true);
} else {
long snapshotTime = System.currentTimeMillis();
networkManager.sendSnapshot(start);
snapshotTime = System.currentTimeMillis() - snapshotTime;
if (snapshotTime > 10) {
Modules.LOG.warn(TAG, "Snapshot transmit time: " + snapshotTime);
}
}
}
Runnable command;
while ((command = commands.poll()) != null)
command.run();
if (!Constants.BALANCING) {
// for (int i = 0; i < players.length; i++) {
// if (players[i].getGrid().checkState()) {
// ModuleManager.LOG.warn(TAG, "Player " + i + " has incorrect board state");
// }
// }
Modules.PROFILER.updateLogicFPS();
long totalTime = System.currentTimeMillis() - start;
long sleepTime = getTickInterval() - totalTime - overSlept;
start = System.currentTimeMillis();
if (sleepTime > 0) {
Thread.sleep(sleepTime);
} else {
Modules.LOG.warn(TAG, "Processing time took: " + totalTime);
}
overSlept = System.currentTimeMillis() - start - sleepTime;
}
}
@Override
public String toString() {
return "Wave: " + gameInfo.getCurrentWave() + " Player1: " + players[0].getLife() + " Player2: "
+ players[1].getLife();
}
public void setAttacking(int player, boolean attacking) {
players[player].setAttacking(attacking);
}
public GameInfo getGameInfo() {
return gameInfo;
}
public boolean hasStarted() {
return started;
}
public boolean hasExited() {
return exited;
}
public boolean isPaused() {
return paused;
}
public boolean isLoading() {
return loading;
}
public boolean isSaveable() {
return networkManager == null;
}
public void setPaused(boolean paused) {
this.paused = paused;
// prevents User and Computer from submitting commands while the game is paused
commands.clear();
}
public void setLoading(boolean loading) {
this.loading = loading;
}
public void quitGame() {
quit = true;
if (networkManager != null)
executor.shutdownNow();
}
private int getTickInterval() {
int speed = Modules.PREFERENCES.get(TDWPreferences.GAME_ENGINE_SPEED, FAST);
switch (speed) {
case SLOW:
return 200;
case NORMAL:
return 100;
case FAST:
return 50;
default:
return 0;
}
}
}