Merge branch 'spectator' of https://github.com/uecasm/PretendYoureXyzzy into uecasm-spectator
This commit is contained in:
commit
4c686010c2
|
@ -137,7 +137,17 @@ h2,h3,h4 {
|
|||
|
||||
.gamelist_lobby_join {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: 75%;
|
||||
border: 1px solid #aaa;
|
||||
background: linear-gradient(#fff, #ddd) #ddd;
|
||||
border-radius: .25em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.gamelist_lobby_spectate {
|
||||
width: 100%;
|
||||
height: 25%;
|
||||
border: 1px solid #aaa;
|
||||
background: linear-gradient(#fff, #ddd) #ddd;
|
||||
border-radius: .25em;
|
||||
|
|
|
@ -183,13 +183,18 @@ HttpSession hSession = request.getSession(true);
|
|||
<div class="gamelist_lobby_left">
|
||||
<h3>
|
||||
<span class="gamelist_lobby_host">host</span>'s Game
|
||||
(<span class="gamelist_lobby_player_count"></span>/<span class="gamelist_lobby_max_players"></span>)
|
||||
(<span class="gamelist_lobby_player_count"></span>/<span class="gamelist_lobby_max_players"></span>,
|
||||
<span class="gamelist_lobby_spectator_count"></span>/<span class="gamelist_lobby_max_spectators"></span>)
|
||||
<span class="gamelist_lobby_status">status</span>
|
||||
</h3>
|
||||
<div>
|
||||
<strong>Players:</strong>
|
||||
<span class="gamelist_lobby_players">host, player1, player2</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Spectators:</strong>
|
||||
<span class="gamelist_lobby_spectators">spectator1</span>
|
||||
</div>
|
||||
<div><strong>Goal:</strong> <span class="gamelist_lobby_goal"></span></div>
|
||||
<div>
|
||||
<strong>Cards:</strong> <span class="gamelist_lobby_cardset"></span>
|
||||
|
@ -198,6 +203,7 @@ HttpSession hSession = request.getSession(true);
|
|||
</div>
|
||||
<div class="gamelist_lobby_right">
|
||||
<input type="button" class="gamelist_lobby_join" value="Join" />
|
||||
<input type="button" class="gamelist_lobby_spectate" value="View" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -317,7 +323,7 @@ HttpSession hSession = request.getSession(true);
|
|||
<div id="scorecard_template" class="scorecard" tabindex="0">
|
||||
<span class="scorecard_player">PlayerName</span>
|
||||
<div class="clear"></div>
|
||||
<span class="scorecard_score">0</span> <span class="scorecard_point_title">Awesome Point<span class="scorecard_s">s</span></span>
|
||||
<span class="scorecard_points"><span class="scorecard_score">0</span> <span class="scorecard_point_title">Awesome Point<span class="scorecard_s">s</span></span></span>
|
||||
<span class="scorecard_status">Status</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -360,6 +366,15 @@ HttpSession hSession = request.getSession(true);
|
|||
</select>
|
||||
Having more than 10 players may get cramped!
|
||||
<br/>
|
||||
<label id="spectator_limit_template_label" for="spectator_limit_template">Spectator limit:</label>
|
||||
<select id="spectator_limit_template" class="spectator_limit"
|
||||
aria-label="Spectator limit.">
|
||||
<% int defaultSpectatorLimit = 10; for (int i = 0; i <= 20; i++) { %>
|
||||
<option <%= i == defaultSpectatorLimit ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
|
||||
<% } %>
|
||||
</select>
|
||||
Spectators can watch and chat, but not actually play. Not even as Czar.
|
||||
<br/>
|
||||
<input type="checkbox" checked="checked" id="use_timer_template" class="use_timer"
|
||||
title="Players will be skipped if they have not played within a reasonable amount of time."
|
||||
aria-label="Use idle timer. Players will be skipped if they have not played within a reasonable amount of time."/>
|
||||
|
|
|
@ -155,6 +155,17 @@ cah.ajax.Builder.prototype.withPlayerLimit = function(playerLimit) {
|
|||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number}
|
||||
* spectatorLimit Spectator limit field to use in the request.
|
||||
* @returns {cah.ajax.Builder} This object.
|
||||
*/
|
||||
cah.ajax.Builder.prototype.withSpectatorLimit = function(spectatorLimit) {
|
||||
this.assertNotExecuted_();
|
||||
this.data[cah.$.AjaxRequest.SPECTATOR_LIMIT] = spectatorLimit;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number}
|
||||
* scoreLimit Score limit field to use in the request.
|
||||
|
|
|
@ -151,6 +151,10 @@ cah.ajax.SuccessHandlers[cah.$.AjaxOperation.JOIN_GAME] = function(data, req) {
|
|||
cah.Game.joinGame(req[cah.$.AjaxRequest.GAME_ID]);
|
||||
};
|
||||
|
||||
cah.ajax.SuccessHandlers[cah.$.AjaxOperation.SPECTATE_GAME] = function(data, req) {
|
||||
cah.Game.joinGame(req[cah.$.AjaxRequest.GAME_ID]);
|
||||
};
|
||||
|
||||
cah.ajax.SuccessHandlers[cah.$.AjaxOperation.CREATE_GAME] = function(data) {
|
||||
cah.Game.joinGame(data[cah.$.AjaxResponse.GAME_ID]);
|
||||
};
|
||||
|
|
|
@ -8,9 +8,10 @@ cah.$.AjaxOperation = function() {
|
|||
cah.$.AjaxOperation.prototype.dummyForAutocomplete = undefined;
|
||||
cah.$.AjaxOperation.START_GAME = "sg";
|
||||
cah.$.AjaxOperation.FIRST_LOAD = "fl";
|
||||
cah.$.AjaxOperation.SPECTATE_GAME = "vg";
|
||||
cah.$.AjaxOperation.LOG_OUT = "lo";
|
||||
cah.$.AjaxOperation.BAN = "b";
|
||||
cah.$.AjaxOperation.JUDGE_SELECT = "js";
|
||||
cah.$.AjaxOperation.BAN = "b";
|
||||
cah.$.AjaxOperation.GAME_LIST = "ggl";
|
||||
cah.$.AjaxOperation.CHANGE_GAME_OPTIONS = "cgo";
|
||||
cah.$.AjaxOperation.GET_GAME_INFO = "ggi";
|
||||
|
@ -31,17 +32,18 @@ cah.$.AjaxRequest = function() {
|
|||
};
|
||||
cah.$.AjaxRequest.prototype.dummyForAutocomplete = undefined;
|
||||
cah.$.AjaxRequest.WALL = "wall";
|
||||
cah.$.AjaxRequest.MESSAGE = "m";
|
||||
cah.$.AjaxRequest.CARD_ID = "cid";
|
||||
cah.$.AjaxRequest.USE_TIMER = "ut";
|
||||
cah.$.AjaxRequest.GAME_ID = "gid";
|
||||
cah.$.AjaxRequest.CARD_SETS = "css";
|
||||
cah.$.AjaxRequest.SERIAL = "s";
|
||||
cah.$.AjaxRequest.PLAYER_LIMIT = "pL";
|
||||
cah.$.AjaxRequest.PASSWORD = "pw";
|
||||
cah.$.AjaxRequest.GAME_ID = "gid";
|
||||
cah.$.AjaxRequest.OP = "o";
|
||||
cah.$.AjaxRequest.SCORE_LIMIT = "sl";
|
||||
cah.$.AjaxRequest.PLAYER_LIMIT = "pL";
|
||||
cah.$.AjaxRequest.NICKNAME = "n";
|
||||
cah.$.AjaxRequest.SCORE_LIMIT = "sl";
|
||||
cah.$.AjaxRequest.CARD_ID = "cid";
|
||||
cah.$.AjaxRequest.MESSAGE = "m";
|
||||
cah.$.AjaxRequest.SPECTATOR_LIMIT = "vL";
|
||||
cah.$.AjaxRequest.SERIAL = "s";
|
||||
cah.$.AjaxRequest.PASSWORD = "pw";
|
||||
|
||||
cah.$.AjaxResponse = function() {
|
||||
// Dummy constructor to make Eclipse auto-complete.
|
||||
|
@ -179,6 +181,8 @@ cah.$.GameInfo.STATE = "S";
|
|||
cah.$.GameInfo.PLAYERS = "P";
|
||||
cah.$.GameInfo.USE_TIMER = "ut";
|
||||
cah.$.GameInfo.CARD_SETS = "css";
|
||||
cah.$.GameInfo.SPECTATORS = "V";
|
||||
cah.$.GameInfo.SPECTATOR_LIMIT = "vL";
|
||||
cah.$.GameInfo.ID = "gid";
|
||||
cah.$.GameInfo.PLAYER_LIMIT = "pL";
|
||||
cah.$.GameInfo.PASSWORD = "pw";
|
||||
|
@ -197,6 +201,7 @@ cah.$.GamePlayerStatus = function() {
|
|||
// Dummy constructor to make Eclipse auto-complete.
|
||||
};
|
||||
cah.$.GamePlayerStatus.prototype.dummyForAutocomplete = undefined;
|
||||
cah.$.GamePlayerStatus.SPECTATOR = "sv";
|
||||
cah.$.GamePlayerStatus.HOST = "sh";
|
||||
cah.$.GamePlayerStatus.IDLE = "si";
|
||||
cah.$.GamePlayerStatus.WINNER = "sw";
|
||||
|
@ -205,6 +210,7 @@ cah.$.GamePlayerStatus.JUDGE = "sj";
|
|||
cah.$.GamePlayerStatus.JUDGING = "sjj";
|
||||
cah.$.GamePlayerStatus_msg = {};
|
||||
cah.$.GamePlayerStatus_msg['sp'] = "Playing";
|
||||
cah.$.GamePlayerStatus_msg['sv'] = "Spectator";
|
||||
cah.$.GamePlayerStatus_msg['sh'] = "Host";
|
||||
cah.$.GamePlayerStatus_msg['sw'] = "Winner!";
|
||||
cah.$.GamePlayerStatus_msg['sj'] = "Card Czar";
|
||||
|
@ -212,6 +218,7 @@ cah.$.GamePlayerStatus_msg['sjj'] = "Selecting";
|
|||
cah.$.GamePlayerStatus_msg['si'] = "";
|
||||
cah.$.GamePlayerStatus_msg_2 = {};
|
||||
cah.$.GamePlayerStatus_msg_2['sp'] = "Select a card to play.";
|
||||
cah.$.GamePlayerStatus_msg_2['sv'] = "You are just spectating.";
|
||||
cah.$.GamePlayerStatus_msg_2['sh'] = "Wait for players then click Start Game.";
|
||||
cah.$.GamePlayerStatus_msg_2['sw'] = "You have won!";
|
||||
cah.$.GamePlayerStatus_msg_2['sj'] = "You are the Card Czar.";
|
||||
|
@ -241,10 +248,12 @@ cah.$.LongPollEvent.prototype.dummyForAutocomplete = undefined;
|
|||
cah.$.LongPollEvent.BANNED = "B&";
|
||||
cah.$.LongPollEvent.KICKED = "k";
|
||||
cah.$.LongPollEvent.HURRY_UP = "hu";
|
||||
cah.$.LongPollEvent.GAME_SPECTATOR_LEAVE = "gvl";
|
||||
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_SPECTATOR_JOIN = "gvj";
|
||||
cah.$.LongPollEvent.GAME_PLAYER_JOIN = "gpj";
|
||||
cah.$.LongPollEvent.GAME_LIST_REFRESH = "glr";
|
||||
cah.$.LongPollEvent.GAME_ROUND_COMPLETE = "grc";
|
||||
|
|
|
@ -57,6 +57,14 @@ cah.Game = function(id) {
|
|||
this.scoreboardElement_ = $("#scoreboard_template").clone()[0];
|
||||
this.scoreboardElement_.id = "scoreboard_" + id;
|
||||
$(this.scoreboardElement_).removeClass("hide");
|
||||
|
||||
/**
|
||||
* The first spectator element within the scoreboard.
|
||||
*
|
||||
* @type {HTMLDivElement}
|
||||
* @private
|
||||
*/
|
||||
this.firstSpectatorElement_ = null;
|
||||
|
||||
/**
|
||||
* The element for the chat room for this game
|
||||
|
@ -82,6 +90,7 @@ cah.Game = function(id) {
|
|||
// TODO: It looks like I'm not changing the id on the label elements...
|
||||
$("#score_limit_template_label", this.optionsElement_).attr("for", "score_limit_" + id);
|
||||
$("#player_limit_template_label", this.optionsElement_).attr("for", "player_limit_" + id);
|
||||
$("#spectator_limit_template_label", this.optionsElement_).attr("for", "spectator_limit_" + id);
|
||||
$("#card_set_template_label", this.optionsElement_).attr("for", "card_set_" + id);
|
||||
$("#game_password_template_label", this.optionsElement_).attr("for", "game_password_" + id);
|
||||
$("#game_hide_password_template_label", this.optionsElement_).attr("for",
|
||||
|
@ -90,6 +99,7 @@ cah.Game = function(id) {
|
|||
|
||||
$("#score_limit_template", this.optionsElement_).attr("id", "score_limit_" + id);
|
||||
$("#player_limit_template", this.optionsElement_).attr("id", "player_limit_" + id);
|
||||
$("#spectator_limit_template", this.optionsElement_).attr("id", "spectator_limit_" + id);
|
||||
$("#card_set_template", this.optionsElement_).attr("id", "card_set_" + id);
|
||||
$("#game_password_template", this.optionsElement_).attr("id", "game_password_" + id);
|
||||
$("#game_fake_password_template", this.optionsElement_).attr("id", "game_fake_password_" + id);
|
||||
|
@ -748,6 +758,7 @@ cah.Game.prototype.updateGameStatus = function(data) {
|
|||
|
||||
$(".score_limit", this.optionsElement_).val(gameInfo[cah.$.GameInfo.SCORE_LIMIT]);
|
||||
$(".player_limit", this.optionsElement_).val(gameInfo[cah.$.GameInfo.PLAYER_LIMIT]);
|
||||
$(".spectator_limit", this.optionsElement_).val(gameInfo[cah.$.GameInfo.SPECTATOR_LIMIT]);
|
||||
$(".game_password", this.optionsElement_).val(gameInfo[cah.$.GameInfo.PASSWORD]);
|
||||
if (gameInfo[cah.$.GameInfo.USE_TIMER]) {
|
||||
$(".use_timer", this.optionsElement_).attr("checked", "checked");
|
||||
|
@ -765,6 +776,11 @@ cah.Game.prototype.updateGameStatus = function(data) {
|
|||
for ( var index in playerInfos) {
|
||||
this.updateUserStatus(playerInfos[index]);
|
||||
}
|
||||
|
||||
var spectators = gameInfo[cah.$.GameInfo.SPECTATORS];
|
||||
for ( var index in spectators) {
|
||||
this.updateSpectator(spectators[index]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -780,7 +796,11 @@ cah.Game.prototype.updateUserStatus = function(playerInfo) {
|
|||
if (!panel) {
|
||||
// new score panel
|
||||
panel = new cah.GameScorePanel(playerName);
|
||||
$(this.scoreboardElement_).append(panel.getElement());
|
||||
if (this.firstSpectatorElement_) {
|
||||
$(this.firstSpectatorElement_).before(panel.getElement());
|
||||
} else {
|
||||
$(this.scoreboardElement_).append(panel.getElement());
|
||||
}
|
||||
this.scoreCards_[playerName] = panel;
|
||||
}
|
||||
var oldStatus = panel.getStatus();
|
||||
|
@ -838,6 +858,32 @@ cah.Game.prototype.updateUserStatus = function(playerInfo) {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update a single spectator's info.
|
||||
*
|
||||
* @param {String}
|
||||
* spectator The spectator name.
|
||||
*/
|
||||
cah.Game.prototype.updateSpectator = function(spectator) {
|
||||
var panel = this.scoreCards_[spectator];
|
||||
if (!panel) {
|
||||
// new score panel
|
||||
panel = new cah.GameScorePanel(spectator);
|
||||
$(this.scoreboardElement_).append(panel.getElement());
|
||||
this.scoreCards_[spectator] = panel;
|
||||
if (!this.firstSpectatorElement_) {
|
||||
this.firstSpectatorElement_ = panel.getElement();
|
||||
}
|
||||
}
|
||||
panel.update(-1, cah.$.GamePlayerStatus.SPECTATOR);
|
||||
|
||||
if (spectator == cah.nickname) {
|
||||
$(".game_message", this.element_).text(cah.$.GamePlayerStatus_msg_2[cah.$.GamePlayerStatus.SPECTATOR]);
|
||||
$(".confirm_card", this.element_).attr("disabled", "disabled");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Round has completed. Update display of round cards to show winner.
|
||||
*
|
||||
|
@ -1140,6 +1186,45 @@ cah.Game.prototype.playerLeave = function(player) {
|
|||
delete this.scoreCards_[player];
|
||||
};
|
||||
|
||||
/**
|
||||
* A spectator has joined the game.
|
||||
*
|
||||
* @param {String}
|
||||
* spectator Spectator that joined.
|
||||
*/
|
||||
cah.Game.prototype.spectatorJoin = function(spectator) {
|
||||
if (spectator != cah.nickname) {
|
||||
cah.log.status_with_game(this, spectator + " has started spectating the game.");
|
||||
this.refreshGameStatus();
|
||||
} else {
|
||||
cah.log.status_with_game(this, "You have started spectating the game.");
|
||||
}
|
||||
this.updateSpectator(spectator);
|
||||
};
|
||||
|
||||
/**
|
||||
* A spectator has left the game.
|
||||
*
|
||||
* @param {String}
|
||||
* spectator Spectator that left.
|
||||
*/
|
||||
cah.Game.prototype.spectatorLeave = function(spectator) {
|
||||
if (spectator != cah.nickname) {
|
||||
cah.log.status_with_game(this, spectator + " has stopped spectating the game.");
|
||||
this.refreshGameStatus();
|
||||
} else {
|
||||
cah.log.status_with_game(this, "You have stopped spectating the game.");
|
||||
}
|
||||
var scorecard = this.scoreCards_[spectator];
|
||||
if (scorecard) {
|
||||
if (this.firstSpectatorElement_ == scorecard.getElement()) {
|
||||
this.firstSpectatorElement_ = this.firstSpectatorElement_.nextSibling;
|
||||
}
|
||||
$(scorecard.getElement()).remove();
|
||||
}
|
||||
delete this.scoreCards_[spectator];
|
||||
};
|
||||
|
||||
/**
|
||||
* Refresh game scoreboard, etc.
|
||||
*/
|
||||
|
@ -1249,7 +1334,8 @@ cah.Game.prototype.optionChanged_ = function(e) {
|
|||
}
|
||||
cah.Ajax.build(cah.$.AjaxOperation.CHANGE_GAME_OPTIONS).withGameId(this.id_).withScoreLimit(
|
||||
$(".score_limit", this.optionsElement_).val()).withPlayerLimit(
|
||||
$(".player_limit", this.optionsElement_).val()).withCardSets(cardSetIds).withPassword(
|
||||
$(".player_limit", this.optionsElement_).val()).withSpectatorLimit(
|
||||
$(".spectator_limit", this.optionsElement_).val()).withCardSets(cardSetIds).withPassword(
|
||||
$(".game_password", this.optionsElement_).val()).withUseTimer(
|
||||
!!$('.use_timer', this.optionsElement_).attr('checked')).run();
|
||||
};
|
||||
|
@ -1337,10 +1423,17 @@ cah.GameScorePanel.prototype.update = function(score, status) {
|
|||
$(".scorecard_score", this.element_).text(score);
|
||||
$(".scorecard_status", this.element_).text(cah.$.GamePlayerStatus_msg[status]);
|
||||
$(".scorecard_s", this.element_).text(score == 1 ? "" : "s");
|
||||
$(this.element_).attr(
|
||||
"aria-label",
|
||||
this.player_ + " has " + score + " Awesome Point" + (score == 1 ? "" : "s") + ". "
|
||||
+ cah.$.GamePlayerStatus_msg[status]);
|
||||
if (score < 0) {
|
||||
$(".scorecard_points", this.element_).addClass("hide");
|
||||
$(this.element_).attr("aria-label", this.player_ + ". "
|
||||
+ cah.$.GamePlayerStatus_msg[status]);
|
||||
} else {
|
||||
$(".scorecard_points", this.element_).removeClass("hide");
|
||||
$(this.element_).attr(
|
||||
"aria-label",
|
||||
this.player_ + " has " + score + " Awesome Point" + (score == 1 ? "" : "s") + ". "
|
||||
+ cah.$.GamePlayerStatus_msg[status]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -211,11 +211,15 @@ cah.GameListLobby = function(parentElem, data) {
|
|||
$(".gamelist_lobby_id", this.element_).text(this.id_);
|
||||
$(".gamelist_lobby_host", this.element_).text(data[cah.$.GameInfo.HOST]);
|
||||
$(".gamelist_lobby_players", this.element_).text(data[cah.$.GameInfo.PLAYERS].join(", "));
|
||||
$(".gamelist_lobby_spectators", this.element_).text(data[cah.$.GameInfo.SPECTATORS].join(", "));
|
||||
var statusMessage = cah.$.GameState_msg[data[cah.$.GameInfo.STATE]];
|
||||
$(".gamelist_lobby_status", this.element_).text(statusMessage);
|
||||
$(".gamelist_lobby_join", this.element_).click(cah.bind(this, this.joinClick));
|
||||
$(".gamelist_lobby_spectate", this.element_).click(cah.bind(this, this.spectateClick));
|
||||
$(".gamelist_lobby_player_count", this.element_).text(data[cah.$.GameInfo.PLAYERS].length);
|
||||
$(".gamelist_lobby_max_players", this.element_).text(data[cah.$.GameInfo.PLAYER_LIMIT]);
|
||||
$(".gamelist_lobby_spectator_count", this.element_).text(data[cah.$.GameInfo.SPECTATORS].length);
|
||||
$(".gamelist_lobby_max_spectators", this.element_).text(data[cah.$.GameInfo.SPECTATOR_LIMIT]);
|
||||
$(".gamelist_lobby_goal", this.element_).text(data[cah.$.GameInfo.SCORE_LIMIT]);
|
||||
var cardSetNames = [];
|
||||
data[cah.$.GameInfo.CARD_SETS].sort();
|
||||
|
@ -232,7 +236,8 @@ cah.GameListLobby = function(parentElem, data) {
|
|||
$(this.element_).attr(
|
||||
"aria-label",
|
||||
data[cah.$.GameInfo.HOST] + "'s game, with " + data[cah.$.GameInfo.PLAYERS].length + " of "
|
||||
+ data[cah.$.GameInfo.PLAYER_LIMIT] + " players. " + statusMessage + ". Goal is "
|
||||
+ data[cah.$.GameInfo.PLAYER_LIMIT] + " players, and " + data[cah.$.GameInfo.SPECTATORS].length
|
||||
+ " of " + data[cah.$.GameInfo.SPECTATOR_LIMIT] + "spectators. " + statusMessage + ". Goal is "
|
||||
+ data[cah.$.GameInfo.SCORE_LIMIT] + " Awesome Points. Using " + cardSetNames.length
|
||||
+ " card set" + (cardSetNames.length == 1 ? "" : "s") + ". "
|
||||
+ (data[cah.$.GameInfo.HAS_PASSWORD] ? "Has" : "Does not have") + " a password.");
|
||||
|
@ -259,10 +264,25 @@ cah.GameListLobby.prototype.join = function() {
|
|||
$('.gamelist_lobby_join', this.element_).click();
|
||||
};
|
||||
|
||||
/**
|
||||
* Event handler for clicking the View button in a game lobby.
|
||||
*/
|
||||
cah.GameListLobby.prototype.spectateClick = function() {
|
||||
var password = "";
|
||||
if (this.data_[cah.$.GameInfo.HAS_PASSWORD]) {
|
||||
password = prompt("Enter the game's password.");
|
||||
if (password == null) {
|
||||
password = "";
|
||||
}
|
||||
}
|
||||
cah.Ajax.build(cah.$.AjaxOperation.SPECTATE_GAME).withGameId(this.id_).withPassword(password).run();
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove the game lobby from the document and free up resources.
|
||||
*/
|
||||
cah.GameListLobby.prototype.dispose = function() {
|
||||
this.parentElem_.removeChild(this.element_);
|
||||
$(".gamelist_lobby_join", this.element_).unbind();
|
||||
$(".gamelist_lobby_spectate", this.element_).unbind();
|
||||
};
|
||||
|
|
|
@ -128,6 +128,17 @@ cah.longpoll.EventHandlers[cah.$.LongPollEvent.GAME_PLAYER_LEAVE] = function(dat
|
|||
data[cah.$.LongPollResponse.NICKNAME], "player leave");
|
||||
};
|
||||
|
||||
cah.longpoll.EventHandlers[cah.$.LongPollEvent.GAME_SPECTATOR_JOIN] = function(data) {
|
||||
cah.longpoll.EventHandlers.__gameEvent(data, cah.Game.prototype.spectatorJoin,
|
||||
data[cah.$.LongPollResponse.NICKNAME],
|
||||
"spectator join (if you just joined a game this may be OK)");
|
||||
};
|
||||
|
||||
cah.longpoll.EventHandlers[cah.$.LongPollEvent.GAME_SPECTATOR_LEAVE] = function(data) {
|
||||
cah.longpoll.EventHandlers.__gameEvent(data, cah.Game.prototype.spectatorLeave,
|
||||
data[cah.$.LongPollResponse.NICKNAME], "spectator leave");
|
||||
};
|
||||
|
||||
cah.longpoll.EventHandlers[cah.$.LongPollEvent.HAND_DEAL] = function(data) {
|
||||
cah.longpoll.EventHandlers.__gameEvent(data, cah.Game.prototype.dealtCards,
|
||||
data[cah.$.LongPollResponse.HAND], "dealt cards");
|
||||
|
|
|
@ -172,6 +172,7 @@ public class Constants {
|
|||
GET_CARDS("gc"),
|
||||
GET_GAME_INFO("ggi"),
|
||||
JOIN_GAME("jg"),
|
||||
SPECTATE_GAME("vg"),
|
||||
JUDGE_SELECT("js"),
|
||||
KICK("K"),
|
||||
LEAVE_GAME("lg"),
|
||||
|
@ -208,6 +209,7 @@ public class Constants {
|
|||
OP("o"),
|
||||
PASSWORD("pw"),
|
||||
PLAYER_LIMIT("pL"),
|
||||
SPECTATOR_LIMIT("vL"),
|
||||
SCORE_LIMIT("sl"),
|
||||
SERIAL("s"),
|
||||
USE_TIMER("ut"),
|
||||
|
@ -371,6 +373,8 @@ public class Constants {
|
|||
GAME_PLAYER_KICKED_IDLE("gpki"),
|
||||
GAME_PLAYER_LEAVE("gpl"),
|
||||
GAME_PLAYER_SKIPPED("gps"),
|
||||
GAME_SPECTATOR_JOIN("gvj"),
|
||||
GAME_SPECTATOR_LEAVE("gvl"),
|
||||
GAME_ROUND_COMPLETE("grc"),
|
||||
GAME_STATE_CHANGE("gsc"),
|
||||
GAME_WHITE_RESHUFFLE("gwr"),
|
||||
|
@ -596,6 +600,9 @@ public class Constants {
|
|||
PLAYER_LIMIT(AjaxRequest.PLAYER_LIMIT),
|
||||
PLAYERS("P"),
|
||||
@DuplicationAllowed
|
||||
SPECTATOR_LIMIT(AjaxRequest.SPECTATOR_LIMIT),
|
||||
SPECTATORS("V"),
|
||||
@DuplicationAllowed
|
||||
SCORE_LIMIT(AjaxRequest.SCORE_LIMIT),
|
||||
STATE("S"),
|
||||
@DuplicationAllowed
|
||||
|
@ -648,7 +655,8 @@ public class Constants {
|
|||
JUDGE("sj", "Card Czar", "You are the Card Czar."),
|
||||
JUDGING("sjj", "Selecting", "Select a winning card."),
|
||||
PLAYING("sp", "Playing", "Select a card to play."),
|
||||
WINNER("sw", "Winner!", "You have won!");
|
||||
WINNER("sw", "Winner!", "You have won!"),
|
||||
SPECTATOR("sv", "Spectator", "You are just spectating.");
|
||||
|
||||
private final String status;
|
||||
private final String message;
|
||||
|
|
|
@ -92,6 +92,7 @@ public class Game {
|
|||
*/
|
||||
private final List<Player> roundPlayers = Collections.synchronizedList(new ArrayList<Player>(9));
|
||||
private final PlayerPlayedCardsTracker playedCards = new PlayerPlayedCardsTracker();
|
||||
private final List<User> spectators = Collections.synchronizedList(new ArrayList<User>(10));
|
||||
private final ConnectedUsers connectedUsers;
|
||||
private final GameManager gameManager;
|
||||
private Player host;
|
||||
|
@ -101,6 +102,7 @@ public class Game {
|
|||
private WhiteDeck whiteDeck;
|
||||
private GameState state;
|
||||
private int maxPlayers = 6;
|
||||
private int maxSpectators = 6;
|
||||
private int judgeIndex = 0;
|
||||
private final static int ROUND_INTERMISSION = 8 * 1000;
|
||||
/**
|
||||
|
@ -302,6 +304,65 @@ public class Game {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a spectator to the game.
|
||||
*
|
||||
* Synchronizes on {@link #spectators}.
|
||||
*
|
||||
* @param user
|
||||
* Spectator to add to this game.
|
||||
* @throws TooManySpectatorsException
|
||||
* Thrown if this game is at its maximum spectator capacity.
|
||||
* @throws IllegalStateException
|
||||
* Thrown if {@code user} is already in a game.
|
||||
*/
|
||||
public void addSpectator(final User user) throws TooManySpectatorsException,
|
||||
IllegalStateException {
|
||||
logger.info(String.format("%s joined game %d as a spectator.", user.toString(), id));
|
||||
synchronized (spectators) {
|
||||
if (spectators.size() >= maxSpectators) {
|
||||
throw new TooManySpectatorsException();
|
||||
}
|
||||
// this will throw IllegalStateException if the user is already in a game, including this one.
|
||||
user.joinGame(this);
|
||||
spectators.add(user);
|
||||
}
|
||||
|
||||
final HashMap<ReturnableData, Object> data = getEventMap();
|
||||
data.put(LongPollResponse.EVENT, LongPollEvent.GAME_SPECTATOR_JOIN.toString());
|
||||
data.put(LongPollResponse.NICKNAME, user.getNickname());
|
||||
broadcastToPlayers(MessageType.GAME_PLAYER_EVENT, data);
|
||||
|
||||
gameManager.broadcastGameListRefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a spectator from the game.
|
||||
* <br/>
|
||||
* Synchronizes on {@link #spectator}.
|
||||
*
|
||||
* @param user
|
||||
* Spectator to remove from the game.
|
||||
*/
|
||||
public void removeSpectator(final User user) {
|
||||
logger.info(String.format("Removing spectator %s from game %d.", user.toString(), id));
|
||||
synchronized (spectators) {
|
||||
if (!spectators.remove(user)) {
|
||||
return;
|
||||
} // not actually spectating
|
||||
user.leaveGame(this);
|
||||
}
|
||||
|
||||
// do this down here so the person that left doesn't get the notice too
|
||||
final HashMap<ReturnableData, Object> data = getEventMap();
|
||||
data.put(LongPollResponse.EVENT, LongPollEvent.GAME_SPECTATOR_LEAVE.toString());
|
||||
data.put(LongPollResponse.NICKNAME, user.getNickname());
|
||||
broadcastToPlayers(MessageType.GAME_PLAYER_EVENT, data);
|
||||
|
||||
// Don't do this anymore, it was driving up a crazy amount of traffic.
|
||||
// gameManager.broadcastGameListRefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all played cards to their respective player's hand.
|
||||
* <br/>
|
||||
|
@ -368,9 +429,11 @@ public class Game {
|
|||
}
|
||||
|
||||
public void updateGameSettings(final int newScoreGoal, final int newMaxPlayers,
|
||||
final int newMaxSpectators,
|
||||
final Set<CardSet> newCardSets, final String newPassword, final boolean newUseTimer) {
|
||||
this.scoreGoal = newScoreGoal;
|
||||
this.maxPlayers = newMaxPlayers;
|
||||
this.maxSpectators = newMaxSpectators;
|
||||
synchronized (this.cardSets) {
|
||||
this.cardSets.clear();
|
||||
this.cardSets.addAll(newCardSets);
|
||||
|
@ -424,6 +487,7 @@ public class Game {
|
|||
}
|
||||
info.put(GameInfo.CARD_SETS, cardSetIds);
|
||||
info.put(GameInfo.PLAYER_LIMIT, maxPlayers);
|
||||
info.put(GameInfo.SPECTATOR_LIMIT, maxSpectators);
|
||||
info.put(GameInfo.SCORE_LIMIT, scoreGoal);
|
||||
info.put(GameInfo.USE_TIMER, useTimer);
|
||||
if (includePassword) {
|
||||
|
@ -437,6 +501,14 @@ public class Game {
|
|||
playerNames.add(player.toString());
|
||||
}
|
||||
info.put(GameInfo.PLAYERS, playerNames);
|
||||
|
||||
final User[] spectatorsCopy = spectators.toArray(new User[spectators.size()]);
|
||||
final List<String> spectatorNames = new ArrayList<String>(spectatorsCopy.length);
|
||||
for (final User spectator : spectatorsCopy) {
|
||||
spectatorNames.add(spectator.toString());
|
||||
}
|
||||
info.put(GameInfo.SPECTATORS, spectatorNames);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
|
@ -1160,6 +1232,9 @@ public class Game {
|
|||
for (final Player player : playersCopy) {
|
||||
users.add(player.getUser());
|
||||
}
|
||||
synchronized (spectators) {
|
||||
users.addAll(spectators);
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
|
@ -1313,4 +1388,11 @@ public class Game {
|
|||
public class TooManyPlayersException extends Exception {
|
||||
private static final long serialVersionUID = -6603422097641992017L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception to be thrown when there are too many spectators in a game.
|
||||
*/
|
||||
public class TooManySpectatorsException extends Exception {
|
||||
private static final long serialVersionUID = -6603422097641992018L;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -166,6 +166,7 @@ public class GameManager implements Provider<Integer> {
|
|||
final List<User> usersToRemove = game.getUsers();
|
||||
for (final User user : usersToRemove) {
|
||||
game.removePlayer(user);
|
||||
game.removeSpectator(user);
|
||||
}
|
||||
|
||||
logger.info(String.format("Destroyed game %d.", game.getId()));
|
||||
|
|
|
@ -43,6 +43,7 @@ public class ChangeGameOptionHandler extends GameWithPlayerHandler {
|
|||
try {
|
||||
final int scoreLimit = Integer.parseInt(request.getParameter(AjaxRequest.SCORE_LIMIT));
|
||||
final int playerLimit = Integer.parseInt(request.getParameter(AjaxRequest.PLAYER_LIMIT));
|
||||
final int spectatorLimit = Integer.parseInt(request.getParameter(AjaxRequest.SPECTATOR_LIMIT));
|
||||
final String[] cardSetsParsed = request.getParameter(AjaxRequest.CARD_SETS).split(",");
|
||||
final Set<CardSet> cardSets = new HashSet<CardSet>();
|
||||
for (final String cardSetId : cardSetsParsed) {
|
||||
|
@ -63,7 +64,7 @@ public class ChangeGameOptionHandler extends GameWithPlayerHandler {
|
|||
if (null != useTimerString && !"".equals(useTimerString)) {
|
||||
useTimer = Boolean.valueOf(useTimerString);
|
||||
}
|
||||
game.updateGameSettings(scoreLimit, playerLimit, cardSets, password, useTimer);
|
||||
game.updateGameSettings(scoreLimit, playerLimit, spectatorLimit, cardSets, password, useTimer);
|
||||
|
||||
// only broadcast an update if the password state has changed, because it needs to change
|
||||
// the text on the join button and the sort order
|
||||
|
|
|
@ -28,6 +28,7 @@ public class Handlers {
|
|||
LIST.put(NamesHandler.OP, NamesHandler.class);
|
||||
LIST.put(PlayCardHandler.OP, PlayCardHandler.class);
|
||||
LIST.put(RegisterHandler.OP, RegisterHandler.class);
|
||||
LIST.put(SpectateGameHandler.OP, SpectateGameHandler.class);
|
||||
LIST.put(StartGameHandler.OP, StartGameHandler.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ public class LeaveGameHandler extends GameWithPlayerHandler {
|
|||
final Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>();
|
||||
|
||||
game.removePlayer(user);
|
||||
game.removeSpectator(user);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* Copyright (c) 2012, Andy Janata
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
* provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this list of conditions
|
||||
* and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
* conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
|
||||
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.socialgamer.cah.handlers;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import net.socialgamer.cah.Constants.AjaxOperation;
|
||||
import net.socialgamer.cah.Constants.AjaxRequest;
|
||||
import net.socialgamer.cah.Constants.ErrorCode;
|
||||
import net.socialgamer.cah.Constants.ReturnableData;
|
||||
import net.socialgamer.cah.RequestWrapper;
|
||||
import net.socialgamer.cah.data.Game;
|
||||
import net.socialgamer.cah.data.Game.TooManySpectatorsException;
|
||||
import net.socialgamer.cah.data.GameManager;
|
||||
import net.socialgamer.cah.data.User;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
|
||||
/**
|
||||
* Handler to spectate a game.
|
||||
*
|
||||
* @author Gavin Lambert (cah@mirality.co.nz)
|
||||
*/
|
||||
public class SpectateGameHandler extends GameHandler {
|
||||
|
||||
public static final String OP = AjaxOperation.SPECTATE_GAME.toString();
|
||||
|
||||
@Inject
|
||||
public SpectateGameHandler(final GameManager gameManager) {
|
||||
super(gameManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<ReturnableData, Object> handle(final RequestWrapper request,
|
||||
final HttpSession session, final User user, final Game game) {
|
||||
final Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>();
|
||||
|
||||
final String password = request.getParameter(AjaxRequest.PASSWORD);
|
||||
final String gamePassword = game.getPassword();
|
||||
if (gamePassword != null && !gamePassword.equals("")) {
|
||||
if (password == null || !gamePassword.equals(password)) {
|
||||
return error(ErrorCode.WRONG_PASSWORD);
|
||||
}
|
||||
}
|
||||
try {
|
||||
game.addSpectator(user);
|
||||
} catch (final IllegalStateException e) {
|
||||
return error(ErrorCode.CANNOT_JOIN_ANOTHER_GAME);
|
||||
} catch (final TooManySpectatorsException e) {
|
||||
return error(ErrorCode.GAME_FULL);
|
||||
}
|
||||
gameManager.broadcastGameListRefresh();
|
||||
return data;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue