Merge pull request #101 from uecasm/options

Refactor game options into subobject.
This commit is contained in:
Andy Janata 2014-04-30 23:46:48 -07:00
commit dae20ee332
11 changed files with 593 additions and 257 deletions

View File

@ -29,7 +29,7 @@ created for the user now.
--%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="javax.servlet.http.HttpSession" %>
<%@ page import="net.socialgamer.cah.data.Game" %>
<%@ page import="net.socialgamer.cah.data.GameOptions" %>
<%
// Ensure a session exists for the user.
@SuppressWarnings("unused")
@ -43,6 +43,7 @@ HttpSession hSession = request.getSession(true);
<title>Pretend You're Xyzzy</title>
<script type="text/javascript" src="js/jquery-1.8.2.js"></script>
<script type="text/javascript" src="js/jquery.cookie.js"></script>
<script type="text/javascript" src="js/jquery.json.js"></script>
<script type="text/javascript" src="js/QTransform.js"></script>
<script type="text/javascript" src="js/jquery-ui.js"></script>
<script type="text/javascript" src="js/cah.js"></script>
@ -387,9 +388,9 @@ HttpSession hSession = request.getSession(true);
<label id="score_limit_template_label" for="score_limit_template">Score limit:</label>
<select id="score_limit_template" class="score_limit">
<%
for (int i = Game.MIN_SCORE_LIMIT; i <= Game.MAX_SCORE_LIMIT; i++) {
for (int i = GameOptions.MIN_SCORE_LIMIT; i <= GameOptions.MAX_SCORE_LIMIT; i++) {
%>
<option <%= i == Game.DEFAULT_SCORE_LIMIT ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
<option <%= i == GameOptions.DEFAULT_SCORE_LIMIT ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
<% } %>
</select>
<br/>
@ -397,9 +398,9 @@ HttpSession hSession = request.getSession(true);
<select id="player_limit_template" class="player_limit"
aria-label="Player limit. Having more than 10 players may cause issues both for screen readers and traditional browsers.">
<%
for (int i = Game.MIN_PLAYER_LIMIT; i <= Game.MAX_PLAYER_LIMIT; i++) {
for (int i = GameOptions.MIN_PLAYER_LIMIT; i <= GameOptions.MAX_PLAYER_LIMIT; i++) {
%>
<option <%= i == Game.DEFAULT_PLAYER_LIMIT ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
<option <%= i == GameOptions.DEFAULT_PLAYER_LIMIT ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
<% } %>
</select>
Having more than 10 players may get cramped!
@ -408,9 +409,9 @@ HttpSession hSession = request.getSession(true);
<select id="spectator_limit_template" class="spectator_limit"
aria-label="Spectator limit.">
<%
for (int i = Game.MIN_SPECTATOR_LIMIT; i <= Game.MAX_SPECTATOR_LIMIT; i++) {
for (int i = GameOptions.MIN_SPECTATOR_LIMIT; i <= GameOptions.MAX_SPECTATOR_LIMIT; i++) {
%>
<option <%= i == Game.DEFAULT_SPECTATOR_LIMIT ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
<option <%= i == GameOptions.DEFAULT_SPECTATOR_LIMIT ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
<% } %>
</select>
Spectators can watch and chat, but not actually play. Not even as Czar.
@ -433,9 +434,9 @@ HttpSession hSession = request.getSession(true);
<label id="blanks_limit_label" title="Blank cards allow a player to type in their own answer.">
Also include <select id="blanks_limit_template" class="blanks_limit">
<%
for (int i = Game.MIN_BLANK_CARD_LIMIT; i <= Game.MAX_BLANK_CARD_LIMIT; i++) {
for (int i = GameOptions.MIN_BLANK_CARD_LIMIT; i <= GameOptions.MAX_BLANK_CARD_LIMIT; i++) {
%>
<option <%= i == Game.DEFAULT_BLANK_CARD_LIMIT ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
<option <%= i == GameOptions.DEFAULT_BLANK_CARD_LIMIT ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
<% } %>
</select> blank white cards.
</label>

View File

@ -134,46 +134,13 @@ cah.ajax.Builder.prototype.withCardId = function(cardId) {
};
/**
* @param {Array}
* cardSets List of card set ids to use in the request.
* @param {Object}
* options Game options to use in the request.
* @returns {cah.ajax.Builder} This object.
*/
cah.ajax.Builder.prototype.withCardSets = function(cardSets) {
cah.ajax.Builder.prototype.withGameOptions = function(options) {
this.assertNotExecuted_();
this.data[cah.$.AjaxRequest.CARD_SETS] = cardSets.join(',');
return this;
};
/**
* @param {number}
* playerLimit Player limit field to use in the request.
* @returns {cah.ajax.Builder} This object.
*/
cah.ajax.Builder.prototype.withPlayerLimit = function(playerLimit) {
this.assertNotExecuted_();
this.data[cah.$.AjaxRequest.PLAYER_LIMIT] = 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.
* @returns {cah.ajax.Builder} This object.
*/
cah.ajax.Builder.prototype.withScoreLimit = function(scoreLimit) {
this.assertNotExecuted_();
this.data[cah.$.AjaxRequest.SCORE_LIMIT] = scoreLimit;
this.data[cah.$.AjaxRequest.GAME_OPTIONS] = $.toJSON(options);
return this;
};
@ -188,28 +155,6 @@ cah.ajax.Builder.prototype.withPassword = function(password) {
return this;
};
/**
* @param {number}
* blanksLimit Blanks limit field to use in the request.
* @returns {cah.ajax.Builder} This object.
*/
cah.ajax.Builder.prototype.withBlanksLimit = function(blanksLimit) {
this.assertNotExecuted_();
this.data[cah.$.AjaxRequest.BLANKS_LIMIT] = blanksLimit;
return this;
};
/**
* @param {boolean}
* useTimer Whether or not the game should use the idle timer.
* @returns {cah.ajax.Builder} This object.
*/
cah.ajax.Builder.prototype.withUseTimer = function(useTimer) {
this.assertNotExecuted_();
this.data[cah.$.AjaxRequest.USE_TIMER] = useTimer;
return this;
};
/**
* @param {boolean}
* wall Whether or not this is a warn-all ("wall").

View File

@ -34,20 +34,15 @@ cah.$.AjaxRequest = function() {
};
cah.$.AjaxRequest.prototype.dummyForAutocomplete = undefined;
cah.$.AjaxRequest.WALL = "wall";
cah.$.AjaxRequest.USE_TIMER = "ut";
cah.$.AjaxRequest.CARD_SETS = "css";
cah.$.AjaxRequest.MESSAGE = "m";
cah.$.AjaxRequest.CARD_ID = "cid";
cah.$.AjaxRequest.GAME_ID = "gid";
cah.$.AjaxRequest.EMOTE = "me";
cah.$.AjaxRequest.OP = "o";
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.BLANKS_LIMIT = "bl";
cah.$.AjaxRequest.SPECTATOR_LIMIT = "vL";
cah.$.AjaxRequest.GAME_OPTIONS = "go";
cah.$.AjaxRequest.SERIAL = "s";
cah.$.AjaxRequest.PASSWORD = "pw";
cah.$.AjaxRequest.OP = "o";
cah.$.AjaxRequest.NICKNAME = "n";
cah.$.AjaxResponse = function() {
// Dummy constructor to make Eclipse auto-complete.
@ -59,6 +54,7 @@ cah.$.AjaxResponse.GAME_ID = "gid";
cah.$.AjaxResponse.HAND = "h";
cah.$.AjaxResponse.PLAYER_INFO = "pi";
cah.$.AjaxResponse.BLACK_CARD = "bc";
cah.$.AjaxResponse.GAME_OPTIONS = "go";
cah.$.AjaxResponse.IN_PROGRESS = "ip";
cah.$.AjaxResponse.GAMES = "gl";
cah.$.AjaxResponse.NICKNAME = "n";
@ -188,16 +184,22 @@ cah.$.GameInfo.prototype.dummyForAutocomplete = undefined;
cah.$.GameInfo.HOST = "H";
cah.$.GameInfo.STATE = "S";
cah.$.GameInfo.PLAYERS = "P";
cah.$.GameInfo.USE_TIMER = "ut";
cah.$.GameInfo.BLANKS_LIMIT = "bl";
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";
cah.$.GameInfo.GAME_OPTIONS = "go";
cah.$.GameInfo.HAS_PASSWORD = "hp";
cah.$.GameInfo.SCORE_LIMIT = "sl";
cah.$.GameOptionData = function() {
// Dummy constructor to make Eclipse auto-complete.
};
cah.$.GameOptionData.prototype.dummyForAutocomplete = undefined;
cah.$.GameOptionData.USE_TIMER = "ut";
cah.$.GameOptionData.CARD_SETS = "css";
cah.$.GameOptionData.BLANKS_LIMIT = "bl";
cah.$.GameOptionData.SPECTATOR_LIMIT = "vL";
cah.$.GameOptionData.PLAYER_LIMIT = "pL";
cah.$.GameOptionData.PASSWORD = "pw";
cah.$.GameOptionData.SCORE_LIMIT = "sl";
cah.$.GamePlayerInfo = function() {
// Dummy constructor to make Eclipse auto-complete.

View File

@ -771,6 +771,7 @@ cah.Game.prototype.insertIntoDocument = function() {
*/
cah.Game.prototype.updateGameStatus = function(data) {
var gameInfo = data[cah.$.AjaxResponse.GAME_INFO];
var options = gameInfo[cah.$.AjaxResponse.GAME_OPTIONS];
this.host_ = gameInfo[cah.$.GameInfo.HOST];
if (this.host_ == cah.nickname && gameInfo[cah.$.GameInfo.STATE] == cah.$.GameState.LOBBY) {
@ -791,22 +792,22 @@ cah.Game.prototype.updateGameStatus = function(data) {
this.hideOptions_();
}
$(".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]) {
$(".score_limit", this.optionsElement_).val(options[cah.$.GameOptionData.SCORE_LIMIT]);
$(".player_limit", this.optionsElement_).val(options[cah.$.GameOptionData.PLAYER_LIMIT]);
$(".spectator_limit", this.optionsElement_).val(options[cah.$.GameOptionData.SPECTATOR_LIMIT]);
$(".game_password", this.optionsElement_).val(options[cah.$.GameOptionData.PASSWORD]);
if (options[cah.$.GameOptionData.USE_TIMER]) {
$(".use_timer", this.optionsElement_).attr("checked", "checked");
} else {
$(".use_timer", this.optionsElement_).removeAttr("checked");
}
var cardSetIds = gameInfo[cah.$.GameInfo.CARD_SETS];// .split(',');
var cardSetIds = options[cah.$.GameOptionData.CARD_SETS];// .split(',');
$(".card_set", this.optionsElement_).removeAttr("checked");
for ( var key in cardSetIds) {
var cardSetId = cardSetIds[key];
$("#card_set_" + this.id_ + "_" + cardSetId, this.optionsElement_).attr("checked", "checked");
}
$(".blanks_limit", this.optionsElement_).val(gameInfo[cah.$.GameInfo.BLANKS_LIMIT]);
$(".blanks_limit", this.optionsElement_).val(options[cah.$.GameOptionData.BLANKS_LIMIT]);
var playerInfos = data[cah.$.AjaxResponse.PLAYER_INFO];
for ( var index in playerInfos) {
@ -1417,13 +1418,16 @@ cah.Game.prototype.optionChanged_ = function(e) {
for ( var i = 0; i < selectedCardSets.length; i++) {
cardSetIds.push(selectedCardSets[i].value);
}
cah.Ajax.build(cah.$.AjaxOperation.CHANGE_GAME_OPTIONS).withGameId(this.id_).withScoreLimit(
$(".score_limit", this.optionsElement_).val()).withPlayerLimit(
$(".player_limit", this.optionsElement_).val()).withSpectatorLimit(
$(".spectator_limit", this.optionsElement_).val()).withCardSets(cardSetIds).withPassword(
$(".game_password", this.optionsElement_).val()).withBlanksLimit(
$(".blanks_limit", this.optionsElement_).val()).withUseTimer(
!!$('.use_timer', this.optionsElement_).attr('checked')).run();
var options = {};
options[cah.$.GameOptionData.CARD_SETS] = cardSetIds.join(',');
options[cah.$.GameOptionData.SCORE_LIMIT] = $(".score_limit", this.optionsElement_).val();
options[cah.$.GameOptionData.PLAYER_LIMIT] = $(".player_limit", this.optionsElement_).val();
options[cah.$.GameOptionData.SPECTATOR_LIMIT] = $(".spectator_limit", this.optionsElement_).val();
options[cah.$.GameOptionData.PASSWORD] = $(".game_password", this.optionsElement_).val();
options[cah.$.GameOptionData.BLANKS_LIMIT] = $(".blanks_limit", this.optionsElement_).val();
options[cah.$.GameOptionData.USE_TIMER] = !!$('.use_timer', this.optionsElement_).attr('checked');
cah.Ajax.build(cah.$.AjaxOperation.CHANGE_GAME_OPTIONS).withGameId(this.id_).withGameOptions(options).run();
};
/**

View File

@ -228,6 +228,8 @@ cah.GameListLobby = function(parentElem, data) {
* @private
*/
this.data_ = data;
var options = data[cah.$.GameInfo.GAME_OPTIONS];
this.element_.id = "gamelist_lobby_" + this.id_;
$(parentElem).append(this.element_);
@ -241,14 +243,15 @@ cah.GameListLobby = function(parentElem, data) {
$(".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_max_players", this.element_).text(options[cah.$.GameOptionData.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]);
$(".gamelist_lobby_max_spectators", this.element_).text(options[cah.$.GameOptionData.SPECTATOR_LIMIT]);
$(".gamelist_lobby_goal", this.element_).text(options[cah.$.GameOptionData.SCORE_LIMIT]);
var cardSets = options[cah.$.GameOptionData.CARD_SETS];
var cardSetNames = [];
data[cah.$.GameInfo.CARD_SETS].sort();
for ( var key in data[cah.$.GameInfo.CARD_SETS]) {
var cardSetId = data[cah.$.GameInfo.CARD_SETS][key];
cardSets.sort();
for (var key in cardSets) {
var cardSetId = cardSets[key];
cardSetNames.push(cah.CardSet.list[cardSetId].getName());
}
$(".gamelist_lobby_cardset", this.element_).html(cardSetNames.join(', '));
@ -260,11 +263,10 @@ 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, 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") + ". "
+ options[cah.$.GameOptionData.PLAYER_LIMIT] + " players, and " + data[cah.$.GameInfo.SPECTATORS].length
+ " of " + options[cah.$.GameOptionData.SPECTATOR_LIMIT] + "spectators. " + statusMessage + ". Goal is "
+ options[cah.$.GameOptionData.SCORE_LIMIT] + " Awesome Points. Using " + cardSetNames.length
+ " card set" + (cardSetNames.length == 1 ? "" : "s") + ". "
+ (data[cah.$.GameInfo.HAS_PASSWORD] ? "Has" : "Does not have") + " a password.");
};

View File

@ -0,0 +1,199 @@
/**
* jQuery JSON plugin 2.4.0
*
* @author Brantley Harris, 2009-2011
* @author Timo Tijhof, 2011-2012
* @source This plugin is heavily influenced by MochiKit's serializeJSON, which is
* copyrighted 2005 by Bob Ippolito.
* @source Brantley Harris wrote this plugin. It is based somewhat on the JSON.org
* website's http://www.json.org/json2.js, which proclaims:
* "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that
* I uphold.
* @license MIT License <http://www.opensource.org/licenses/mit-license.php>
*/
(function ($) {
'use strict';
var escape = /["\\\x00-\x1f\x7f-\x9f]/g,
meta = {
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
hasOwn = Object.prototype.hasOwnProperty;
/**
* jQuery.toJSON
* Converts the given argument into a JSON representation.
*
* @param o {Mixed} The json-serializable *thing* to be converted
*
* If an object has a toJSON prototype, that will be used to get the representation.
* Non-integer/string keys are skipped in the object, as are keys that point to a
* function.
*
*/
$.toJSON = typeof JSON === 'object' && JSON.stringify ? JSON.stringify : function (o) {
if (o === null) {
return 'null';
}
var pairs, k, name, val,
type = $.type(o);
if (type === 'undefined') {
return undefined;
}
// Also covers instantiated Number and Boolean objects,
// which are typeof 'object' but thanks to $.type, we
// catch them here. I don't know whether it is right
// or wrong that instantiated primitives are not
// exported to JSON as an {"object":..}.
// We choose this path because that's what the browsers did.
if (type === 'number' || type === 'boolean') {
return String(o);
}
if (type === 'string') {
return $.quoteString(o);
}
if (typeof o.toJSON === 'function') {
return $.toJSON(o.toJSON());
}
if (type === 'date') {
var month = o.getUTCMonth() + 1,
day = o.getUTCDate(),
year = o.getUTCFullYear(),
hours = o.getUTCHours(),
minutes = o.getUTCMinutes(),
seconds = o.getUTCSeconds(),
milli = o.getUTCMilliseconds();
if (month < 10) {
month = '0' + month;
}
if (day < 10) {
day = '0' + day;
}
if (hours < 10) {
hours = '0' + hours;
}
if (minutes < 10) {
minutes = '0' + minutes;
}
if (seconds < 10) {
seconds = '0' + seconds;
}
if (milli < 100) {
milli = '0' + milli;
}
if (milli < 10) {
milli = '0' + milli;
}
return '"' + year + '-' + month + '-' + day + 'T' +
hours + ':' + minutes + ':' + seconds +
'.' + milli + 'Z"';
}
pairs = [];
if ($.isArray(o)) {
for (k = 0; k < o.length; k++) {
pairs.push($.toJSON(o[k]) || 'null');
}
return '[' + pairs.join(',') + ']';
}
// Any other object (plain object, RegExp, ..)
// Need to do typeof instead of $.type, because we also
// want to catch non-plain objects.
if (typeof o === 'object') {
for (k in o) {
// Only include own properties,
// Filter out inherited prototypes
if (hasOwn.call(o, k)) {
// Keys must be numerical or string. Skip others
type = typeof k;
if (type === 'number') {
name = '"' + k + '"';
} else if (type === 'string') {
name = $.quoteString(k);
} else {
continue;
}
type = typeof o[k];
// Invalid values like these return undefined
// from toJSON, however those object members
// shouldn't be included in the JSON string at all.
if (type !== 'function' && type !== 'undefined') {
val = $.toJSON(o[k]);
pairs.push(name + ':' + val);
}
}
}
return '{' + pairs.join(',') + '}';
}
};
/**
* jQuery.evalJSON
* Evaluates a given json string.
*
* @param str {String}
*/
$.evalJSON = typeof JSON === 'object' && JSON.parse ? JSON.parse : function (str) {
/*jshint evil: true */
return eval('(' + str + ')');
};
/**
* jQuery.secureEvalJSON
* Evals JSON in a way that is *more* secure.
*
* @param str {String}
*/
$.secureEvalJSON = typeof JSON === 'object' && JSON.parse ? JSON.parse : function (str) {
var filtered =
str
.replace(/\\["\\\/bfnrtu]/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
if (/^[\],:{}\s]*$/.test(filtered)) {
/*jshint evil: true */
return eval('(' + str + ')');
}
throw new SyntaxError('Error parsing JSON, source is not valid.');
};
/**
* jQuery.quoteString
* Returns a string-repr of a string, escaping quotes intelligently.
* Mostly a support function for toJSON.
* Examples:
* >>> jQuery.quoteString('apple')
* "apple"
*
* >>> jQuery.quoteString('"Where are we going?", she asked.')
* "\"Where are we going?\", she asked."
*/
$.quoteString = function (str) {
if (str.match(escape)) {
return '"' + str.replace(escape, function (a) {
var c = meta[a];
if (typeof c === 'string') {
return c;
}
c = a.charCodeAt();
return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
}) + '"';
}
return '"' + str + '"';
};
}(jQuery));

View File

@ -208,19 +208,14 @@ public class Constants {
*/
public enum AjaxRequest {
CARD_ID("cid"),
CARD_SETS("css"),
EMOTE("me"),
GAME_ID("gid"),
GAME_OPTIONS("go"),
MESSAGE("m"),
NICKNAME("n"),
OP("o"),
PASSWORD("pw"),
PLAYER_LIMIT("pL"),
SPECTATOR_LIMIT("vL"),
SCORE_LIMIT("sl"),
BLANKS_LIMIT("bl"),
SERIAL("s"),
USE_TIMER("ut"),
WALL("wall");
private final String field;
@ -242,13 +237,14 @@ public class Constants {
BLACK_CARD("bc"),
@DuplicationAllowed
CARD_ID(AjaxRequest.CARD_ID),
@DuplicationAllowed
CARD_SETS(AjaxRequest.CARD_SETS),
CARD_SETS("css"),
ERROR("e"),
ERROR_CODE("ec"),
@DuplicationAllowed
GAME_ID(AjaxRequest.GAME_ID),
GAME_INFO("gi"),
@DuplicationAllowed
GAME_OPTIONS(AjaxRequest.GAME_OPTIONS),
GAMES("gl"),
HAND("h"),
/**
@ -601,27 +597,15 @@ public class Constants {
* Fields for information about a game.
*/
public enum GameInfo {
@DuplicationAllowed
CARD_SETS(AjaxRequest.CARD_SETS),
HAS_PASSWORD("hp"),
HOST("H"),
@DuplicationAllowed
ID(AjaxRequest.GAME_ID),
@DuplicationAllowed
PASSWORD(AjaxRequest.PASSWORD),
@DuplicationAllowed
PLAYER_LIMIT(AjaxRequest.PLAYER_LIMIT),
GAME_OPTIONS(AjaxRequest.GAME_OPTIONS),
HAS_PASSWORD("hp"),
PLAYERS("P"),
@DuplicationAllowed
SPECTATOR_LIMIT(AjaxRequest.SPECTATOR_LIMIT),
SPECTATORS("V"),
@DuplicationAllowed
SCORE_LIMIT(AjaxRequest.SCORE_LIMIT),
@DuplicationAllowed
BLANKS_LIMIT(AjaxRequest.BLANKS_LIMIT),
STATE("S"),
@DuplicationAllowed
USE_TIMER(AjaxRequest.USE_TIMER);
STATE("S");
private final String key;
@ -639,6 +623,36 @@ public class Constants {
}
}
/**
* Fields for options about a game.
*/
public enum GameOptionData {
BLANKS_LIMIT("bl"),
@DuplicationAllowed
CARD_SETS(AjaxResponse.CARD_SETS),
@DuplicationAllowed
PASSWORD(AjaxRequest.PASSWORD),
PLAYER_LIMIT("pL"),
SPECTATOR_LIMIT("vL"),
SCORE_LIMIT("sl"),
USE_TIMER("ut");
private final String key;
GameOptionData(final String key) {
this.key = key;
}
GameOptionData(final Enum<?> key) {
this.key = key.toString();
}
@Override
public String toString() {
return key;
}
}
/**
* Keys for the information about players in a game.
*/

View File

@ -0,0 +1,106 @@
/**
* 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;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
/**
* Wrap around an {@code JSONObject}, to allow parameters to be retrieved by enum value.
*
* @author Gavin Lambert (uecasm)
*/
public class JsonWrapper {
private final JSONObject json;
/**
* Create a new JsonWrapper.
*
* @param json
* The JSON text to parse.
*/
public JsonWrapper(final String json) {
this.json = (JSONObject) JSONValue.parse(json);
}
/**
* Returns the value of a property as an Object, or null if the property does not exist.
*
* @param key
* Property to get (should be one of the defined Constants).
* @return Value of the property, or null if property does not exist.
*/
public Object getValue(final Object key) {
return json.get(key.toString());
}
/**
* Returns the value of a property as a String, or the default if the parameter does not exist.
*
* @param parameter
* Parameter to get.
* @param defaultValue
* The value to return if the parameter does not exist.
* @return Value of parameter, or the default if parameter does not exist.
*/
public String getString(final Object key, final String defaultValue) {
final Object value = getValue(key);
return (value == null) ? defaultValue : value.toString();
}
/**
* Returns the value of a property as an integer, or the default if the parameter does not exist.
*
* @param parameter
* Parameter to get.
* @param defaultValue
* The value to return if the parameter does not exist.
* @return Value of parameter, or the default if parameter does not exist.
*/
public int getInteger(final Object key, final int defaultValue) {
final Object value = getValue(key);
if (value instanceof Number) {
return ((Number) value).intValue();
}
return (value == null) ? defaultValue : Integer.parseInt(value.toString());
}
/**
* Returns the value of a property as a boolean, or the default if the parameter does not exist.
*
* @param parameter
* Parameter to get.
* @param defaultValue
* The value to return if the parameter does not exist.
* @return Value of parameter, or the default if parameter does not exist.
*/
public boolean getBoolean(final Object key, final boolean defaultValue) {
final Object value = getValue(key);
if (value instanceof Boolean) {
return (Boolean) value;
}
return (value == null) ? defaultValue : Boolean.parseBoolean(value.toString());
}
}

View File

@ -25,15 +25,12 @@ package net.socialgamer.cah.data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@ -87,20 +84,6 @@ import com.google.inject.Provider;
public class Game {
private static final Logger logger = Logger.getLogger(Game.class);
// TODO move these out to pyx.properties
public static final int MIN_SCORE_LIMIT = 4;
public static final int DEFAULT_SCORE_LIMIT = 8;
public static final int MAX_SCORE_LIMIT = 69;
public static final int MIN_PLAYER_LIMIT = 3;
public static final int DEFAULT_PLAYER_LIMIT = 10;
public static final int MAX_PLAYER_LIMIT = 20;
public static final int MIN_SPECTATOR_LIMIT = 0;
public static final int DEFAULT_SPECTATOR_LIMIT = 10;
public static final int MAX_SPECTATOR_LIMIT = 20;
public static final int MIN_BLANK_CARD_LIMIT = 0;
public static final int DEFAULT_BLANK_CARD_LIMIT = 0;
public static final int MAX_BLANK_CARD_LIMIT = 30;
private final int id;
/**
* All players present in the game.
@ -121,11 +104,7 @@ public class Game {
private final Object blackCardLock = new Object();
private WhiteDeck whiteDeck;
private GameState state;
// These are the default values new games get.
private int blanksInDeck = DEFAULT_BLANK_CARD_LIMIT;
private int playerLimit = DEFAULT_PLAYER_LIMIT;
private int spectatorLimit = DEFAULT_SPECTATOR_LIMIT;
private final GameOptions options = new GameOptions();
private int judgeIndex = 0;
@ -169,11 +148,6 @@ public class Game {
private volatile ScheduledFuture<?> lastScheduledFuture;
private final ScheduledThreadPoolExecutor globalTimer;
private int scoreGoal = DEFAULT_SCORE_LIMIT;
private final Set<Integer> cardSetIds = new HashSet<Integer>();
private String password = "";
private boolean useIdleTimer = true;
/**
* Create a new game.
*
@ -215,7 +189,7 @@ public class Game {
public void addPlayer(final User user) throws TooManyPlayersException, IllegalStateException {
logger.info(String.format("%s joined game %d.", user.toString(), id));
synchronized (players) {
if (playerLimit >= 3 && players.size() >= playerLimit) {
if (options.playerLimit >= 3 && players.size() >= options.playerLimit) {
throw new TooManyPlayersException();
}
// this will throw IllegalStateException if the user is already in a game, including this one.
@ -351,7 +325,7 @@ public class Game {
IllegalStateException {
logger.info(String.format("%s joined game %d as a spectator.", user.toString(), id));
synchronized (spectators) {
if (spectators.size() >= spectatorLimit) {
if (spectators.size() >= options.spectatorLimit) {
throw new TooManySpectatorsException();
}
// this will throw IllegalStateException if the user is already in a game, including this one.
@ -424,6 +398,29 @@ public class Game {
connectedUsers.broadcastToList(playersToUsers(), type, masterData);
}
/**
* Sends updated player information about a specific player to all players in the game.
*
* @param player
* The player whose information has been changed.
*/
public void notifyPlayerInfoChange(final Player player) {
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);
}
/**
* Sends updated game information to all players in the game.
*/
private void notifyGameOptionsChanged() {
final HashMap<ReturnableData, Object> data = getEventMap();
data.put(LongPollResponse.EVENT, LongPollEvent.GAME_OPTIONS_CHANGED.toString());
data.put(LongPollResponse.GAME_INFO, getInfo(true));
broadcastToPlayers(MessageType.GAME_EVENT, data);
}
/**
* @return The game's current state.
*/
@ -456,27 +453,12 @@ public class Game {
}
public String getPassword() {
return password;
return options.password;
}
public void updateGameSettings(final int newScoreGoal, final int newMaxPlayers,
final int newMaxSpectators, final Collection<Integer> newCardSetIds, final int newMaxBlanks,
final String newPassword, final boolean newUseTimer) {
this.scoreGoal = newScoreGoal;
this.playerLimit = newMaxPlayers;
this.spectatorLimit = newMaxSpectators;
synchronized (this.cardSetIds) {
this.cardSetIds.clear();
this.cardSetIds.addAll(newCardSetIds);
}
this.blanksInDeck = newMaxBlanks;
this.password = newPassword;
this.useIdleTimer = newUseTimer;
final HashMap<ReturnableData, Object> data = getEventMap();
data.put(LongPollResponse.EVENT, LongPollEvent.GAME_OPTIONS_CHANGED.toString());
data.put(LongPollResponse.GAME_INFO, getInfo(true));
broadcastToPlayers(MessageType.GAME_EVENT, data);
public void updateGameSettings(final GameOptions newOptions) {
this.options.update(newOptions);
notifyGameOptionsChanged();
}
/**
@ -510,20 +492,8 @@ public class Game {
}
info.put(GameInfo.HOST, host.getUser().getNickname());
info.put(GameInfo.STATE, state.toString());
final List<Integer> cardSetIdsCopy;
synchronized (this.cardSetIds) {
cardSetIdsCopy = new ArrayList<Integer>(this.cardSetIds);
}
info.put(GameInfo.CARD_SETS, cardSetIdsCopy);
info.put(GameInfo.BLANKS_LIMIT, blanksInDeck);
info.put(GameInfo.PLAYER_LIMIT, playerLimit);
info.put(GameInfo.SPECTATOR_LIMIT, spectatorLimit);
info.put(GameInfo.SCORE_LIMIT, scoreGoal);
info.put(GameInfo.USE_TIMER, useIdleTimer);
if (includePassword) {
info.put(GameInfo.PASSWORD, password);
}
info.put(GameInfo.HAS_PASSWORD, password != null && !password.equals(""));
info.put(GameInfo.GAME_OPTIONS, options.serialize(includePassword));
info.put(GameInfo.HAS_PASSWORD, options.password != null && !options.password.equals(""));
final Player[] playersCopy = players.toArray(new Player[players.size()]);
final List<String> playerNames = new ArrayList<String>(playersCopy.length);
@ -632,7 +602,7 @@ public class Game {
playerStatus = GamePlayerStatus.JUDGE;
}
// TODO win-by-x
else if (player.getScore() >= scoreGoal) {
else if (player.getScore() >= options.scoreGoal) {
playerStatus = GamePlayerStatus.WINNER;
} else {
playerStatus = GamePlayerStatus.IDLE;
@ -669,16 +639,17 @@ public class Game {
if (started) {
logger.info(String.format("Starting game %d with card sets %s, %d blanks, %d max players, " +
"%d max spectators, %d score limit, players %s.",
id, cardSetIds, blanksInDeck, playerLimit, spectatorLimit, scoreGoal, players));
id, options.cardSetIds, options.blanksInDeck, options.playerLimit,
options.spectatorLimit, options.scoreGoal, players));
// do this stuff outside the players lock; they will lock players again later for much less
// time, and not at the same time as trying to lock users, which has caused deadlocks
synchronized (cardSetIds) {
synchronized (options.cardSetIds) {
Session session = null;
try {
session = sessionProvider.get();
@SuppressWarnings("unchecked")
final List<CardSet> cardSets = session.createQuery("from CardSet where id in (:ids)")
.setParameterList("ids", cardSetIds).list();
.setParameterList("ids", options.cardSetIds).list();
blackDeck = new BlackDeck(cardSets);
whiteDeck = new WhiteDeck(cardSets, blanksInDeck);
@ -698,8 +669,8 @@ public class Game {
}
public boolean hasBaseDeck() {
synchronized (cardSetIds) {
if (cardSetIds.isEmpty()) {
synchronized (options.cardSetIds) {
if (options.cardSetIds.isEmpty()) {
return false;
}
@ -708,7 +679,7 @@ public class Game {
session = sessionProvider.get();
final Number baseDeckCount = (Number) session
.createQuery("select count(*) from CardSet where id in (:ids) and base_deck = true")
.setParameterList("ids", cardSetIds).uniqueResult();
.setParameterList("ids", options.cardSetIds).uniqueResult();
return baseDeckCount.intValue() > 0;
} catch (final Exception e) {
@ -779,7 +750,7 @@ public class Game {
}
// Perhaps figure out a better way to do this...
final int playTimer = useIdleTimer ? PLAY_TIMEOUT_BASE
final int playTimer = options.useIdleTimer ? PLAY_TIMEOUT_BASE
+ (PLAY_TIMEOUT_PER_CARD * blackCard.getPick()) : Integer.MAX_VALUE;
final HashMap<ReturnableData, Object> data = getEventMap();
@ -971,7 +942,7 @@ public class Game {
state = GameState.JUDGING;
// Perhaps figure out a better way to do this...
final int judgeTimer = useIdleTimer ? JUDGE_TIMEOUT_BASE
final int judgeTimer = options.useIdleTimer ? JUDGE_TIMEOUT_BASE
+ (JUDGE_TIMEOUT_PER_CARD * playedCards.size() * blackCard.getPick()) : Integer.MAX_VALUE;
final HashMap<ReturnableData, Object> data = getEventMap();
@ -1364,19 +1335,6 @@ public class Game {
}
}
/**
* Sends updated player information about a specific player to all players in the game.
*
* @param player
* The player whose information has been changed.
*/
public void notifyPlayerInfoChange(final Player player) {
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);
}
/**
* The judge has selected a card. The {@code cardId} passed in may be any white cards's ID for
* black cards that have multiple selection, however only the first card in the set's ID will be
@ -1426,7 +1384,7 @@ public class Game {
synchronized (roundTimerLock) {
final SafeTimerTask task;
// TODO win-by-x option
if (cardPlayer.getScore() >= scoreGoal) {
if (cardPlayer.getScore() >= options.scoreGoal) {
task = new SafeTimerTask() {
@Override
public void process() {

View File

@ -0,0 +1,138 @@
/**
* 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.data;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import net.socialgamer.cah.Constants.GameOptionData;
import net.socialgamer.cah.JsonWrapper;
/**
* Options for an individual game.
*
* @author Gavin Lambert (uecasm)
*/
public class GameOptions {
// TODO move these out to pyx.properties
public static final int MIN_SCORE_LIMIT = 4;
public static final int DEFAULT_SCORE_LIMIT = 8;
public static final int MAX_SCORE_LIMIT = 69;
public static final int MIN_PLAYER_LIMIT = 3;
public static final int DEFAULT_PLAYER_LIMIT = 10;
public static final int MAX_PLAYER_LIMIT = 20;
public static final int MIN_SPECTATOR_LIMIT = 0;
public static final int DEFAULT_SPECTATOR_LIMIT = 10;
public static final int MAX_SPECTATOR_LIMIT = 20;
public static final int MIN_BLANK_CARD_LIMIT = 0;
public static final int DEFAULT_BLANK_CARD_LIMIT = 0;
public static final int MAX_BLANK_CARD_LIMIT = 30;
// These are the default values new games get.
public int blanksInDeck = DEFAULT_BLANK_CARD_LIMIT;
public int playerLimit = DEFAULT_PLAYER_LIMIT;
public int spectatorLimit = DEFAULT_SPECTATOR_LIMIT;
public int scoreGoal = DEFAULT_SCORE_LIMIT;
public final Set<Integer> cardSetIds = new HashSet<Integer>();
public String password = "";
public boolean useIdleTimer = true;
/**
* Update the options in-place (so that the Game doesn't need more locks).
*
* @param newOptions
* The new options to use.
*/
public void update(final GameOptions newOptions) {
this.scoreGoal = newOptions.scoreGoal;
this.playerLimit = newOptions.playerLimit;
this.spectatorLimit = newOptions.spectatorLimit;
synchronized (this.cardSetIds) {
this.cardSetIds.clear();
this.cardSetIds.addAll(newOptions.cardSetIds);
}
this.blanksInDeck = newOptions.blanksInDeck;
this.password = newOptions.password;
this.useIdleTimer = newOptions.useIdleTimer;
}
/**
* Get the options in a form that can be sent to clients.
*
* @param includePassword
* Include the actual password with the information. This should only be
* sent to people in the game.
* @return This game's general information: ID, host, state, player list, etc.
*/
public Map<GameOptionData, Object> serialize(final boolean includePassword) {
final Map<GameOptionData, Object> info = new HashMap<GameOptionData, Object>();
info.put(GameOptionData.CARD_SETS, cardSetIds);
info.put(GameOptionData.BLANKS_LIMIT, blanksInDeck);
info.put(GameOptionData.PLAYER_LIMIT, playerLimit);
info.put(GameOptionData.SPECTATOR_LIMIT, spectatorLimit);
info.put(GameOptionData.SCORE_LIMIT, scoreGoal);
info.put(GameOptionData.USE_TIMER, useIdleTimer);
if (includePassword) {
info.put(GameOptionData.PASSWORD, password);
}
return info;
}
public static GameOptions deserialize(final String text) {
final GameOptions options = new GameOptions();
if (text == null || text.isEmpty()) {
return options;
}
final JsonWrapper json = new JsonWrapper(text);
final String[] cardSetsParsed = json.getString(GameOptionData.CARD_SETS, "").split(",");
for (final String cardSetId : cardSetsParsed) {
if (!cardSetId.isEmpty()) {
options.cardSetIds.add(Integer.parseInt(cardSetId));
}
}
options.blanksInDeck = Math.max(MIN_BLANK_CARD_LIMIT, Math.min(MAX_BLANK_CARD_LIMIT,
json.getInteger(GameOptionData.BLANKS_LIMIT, options.blanksInDeck)));
options.playerLimit = Math.max(MIN_PLAYER_LIMIT, Math.min(MAX_PLAYER_LIMIT,
json.getInteger(GameOptionData.PLAYER_LIMIT, options.playerLimit)));
options.spectatorLimit = Math.max(MIN_SPECTATOR_LIMIT, Math.min(MAX_SPECTATOR_LIMIT,
json.getInteger(GameOptionData.SPECTATOR_LIMIT, options.spectatorLimit)));
options.scoreGoal = Math.max(MIN_SCORE_LIMIT, Math.min(MAX_SCORE_LIMIT,
json.getInteger(GameOptionData.SCORE_LIMIT, options.scoreGoal)));
options.useIdleTimer = json.getBoolean(GameOptionData.USE_TIMER, options.useIdleTimer);
options.password = json.getString(GameOptionData.PASSWORD, options.password);
return options;
}
}

View File

@ -1,9 +1,7 @@
package net.socialgamer.cah.handlers;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpSession;
@ -15,6 +13,7 @@ import net.socialgamer.cah.Constants.ReturnableData;
import net.socialgamer.cah.RequestWrapper;
import net.socialgamer.cah.data.Game;
import net.socialgamer.cah.data.GameManager;
import net.socialgamer.cah.data.GameOptions;
import net.socialgamer.cah.data.User;
import com.google.inject.Inject;
@ -40,49 +39,17 @@ public class ChangeGameOptionHandler extends GameWithPlayerHandler {
return error(ErrorCode.ALREADY_STARTED);
} else {
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<Integer> cardSetIds = new HashSet<Integer>();
for (final String cardSetId : cardSetsParsed) {
if (!cardSetId.isEmpty()) {
cardSetIds.add(Integer.parseInt(cardSetId));
}
}
final int blanksLimit = Integer.parseInt(request.getParameter(AjaxRequest.BLANKS_LIMIT));
final String value = request.getParameter(AjaxRequest.GAME_OPTIONS);
final GameOptions options = GameOptions.deserialize(value);
final String oldPassword = game.getPassword();
String password = request.getParameter(AjaxRequest.PASSWORD);
if (password == null) {
password = "";
}
// We're not directly assigning this with Boolean.valueOf() because we want to default to
// true if it isn't specified, though that should never happen.
boolean useTimer = true;
final String useTimerString = request.getParameter(AjaxRequest.USE_TIMER);
if (null != useTimerString && !"".equals(useTimerString)) {
useTimer = Boolean.valueOf(useTimerString);
}
// make sure the new settings are in the valid range
if (scoreLimit < Game.MIN_SCORE_LIMIT || scoreLimit > Game.MAX_SCORE_LIMIT
|| playerLimit < Game.MIN_PLAYER_LIMIT || playerLimit > Game.MAX_PLAYER_LIMIT
|| spectatorLimit < Game.MIN_SPECTATOR_LIMIT
|| spectatorLimit > Game.MAX_SPECTATOR_LIMIT
|| blanksLimit < Game.MIN_BLANK_CARD_LIMIT || blanksLimit > Game.MAX_BLANK_CARD_LIMIT) {
return error(ErrorCode.BAD_REQUEST);
}
game.updateGameSettings(scoreLimit, playerLimit, spectatorLimit, cardSetIds, blanksLimit,
password, useTimer);
game.updateGameSettings(options);
// 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
if (!password.equals(oldPassword)) {
if (!game.getPassword().equals(oldPassword)) {
gameManager.broadcastGameListRefresh();
}
} catch (final NumberFormatException nfe) {
} catch (final Exception e) {
return error(ErrorCode.BAD_REQUEST);
}