/**
* 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.LinkedList;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.view.Gravity;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import com.godsandtowers.R;
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.graphics.game.BitmapCache;
import com.godsandtowers.messaging.ApplicationMessageProcessor;
import com.godsandtowers.messaging.GLMessageProcessor;
import com.godsandtowers.messaging.ViewMessageProcessor;
import com.godsandtowers.sprites.BaseCreature;
import com.godsandtowers.sprites.BaseSpecial;
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.sprites.Upgradeable;
import com.godsandtowers.util.ResourceUtilities;
import com.godsandtowers.util.TDWPreferences;
import com.gundogstudios.gl.Sprite;
import com.gundogstudios.modules.Modules;
import com.gundogstudios.util.FastMath;
public class TutorialGameEngine implements GameEngine {
private static final String TAG = "TutorialGameEngine";
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 Activity activity;
private Queue<Task> tasks;
private long timeOfLastTask;
public TutorialGameEngine(Activity activity, GameInfo gameInfo) {
this.activity = activity;
this.tasks = new LinkedList<TutorialGameEngine.Task>();
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>();
populateTaskList();
}
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 launchUpgradeView(int player, Tower tower) {
if (player == gameInfo.getLocalPlayerID())
Modules.MESSENGER.submit(ViewMessageProcessor.ID, ViewMessageProcessor.UPGRADE_TOWER, tower);
}
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 processResults(int winnerID) {
throw new RuntimeException("ProcessResults called on HostGameEngine, someone is hacking the code");
}
@Override
public void run() {
try {
timeOfLastTask = System.currentTimeMillis();
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;
}
}
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 {
Modules.LOG.info(TAG, "EXITING");
synchronized (this) {
exited = true;
this.notifyAll();
}
if (finished) {
Modules.MESSENGER.submit(ApplicationMessageProcessor.ID, ApplicationMessageProcessor.GAME_COMPLETED,
gameInfo);
}
}
}
private void nextTurn() throws InterruptedException {
Task task = tasks.peek();
if (task == null) {
finished = true;
quit = true;
return;
} else {
if (task.displayWhenReady()) {
tasks.remove();
}
}
long start = System.currentTimeMillis();
int timePassed = (int) (start - lastUpdate);
if (timePassed < 0 || timePassed > 1000)
timePassed = 50;
lastUpdate = start;
boolean addIncome = gameInfo.update(timePassed);
for (int i = 0; i < players.length; i++) {
Player player = players[i];
int id = player.getID();
String autoBuyCreatureName = player.getAutoBuyCreatureName();
if (autoBuyCreatureName != null) {
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);
if (player.getLife() < 1)
player.setLife(20);
for (Creature creature : creaturesReachedFinished) {
Modules.MESSENGER.submit(GLMessageProcessor.ID, GLMessageProcessor.REMOVE_SPRITE, id, creature);
}
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);
}
Runnable command;
while ((command = commands.poll()) != null)
command.run();
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 false;
}
public void setPaused(boolean paused) {
this.paused = paused;
// prevents User and Computer from submitting commands while the game is paused
commands.clear();
Task task = tasks.peek();
task.cancel();
}
public void setLoading(boolean loading) {
this.loading = loading;
}
public void quitGame() {
quit = true;
}
private Player getHuman() {
return players[0];
}
private Player getComputer() {
return players[1];
}
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;
}
}
private void populateTaskList() {
Task task;
task = new SimpleTask(generateDialog(0, R.string.tutorial_intro));
tasks.add(task);
task = new SimpleTask(generateDialog(0, R.string.tutorial_game_area));
tasks.add(task);
task = new WaitTask(generateDialog(0, R.string.tutorial_top_menu, R.drawable.topmenu_life,
R.string.tutorial_top_menu_life, R.drawable.topmenu_money, R.string.tutorial_top_menu_gold,
R.drawable.topmenu_money, R.string.tutorial_top_menu_income), 10000);
tasks.add(task);
task = new WaitTask(generateDialog(0, R.string.tutorial_bottom_menu, R.drawable.bottommenu_towers,
R.string.tutorial_bottom_menu_tower, R.drawable.bottommenu_creatures,
R.string.tutorial_bottom_menu_creature, R.drawable.bottommenu_specials,
R.string.tutorial_bottom_menu_special, R.drawable.bottommenu_options,
R.string.tutorial_bottom_menu_option, R.drawable.bottommenu_attack,
R.string.tutorial_bottom_menu_attacking, R.drawable.bottommenu_pause,
R.string.tutorial_bottom_menu_pause), 5000);
tasks.add(task);
task = new WaitTask(generateDialog(0, R.string.tutorial_buying_tower, R.drawable.bottommenu_towers,
R.string.tutorial_bottom_menu_tower, R.drawable.icon_water_cannon, R.string.tutorial_buying_tower_pick,
R.drawable.bottommenu_cancel, R.string.tutorial_buying_tower_cancel), 5000);
tasks.add(task);
task = new WaitTask(generateDialog(0, R.string.tutorial_buying_tower_selected, R.drawable.bottommenu_buy,
R.string.tutorial_buying_tower_purchase, R.drawable.bottommenu_cancel,
R.string.tutorial_buying_tower_cancel), 5000);
task = new TowerTask(generateDialog(0, R.string.tutorial_build_maze), 1);
tasks.add(task);
task = new TowerTask(generateDialog(0, R.string.tutorial_upgrade_tower), 10);
tasks.add(task);
task = new UpgradeTask(generateDialog(0, R.string.tutorial_upgrade_tower_selected,
R.drawable.icon_water_cannon, R.string.tutorial_upgrade_tower_purchase, R.drawable.bottommenu_buy,
R.string.tutorial_upgrade_tower_sell, R.drawable.bottommenu_cancel,
R.string.tutorial_upgrade_tower_cancel, R.drawable.icon_water_cannon,
R.string.tutorial_upgrade_tower_long_press));
tasks.add(task);
task = new WaitTask(generateDialog(0, R.string.tutorial_side_buttons, R.drawable.button_back,
R.string.tutorial_side_buttons_pic), 5000);
tasks.add(task);
task = new WaitTask(generateDialog(0, R.string.tutorial_buying_creature, R.drawable.bottommenu_creatures,
R.string.tutorial_bottom_menu_creature, R.drawable.icon_frozen_soldier,
R.string.tutorial_buying_creature_purchase, R.drawable.bottommenu_cancel,
R.string.tutorial_buying_creature_cancel, R.drawable.icon_frozen_soldier,
R.string.tutorial_buying_creature_long_press), 10000);
tasks.add(task);
task = new CreatureTask(generateDialog(0, R.string.tutorial_attacking, R.drawable.bottommenu_attack,
R.string.tutorial_attacking_button, R.drawable.bottommenu_move, R.string.tutorial_attacking_move), 5);
tasks.add(task);
task = new AttackingTask(generateDialog(0, R.string.tutorial_specials, R.drawable.icon_special_damage,
R.string.tutorial_specials_damage, R.drawable.icon_special_heal, R.string.tutorial_specials_heal,
R.drawable.icon_special_kill, R.string.tutorial_specials_kill, R.drawable.icon_special_slow,
R.string.tutorial_specials_slow, R.drawable.icon_special_speed, R.string.tutorial_specials_speed,
R.drawable.icon_special_stun, R.string.tutorial_specials_stun));
tasks.add(task);
task = new SpecialTask(generateDialog(0, R.string.tutorial_options_menu, R.drawable.option_zoomin,
R.string.tutorial_options_menu_zoomin, R.drawable.option_zoomout,
R.string.tutorial_options_menu_zoomout, R.drawable.option_yrotate_cw,
R.string.tutorial_options_menu_rotateup, R.drawable.option_yrotate_ccw,
R.string.tutorial_options_menu_rotatedown, R.drawable.option_resetview,
R.string.tutorial_options_menu_default, R.drawable.option_play, R.string.tutorial_options_menu_play,
R.drawable.option_pause, R.string.tutorial_options_menu_pause));
tasks.add(task);
task = new WaitTask(generateUpgradingDialog(), 10000);
tasks.add(task);
task = new WaitTask(generateDialog(0, R.string.tutorial_completion), 10000);
tasks.add(task);
}
private TutorialDialog generateUpgradingDialog() {
ArrayList<Integer> ids = new ArrayList<Integer>();
ids.add(0);
ids.add(R.string.tutorial_upgrading);
Player player = players[0];
addUpgradeable(ids, player.getBasePlayer());
addUpgradeable(ids, player.getRace().getBaseRace());
addUpgradeable(ids, player.getCreature(BaseCreature.FROZEN_SOLDIER));
int[] idArray = new int[ids.size()];
for (int i = 0; i < ids.size(); i++) {
idArray[i] = ids.get(i);
}
return generateDialog(idArray);
}
private void addUpgradeable(ArrayList<Integer> ids, Upgradeable upgradeable) {
for (int nameID : upgradeable.getUpgradeIDs()) {
final String name = upgradeable.getUpgradeName(nameID);
int iconID = ResourceUtilities.getIconID(name);
int stringID = ResourceUtilities.getStringID(name);
ids.add(iconID);
ids.add(stringID);
}
}
private TutorialDialog generateDialog(int... ids) {
int[][] format = new int[ids.length / 2][2];
for (int i = 0; i < ids.length; i++) {
format[FastMath.floor(i / 2)][i % 2] = ids[i];
}
return new TutorialDialog(format);
}
private class SpecialTask extends Task {
public SpecialTask(TutorialDialog dialog) {
super(dialog);
}
@Override
protected boolean completed() {
for (BaseSpecial special : getHuman().getPlayerStats().getSpecials()) {
if (special.getName().equals(BaseSpecial.SLOW_CREATURES))
return startingSpecialCount >= special.getCount();
}
return false;
}
}
private int startingSpecialCount;
private class AttackingTask extends Task {
public AttackingTask(TutorialDialog dialog) {
super(dialog);
}
@Override
protected boolean completed() {
if (getHuman().isAttacking()) {
for (BaseSpecial special : getHuman().getPlayerStats().getSpecials()) {
if (special.getName().equals(BaseSpecial.SLOW_CREATURES)) {
startingSpecialCount = special.getCount();
special.upgrade(1);
}
}
return true;
}
return false;
}
}
private class UpgradeTask extends Task {
public UpgradeTask(TutorialDialog dialog) {
super(dialog);
}
@Override
protected boolean completed() {
return getHuman().getGrid().getBuildingTowers().size() != getHuman().getGrid().getBuildingSpheres().size();
}
}
private class CreatureTask extends Task {
private int count;
public CreatureTask(TutorialDialog dialog, int count) {
super(dialog);
this.count = count;
}
@Override
protected boolean completed() {
return getComputer().getGrid().getCreatures().size() >= count;
}
}
private class TowerTask extends Task {
private int count;
public TowerTask(TutorialDialog dialog, int count) {
super(dialog);
this.count = count;
}
@Override
protected boolean completed() {
return getHuman().getGrid().getTowers().size() >= count;
}
}
private class SimpleTask extends Task {
public SimpleTask(TutorialDialog dialog) {
super(dialog);
}
@Override
protected boolean completed() {
return true;
}
}
private class WaitTask extends Task {
public long waitTime;
public WaitTask(TutorialDialog dialog, long waitTime) {
super(dialog);
this.waitTime = waitTime;
}
@Override
protected boolean completed() {
return (System.currentTimeMillis() - timeOfLastTask) > waitTime;
}
}
private abstract class Task {
private TutorialDialog dialog;
public Task(TutorialDialog dialog) {
this.dialog = dialog;
}
public void cancel() {
dialog.cancel();
}
protected abstract boolean completed();
public boolean displayWhenReady() {
if (!completed())
return false;
dialog.show();
return true;
}
}
private class TutorialDialog {
private AlertDialog dialog;
private CountDownLatch latch;
private int[][] ids;
public TutorialDialog(int[][] ids) {
this.ids = ids;
this.dialog = null;
}
public void cancel() {
if (dialog != null) {
dialog.dismiss();
latch.countDown();
}
}
public void show() {
if (ids == null)
return;
latch = new CountDownLatch(1);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
ScrollView scrollView = new ScrollView(activity);
scrollView.setScrollbarFadingEnabled(false);
LinearLayout outsideLayout = new LinearLayout(activity);
outsideLayout.setOrientation(LinearLayout.VERTICAL);
outsideLayout.setGravity(Gravity.CENTER);
for (int[] idPair : ids) {
LinearLayout insideLayout = new LinearLayout(activity);
insideLayout.setOrientation(LinearLayout.HORIZONTAL);
if (idPair[0] != 0) {
ImageView image = new ImageView(activity);
image.setImageResource(idPair[0]);
insideLayout.addView(image);
}
TextView text = new TextView(activity);
text.setText(idPair[1]);
text.setPadding(5, 0, 5, 0);
insideLayout.addView(text);
outsideLayout.addView(insideLayout);
ImageView seperator = new ImageView(activity);
seperator.setImageBitmap(BitmapCache.getBitmap(R.drawable.menu_seperator));
outsideLayout.addView(seperator);
}
scrollView.addView(outsideLayout);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setView(scrollView);
builder.setCancelable(false);
builder.setNeutralButton(R.string.tutorial_continue, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
latch.countDown();
}
});
dialog = builder.create();
dialog.show();
}
});
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
finished = true;
} finally {
timeOfLastTask = System.currentTimeMillis();
}
}
}
}