package net.sf.colossus.client;
import java.awt.Color;
import java.util.logging.Logger;
import net.sf.colossus.util.HTMLColor;
/**
* Class InactivityWatchdog follows whether there is any GUI activity by the
* user. If it's the player's turn and he's inactive for too long, it shows a
* notification dialogs after each interval of "inactivityWarningInterval"
* seconds.
* After INITIALLY_TOLERATED_INTERVALS times, it triggers the AI to finish the
* turn. In following player's turn, it waits only for one intverval.
* Once user becomes active again, number of intervals it waits is reset to
* INITIALLY_TOLERATED_INTERVALS.
*
* @author Clemens Katzer
*/
public class InactivityWatchdog extends Thread
{
private static final Logger LOGGER = Logger
.getLogger(InactivityWatchdog.class.getName());
private static final int INACTIVITY_CHECK_INTERVAL = 5;
public static final int DEFAULT_INTERVAL = 30;
public static final int DEBUG_INTERVAL = 10;
private static final boolean IA_DEBUG = false;
private final Client client;
private final Autoplay autoplay;
private final int inactivityWarningInterval;
private static int INITIALLY_TOLERATED_INTERVALS = 3;
private int currentlyStillToleratedIntervals;
private int currentInterval;
private int inactiveSeconds = 0;
private boolean clockIsTicking = false;
private boolean aiWasInControl = false;
private boolean aiIsInControl = false;
private boolean wasTicking = false;
private boolean userRequestsControlBack = false;
private boolean somethingHappened = false;
private boolean done;
public InactivityWatchdog(Client client, int inactivityWarningInterval)
{
this.client = client;
this.autoplay = client.getAutoplay();
this.inactivityWarningInterval = inactivityWarningInterval;
done = false;
LOGGER.fine("\n\nInactivityWatchdog instantiated");
}
public void setDone(boolean value)
{
done = value;
}
public void setClockTicking()
{
synchronized (this)
{
clockIsTicking = true;
// markThatSomethingHappened();
}
if (aiWasInControl || userRequestsControlBack)
{
printDebugValues("inactivityContinues");
aiWasInControl = false;
inactivityContinues();
}
}
public void stopClockTicking()
{
synchronized (this)
{
clockIsTicking = false;
}
inactiveSeconds = 0;
currentInterval = 0;
// If ClientThread switched us back to clock not ticking while
// AI is in control, it means AI has completed the turn.
if (aiIsInControl)
{
aiIsInControl = false;
autoplay.switchOffInactivityAutoplay();
aiWasInControl = true;
inactivityAiCompletedTurn();
}
}
private void printDebugText(String text)
{
if (IA_DEBUG)
{
System.out.print(text);
}
}
@Override
public void run()
{
currentlyStillToleratedIntervals = INITIALLY_TOLERATED_INTERVALS;
currentInterval = 0;
while (!done)
{
sleepForCheckIntervalSecs();
if (done)
{
continue;
}
if (somethingHappened)
{
inactiveSeconds = 0;
currentInterval = 0;
currentlyStillToleratedIntervals = INITIALLY_TOLERATED_INTERVALS;
somethingHappened = false;
}
else
{
printDebugText(isClockTicking() ? ":" : ".");
}
if (isClockTicking())
{
if (!wasTicking)
{
LOGGER
.fine("Noticed now that Clock has started ticking (= this user's turn started, or was attacked)...");
wasTicking = true;
}
if (autoplay.isInactivityAutoplayActive())
{
printDebugText("watchdog: clock is ticking but AI is working anyway.\n");
inactiveSeconds = 0;
}
else
{
checkInactivityStatus();
}
}
else
// not ticking
{
if (wasTicking)
{
LOGGER
.fine("Noticed now that Clock has stopped ticking...");
wasTicking = false;
printDebugValues("Clock stopped ticking");
}
else
{
LOGGER.finest("clock is still not ticking...");
}
inactiveSeconds = 0;
currentInterval = 0;
}
}
LOGGER.info("Done flag set, watchdog ends now...");
}
private void checkInactivityStatus()
{
printDebugValues("check start");
if (inactiveSeconds >= inactivityWarningInterval)
{
inactiveSeconds -= inactivityWarningInterval;
currentInterval++;
int totalInactiveSeconds = currentInterval
* inactivityWarningInterval + inactiveSeconds;
if (currentInterval >= currentlyStillToleratedIntervals)
{
LOGGER.finer("ClientThread "
+ client.getOwningPlayer().getName()
+ ": reached inactivity timeout! Enabling Autoplay.");
printDebugValues("TIMEOUT");
aiIsInControl = true;
currentlyStillToleratedIntervals = 1;
inactiveSeconds = 0;
currentInterval = 0;
autoplay.switchOnInactivityAutoplay();
inactivityTimeoutReached();
client.getEventExecutor().retriggerEvent();
}
else
{
LOGGER.fine("Turn #" + client.getTurnNumber()
+ " - user was already " + currentInterval
+ " intervals (" + totalInactiveSeconds
+ " seconds) doing nothing (away from screen?)!");
inactivityWarning(
totalInactiveSeconds,
(currentlyStillToleratedIntervals * inactivityWarningInterval));
}
}
}
public void inactivityWarning(int inactiveSecs, int timeoutSecs)
{
LOGGER.finer("ClientThread " + client.getOwningPlayer().getName()
+ ": idle for " + inactiveSeconds + "seconds!");
final String title = "It's your turn (" + inactiveSecs
+ " seconds inactive of allowed " + timeoutSecs + " seconds)!";
String pluralS = (currentInterval == 1 ? "" : "s");
final String text = "\nHey, it's your turn to do something, and "
+ "you've been doing nothing for " + currentInterval + " interval"
+ pluralS + " à " + inactivityWarningInterval + " seconds.\n"
+ "After a total of " + timeoutSecs
+ " seconds of inactivity the AI will take over for you!";
Color color = (currentInterval == 1 ? HTMLColor.lightYellow
: HTMLColor.yellow);
client.getGUI().displayInactivityDialogEnsureEDT(title, text, color);
}
public void inactivityTimeoutReached()
{
LOGGER.finer("ClientThread " + client.getOwningPlayer().getName()
+ ": reached inactivity timeout! Enabling Autoplay.");
final String title = "AI took over for you!";
final String text = "\n"
+ "You've been inactive for too long. AI took over for you in this round, "
+ "and can not be safely interrupted (might hang the game).\n\n"
+ "If you click OK now, then you will be back in control at next possible occasion.";
client.getGUI().displayInactivityDialogEnsureEDT(title, text,
HTMLColor.orange);
}
public void inactivityAiCompletedTurn()
{
String part2;
Color color;
if (userRequestsControlBack)
{
aiWasInControl = false;
markThatSomethingHappened();
return;
}
part2 = "You clicked OK, so you will be back in control next time when you are supposed to do "
+ " something (your turn starts, or you are attacked). You can safely close this window now.";
color = Color.lightGray;
part2 = "Click OK to signal that you are back, otherwise time AI will take over again "
+ "when you are next time\nsupposed to do something\n"
+ "(your turn starts, or you are attacked), but then after a shorter waiting period.\n";
color = HTMLColor.yellow;
final String title = "The AI finished for you!";
final String text = "\nThe AI had finished your turn or engagement, "
+ "so right now it's probably some other players turn.\n" + part2;
client.getGUI().displayInactivityDialogEnsureEDT(title, text, color);
}
public void inactivityContinues()
{
if (userRequestsControlBack)
{
userRequestsControlBack = false;
printDebugText("\n\n OK, go ahead!\n\n");
final String title = "Your turn #" + client.getTurnNumber()
+ " has started!";
final String text = "\n" + "All right, go ahead!";
client.getGUI().displayInactivityDialogEnsureEDT(title, text,
HTMLColor.lightYellow);
}
else
{
printDebugText("inactivityContinues");
final String title = "Your turn #" + client.getTurnNumber()
+ " has started!";
final String text = "\n"
+ "You have not done anything since last turn where AI took over,\n"
+ "so this time AI will take over after a shorter waiting period.\n"
+ "Click OK to signal that you are back and take back control.";
client.getGUI().displayInactivityDialogEnsureEDT(title, text,
HTMLColor.lightSalmon);
}
}
public boolean isClockTicking()
{
synchronized (this)
{
return clockIsTicking;
}
}
public boolean userRequestsControlBack()
{
printDebugValues("userRequestsControlBack");
boolean canCloseImmediately;
if (aiIsInControl)
{
printDebugText("ai in Control - return false");
canCloseImmediately = false;
userRequestsControlBack = true;
}
else
{
printDebugText("ai not in Control - return true");
canCloseImmediately = true;
somethingHappened = true;
}
return canCloseImmediately;
}
public void markThatSomethingHappened()
{
somethingHappened = true;
}
private void sleepForCheckIntervalSecs()
{
try
{
Thread.sleep(INACTIVITY_CHECK_INTERVAL * 1000);
}
catch (InterruptedException e)
{
LOGGER.finest("got interrupted, done=" + done);
if (done)
{
LOGGER.fine("Watchdog sleep was interrupted "
+ "and done is true; that's fine.");
}
else
{
LOGGER.warning("watchdog: interruptedException "
+ "but done not set??");
}
}
inactiveSeconds += INACTIVITY_CHECK_INTERVAL;
}
private void printDebugValues(String msg)
{
if (IA_DEBUG)
{
System.out.println(msg + ": inactiveSecs: " + inactiveSeconds
+ ", currentInterval: " + currentInterval + ", tolerated: "
+ currentlyStillToleratedIntervals);
}
}
public void finish()
{
if (done)
{
LOGGER.fine("watchdog finish: done is already set.");
return;
}
LOGGER.fine("finnish: setting done true");
setDone(true);
LOGGER.fine("interrupting sleep");
this.interrupt();
}
}