package stetson.CTF;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.json.JSONObject;
import stetson.CTF.Game.ItemizedOverlays;
import stetson.CTF.Game.GameData;
import stetson.CTF.Game.GameInfoBar;
import stetson.CTF.Game.GameMenu;
import stetson.CTF.Game.GameScores;
import stetson.CTF.Game.Player;
import stetson.CTF.utils.Connections;
import stetson.CTF.utils.CurrentUser;
import stetson.CTF.utils.Sound;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Menu;
import android.os.PowerManager;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.OverlayItem;
public class GameCTF extends MapActivity {
// Constant: Debugging tag
public static final String TAG = "GameCTF";
// Constant: How often should we wait between game update cycles?
public static final int GAME_UPDATE_DELAY = 1500;
// Constants: Are we moving a flag?
public static final int MOVING_FLAG_NONE = -1;
public static final int MOVING_FLAG_RED = 0;
public static final int MOVING_FLAG_BLUE = 1;
// Constants: Where should we center to on the next game update cycle?
public static final int CENTER_NONE = -1;
public static final int CENTER_ORIGIN = 0;
public static final int CENTER_SELF = 1;
public static final int CENTER_RED = 2;
public static final int CENTER_BLUE = -3;
public static boolean hasStarted = false;
// Constant: What is the minimum accuracy (in meters) we should expect ?
public static final int MIN_ACCURACY = 40;
// Drawables hash map
private HashMap<Integer,Drawable> drawable = new HashMap<Integer, Drawable>(10);
// Application Mechanics
private PowerManager.WakeLock ctfWakeLock;
// Map Mechanics
private MapView mapView;
private boolean hasCenteredOrigin = false;
private List<Overlay> mapOverlay;
private ItemizedOverlays mapOverlayMarkers;
// Game Mechanics
private boolean isStopped = false;
private int isMovingFlag;
private GameData myGameData;
private GameMenu myMenu;
private GameInfoBar myInfoBar;
private GameScores myScores;
private TaskGameProcess cycle;
private Sound sound;
private Handler gameHandler = new Handler();
/*----------------------------------*
/* ACTIVITY RELATED FUNCTIONS START
/*----------------------------------*/
/**
* Called when the activity is first created.
* Default behavior is NULL. Nothing is happening yet!
* @param saved instance state
*/
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "Starting map activity...");
// Restore a saved instance of the application
super.onCreate(savedInstanceState);
isStopped = false;
// Make sure the user is actually in a game
if(CurrentUser.getGameId().equals("")) {
this.finish();
return;
}
// Setup the wake lock
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
ctfWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, TAG);
// Move back to the game selection panel
setContentView(R.layout.game);
// No, we aren't moving the flag right now :)
isMovingFlag = MOVING_FLAG_NONE;
// Create the menu [must be created after setContentView() is run]
myMenu = new GameMenu(this);
myMenu.setMenu(GameMenu.MENU_DEFAULT, null, null);
// Create the game info bar (top of screen)
myInfoBar = new GameInfoBar(this);
myInfoBar.setLoading();
sound = new Sound(this);
// Create the class for game scores
myScores = new GameScores(this);
hasStarted = true;
// Make sure gps is running at the right speed
CurrentUser.userLocation((LocationManager) this.getSystemService(Context.LOCATION_SERVICE), JoinCTF.GPS_UPDATE_FREQUENCY_GAME);
// Turns on built-in zoom controls and satellite view
mapView = (MapView) findViewById(R.id.mapView);
mapView.getController();
mapView.setBuiltInZoomControls(true);
mapView.setSatellite(true);
// Build everything we need to run
buildDrawables();
// Setup overlay stuff
mapOverlay = mapView.getOverlays();
mapOverlayMarkers = new ItemizedOverlays(drawable.get(R.drawable.star), this);
// Start game processing
myGameData = new GameData();
gameHandler.postDelayed(gameProcess, GAME_UPDATE_DELAY);
}
/**
* Run when the activity is stopped, such as .finish()
*/
public void onDestroy() {
// Call the super
super.onDestroy();
// Stop any running cycle that may exist
isStopped = true;
if(cycle != null) {
cycle.cancel(true);
cycle = null;
}
// Leave the game (server side)
Object[] obj = new Object[2];
obj[0] = CurrentUser.getGameId();
obj[1] = CurrentUser.getUID();
new LeaveGame().execute(obj);
// Leave the game (client side)
CurrentUser.setGameId("");
hasStarted = false;
}
/**
* The activity has regained focus. Acquire a wake lock.
*/
public void onResume() {
super.onResume();
ctfWakeLock.acquire();
}
/**
* The activity has lost focus. Release the wake lock.
*/
public void onPause(){
super.onPause();
ctfWakeLock.release();
}
/**
* Handles action when the Menu button is pressed.
* Toggles the graphical menu.
*/
public boolean onCreateOptionsMenu(Menu x) {
myMenu.toggleMenu();
// True means the native menu was launched successfully, so we must return false!
return false;
}
/**
* Centers the map view on the requested centering target.
*/
public void centerMapView(int isCentering) {
// We can't center if there isn't any data!
if(myGameData == null || myGameData.hasError()) {
return;
}
GeoPoint location;
switch(isCentering) {
case CENTER_ORIGIN:
location = myGameData.getOrigin();
break;
case CENTER_SELF:
location = new GeoPoint((int) (1E6 * CurrentUser.getLatitude()), (int) (1E6 * CurrentUser.getLongitude()));
break;
case CENTER_RED:
location = myGameData.getRedFlag();
break;
case CENTER_BLUE:
location = myGameData.getBlueFlag();
break;
default:
return;
}
// Reset centering
isCentering = CENTER_NONE;
// Move the map view
if(location != null) {
mapView.getController().animateTo(location);
}
}
/**
* Sets up all the drawables to be used as markers
*/
private void buildDrawables() {
// All of our markers here
drawable.put(R.drawable.star, this.getResources().getDrawable(R.drawable.star));
drawable.put(R.drawable.red_flag, this.getResources().getDrawable(R.drawable.red_flag));
drawable.put(R.drawable.blue_flag, this.getResources().getDrawable(R.drawable.blue_flag));
drawable.put(R.drawable.person_red_owner, this.getResources().getDrawable(R.drawable.person_red_owner));
drawable.put(R.drawable.person_blue_owner, this.getResources().getDrawable(R.drawable.person_blue_owner));
drawable.put(R.drawable.person_red, this.getResources().getDrawable(R.drawable.person_red));
drawable.put(R.drawable.person_blue, this.getResources().getDrawable(R.drawable.person_blue));
drawable.put(R.drawable.grey_observer, this.getResources().getDrawable(R.drawable.grey_observer));
drawable.put(R.drawable.grey_observer_owner, this.getResources().getDrawable(R.drawable.grey_observer_owner));
drawable.put(R.drawable.selection_reticle, this.getResources().getDrawable(R.drawable.selection_reticle));
// Set the anchors here
Collection<Drawable> c = drawable.values();
Iterator<Drawable> itr = c.iterator();
while(itr.hasNext()) {
Drawable icon = itr.next();
int redW = icon.getIntrinsicWidth();
int redH = icon.getIntrinsicHeight();
icon.setBounds(-redW / 2, -redH, redH / 2, 0);
}
}
/**
* Moves the flag based on the isMovingFlag value. (If allowed!)
* @param location
*/
public void moveFlag(final GeoPoint loc) {
// If a user doesn't have permissions, don't let them move the flag :)
if(!myGameData.getCreator().equals(CurrentUser.getUID())) {
return;
}
String team = "";
if(isMovingFlag == MOVING_FLAG_RED) {
team = "red";
} else if(isMovingFlag == MOVING_FLAG_BLUE) {
team = "blue";
}
isMovingFlag = MOVING_FLAG_NONE;
if(!team.equals("")) {
Object[] obj = new Object[2];
obj[0]=loc;
obj[1]=team;
new MoveFlag().execute(obj);
}
}
/**
* Returns false (required by MapActivity)
* @return false
*/
protected boolean isRouteDisplayed() {
return false;
}
/**
* If the thread isn't already busy updating, go ahead and update the map markers now.
*/
public void updateMapMarkers() {
if(cycle.getStatus() == AsyncTask.Status.FINISHED){
cycle.updateGame();
}
}
/**
* Game processor. Runs the GameProcess task every GAME_UPDATE_DELAY (ms).
*/
private final Runnable gameProcess = new Runnable() {
public void run() {
// Run the task only if the previous one is null or finished
// AsyncTasks are designed to run only ONCE per lifetime
if(cycle == null || cycle.getStatus() == AsyncTask.Status.FINISHED && !isStopped) {
cycle = new TaskGameProcess();
cycle.execute();
}
// Call for another execution later
gameHandler.postDelayed(this, GAME_UPDATE_DELAY);
}
};
/**
* The AsyncTask used for processing game updates.
* (Generics: Params, Progress, Result)
*/
public class TaskGameProcess extends AsyncTask<Void, Void, JSONObject> {
/**
* Run as the work on another thread.
* Sends a location update and grabs data from the server.
*/
protected JSONObject doInBackground(Void... params) {
Log.i(TAG, "Grabbing game data...");
if(isStopped) {
return null;
}
return Connections.getGameData();
}
/**
* Run after execution on the UI thread.
* Processes the game object retrieved from the worker thread.
* If game is NULL then the game will be stopped and the activity terminated.
*/
protected void onPostExecute(final JSONObject gameObject) {
if(gameObject == null)
{
return;
}
else
{
myGameData.parseJSONObject(gameObject);
}
// Handle Errors
if(myGameData.hasError()) {
Log.e(TAG, "Error: " + myGameData.getErrorMessage());
return;
}
// Call for a game update
updateGame();
}
public void updateGame() {
// Is this the first map data we have gotten?
this.firstCenter();
// Remove all overlay items
mapOverlay.clear();
mapOverlayMarkers.clear();
mapView.invalidate();
// Update game info bar
myInfoBar.update(myGameData.getRedScore(), myGameData.getBlueScore(), CurrentUser.getAccuracy());
// Add flags
if(!myGameData.isRedFlagTaken()) {
addFlagMarker(myGameData.getRedFlag(), "Red Flag", R.drawable.red_flag);
}
if(!myGameData.isBlueFlagTaken()) {
addFlagMarker(myGameData.getBlueFlag(), "Blue Flag", R.drawable.blue_flag);
}
// Add players
Player player;
for(int p = 0; p < myGameData.getPlayerCount(); p++) {
player = myGameData.getPlayer(p);
this.addPlayerMarker(player);
Log.i(TAG, "Added player: " + player.getName());
}
// Add boundaries & markers
mapOverlay.add(myGameData.getBounds());
mapOverlay.add(mapOverlayMarkers);
// Let the make know we're done!
mapView.invalidate();
}
/**
* If the map hasn't been centered around the origin and smart-zoomed
* this function will take care of that.
*/
protected void firstCenter() {
if(!hasCenteredOrigin) {
hasCenteredOrigin = true;
GeoPoint topLeft = myGameData.getBounds().getTopLeft();
GeoPoint botRight = myGameData.getBounds().getBottomRight();
int maxLatitude = topLeft.getLatitudeE6();
int minLatitude = botRight.getLatitudeE6();
int maxLongitude = topLeft.getLongitudeE6();
int minLongitude = botRight.getLongitudeE6();
mapView.getController().zoomToSpan((maxLatitude - minLatitude),(maxLongitude - minLongitude));
centerMapView(GameCTF.CENTER_ORIGIN);
}
}
/**
* Adds a player marker to the map using the given player data.
* @param player
*/
protected void addPlayerMarker(Player player) {
GeoPoint playerPoint = new GeoPoint(player.getLatitude(), player.getLongitude());
OverlayItem playerItem = new OverlayItem(playerPoint, ItemizedOverlays.OVERLAY_PLAYER, player.getUID());
// If we have menu focus on this person, show a reticle
if(myMenu.getMenuFocus().equals(player.getUID())) {
this.addSelectionReticle(playerPoint);
}
boolean isCurrentPlayer = player.getUID().equals(CurrentUser.getUID());
// if player is observer, we don't care about their team
if(player.hasObserverMode()) {
if(isCurrentPlayer) {
playerItem.setMarker(drawable.get(R.drawable.grey_observer_owner));
} else {
playerItem.setMarker(drawable.get(R.drawable.grey_observer));
}
// if player is on the red team
} else if (player.getTeam().equals("red")) {
// Default marker for a red member
playerItem.setMarker(drawable.get(R.drawable.person_red));
// Logical order: flag, observer, self
if(player.hasFlag()) {
playerItem.setMarker(drawable.get(R.drawable.blue_flag));
} else if(isCurrentPlayer) {
playerItem.setMarker(drawable.get(R.drawable.person_red_owner));
}
// if player is on the blue team
} else if(player.getTeam().equals("blue")) {
// Default marker for a blue member
playerItem.setMarker(drawable.get(R.drawable.person_blue));
// Logical order: flag, observer, self
if(player.hasFlag()) {
playerItem.setMarker(drawable.get(R.drawable.red_flag));
} else if(isCurrentPlayer) {
playerItem.setMarker(drawable.get(R.drawable.person_blue_owner));
}
}
mapOverlayMarkers.addOverlay(playerItem);
}
/**
* Adds a flag given the location, title and drawable id.
* @param location
* @param team
*/
protected void addFlagMarker(GeoPoint location, String title, int iconId) {
// Add selection reticles
if(myMenu.getMenuFocus().equals(title)) {
this.addSelectionReticle(location);
}
// Add flag
OverlayItem flag = new OverlayItem(location, ItemizedOverlays.OVERLAY_FLAG, title);
flag.setMarker(drawable.get(iconId));
mapOverlayMarkers.addOverlay(flag);
}
/**
* Adds a selection reticle at the given location.
* @param location
*/
protected void addSelectionReticle(GeoPoint location) {
OverlayItem reticleItem = new OverlayItem(location, ItemizedOverlays.OVERLAY_OTHER, "");
reticleItem.setMarker(drawable.get(R.drawable.selection_reticle));
mapOverlayMarkers.addOverlay(reticleItem);
}
}
/**
* Private class for the MoveFlag server call.
* Runs on a worker thread as to not interrupt the UI thread.
* The call requires the current user to be the creator of the game in order to function.
* This task does not handle a server response.
*/
private class MoveFlag extends AsyncTask<Object[],Void, Void> {
protected Void doInBackground(Object[]... params) {
Object[] obj = params[0];
GeoPoint loc = (GeoPoint) obj[0];
String team = (String) obj[1];
Connections.moveFlag(loc, team);
return null;
}
}
/**
* Private calls for the LeaveGame server call.
* Runs on a worker thread as to not interrupt the UI thread.
* This task does not handle a server response.
*/
private class LeaveGame extends AsyncTask<Object[],Void, Void> {
protected Void doInBackground(Object[]... params) {
Object[] obj = params[0];
String game = (String) obj[0];
String uid = (String) obj[1];
Connections.leaveGame(game, uid);
return null;
}
}
/**
* Returns a reference to the game's menu
* @return
*/
public GameScores getGameScores() {
return myScores;
}
/**
* Returns a reference to the game's menu
* @return
*/
public GameMenu getGameMenu() {
return myMenu;
}
/**
* Returns a reference to the game's game data
* @return
*/
public GameData getGameData() {
return myGameData;
}
/**
* Change if the user is currently moving a flag
* @param moving
*/
public void setMovingFlag(int moving) {
isMovingFlag = moving;
}
/**
* Is the user is currently moving a flag.
* @return
*/
public int isMovingFlag() {
return isMovingFlag;
}
}