New feature: blank cards
Add support for blank white cards, that allow the player to enter their own answer. (Game host can choose how many to include in the deck; the default is none.)
This commit is contained in:
parent
40c8c1e09b
commit
a67eedd4ba
|
@ -368,6 +368,14 @@ HttpSession hSession = request.getSession(true);
|
|||
<br/>
|
||||
Select any number of: <span class="extra_card_sets"></span>
|
||||
</fieldset>
|
||||
<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 = 0; i <= 10; i++) { %>
|
||||
<option <%= i == 0 ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
|
||||
<% } %>
|
||||
</select> blank white cards.
|
||||
</label>
|
||||
<br/>
|
||||
<br/>
|
||||
<label id="game_password_template_label" for="game_password_template">Game password:</label>
|
||||
<input type="text" id="game_password_template" class="game_password"
|
||||
|
|
|
@ -177,6 +177,17 @@ cah.ajax.Builder.prototype.withPassword = function(password) {
|
|||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number}
|
||||
* scoreLimit Score 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.
|
||||
|
|
|
@ -335,6 +335,15 @@ cah.card.WhiteCard = function(opt_faceUp, opt_id) {
|
|||
};
|
||||
cah.inherits(cah.card.WhiteCard, cah.card.BaseCard);
|
||||
|
||||
/**
|
||||
* Checks if this is a blank card.
|
||||
*
|
||||
* @returns True if this is a blank card.
|
||||
*/
|
||||
cah.card.WhiteCard.prototype.isBlankCard = function() {
|
||||
return this.getServerId() == 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
|
|
|
@ -31,17 +31,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.BLANKS_LIMIT = "bl";
|
||||
cah.$.AjaxRequest.SERIAL = "s";
|
||||
cah.$.AjaxRequest.PASSWORD = "pw";
|
||||
|
||||
cah.$.AjaxResponse = function() {
|
||||
// Dummy constructor to make Eclipse auto-complete.
|
||||
|
@ -178,6 +179,7 @@ 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.ID = "gid";
|
||||
cah.$.GameInfo.PLAYER_LIMIT = "pL";
|
||||
|
|
|
@ -95,6 +95,7 @@ cah.Game = function(id) {
|
|||
$("#game_fake_password_template", this.optionsElement_).attr("id", "game_fake_password_" + id);
|
||||
$("#game_hide_password_template", this.optionsElement_).attr("id", "game_hide_password_" + id);
|
||||
$("#use_timer_template", this.optionsElement_).attr("id", "use_timer_" + id);
|
||||
$("#blanks_limit_template", this.optionsElement_).attr("id", "blanks_limit_" + id);
|
||||
|
||||
for ( var key in cah.CardSet.byWeight) {
|
||||
/** @type {cah.CardSet} */
|
||||
|
@ -758,6 +759,7 @@ cah.Game.prototype.updateGameStatus = function(data) {
|
|||
var cardSetId = cardSetIds[key];
|
||||
$("#card_set_" + this.id_ + "_" + cardSetId, this.optionsElement_).attr("checked", "checked");
|
||||
}
|
||||
$(".blanks_limit", this.optionsElement_).val(gameInfo[cah.$.GameInfo.BLANKS_LIMIT]);
|
||||
|
||||
var playerInfos = data[cah.$.AjaxResponse.PLAYER_INFO];
|
||||
for ( var index in playerInfos) {
|
||||
|
@ -943,8 +945,17 @@ cah.Game.prototype.confirmClick_ = function() {
|
|||
}
|
||||
} else {
|
||||
if (this.handSelectedCard_ != null) {
|
||||
cah.Ajax.build(cah.$.AjaxOperation.PLAY_CARD).withGameId(this.id_).withCardId(
|
||||
if (this.handSelectedCard_.isBlankCard()) {
|
||||
// blank card
|
||||
var text = prompt("What would you like this card to say?", "");
|
||||
if (text == null || text == '') { return; }
|
||||
text = $("<div/>").text(text).html(); // html sanitise
|
||||
this.handSelectedCard_.setText(text);
|
||||
cah.Ajax.build(cah.$.AjaxOperation.PLAY_CARD).withGameId(this.id_).withCardId(0).withMessage(text).run();
|
||||
} else {
|
||||
cah.Ajax.build(cah.$.AjaxOperation.PLAY_CARD).withGameId(this.id_).withCardId(
|
||||
this.handSelectedCard_.getServerId()).run();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1246,7 +1257,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(
|
||||
$(".game_password", this.optionsElement_).val()).withUseTimer(
|
||||
$(".game_password", this.optionsElement_).val()).withBlanksLimit(
|
||||
$(".blanks_limit", this.optionsElement_).val()).withUseTimer(
|
||||
!!$('.use_timer', this.optionsElement_).attr('checked')).run();
|
||||
};
|
||||
|
||||
|
|
|
@ -209,6 +209,7 @@ public class Constants {
|
|||
PASSWORD("pw"),
|
||||
PLAYER_LIMIT("pL"),
|
||||
SCORE_LIMIT("sl"),
|
||||
BLANKS_LIMIT("bl"),
|
||||
SERIAL("s"),
|
||||
USE_TIMER("ut"),
|
||||
WALL("wall");
|
||||
|
@ -597,6 +598,8 @@ public class Constants {
|
|||
PLAYERS("P"),
|
||||
@DuplicationAllowed
|
||||
SCORE_LIMIT(AjaxRequest.SCORE_LIMIT),
|
||||
@DuplicationAllowed
|
||||
BLANKS_LIMIT(AjaxRequest.BLANKS_LIMIT),
|
||||
STATE("S"),
|
||||
@DuplicationAllowed
|
||||
USE_TIMER(AjaxRequest.USE_TIMER);
|
||||
|
|
|
@ -100,6 +100,7 @@ public class Game {
|
|||
private final Object blackCardLock = new Object();
|
||||
private WhiteDeck whiteDeck;
|
||||
private GameState state;
|
||||
private int maxBlanks = 0;
|
||||
private int maxPlayers = 6;
|
||||
private int judgeIndex = 0;
|
||||
private final static int ROUND_INTERMISSION = 8 * 1000;
|
||||
|
@ -366,13 +367,15 @@ public class Game {
|
|||
}
|
||||
|
||||
public void updateGameSettings(final int newScoreGoal, final int newMaxPlayers,
|
||||
final Set<CardSet> newCardSets, final String newPassword, final boolean newUseTimer) {
|
||||
final Set<CardSet> newCardSets, final int newMaxBlanks, final String newPassword,
|
||||
final boolean newUseTimer) {
|
||||
this.scoreGoal = newScoreGoal;
|
||||
this.maxPlayers = newMaxPlayers;
|
||||
synchronized (this.cardSets) {
|
||||
this.cardSets.clear();
|
||||
this.cardSets.addAll(newCardSets);
|
||||
}
|
||||
this.maxBlanks = newMaxBlanks;
|
||||
this.password = newPassword;
|
||||
this.useTimer = newUseTimer;
|
||||
|
||||
|
@ -421,6 +424,7 @@ public class Game {
|
|||
}
|
||||
}
|
||||
info.put(GameInfo.CARD_SETS, cardSetIds);
|
||||
info.put(GameInfo.BLANKS_LIMIT, maxBlanks);
|
||||
info.put(GameInfo.PLAYER_LIMIT, maxPlayers);
|
||||
info.put(GameInfo.SCORE_LIMIT, scoreGoal);
|
||||
info.put(GameInfo.USE_TIMER, useTimer);
|
||||
|
@ -564,7 +568,7 @@ public class Game {
|
|||
// time, and not at the same time as trying to lock users, which has caused deadlocks
|
||||
synchronized (cardSets) {
|
||||
blackDeck = new BlackDeck(cardSets);
|
||||
whiteDeck = new WhiteDeck(cardSets);
|
||||
whiteDeck = new WhiteDeck(cardSets, maxBlanks);
|
||||
}
|
||||
startNextRound();
|
||||
gameManager.broadcastGameListRefresh();
|
||||
|
@ -1187,11 +1191,13 @@ public class Game {
|
|||
* User playing the card.
|
||||
* @param cardId
|
||||
* ID of the card to play.
|
||||
* @param cardText
|
||||
* User text for a blank card. Ignored for normal cards.
|
||||
* @return An {@code ErrorCode} if the play was unsuccessful ({@code user} doesn't have the card,
|
||||
* {@code user} is the judge, etc.), or {@code null} if there was no error and the play
|
||||
* was successful.
|
||||
*/
|
||||
public ErrorCode playCard(final User user, final int cardId) {
|
||||
public ErrorCode playCard(final User user, final int cardId, final String cardText) {
|
||||
final Player player = getPlayerForUser(user);
|
||||
if (player != null) {
|
||||
player.resetSkipCount();
|
||||
|
@ -1206,6 +1212,11 @@ public class Game {
|
|||
final WhiteCard card = iter.next();
|
||||
if (card.getId() == cardId) {
|
||||
playCard = card;
|
||||
if (WhiteDeck.isBlankCard(card)) {
|
||||
playCard.setText(cardText);
|
||||
// note that since blank cards are indistinguishable to the server, we might end up
|
||||
// removing a different card than the client did. but this shouldn't break anything.
|
||||
}
|
||||
// remove the card from their hand. the client will also do so when we return
|
||||
// success, so no need to tell it to do so here.
|
||||
iter.remove();
|
||||
|
|
|
@ -49,12 +49,15 @@ public class WhiteDeck {
|
|||
/**
|
||||
* Create a new white card deck, loading the cards from the database and shuffling them.
|
||||
*/
|
||||
public WhiteDeck(final Set<CardSet> cardSets) {
|
||||
public WhiteDeck(final Set<CardSet> cardSets, final int numBlanks) {
|
||||
final Set<WhiteCard> allCards = new HashSet<WhiteCard>();
|
||||
for (final CardSet cardSet : cardSets) {
|
||||
allCards.addAll(cardSet.getWhiteCards());
|
||||
}
|
||||
deck = new ArrayList<WhiteCard>(allCards);
|
||||
for (int i = 0; i < numBlanks; i++) {
|
||||
deck.add(createBlankCard());
|
||||
}
|
||||
Collections.shuffle(deck);
|
||||
dealt = new LinkedList<WhiteCard>();
|
||||
discard = new ArrayList<WhiteCard>(deck.size());
|
||||
|
@ -85,7 +88,12 @@ public class WhiteDeck {
|
|||
*/
|
||||
public synchronized void discard(final WhiteCard card) {
|
||||
if (card != null) {
|
||||
discard.add(card);
|
||||
if (isBlankCard(card)) {
|
||||
// create a fresh blank card to ensure player text is cleared
|
||||
discard.add(createBlankCard());
|
||||
} else {
|
||||
discard.add(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,4 +105,28 @@ public class WhiteDeck {
|
|||
deck.addAll(0, discard);
|
||||
discard.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new blank card.
|
||||
*
|
||||
* @return A newly created blank card.
|
||||
*/
|
||||
private WhiteCard createBlankCard() {
|
||||
final WhiteCard blank = new WhiteCard();
|
||||
blank.setId(0);
|
||||
blank.setText("____");
|
||||
blank.setWatermark("____");
|
||||
return blank;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a particular card is a blank card.
|
||||
*
|
||||
* @param card
|
||||
* Card to check.
|
||||
* @return True if the card is a blank card.
|
||||
*/
|
||||
public static boolean isBlankCard(final WhiteCard card) {
|
||||
return card.getId() == 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ public class ChangeGameOptionHandler extends GameWithPlayerHandler {
|
|||
Integer.parseInt(cardSetId)));
|
||||
}
|
||||
}
|
||||
final int blanksLimit = Integer.parseInt(request.getParameter(AjaxRequest.BLANKS_LIMIT));
|
||||
String password = request.getParameter(AjaxRequest.PASSWORD);
|
||||
if (password == null) {
|
||||
password = "";
|
||||
|
@ -62,7 +63,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, cardSets, blanksLimit, password, useTimer);
|
||||
} catch (final NumberFormatException nfe) {
|
||||
return error(ErrorCode.BAD_REQUEST);
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@ import net.socialgamer.cah.data.Game;
|
|||
import net.socialgamer.cah.data.GameManager;
|
||||
import net.socialgamer.cah.data.User;
|
||||
|
||||
import org.apache.commons.lang3.StringEscapeUtils;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
|
||||
|
@ -69,8 +71,13 @@ public class PlayCardHandler extends GameWithPlayerHandler {
|
|||
} catch (final NumberFormatException nfe) {
|
||||
return error(ErrorCode.INVALID_CARD);
|
||||
}
|
||||
String text = request.getParameter(AjaxRequest.MESSAGE);
|
||||
if (text != null && text.contains("<")) {
|
||||
// somebody must be using a hacked client, because this should have been escaped already.
|
||||
text = StringEscapeUtils.escapeXml(text);
|
||||
}
|
||||
|
||||
final ErrorCode ec = game.playCard(user, cardId);
|
||||
final ErrorCode ec = game.playCard(user, cardId, text);
|
||||
if (ec != null) {
|
||||
return error(ec);
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue