package onlinefrontlines.game.web;
import java.io.*;
import java.util.ArrayList;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.log4j.Logger;
import onlinefrontlines.auth.*;
import onlinefrontlines.game.*;
import onlinefrontlines.game.actions.Action;
import onlinefrontlines.web.*;
import onlinefrontlines.profiler.Profiler;
import onlinefrontlines.profiler.Sampler;
import onlinefrontlines.utils.IllegalRequestException;
import onlinefrontlines.utils.Tools;
/**
* This servlet is being called by the flash game application to update the state of the game
*
* @author jorrit
*
* Copyright (C) 2009-2013 Jorrit Rouwe
*
* This file is part of Online Frontlines.
*
* Online Frontlines 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.
*
* Online Frontlines 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 Online Frontlines. If not, see <http://www.gnu.org/licenses/>.
*/
public final class GameUpdateServlet extends HttpServlet
{
private static final Logger log = Logger.getLogger(GameUpdateServlet.class);
private static final long serialVersionUID = 0;
/**
* Diff two blocks of text
*/
private String diff(String s1, String s2)
{
String rv = "";
// Split into lines
String[] l1 = s1.split("\n");
String[] l2 = s2.split("\n");
// opt[i][j] = length of LCS of x[i .. l1.length] and y[j .. l2.length]
int[][] opt = new int[l1.length + 1][l2.length + 1];
// Compute length of LCS and all subproblems via dynamic programming
for (int i = l1.length - 1; i >= 0; i--)
for (int j = l2.length - 1; j >= 0; j--)
if (l1[i].equals(l2[j]))
opt[i][j] = opt[i + 1][j + 1] + 1;
else
opt[i][j] = Math.max(opt[i + 1][j], opt[i][j + 1]);
// Recover LCS itself and print out non-matching lines to standard output
int i = 0, j = 0;
while (i < l1.length && j < l2.length)
{
if (l1[i].equals(l2[j]))
{
i++;
j++;
}
else if (opt[i + 1][j] >= opt[i][j+1]) rv += "< " + l1[i++] + "\n";
else rv += "> " + l2[j++] + "\n";
}
// Dump out one remainder of one string if the other is exhausted
while (i < l1.length || j < l2.length)
{
if (i == l1.length) rv += "> " + l2[j++] + "\n";
else if (j == l2.length) rv += "< " + l1[i++] + "\n";
}
return rv;
}
/**
* Output error
*/
public void printError(PrintWriter out)
{
out.print("<response><code>-1</code></response>");
}
/**
* Handle get
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
Sampler sampler = Profiler.getInstance().startSampler(Profiler.CATEGORY_HTTP_REQUEST, request.getRequestURI());
try
{
// Set content type
response.setContentType("text/xml");
// Do not cache results
WebUtils.setNoCacheHeaders(response);
// Get output
PrintWriter out = response.getWriter();
// Get user
AutoAuth.AuthResult result = AutoAuth.getAuthenticatedUser(request, response);
User user = result != null? result.user : null;
// Get game
int gameId;
try
{
gameId = Integer.parseInt(request.getParameter("gameId"));
}
catch (NumberFormatException e)
{
gameId = 0;
}
// Get local player
int localPlayer;
try
{
localPlayer = Integer.parseInt(request.getParameter("localPlayer"));
}
catch (NumberFormatException e)
{
localPlayer = 0;
}
// Get number of actions received
int numActionsReceived;
try
{
numActionsReceived = Integer.parseInt(request.getParameter("numActionsReceived"));
}
catch (NumberFormatException e)
{
numActionsReceived = 0;
}
// Get other parameters
String requestedAction = request.getParameter("requestedAction");
String remoteStateHash = request.getParameter("remoteStateHash");
String remoteState = request.getParameter("remoteState");
try
{
// Get the game
GameState gameState = GameStateCache.getInstance().get(gameId);
if (gameState == null)
{
printError(out);
return;
}
// Prevent concurrent access
synchronized (gameState)
{
// Get requesting user faction
Faction userFaction = Faction.fromInt(localPlayer);
// Games in progress cannot be viewed by other users
if (gameState.winningFaction == Faction.invalid && userFaction == Faction.none)
{
printError(out);
return;
}
// Check if requested faction is correct
if (userFaction != gameState.getUserFaction(user) && userFaction != Faction.none)
{
printError(out);
return;
}
// Get current player
Player currentPlayer = gameState.getPlayer(gameState.currentPlayer);
// Mark request time
gameState.markPlayerConnected(userFaction);
// Determine list of actions to send from
ArrayList<String> sendList = gameState.getFilteredList(userFaction).sendList;
// Check numActionsReceived parameter
if (numActionsReceived < 0 || numActionsReceived > sendList.size())
throw new IllegalRequestException("Invalid num actions received specified: '" + numActionsReceived + "'");
// Handle requested action
if (requestedAction != null && requestedAction.length() > 0)
{
Sampler executeActionSampler = Profiler.getInstance().startSampler(Profiler.CATEGORY_GENERAL, "GameUpdateServlet.doGet execute action");
try
{
// Check if user participates
if (userFaction == Faction.none)
throw new IllegalRequestException("This user does not participate in this game");
// Split parameters
String[] params = requestedAction.split(",");
// Determine action
Action action = Action.createAction(params[0]);
// Check if permission to execute this action
if (!action.remoteHasPermissionToSend())
throw new IllegalRequestException("Action not expected from remote");
// Check if current user can do actions
if (!action.remoteHasPermissionToSendWhenNotHisTurn()
&& (currentPlayer == null || currentPlayer.id != user.id))
throw new IllegalRequestException("This user cannot perform this action right now");
// Set properties
action.setGameState(gameState);
// Convert action from string
action.fromString(params, user);
// Perform the action
gameState.execute(action, true);
}
finally
{
executeActionSampler.stop();
}
}
// If we have nothing for the remote we can compare state
boolean dumpStateRequested = false;
if (!gameState.loggedStateMismatch
&& gameState.turnNumber > 0
&& numActionsReceived == sendList.size())
{
Sampler compareStateSampler = Profiler.getInstance().startSampler(Profiler.CATEGORY_GENERAL, "GameUpdateServlet.doGet compare state");
try
{
// See if we can compare full state
String localState = gameState.dumpState(userFaction);
if (remoteState != null && !localState.equals(remoteState))
{
log.error("State mismatch.\n"
+ "game: '" + gameId + "', "
+ "user: '" + (user != null? user.id : 0) + "', "
+ "user faction: '" + userFaction.toString() + "', "
+ "num actions: '" + gameState.actions.split("\n").length + "', "
+ "local state:\n"
+ localState + "\n"
+ "remote state:\n"
+ remoteState + "\n"
+ "diff:\n"
+ diff(localState, remoteState));
// We have logged the state mismatch, don't do it again
gameState.loggedStateMismatch = true;
}
else if (remoteStateHash != null)
{
// Compare hash only
String localStateHash = AuthTools.hashMD5(localState);
if (!localStateHash.equals(remoteStateHash))
{
log.error("State hash mismatch.\n"
+ "game: '" + gameId + "', "
+ "user: '" + (user != null? user.id : 0) + "', "
+ "user faction: '" + userFaction.toString() + "', "
+ "num actions: '" + gameState.actions.split("\n").length + "'");
// Request full state from remote
dumpStateRequested = true;
}
}
}
finally
{
compareStateSampler.stop();
}
}
// Check for timeouts
gameState.checkTimeout();
// Update cache
GameStateCache.getInstance().put(gameState.id, gameState);
// Render output
out.print("<response><code>0</code><time>");
out.print(gameState.getTimeLeft());
out.print("</time><p1c>");
out.print(gameState.isPlayerConnected(Faction.f1)? 1 : 0);
out.print("</p1c><p2c>");
out.print(gameState.isPlayerConnected(Faction.f2)? 1 : 0);
out.print("</p2c><dsr>");
out.print(dumpStateRequested? 1 : 0);
out.print("</dsr><tbp>");
out.print(gameState.getTimeBetweenPolls());
out.print("</tbp>");
for (int a = numActionsReceived; a < sendList.size(); ++a)
{
out.print("<act>");
out.print(sendList.get(a));
out.print("</act>");
}
if (gameState.canPerformAction(userFaction))
out.print("<turn>1</turn>");
else
out.print("<turn>0</turn>");
out.print("</response>");
}
}
catch (Exception e)
{
Tools.logException("Exception caught while executing action, "
+ "game: '" + gameId + "', "
+ "user: '" + (user != null? user.id : 0) + "', "
+ "request: '" + requestedAction + "', "
+ "num actions received: '" + numActionsReceived + "', "
+ "message: ", e);
printError(out);
}
}
finally
{
sampler.stop();
}
}
/**
* Handle post
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
doGet(request, response);
}
}