package games.strategy.engine.framework;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import games.strategy.debug.ClientLogger;
import games.strategy.engine.data.Change;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.gamePlayer.IGamePlayer;
import games.strategy.engine.history.EventChild;
import games.strategy.engine.message.RemoteName;
import games.strategy.engine.random.IRandomSource;
import games.strategy.engine.random.IRemoteRandom;
import games.strategy.engine.random.RemoteRandom;
import games.strategy.net.INode;
import games.strategy.net.Messengers;
import games.strategy.util.ThreadUtil;
public class ClientGame extends AbstractGame {
public static RemoteName getRemoteStepAdvancerName(final INode node) {
return new RemoteName(ClientGame.class.getName() + ".REMOTE_STEP_ADVANCER:" + node.getName(),
IGameStepAdvancer.class);
}
public ClientGame(final GameData data, final Set<IGamePlayer> gamePlayers,
final Map<String, INode> remotePlayerMapping, final Messengers messengers) {
super(data, gamePlayers, remotePlayerMapping, messengers);
m_gameModifiedChannel = new IGameModifiedChannel() {
@Override
public void gameDataChanged(final Change aChange) {
m_data.performChange(aChange);
m_data.getHistory().getHistoryWriter().addChange(aChange);
}
@Override
public void startHistoryEvent(final String event, final Object renderingData) {
startHistoryEvent(event);
if (renderingData != null) {
setRenderingData(renderingData);
}
}
@Override
public void startHistoryEvent(final String event) {
m_data.getHistory().getHistoryWriter().startEvent(event);
}
@Override
public void addChildToEvent(final String text, final Object renderingData) {
m_data.getHistory().getHistoryWriter().addChildToEvent(new EventChild(text, renderingData));
}
protected void setRenderingData(final Object renderingData) {
m_data.getHistory().getHistoryWriter().setRenderingData(renderingData);
}
@Override
public void stepChanged(final String stepName, final String delegateName, final PlayerID player, final int round,
final String displayName, final boolean loadedFromSavedGame) {
// we want to skip the first iteration, since that simply advances us to step 0
if (m_firstRun) {
m_firstRun = false;
} else {
m_data.acquireWriteLock();
try {
m_data.getSequence().next();
final int ourOriginalCurrentRound = m_data.getSequence().getRound();
int currentRound = ourOriginalCurrentRound;
if (m_data.getSequence().testWeAreOnLastStep()) {
m_data.getHistory().getHistoryWriter().startNextRound(++currentRound);
}
while (!m_data.getSequence().getStep().getName().equals(stepName)
|| !m_data.getSequence().getStep().getPlayerID().equals(player)
|| !m_data.getSequence().getStep().getDelegate().getName().equals(delegateName)) {
m_data.getSequence().next();
if (m_data.getSequence().testWeAreOnLastStep()) {
m_data.getHistory().getHistoryWriter().startNextRound(++currentRound);
}
}
// TODO: this is causing problems if the very last step is a client step. we end up creating a new round
// before the host's
// rounds has started.
// right now, fixing it with a hack. but in reality we probably need to have a better way of determining
// when a new round has
// started (like with a roundChanged listener).
if ((currentRound - 1 > round && ourOriginalCurrentRound >= round)
|| (currentRound > round && ourOriginalCurrentRound < round)) {
System.err.println("Cannot create more rounds that host currently has. Host Round:" + round
+ " and new Client Round:" + currentRound);
throw new IllegalStateException("Cannot create more rounds that host currently has. Host Round:" + round
+ " and new Client Round:" + currentRound);
}
} finally {
m_data.releaseWriteLock();
}
}
if (!loadedFromSavedGame) {
m_data.getHistory().getHistoryWriter().startNextStep(stepName, delegateName, player, displayName);
}
notifyGameStepListeners(stepName, delegateName, player, round, displayName);
}
@Override
public void shutDown() {
ClientGame.this.shutDown();
}
};
m_channelMessenger.registerChannelSubscriber(m_gameModifiedChannel, IGame.GAME_MODIFICATION_CHANNEL);
final IGameStepAdvancer m_gameStepAdvancer = (stepName, player) -> {
if (m_isGameOver) {
return;
}
// make sure we are in the correct step
// steps are advanced on a different channel, and the
// message advancing the step may be being processed in a different thread
{
int i = 0;
boolean shownErrorMessage = false;
while (true) {
m_data.acquireReadLock();
try {
if (m_data.getSequence().getStep().getName().equals(stepName) || m_isGameOver) {
break;
}
} finally {
m_data.releaseReadLock();
}
if (!ThreadUtil.sleep(100)) {
break;
}
i++;
if (i > 300 && !shownErrorMessage) {
System.err.println("Waited more than 30 seconds for step to update. Something wrong.");
shownErrorMessage = true;
// TODO: should we throw an illegal state error? or just return? or a game over exception? should we
// request the server to
// send the step update again or something?
}
}
}
if (m_isGameOver) {
return;
}
final IGamePlayer gp = m_gamePlayers.get(player);
if (gp == null) {
throw new IllegalStateException(
"Game player not found. Player:" + player + " on:" + m_channelMessenger.getLocalNode());
}
gp.start(stepName);
};
m_remoteMessenger.registerRemote(m_gameStepAdvancer, getRemoteStepAdvancerName(m_channelMessenger.getLocalNode()));
for (final PlayerID player : m_gamePlayers.keySet()) {
final IRemoteRandom remoteRandom = new RemoteRandom(this);
m_remoteMessenger.registerRemote(remoteRandom, ServerGame.getRemoteRandomName(player));
}
}
public void shutDown() {
if (m_isGameOver) {
return;
}
m_isGameOver = true;
try {
m_channelMessenger.unregisterChannelSubscriber(m_gameModifiedChannel, IGame.GAME_MODIFICATION_CHANNEL);
m_remoteMessenger.unregisterRemote(getRemoteStepAdvancerName(m_channelMessenger.getLocalNode()));
m_vault.shutDown();
for (final IGamePlayer gp : m_gamePlayers.values()) {
PlayerID player;
m_data.acquireReadLock();
try {
player = m_data.getPlayerList().getPlayerID(gp.getName());
} finally {
m_data.releaseReadLock();
}
m_gamePlayers.put(player, gp);
m_remoteMessenger.unregisterRemote(ServerGame.getRemoteName(gp.getPlayerID(), m_data));
m_remoteMessenger.unregisterRemote(ServerGame.getRemoteRandomName(player));
}
} catch (final RuntimeException e) {
ClientLogger.logQuietly(e);
}
m_data.getGameLoader().shutDown();
}
@Override
public void addChange(final Change aChange) {
throw new UnsupportedOperationException();
}
/**
* Clients cant save because they do not have the delegate data.
* It would be easy to get the server to save the game, and send the
* data to the client, I just havent bothered.
*/
@Override
public boolean canSave() {
return false;
}
@Override
public IRandomSource getRandomSource() {
return null;
}
@Override
public void saveGame(final File f) {
final IServerRemote server = (IServerRemote) m_remoteMessenger.getRemote(ServerGame.SERVER_REMOTE);
final byte[] bytes = server.getSavedGame();
try (FileOutputStream fout = new FileOutputStream(f)) {
fout.write(bytes);
} catch (final IOException e) {
ClientLogger.logQuietly(e);
throw new IllegalStateException(e.getMessage());
}
}
}