skip and kick idle players
This commit is contained in:
parent
95fe597184
commit
e0391b8d4e
|
@ -51,6 +51,18 @@ to, for instance, display the number of connected players.
|
|||
The name you enter and your computer's IP address will <strong>always</strong> be logged when you
|
||||
load the game client. Chat and gameplay may also be logged.
|
||||
</p>
|
||||
<p>Recent Changes:</p>
|
||||
<ul>
|
||||
<li>17 March, Midnight UTC:<ul>
|
||||
<li>Initial AFK timer support added. This will skip (or kick, if there are not enough players) a
|
||||
player that takes longer than 15 + 15 * PICK seconds to play, or skip a judge that takes longer
|
||||
than 20 + 5 * PICK * PLAYERS seconds to select a winner. If a player is idle for two consecutive
|
||||
rounds, they will be kicked from the game. All of these numbers are adjustable; if the timeouts
|
||||
are too long or too short, please let me know!</li>
|
||||
<li>The game host can specify the Awesome Point goal from 4 to 10.</li>
|
||||
<li>The game host can specify the maximum number of players in a game from 3 to 10.</li>
|
||||
</ul></li>
|
||||
</ul>
|
||||
<p>Known issues:</p>
|
||||
<ul>
|
||||
<li><strong>Do not open the game more than once in the same browser.</strong> Neither instances
|
||||
|
@ -80,7 +92,7 @@ to, for instance, display the number of connected players.
|
|||
game state until the next round begins.</li>
|
||||
<li>Reloading the page when the winning card is displayed does not display the winning card
|
||||
again.</li>
|
||||
<li>Played cards seem to blank when someone joins (or leaves?). You may have to refresh the page
|
||||
<li>Played cards seem to blank when someone joins or leaves. You may have to refresh the page
|
||||
to see the cards again if you're the Card Czar.</li>
|
||||
</ul>
|
||||
<p>Current limitations:</p>
|
||||
|
@ -100,17 +112,10 @@ to, for instance, display the number of connected players.
|
|||
</ul>
|
||||
</li>
|
||||
<li>All games and the main lobby share the same chat.</li>
|
||||
<li>There is no play timer to keep the game moving if one person goes idle. However, if their
|
||||
browser crashes or they lose connection, they will be removed from the game after approximately 45
|
||||
seconds. An AFK timer is near the top of the priority list to add.</li>
|
||||
<li>The first player to 8 Awesome Points wins. This is currently hard-coded, but you will be able
|
||||
to change it later.</li>
|
||||
<li>You can't bet Awesome Points to play another card, and I am unsure if I will add this.</li>
|
||||
</ul>
|
||||
<p>Future enhancements:</p>
|
||||
<ul>
|
||||
<li>There will be host options to limit the number of players and set the target score soon.</li>
|
||||
<li>There will be a timer to keep the game moving if somebody goes AFK soon.</li>
|
||||
<li>There may be an option to display who played every card.</li>
|
||||
<li>A registration system and long-term statistics tracking may be added at some point.</li>
|
||||
<li>Support for custom Black and White cards will also likely be added, with a game host option to
|
||||
|
|
|
@ -201,6 +201,9 @@ cah.$.LongPollEvent = function() {
|
|||
};
|
||||
cah.$.LongPollEvent.prototype.dummyForAutocomplete = undefined;
|
||||
cah.$.LongPollEvent.KICKED = "k";
|
||||
cah.$.LongPollEvent.HURRY_UP = "hu";
|
||||
cah.$.LongPollEvent.KICKED_FROM_GAME_IDLE = "kfgi";
|
||||
cah.$.LongPollEvent.GAME_JUDGE_SKIPPED = "gjs";
|
||||
cah.$.LongPollEvent.GAME_PLAYER_LEAVE = "gpl";
|
||||
cah.$.LongPollEvent.NEW_PLAYER = "np";
|
||||
cah.$.LongPollEvent.GAME_PLAYER_JOIN = "gpj";
|
||||
|
@ -208,13 +211,15 @@ cah.$.LongPollEvent.GAME_LIST_REFRESH = "glr";
|
|||
cah.$.LongPollEvent.GAME_ROUND_COMPLETE = "grc";
|
||||
cah.$.LongPollEvent.NOOP = "_";
|
||||
cah.$.LongPollEvent.GAME_PLAYER_INFO_CHANGE = "gpic";
|
||||
cah.$.LongPollEvent.GAME_PLAYER_KICKED_IDLE = "gpki";
|
||||
cah.$.LongPollEvent.GAME_BLACK_RESHUFFLE = "gbr";
|
||||
cah.$.LongPollEvent.GAME_WHITE_RESHUFFLE = "gwr";
|
||||
cah.$.LongPollEvent.GAME_STATE_CHANGE = "gsc";
|
||||
cah.$.LongPollEvent.GAME_OPTIONS_CHANGED = "goc";
|
||||
cah.$.LongPollEvent.GAME_PLAYER_SKIPPED = "gps";
|
||||
cah.$.LongPollEvent.PLAYER_LEAVE = "pl";
|
||||
cah.$.LongPollEvent.CHAT = "c";
|
||||
cah.$.LongPollEvent.HAND_DEAL = "hd";
|
||||
cah.$.LongPollEvent.CHAT = "c";
|
||||
cah.$.LongPollEvent.GAME_JUDGE_LEFT = "gjl";
|
||||
|
||||
cah.$.LongPollResponse = function() {
|
||||
|
@ -222,8 +227,8 @@ cah.$.LongPollResponse = function() {
|
|||
};
|
||||
cah.$.LongPollResponse.prototype.dummyForAutocomplete = undefined;
|
||||
cah.$.LongPollResponse.WHITE_CARDS = "wc";
|
||||
cah.$.LongPollResponse.GAME_ID = "gid";
|
||||
cah.$.LongPollResponse.REASON = "qr";
|
||||
cah.$.LongPollResponse.GAME_ID = "gid";
|
||||
cah.$.LongPollResponse.HAND = "h";
|
||||
cah.$.LongPollResponse.INTERMISSION = "i";
|
||||
cah.$.LongPollResponse.PLAYER_INFO = "pi";
|
||||
|
@ -231,6 +236,7 @@ cah.$.LongPollResponse.BLACK_CARD = "bc";
|
|||
cah.$.LongPollResponse.WINNING_CARD = "WC";
|
||||
cah.$.LongPollResponse.GAME_STATE = "gs";
|
||||
cah.$.LongPollResponse.NICKNAME = "n";
|
||||
cah.$.LongPollResponse.PLAY_TIMER = "pt";
|
||||
cah.$.LongPollResponse.MESSAGE = "m";
|
||||
cah.$.LongPollResponse.ERROR = "e";
|
||||
cah.$.LongPollResponse.EVENT = "E";
|
||||
|
|
|
@ -749,6 +749,42 @@ cah.Game.prototype.roundComplete = function(data) {
|
|||
$(".game_show_last_round", this.element_).removeAttr("disabled");
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify the user that they are running out of time to play.
|
||||
*/
|
||||
cah.Game.prototype.hurryUp = function() {
|
||||
cah.log.status("Hurry up! You have less than 10 seconds to decide, or you will be skipped.");
|
||||
};
|
||||
|
||||
/**
|
||||
* A player was kicked due to being idle.
|
||||
*
|
||||
* @param {object}
|
||||
* data Event data from server.
|
||||
*/
|
||||
cah.Game.prototype.playerKickedIdle = function(data) {
|
||||
cah.log.status(data[cah.$.LongPollResponse.NICKNAME]
|
||||
+ " was kicked for being idle for too many rounds.");
|
||||
};
|
||||
|
||||
/**
|
||||
* A player was skipped due to being idle.
|
||||
*
|
||||
* @param {obejct}
|
||||
* data Event data from server.
|
||||
*/
|
||||
cah.Game.prototype.playerSkipped = function(data) {
|
||||
cah.log.status(data[cah.$.LongPollResponse.NICKNAME]
|
||||
+ " was skipped this round for being idle for too long.");
|
||||
};
|
||||
|
||||
/**
|
||||
* This player was kicked due to being idle.
|
||||
*/
|
||||
cah.Game.prototype.iWasKickedIdle = function() {
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify the player that a deck has been reshuffled.
|
||||
*
|
||||
|
@ -766,13 +802,21 @@ cah.Game.prototype.reshuffle = function(deck) {
|
|||
* data Event data from the server.
|
||||
*/
|
||||
cah.Game.prototype.judgeLeft = function(data) {
|
||||
cah.log
|
||||
.status("The judge has left the game. Cards played this round are being returned to hands.");
|
||||
cah.log.status("The Card Czar has left the game. Cards played this round are being returned to "
|
||||
+ "hands.");
|
||||
cah.log.status("The next round will begin in "
|
||||
+ (data[cah.$.LongPollResponse.INTERMISSION] / 1000) + " seconds.");
|
||||
cah.log.status("(Displayed state will look weird until the next round.)");
|
||||
};
|
||||
|
||||
/**
|
||||
* The judge was skipped for taking too long.
|
||||
*/
|
||||
cah.Game.prototype.judgeSkipped = function() {
|
||||
cah.log.status("The Card Czar has taken too long to decide and has been skipped. "
|
||||
+ "Cards played this round are being returned to hands.");
|
||||
};
|
||||
|
||||
/**
|
||||
* Event handler for confirm selection button.
|
||||
*
|
||||
|
|
|
@ -133,6 +133,37 @@ cah.longpoll.EventHandlers[cah.$.LongPollEvent.GAME_OPTIONS_CHANGED] = function(
|
|||
"options changed");
|
||||
};
|
||||
|
||||
cah.longpoll.EventHandlers[cah.$.LongPollEvent.HURRY_UP] = function(data) {
|
||||
cah.longpoll.EventHandlers.__gameEvent(data, cah.Game.prototype.hurryUp, "", "hurry up");
|
||||
};
|
||||
|
||||
cah.longpoll.EventHandlers[cah.$.LongPollEvent.GAME_PLAYER_KICKED_IDLE] = function(data) {
|
||||
cah.longpoll.EventHandlers.__gameEvent(data, cah.Game.prototype.playerKickedIdle, data,
|
||||
"idle kick");
|
||||
};
|
||||
|
||||
cah.longpoll.EventHandlers[cah.$.LongPollEvent.GAME_PLAYER_SKIPPED] = function(data) {
|
||||
cah.longpoll.EventHandlers.__gameEvent(data, cah.Game.prototype.playerSkipped, data,
|
||||
"player skip");
|
||||
};
|
||||
|
||||
cah.longpoll.EventHandlers[cah.$.LongPollEvent.GAME_JUDGE_SKIPPED] = function(data) {
|
||||
cah.longpoll.EventHandlers.__gameEvent(data, cah.Game.prototype.judgeSkipped, "", "judge skip");
|
||||
};
|
||||
|
||||
cah.longpoll.EventHandlers[cah.$.LongPollEvent.KICKED_FROM_GAME_IDLE] = function(data) {
|
||||
var game = cah.currentGames[data[cah.$.LongPollResponse.GAME_ID]];
|
||||
if (game) {
|
||||
game.dispose();
|
||||
delete cah.currentGames[data[cah.$.LongPollResponse.GAME_ID]];
|
||||
}
|
||||
cah.GameList.instance.update();
|
||||
cah.GameList.instance.show();
|
||||
|
||||
cah.log.error("You were kicked from game " + data[cah.$.LongPollResponse.GAME_ID]
|
||||
+ " for being idle for too long.");
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper for event handlers for game events.
|
||||
*
|
||||
|
|
|
@ -317,17 +317,22 @@ public class Constants {
|
|||
CHAT(AjaxOperation.CHAT),
|
||||
GAME_BLACK_RESHUFFLE("gbr"),
|
||||
GAME_JUDGE_LEFT("gjl"),
|
||||
GAME_JUDGE_SKIPPED("gjs"),
|
||||
GAME_LIST_REFRESH("glr"),
|
||||
GAME_OPTIONS_CHANGED("goc"),
|
||||
GAME_PLAYER_INFO_CHANGE("gpic"),
|
||||
GAME_PLAYER_JOIN("gpj"),
|
||||
GAME_PLAYER_KICKED_IDLE("gpki"),
|
||||
GAME_PLAYER_LEAVE("gpl"),
|
||||
GAME_PLAYER_SKIPPED("gps"),
|
||||
GAME_ROUND_COMPLETE("grc"),
|
||||
GAME_STATE_CHANGE("gsc"),
|
||||
GAME_WHITE_RESHUFFLE("gwr"),
|
||||
HAND_DEAL("hd"),
|
||||
HURRY_UP("hu"),
|
||||
@DuplicationAllowed
|
||||
KICKED(DisconnectReason.KICKED),
|
||||
KICKED_FROM_GAME_IDLE("kfgi"),
|
||||
NEW_PLAYER("np"),
|
||||
/**
|
||||
* There has been no other action to inform the client about in a certain timeframe, so inform
|
||||
|
@ -380,6 +385,7 @@ public class Constants {
|
|||
MESSAGE(AjaxRequest.MESSAGE),
|
||||
@DuplicationAllowed
|
||||
NICKNAME(AjaxRequest.NICKNAME),
|
||||
PLAY_TIMER("pt"),
|
||||
@DuplicationAllowed
|
||||
PLAYER_INFO(AjaxResponse.PLAYER_INFO),
|
||||
/**
|
||||
|
|
|
@ -94,6 +94,30 @@ public class Game {
|
|||
private int maxPlayers = 10;
|
||||
private int judgeIndex = 0;
|
||||
private final static int ROUND_INTERMISSION = 8 * 1000;
|
||||
/**
|
||||
* Duration, in milliseconds, for the minimum timeout a player has to choose a card to play.
|
||||
* Minimum 10 seconds.
|
||||
*/
|
||||
private final static int PLAY_TIMEOUT_BASE = 30 * 1000;
|
||||
/**
|
||||
* Duration, in milliseconds, for the additional timeout a player has to choose a card to play,
|
||||
* for each additional card that must be played. For example, on a PICK 2 card, this amount of
|
||||
* time is added to {@code PLAY_TIMEOUT_BASE}.
|
||||
*/
|
||||
private final static int PLAY_TIMEOUT_PER_CARD = 15 * 1000;
|
||||
/**
|
||||
* Duration, in milliseconds, for the minimum timeout a judge has to choose a winner.
|
||||
* Minimum combined of this and 2 * {@code JUDGE_TIMEOUT_PER_CARD} is 10 seconds.
|
||||
*/
|
||||
private final static int JUDGE_TIMEOUT_BASE = 20 * 1000;
|
||||
/**
|
||||
* Duration, in milliseconds, for the additional timeout a judge has to choose a winning card,
|
||||
* for each additional card that was played in the round. For example, on a PICK 2 card with
|
||||
* 3 non-judge players, 6 times this value is added to {@code JUDGE_TIMEOUT_BASE}.
|
||||
*/
|
||||
private final static int JUDGE_TIMEOUT_PER_CARD = 5 * 1000;
|
||||
private final static int MAX_SKIPS_BEFORE_KICK = 2;
|
||||
private final Object judgeLock = new Object();
|
||||
private Timer nextRoundTimer;
|
||||
private final Object nextRoundTimerLock = new Object();
|
||||
private int scoreGoal = 8;
|
||||
|
@ -202,14 +226,7 @@ public class Game {
|
|||
data.put(LongPollResponse.EVENT, LongPollEvent.GAME_JUDGE_LEFT.toString());
|
||||
data.put(LongPollResponse.INTERMISSION, ROUND_INTERMISSION);
|
||||
broadcastToPlayers(MessageType.GAME_EVENT, data);
|
||||
synchronized (playedCards) {
|
||||
for (final Player p : playedCards.playedPlayers()) {
|
||||
p.getHand().addAll(playedCards.getCards(p));
|
||||
sendCardsToPlayer(p, playedCards.getCards(p));
|
||||
}
|
||||
// prevent startNextRound from discarding cards
|
||||
playedCards.clear();
|
||||
}
|
||||
returnCardsToHand();
|
||||
// startNextRound will advance it again.
|
||||
judgeIndex--;
|
||||
// Can't start the next round right here.
|
||||
|
@ -252,13 +269,13 @@ public class Game {
|
|||
resetState(true);
|
||||
} else if (wasJudge) {
|
||||
synchronized (nextRoundTimerLock) {
|
||||
nextRoundTimer = new Timer();
|
||||
final TimerTask task = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
startNextRound();
|
||||
}
|
||||
};
|
||||
nextRoundTimer = new Timer("judge-left-" + id, true);
|
||||
nextRoundTimer.schedule(task, ROUND_INTERMISSION);
|
||||
}
|
||||
}
|
||||
|
@ -266,6 +283,17 @@ public class Game {
|
|||
}
|
||||
}
|
||||
|
||||
private void returnCardsToHand() {
|
||||
synchronized (playedCards) {
|
||||
for (final Player p : playedCards.playedPlayers()) {
|
||||
p.getHand().addAll(playedCards.getCards(p));
|
||||
sendCardsToPlayer(p, playedCards.getCards(p));
|
||||
}
|
||||
// prevent startNextRound from discarding cards
|
||||
playedCards.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a message to all players in this game.
|
||||
*
|
||||
|
@ -520,30 +548,203 @@ public class Game {
|
|||
}
|
||||
}
|
||||
|
||||
final int playTimer = PLAY_TIMEOUT_BASE + (PLAY_TIMEOUT_PER_CARD * (blackCard.getPick() - 1));
|
||||
|
||||
final HashMap<ReturnableData, Object> data = getEventMap();
|
||||
data.put(LongPollResponse.EVENT, LongPollEvent.GAME_STATE_CHANGE.toString());
|
||||
data.put(LongPollResponse.BLACK_CARD, getBlackCard());
|
||||
data.put(LongPollResponse.GAME_STATE, GameState.PLAYING.toString());
|
||||
data.put(LongPollResponse.PLAY_TIMER, playTimer);
|
||||
|
||||
broadcastToPlayers(MessageType.GAME_EVENT, data);
|
||||
|
||||
synchronized (nextRoundTimerLock) {
|
||||
killRoundTimer();
|
||||
final TimerTask task = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
warnPlayersToPlay();
|
||||
}
|
||||
};
|
||||
// 10 second warning
|
||||
nextRoundTimer = new Timer("hurry-up-" + id, true);
|
||||
nextRoundTimer.schedule(task, playTimer - 10 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private void warnPlayersToPlay() {
|
||||
// have to do this all synchronized in case they play while we're processing this
|
||||
synchronized (nextRoundTimerLock) {
|
||||
killRoundTimer();
|
||||
|
||||
synchronized (players) {
|
||||
for (final Player player : players) {
|
||||
if (getJudge() == player) {
|
||||
continue;
|
||||
}
|
||||
synchronized (playedCards) {
|
||||
if (!playedCards.hasPlayer(player)) {
|
||||
final Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>();
|
||||
data.put(LongPollResponse.EVENT, LongPollEvent.HURRY_UP.toString());
|
||||
data.put(LongPollResponse.GAME_ID, this.id);
|
||||
final QueuedMessage q = new QueuedMessage(MessageType.GAME_EVENT, data);
|
||||
player.getUser().enqueueMessage(q);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final TimerTask task = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
skipIdlePlayers();
|
||||
}
|
||||
};
|
||||
// 10 seconds to finish playing
|
||||
nextRoundTimer = new Timer("hurry-up-" + id, true);
|
||||
nextRoundTimer.schedule(task, 10 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private void warnJudgeToJudge() {
|
||||
// have to do this all synchronized in case they play while we're processing this
|
||||
synchronized (nextRoundTimerLock) {
|
||||
killRoundTimer();
|
||||
|
||||
if (state == GameState.JUDGING) {
|
||||
final Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>();
|
||||
data.put(LongPollResponse.EVENT, LongPollEvent.HURRY_UP.toString());
|
||||
data.put(LongPollResponse.GAME_ID, this.id);
|
||||
final QueuedMessage q = new QueuedMessage(MessageType.GAME_EVENT, data);
|
||||
getJudge().getUser().enqueueMessage(q);
|
||||
}
|
||||
|
||||
final TimerTask task = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
skipIdleJudge();
|
||||
}
|
||||
};
|
||||
// 10 seconds to finish playing
|
||||
nextRoundTimer = new Timer("hurry-up-" + id, true);
|
||||
nextRoundTimer.schedule(task, 10 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private void skipIdleJudge() {
|
||||
killRoundTimer();
|
||||
synchronized (judgeLock) {
|
||||
if (state != GameState.JUDGING) {
|
||||
return;
|
||||
}
|
||||
getJudge().skipped();
|
||||
final HashMap<ReturnableData, Object> data = getEventMap();
|
||||
data.put(LongPollResponse.EVENT, LongPollEvent.GAME_JUDGE_SKIPPED.toString());
|
||||
broadcastToPlayers(MessageType.GAME_EVENT, data);
|
||||
returnCardsToHand();
|
||||
startNextRound();
|
||||
}
|
||||
}
|
||||
|
||||
private void skipIdlePlayers() {
|
||||
killRoundTimer();
|
||||
synchronized (players) {
|
||||
final List<User> playersToRemove = new ArrayList<User>();
|
||||
final List<Player> playersToUpdateStatus = new ArrayList<Player>();
|
||||
|
||||
for (final Player player : players) {
|
||||
if (getJudge() == player) {
|
||||
continue;
|
||||
}
|
||||
synchronized (playedCards) {
|
||||
if (!playedCards.hasPlayer(player)) {
|
||||
player.skipped();
|
||||
final HashMap<ReturnableData, Object> data = getEventMap();
|
||||
|
||||
if (player.getSkipCount() >= MAX_SKIPS_BEFORE_KICK || playedCards.size() < 2) {
|
||||
data.put(LongPollResponse.EVENT, LongPollEvent.GAME_PLAYER_KICKED_IDLE.toString());
|
||||
data.put(LongPollResponse.NICKNAME, player.getUser().getNickname());
|
||||
playersToRemove.add(player.getUser());
|
||||
} else {
|
||||
data.put(LongPollResponse.EVENT, LongPollEvent.GAME_PLAYER_SKIPPED.toString());
|
||||
data.put(LongPollResponse.NICKNAME, player.getUser().getNickname());
|
||||
playersToUpdateStatus.add(player);
|
||||
}
|
||||
broadcastToPlayers(MessageType.GAME_EVENT, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final User user : playersToRemove) {
|
||||
removePlayer(user);
|
||||
final HashMap<ReturnableData, Object> data = getEventMap();
|
||||
data.put(LongPollResponse.EVENT, LongPollEvent.KICKED_FROM_GAME_IDLE.toString());
|
||||
final QueuedMessage q = new QueuedMessage(MessageType.GAME_PLAYER_EVENT, data);
|
||||
user.enqueueMessage(q);
|
||||
}
|
||||
|
||||
synchronized (playedCards) {
|
||||
// not sure how much of this check is actually required
|
||||
if (players.size() < 3 || playedCards.size() < 2 || state != GameState.PLAYING) {
|
||||
resetState(true);
|
||||
} else {
|
||||
judgingState();
|
||||
}
|
||||
}
|
||||
|
||||
// have to do this after we move to judging state
|
||||
for (final Player player : playersToUpdateStatus) {
|
||||
final HashMap<ReturnableData, Object> data = getEventMap();
|
||||
data.put(LongPollResponse.EVENT, LongPollEvent.GAME_PLAYER_INFO_CHANGE.toString());
|
||||
data.put(LongPollResponse.PLAYER_INFO, getPlayerInfo(player));
|
||||
broadcastToPlayers(MessageType.GAME_PLAYER_EVENT, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void killRoundTimer() {
|
||||
synchronized (nextRoundTimerLock) {
|
||||
if (nextRoundTimer != null) {
|
||||
nextRoundTimer.cancel();
|
||||
nextRoundTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the game into the {@code JUDGING} state.
|
||||
*/
|
||||
private void judgingState() {
|
||||
killRoundTimer();
|
||||
state = GameState.JUDGING;
|
||||
|
||||
final int judgeTimer = JUDGE_TIMEOUT_BASE
|
||||
+ (JUDGE_TIMEOUT_PER_CARD * playedCards.size() * blackCard.getPick());
|
||||
|
||||
HashMap<ReturnableData, Object> data = getEventMap();
|
||||
data.put(LongPollResponse.EVENT, LongPollEvent.GAME_STATE_CHANGE.toString());
|
||||
data.put(LongPollResponse.GAME_STATE, GameState.JUDGING.toString());
|
||||
data.put(LongPollResponse.WHITE_CARDS, getWhiteCards());
|
||||
data.put(LongPollResponse.PLAY_TIMER, judgeTimer);
|
||||
broadcastToPlayers(MessageType.GAME_EVENT, data);
|
||||
|
||||
data = getEventMap();
|
||||
data.put(LongPollResponse.EVENT, LongPollEvent.GAME_PLAYER_INFO_CHANGE.toString());
|
||||
data.put(LongPollResponse.PLAYER_INFO, getPlayerInfo(getJudge()));
|
||||
broadcastToPlayers(MessageType.GAME_PLAYER_EVENT, data);
|
||||
|
||||
synchronized (nextRoundTimerLock) {
|
||||
killRoundTimer();
|
||||
final TimerTask task = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
warnJudgeToJudge();
|
||||
}
|
||||
};
|
||||
// 10 second warning
|
||||
nextRoundTimer = new Timer("hurry-up-" + id, true);
|
||||
nextRoundTimer.schedule(task, judgeTimer - 10 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -889,6 +1090,7 @@ public class Game {
|
|||
public ErrorCode playCard(final User user, final int cardId) {
|
||||
final Player player = getPlayerForUser(user);
|
||||
if (player != null) {
|
||||
player.resetSkipCount();
|
||||
if (getJudge() == player || state != GameState.PLAYING) {
|
||||
return ErrorCode.NOT_YOUR_TURN;
|
||||
}
|
||||
|
@ -941,6 +1143,8 @@ public class Game {
|
|||
* @return Error code if there is an error, or null if success.
|
||||
*/
|
||||
public ErrorCode judgeCard(final User user, final int cardId) {
|
||||
final Player cardPlayer;
|
||||
synchronized (judgeLock) {
|
||||
final Player player = getPlayerForUser(user);
|
||||
if (getJudge() != player) {
|
||||
return ErrorCode.NOT_JUDGE;
|
||||
|
@ -948,7 +1152,8 @@ public class Game {
|
|||
return ErrorCode.NOT_YOUR_TURN;
|
||||
}
|
||||
|
||||
final Player cardPlayer;
|
||||
player.resetSkipCount();
|
||||
|
||||
synchronized (playedCards) {
|
||||
cardPlayer = playedCards.getPlayerForId(cardId);
|
||||
}
|
||||
|
@ -958,6 +1163,7 @@ public class Game {
|
|||
|
||||
cardPlayer.increaseScore();
|
||||
state = GameState.ROUND_OVER;
|
||||
}
|
||||
final int clientCardId = playedCards.getCards(cardPlayer).get(0).getId();
|
||||
|
||||
HashMap<ReturnableData, Object> data = getEventMap();
|
||||
|
@ -978,7 +1184,7 @@ public class Game {
|
|||
broadcastToPlayers(MessageType.GAME_PLAYER_EVENT, data);
|
||||
|
||||
synchronized (nextRoundTimerLock) {
|
||||
nextRoundTimer = new Timer();
|
||||
killRoundTimer();
|
||||
final TimerTask task;
|
||||
// TODO win-by-x option
|
||||
if (cardPlayer.getScore() == scoreGoal) {
|
||||
|
@ -996,6 +1202,7 @@ public class Game {
|
|||
}
|
||||
};
|
||||
}
|
||||
nextRoundTimer = new Timer("round-intermission-" + id, true);
|
||||
nextRoundTimer.schedule(task, ROUND_INTERMISSION);
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ public class Player {
|
|||
|
||||
private final List<WhiteCard> hand = new LinkedList<WhiteCard>();
|
||||
private int score = 0;
|
||||
private int skipCount = 0;
|
||||
|
||||
/**
|
||||
* Create a new player object.
|
||||
|
@ -78,6 +79,27 @@ public class Player {
|
|||
score = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases this player's skipped round count.
|
||||
*/
|
||||
public void skipped() {
|
||||
skipCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset this player's skipped round count to 0, because they have been back for a round.
|
||||
*/
|
||||
public void resetSkipCount() {
|
||||
skipCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return This player's skipped round count.
|
||||
*/
|
||||
public int getSkipCount() {
|
||||
return skipCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The backing object for the player's hand (i.e., it can be modified).
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue