package net.sf.colossus.server;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import net.sf.colossus.client.IClient;
import net.sf.colossus.common.Constants;
import net.sf.colossus.game.BattlePhase;
import net.sf.colossus.game.EntrySide;
import net.sf.colossus.game.Legion;
import net.sf.colossus.game.Player;
import net.sf.colossus.game.PlayerColor;
import net.sf.colossus.util.Glob;
import net.sf.colossus.util.InstanceTracker;
import net.sf.colossus.variant.BattleHex;
import net.sf.colossus.variant.CreatureType;
import net.sf.colossus.variant.MasterHex;
public class ClientHandlerStub implements IClient
{
private static final Logger LOGGER = Logger
.getLogger(ClientHandlerStub.class.getName());
protected static final String sep = Constants.protocolTermSeparator;
private static int TRUNC_LENGTH = 8;
private static String TRUNC_FILLER = " ";
protected Server server;
protected static int counter = 0;
protected boolean isGone = false;
protected String isGoneReason = "";
protected String signonName;
protected String playerName;
// NOTE: this "default" truncated name here should be TRUNC_LENGTH chars
protected String truncatedPlayerName = "<notset>";
protected int connectionId;
// sync-when-disconnected stuff
protected int messageCounter = 0;
protected boolean isCommitPoint = false;
protected long pingRequestCounter = 0;
/**
* Messages sent since last commit, that would need to be resent after a reconnect
* Should perhaps be rather called "resendQueue"
*/
protected final ArrayList<MessageForClient> resendQueue = new ArrayList<MessageForClient>(
100);
/**
* Messages before last commitpoint. Those will be needed only if a player
* connects from scratch.
*/
protected final ArrayList<MessageForClient> historyQueue = new ArrayList<MessageForClient>(
100);
// for optimization, do not re-send if exactly identical to the one sent
// last time.
private String previousInfoStringsString = "";
/** We might still get battle related messages from client, who
* has not received/processed the battle end (concede) yet.
* We use this to check whether there is a "reason" why we try to
* refer to a not-existing-any-more battle (e.g. conceded).
*/
private boolean battleRecentlyFinished = false;
public ClientHandlerStub(Server server)
{
LOGGER.finest("ClientHandlerStub for a real client instantiated");
this.server = server;
String tempId = "<no name yet #" + (counter++) + ">";
InstanceTracker.register(this, tempId);
}
public ClientHandlerStub(Server server, String clientName)
{
LOGGER.finest("ClientHandlerStub instantiated");
this.server = server;
this.signonName = clientName;
this.playerName = clientName;
this.truncatedPlayerName = (playerName + TRUNC_FILLER).substring(0,
TRUNC_LENGTH);
String tempId = "<no name yet #" + (counter++) + ">";
InstanceTracker.register(this, tempId);
}
protected boolean isStub()
{
return true;
}
protected boolean canHandlePingRequest()
{
return true;
}
public boolean canHandleAdvancedSync()
{
return true;
}
protected boolean supportsReconnect()
{
// dummy
return true;
}
protected boolean canHandleExtraRollRequest()
{
// dummy
return true;
}
public boolean canHandleSuspendRequests()
{
// Always approves
return true;
}
public boolean canHandleChangedValuesOnlyStyle()
{
// stub can, real clients depending on client version
return true;
}
public void setConnectionId(int id)
{
this.connectionId = id;
sendToClient(Constants.setConnectionId + sep + id);
}
int getConnectionId()
{
return this.connectionId;
}
public void setIsGone(String reason)
{
LOGGER.info("Setting isGone to true in CH for '" + getClientName()
+ "' (reason: " + reason + ")");
this.isGone = true;
this.isGoneReason = reason;
}
protected void sendToClient(String message)
{
enqueueToRedoQueue(messageCounter, message);
}
/**
* Selector reported that client became writable again (after a prior
* write attempt had not written all bytes). Now start/try writing the
* message(s) which are still in the queue.
*/
protected void flushQueuedContent()
{
// not needed here
}
protected void enqueueToRedoQueue(int messageNr, String message)
{
resendQueue.add(new MessageForClient(messageNr, 0, message));
messageCounter++;
}
protected void commitPoint()
{
PrintWriter writer = server.getGame().getIscMessageFile();
if (writer == null)
{
return;
}
MessageForClient mfc;
while (!resendQueue.isEmpty())
{
mfc = resendQueue.remove(0);
historyQueue.add(mfc);
writer.println(mfc.getMessage());
}
/*
Iterator<MessageForClient> it = redoQueue.listIterator(alreadyHandled);
while (it.hasNext())
{
mfc = it.next();
String msg = mfc.getMessage();
writer.println(msg);
alreadyHandled++;
}
*/
writer.flush();
}
protected void setBattleRecentlyFinished(boolean value)
{
battleRecentlyFinished = value;
}
protected boolean hasBattleRecentlyFinished()
{
return battleRecentlyFinished;
}
// ======================================================================
// IClient methods to sent requests to client over socket.
/**
* Server side disposes a client (and informs it about it first)
* To be used only for "disposeAllClients()", otherwise setIsGone
* reason is misleading.
*/
public void disposeClient()
{
// Don't do it again
if (isGone)
{
return;
}
setIsGone("Server disposes client (all clients)");
server.queueClientHandlerForChannelChanges(this);
}
public void tellEngagement(MasterHex hex, Legion attacker, Legion defender)
{
sendToClient(Constants.tellEngagement + sep + hex.getLabel() + sep
+ attacker.getMarkerId() + sep + defender.getMarkerId());
}
public void tellEngagementResults(Legion winner, String method,
int points, int turns)
{
setBattleRecentlyFinished(true);
sendToClient(Constants.tellEngagementResults + sep
+ (winner != null ? winner.getMarkerId() : null) + sep + method
+ sep + points + sep + turns);
}
public void tellWhatsHappening(String message)
{
sendToClient(Constants.tellWhatsHappening + sep + message);
}
public void tellMovementRoll(int roll, String reason)
{
sendToClient(Constants.tellMovementRoll + sep + roll + sep + reason);
}
public void syncOption(String optname, String value)
{
sendToClient(Constants.syncOption + sep + optname + sep + value);
}
public void updatePlayerInfo(List<String> infoStrings)
{
String infoStringsString = Glob.glob(infoStrings);
if (previousInfoStringsString.equals(infoStringsString))
{
LOGGER.finest("Skipping the re-send of identical player infos.");
}
else
{
sendToClient(Constants.updatePlayerInfo + sep + infoStringsString);
previousInfoStringsString = infoStringsString;
}
}
/**
* Send an update of the frequently changing values. One message for each
* player.
*/
public void updateChangedPlayerValues(String changedValues, String reason)
{
sendToClient(Constants.updateChangedValues + sep + changedValues + sep
+ reason);
}
public void setColor(PlayerColor color)
{
sendToClient(Constants.setColor + sep + color.getName());
}
public void updateCreatureCount(CreatureType type, int count, int deadCount)
{
sendToClient(Constants.updateCreatureCount + sep + type.getName()
+ sep + count + sep + deadCount);
}
public void removeLegion(Legion legion)
{
sendToClient(Constants.removeLegion + sep + legion.getMarkerId());
}
public void setLegionStatus(Legion legion, boolean moved,
boolean teleported, EntrySide entrySide, CreatureType lastRecruit)
{
sendToClient(Constants.setLegionStatus + sep + legion.getMarkerId()
+ sep + moved + sep + teleported + sep + entrySide.ordinal() + sep
+ lastRecruit);
}
public void addCreature(Legion legion, CreatureType creature, String reason)
{
sendToClient(Constants.addCreature + sep + legion.getMarkerId() + sep
+ creature + sep + reason);
}
public void removeCreature(Legion legion, CreatureType creature,
String reason)
{
sendToClient(Constants.removeCreature + sep + legion + sep + creature
+ sep + reason);
}
public void revealCreatures(Legion legion,
final List<CreatureType> creatures, String reason)
{
sendToClient(Constants.revealCreatures + sep + legion.getMarkerId()
+ sep + Glob.glob(creatures) + sep + reason);
}
/** print the 'revealEngagagedCreature'-message,
* args: markerId, isAttacker, list of creature names
* @param markerId legion marker name that is currently in battle
* @param creatures List of creatures in this legion
* @param isAttacker true for attacker, false for defender
* @param reason why this was revealed
* @author Towi, copied from revealCreatures
*/
public void revealEngagedCreatures(final Legion legion,
final List<CreatureType> creatures, final boolean isAttacker,
String reason)
{
sendToClient(Constants.revealEngagedCreatures + sep
+ legion.getMarkerId() + sep + isAttacker + sep
+ Glob.glob(creatures) + sep + reason);
}
public void removeDeadBattleChits()
{
sendToClient(Constants.removeDeadBattleChits);
}
public void placeNewChit(String imageName, boolean inverted, int tag,
BattleHex hex)
{
sendToClient(Constants.placeNewChit + sep + imageName + sep + inverted
+ sep + tag + sep + hex.getLabel());
}
public void tellReplay(boolean val, int maxTurn)
{
sendToClient(Constants.replayOngoing + sep + val + sep + maxTurn);
}
public void tellRedo(boolean val)
{
sendToClient(Constants.redoOngoing + sep + val);
}
public void initBoard()
{
sendToClient(Constants.initBoard);
}
public void setPlayerName(String playerName)
{
setPlayerNameNoSend(playerName);
sendToClient(Constants.setPlayerName + sep + playerName);
}
public void setPlayerNameNoSend(String playerName)
{
this.playerName = playerName;
this.truncatedPlayerName = (playerName + TRUNC_FILLER).substring(0,
TRUNC_LENGTH);
}
public String getSignonName()
{
return this.signonName;
}
// silently choose whatever useful, mostly for logging
@Override
public String getClientName()
{
return playerName != null ? playerName
: (signonName != null ? signonName : "ClientNameNotSet");
}
public String getPlayerName()
{
if (this.playerName == null)
{
LOGGER.warning("CH.playerName still null, returning signOnName '"
+ signonName + "'");
return this.signonName;
}
return this.playerName;
}
public String getTruncatedPlayerName()
{
return this.truncatedPlayerName;
}
public void createSummonAngel(Legion legion)
{
sendToClient(Constants.createSummonAngel + sep + legion.getMarkerId());
}
public void askAcquireAngel(Legion legion, List<CreatureType> recruits)
{
sendToClient(Constants.askAcquireAngel + sep + legion.getMarkerId()
+ sep + Glob.glob(recruits));
}
public void askChooseStrikePenalty(List<String> choices)
{
sendToClient(Constants.askChooseStrikePenalty + sep
+ Glob.glob(choices));
}
public void tellGameOver(String message, boolean disposeFollows, boolean suspended)
{
sendToClient(Constants.tellGameOver + sep + message + sep
+ disposeFollows + sep + suspended);
}
public void tellPlayerElim(Player player, Player slayer)
{
// slayer can be null
sendToClient(Constants.tellPlayerElim + sep + player.getName() + sep
+ (slayer != null ? slayer.getName() : null));
}
public void askConcede(Legion ally, Legion enemy)
{
sendToClient(Constants.askConcede + sep + ally.getMarkerId() + sep
+ enemy.getMarkerId());
}
public void askFlee(Legion ally, Legion enemy)
{
sendToClient(Constants.askFlee + sep + ally.getMarkerId() + sep
+ enemy.getMarkerId());
}
public void askNegotiate(Legion attacker, Legion defender)
{
sendToClient(Constants.askNegotiate + sep + attacker.getMarkerId()
+ sep + defender.getMarkerId());
}
public void tellProposal(String proposalString)
{
sendToClient(Constants.tellProposal + sep + proposalString);
}
public void tellSlowResults(int targetTag, int slowValue)
{
sendToClient(Constants.tellSlowResults + sep + targetTag + sep
+ slowValue);
}
public void tellStrikeResults(int strikerTag, int targetTag,
int strikeNumber, List<String> rolls, int damage, boolean killed,
boolean wasCarry, int carryDamageLeft,
Set<String> carryTargetDescriptions)
{
sendToClient(Constants.tellStrikeResults + sep + strikerTag + sep
+ targetTag + sep + strikeNumber + sep + Glob.glob(rolls) + sep
+ damage + sep + killed + sep + wasCarry + sep + carryDamageLeft
+ sep + Glob.glob(carryTargetDescriptions));
}
public void initBattle(MasterHex hex, int battleTurnNumber,
Player battleActivePlayer, BattlePhase battlePhase, Legion attacker,
Legion defender)
{
sendToClient(Constants.initBattle + sep + hex.getLabel() + sep
+ battleTurnNumber + sep + battleActivePlayer.getName() + sep
+ battlePhase.ordinal() + sep + attacker.getMarkerId() + sep
+ defender.getMarkerId());
}
public void cleanupBattle()
{
sendToClient(Constants.cleanupBattle);
}
public void nextEngagement()
{
sendToClient(Constants.nextEngagement);
}
public void doReinforce(Legion legion)
{
sendToClient(Constants.doReinforce + sep + legion.getMarkerId());
}
public void didRecruit(Legion legion, CreatureType recruit,
CreatureType recruiter, int numRecruiters)
{
sendToClient(Constants.didRecruit + sep + legion.getMarkerId() + sep
+ recruit + sep + recruiter + sep + numRecruiters);
}
public void undidRecruit(Legion legion, CreatureType recruit)
{
sendToClient(Constants.undidRecruit + sep + legion + sep + recruit);
}
public void setupTurnState(Player activePlayer, int turnNumber)
{
commitPoint();
sendToClient(Constants.setupTurnState + sep + activePlayer.getName()
+ sep + turnNumber);
}
public void setupSplit(Player activePlayer, int turnNumber)
{
commitPoint();
sendToClient(Constants.setupSplit + sep + activePlayer.getName() + sep
+ turnNumber);
}
public void setupMove()
{
commitPoint();
sendToClient(Constants.setupMove);
}
public void setupFight()
{
commitPoint();
sendToClient(Constants.setupFight);
}
public void setupMuster()
{
commitPoint();
sendToClient(Constants.setupMuster);
}
public void kickPhase()
{
sendToClient(Constants.kickPhase);
}
public void setupBattleSummon(Player battleActivePlayer,
int battleTurnNumber)
{
sendToClient(Constants.setupBattleSummon + sep
+ battleActivePlayer.getName() + sep + battleTurnNumber);
}
public void setupBattleRecruit(Player battleActivePlayer,
int battleTurnNumber)
{
sendToClient(Constants.setupBattleRecruit + sep
+ battleActivePlayer.getName() + sep + battleTurnNumber);
}
public void setupBattleMove(Player battleActivePlayer, int battleTurnNumber)
{
sendToClient(Constants.setupBattleMove + sep
+ battleActivePlayer.getName() + sep + battleTurnNumber);
}
public void setupBattleFight(BattlePhase battlePhase,
Player battleActivePlayer)
{
sendToClient(Constants.setupBattleFight + sep + battlePhase.ordinal()
+ sep + battleActivePlayer.getName());
}
public void tellLegionLocation(Legion legion, MasterHex hex)
{
sendToClient(Constants.tellLegionLocation + sep + legion.getMarkerId()
+ sep + hex.getLabel());
}
public void tellBattleMove(int tag, BattleHex startingHex,
BattleHex endingHex, boolean undo)
{
sendToClient(Constants.tellBattleMove + sep + tag + sep
+ startingHex.getLabel() + sep + endingHex.getLabel() + sep + undo);
}
public void didMove(Legion legion, MasterHex startingHex,
MasterHex currentHex, EntrySide entrySide, boolean teleport,
CreatureType teleportingLord, boolean splitLegionHasForcedMove)
{
sendToClient(Constants.didMove + sep + legion.getMarkerId() + sep
+ startingHex.getLabel() + sep + currentHex.getLabel() + sep
+ entrySide.getLabel() + sep + teleport + sep
+ (teleportingLord == null ? "null" : teleportingLord) + sep
+ splitLegionHasForcedMove);
}
public void undidMove(Legion legion, MasterHex formerHex,
MasterHex currentHex, boolean splitLegionHasForcedMove)
{
sendToClient(Constants.undidMove + sep + legion.getMarkerId() + sep
+ formerHex.getLabel() + sep + currentHex.getLabel() + sep
+ splitLegionHasForcedMove);
}
public void didSummon(Legion summoner, Legion donor, CreatureType summon)
{
sendToClient(Constants.didSummon + sep + summoner + sep + donor + sep
+ summon);
}
public void undidSplit(Legion splitoff, Legion survivor, int turn)
{
sendToClient(Constants.undidSplit + sep + splitoff.getMarkerId() + sep
+ survivor.getMarkerId() + sep + turn);
}
public void didSplit(MasterHex hex, Legion parent, Legion child,
int childHeight, List<CreatureType> splitoffs, int turn)
{
// hex can be null when loading a game
// TODO make sure we always have a hex
assert parent != null : " Split needs parent";
assert child != null : " Split needs child";
assert hex != null : "Split needs location";
sendToClient(Constants.didSplit + sep + hex.getLabel() + sep
+ parent.getMarkerId() + sep + child.getMarkerId() + sep
+ childHeight + sep + Glob.glob(splitoffs) + sep + turn);
}
public void askPickColor(List<PlayerColor> colorsLeft)
{
sendToClient(Constants.askPickColor + sep + Glob.glob(colorsLeft));
}
public void askPickFirstMarker()
{
sendToClient(Constants.askPickFirstMarker);
}
public void log(String message)
{
sendToClient(Constants.log + sep + message);
}
public void nak(String reason, String errmsg)
{
sendToClient(Constants.nak + sep + reason + sep + errmsg);
}
public void setBoardActive(boolean val)
{
sendToClient(Constants.boardActive + sep + val);
}
public void tellInitialGameInfo(String variantName,
Collection<String> playerNames)
{
String allPlayerNames = Glob.glob(playerNames);
sendToClient(Constants.gameInitInfo + sep + variantName + sep
+ allPlayerNames);
}
public void confirmWhenCaughtUp()
{
LOGGER.info("Sending request to confirm catchup to client "
+ playerName);
sendToClient(Constants.askConfirmCatchUp);
}
public void serverConfirmsConnection()
{
LOGGER.info("Sending server connection confirmation to client "
+ playerName);
sendToClient(Constants.serverConnectionOK);
}
/* One client has asked for connnection confirmation; here server
* relays this to each client.
*/
public void relayedPeerRequest(String requestingClientName)
{
LOGGER.info("Relaying peerRequest from client " + requestingClientName
+ " to client " + getClientName());
sendToClient(Constants.relayedPeerRequest + sep + requestingClientName);
}
/* Relay the "received" response back to requester */
public void peerRequestReceivedBy(String respondingClientName, int queueLen)
{
LOGGER.info("Relaying back the received message of client "
+ getClientName() + " to " + respondingClientName);
sendToClient(Constants.relayBackReceivedMsg + sep
+ respondingClientName + sep + queueLen);
}
/* Relay the "processed" response back to requester */
public void peerRequestProcessedBy(String respondingClientName)
{
LOGGER.info("Relaying back the processed message of client "
+ getClientName() + " to " + respondingClientName);
sendToClient(Constants.relayBackProcessedMsg + sep
+ respondingClientName);
}
public void pingRequest(long requestSent)
{
if (canHandlePingRequest())
{
pingRequestCounter++;
sendToClient(Constants.pingRequest + sep + pingRequestCounter
+ sep + requestSent);
}
}
public long getLastUsedPingRequestCounter()
{
return pingRequestCounter;
}
public void messageFromServer(String message)
{
// appears as pop-up window with "OK" button
sendToClient(Constants.messageFromServer + sep + message);
}
public void appendToConnectionLog(String message)
{
if (canHandleAdvancedSync())
{
sendToClient(Constants.appendToConnectionLog + sep + message);
}
}
public void tellSyncCompleted(int syncRequestNumber)
{
sendToClient(Constants.syncCompleted + sep + syncRequestNumber);
}
public void requestExtraRollApproval(String requestor, int requestId)
{
sendToClient(Constants.requestExtraRollApproval + sep + requestor
+ sep + requestId);
}
public void askSuspendConfirmation(String requestorName, int timeout)
{
sendToClient(Constants.askSuspendConfirmation + sep + requestorName
+ sep + timeout);
}
void prn(String text)
{
if ("remote".equals(getClientName()))
{
System.out.println(text);
}
}
}