/**
* 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 java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashSet;
import java.util.Set;
import net.socialgamer.cah.data.Game;
/**
* Constants needed on both the CAH server and client. This file is examined with reflection to
* produce a Javascript version for the client to use.
*
* All of the enums in here take a string in their constructor to define the over-the-wire value to
* be used to represent that enum value. This allows for verbose names while debugging, and short
* names to reduce traffic and latency, by only having to change it in one place for both the server
* and client.
*
* @author Andy Janata (ajanata@socialgamer.net)
*/
public class Constants {
public static final int CHAT_FLOOD_MESSAGE_COUNT = 4;
public static final int CHAT_FLOOD_TIME = 30 * 1000;
public static final int CHAT_MAX_LENGTH = 200;
@SuppressWarnings("serial")
public static final Set<String> ADMIN_IP_ADDRESSES = new HashSet<String>() {
{
add("0:0:0:0:0:0:0:1");
add("127.0.0.1");
// ajanata
add("107.218.154.202");
}
};
/**
* Enums that implement this interface are valid keys for data returned to clients.
*/
public interface ReturnableData {
}
/**
* Enums that implement this interface have a user-visible string associated with them.
*
* There presently is not support for localization, but the name fits.
*/
public interface Localizable {
/**
* @return The user-visible string that is associated with this enum value.
*/
public String getString();
}
/**
* Enums that implement this interface have two user-visible strings associated with them.
*
* There presently is not support for localization, but the name fits.
*/
public interface DoubleLocalizable {
/**
* @return The first user-visible string that is associated with this enum value.
*/
public String getString();
/**
* @return The second user-visible string that is associated with this enum value.
*/
public String getString2();
}
/**
* Reason why a client disconnected.
*/
public enum DisconnectReason {
/**
* The client was banned by the server administrator.
*/
BANNED("B&"),
/**
* The client made no user-caused requests within the timeout window.
*/
IDLE_TIMEOUT("it"),
/**
* The client was kicked by the server administrator.
*/
KICKED("k"),
/**
* The user clicked the "log out" button.
*/
MANUAL("man"),
/**
* The client failed to make any queries within the timeout window.
*/
PING_TIMEOUT("pt");
private final String reason;
DisconnectReason(final String reason) {
this.reason = reason;
}
@Override
public String toString() {
return reason;
}
}
/**
* The next thing the client should do during reconnect phase.
*
* Leaving these as longer strings as they are only used once per client.
*/
public enum ReconnectNextAction {
/**
* The client should load a game as part of the reconnect process.
*/
GAME("game"),
/**
* There is nothing for the client to reload, perhaps because they were not in any special
* state, or they are a new client.
*/
NONE("none");
private final String action;
ReconnectNextAction(final String action) {
this.action = action;
}
@Override
public String toString() {
return action;
}
}
/**
* Valid client request operations.
*/
public enum AjaxOperation {
ADMIN_SET_VERBOSE_LOG("svl"),
BAN("b"),
CARDCAST_ADD_CARDSET("cac"),
CARDCAST_LIST_CARDSETS("clc"),
CARDCAST_REMOVE_CARDSET("crc"),
CHANGE_GAME_OPTIONS("cgo"),
CHAT("c"),
CREATE_GAME("cg"),
FIRST_LOAD("fl"),
GAME_CHAT("GC"),
GAME_LIST("ggl"),
/**
* Get all cards for a particular game: black, hand, and round white cards.
*/
GET_CARDS("gc"),
GET_GAME_INFO("ggi"),
JOIN_GAME("jg"),
SPECTATE_GAME("vg"),
JUDGE_SELECT("js"),
KICK("K"),
LEAVE_GAME("lg"),
LOG_OUT("lo"),
/**
* Get the names of all clients connected to the server.
*/
NAMES("gn"),
PLAY_CARD("pc"),
REGISTER("r"),
SCORE("SC"),
START_GAME("sg"),
STOP_GAME("Sg");
private final String op;
AjaxOperation(final String op) {
this.op = op;
}
@Override
public String toString() {
return op;
}
}
/**
* Parameters for client requests.
*/
public enum AjaxRequest {
CARD_ID("cid"),
CARDCAST_ID("cci"),
EMOTE("me"),
GAME_ID("gid"),
GAME_OPTIONS("go"),
MESSAGE("m"),
NICKNAME("n"),
OP("o"),
PASSWORD("pw"),
SERIAL("s"),
WALL("wall");
private final String field;
AjaxRequest(final String field) {
this.field = field;
}
@Override
public String toString() {
return field;
}
}
/**
* Keys for client request responses.
*/
public enum AjaxResponse implements ReturnableData {
BLACK_CARD("bc"),
@DuplicationAllowed
CARD_ID(AjaxRequest.CARD_ID),
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"),
/**
* Whether this client is reconnecting or not.
*/
IN_PROGRESS("ip"),
MAX_GAMES("mg"),
NAMES("nl"),
/**
* Next thing that should be done in reconnect process. Used once, long string OK.
*/
NEXT("next"),
@DuplicationAllowed
NICKNAME(AjaxRequest.NICKNAME),
PLAYER_INFO("pi"),
@DuplicationAllowed
SERIAL(AjaxRequest.SERIAL),
WHITE_CARDS("wc");
private final String field;
AjaxResponse(final String field) {
this.field = field;
}
AjaxResponse(final Enum<?> field) {
this.field = field.toString();
}
@Override
public String toString() {
return field;
}
}
public enum ErrorInformation implements ReturnableData {
BLACK_CARDS_PRESENT("bcp"),
BLACK_CARDS_REQUIRED("bcr"),
WHITE_CARDS_PRESENT("wcp"),
WHITE_CARDS_REQUIRED("wcr");
private final String code;
ErrorInformation(final String code) {
this.code = code;
}
@Override
public String toString() {
return code;
}
}
/**
* Client request and long poll response errors.
*/
public enum ErrorCode implements Localizable {
ACCESS_DENIED("ad", "Access denied."),
ALREADY_STARTED("as", "The game has already started."),
ALREADY_STOPPED("aS", "The game has already stopped."),
BAD_OP("bo", "Invalid operation."),
BAD_REQUEST("br", "Bad request."),
@DuplicationAllowed
BANNED(DisconnectReason.BANNED, "Banned."),
CANNOT_JOIN_ANOTHER_GAME("cjag", "You cannot join another game."),
CARDCAST_CANNOT_FIND("ccf", "Cannot find Cardcast deck with given ID. If you just added this"
+ " deck to Cardcast, wait a few minutes and try again."),
CARDCAST_INVALID_ID("cii", "Invalid Cardcast ID. Must be exactly 5 characters."),
DO_NOT_HAVE_CARD("dnhc", "You don't have that card."),
GAME_FULL("gf", "That game is full. Join another."),
INVALID_CARD("ic", "Invalid card specified."),
INVALID_GAME("ig", "Invalid game specified."),
/**
* TODO this probably should be pulled in from a static inside the RegisterHandler.
*/
INVALID_NICK("in", "Nickname must contain only upper and lower case letters, " +
"numbers, or underscores, must be 3 to 30 characters long, and must not start with a " +
"number."),
/**
* TODO this probably should be pulled in from a static inside the ChatHandler.
*/
MESSAGE_TOO_LONG("mtl", "Messages cannot be longer than " + CHAT_MAX_LENGTH + " characters."),
NICK_IN_USE("niu", "Nickname is already in use."),
NO_CARD_SPECIFIED("ncs", "No card specified."),
NO_GAME_SPECIFIED("ngs", "No game specified."),
NO_MSG_SPECIFIED("nms", "No message specified."),
NO_NICK_SPECIFIED("nns", "No nickname specified."),
NO_SESSION("ns", "Session not detected. Make sure you have cookies enabled."),
NO_SUCH_USER("nsu", "No such user."),
NOT_ADMIN("na", "You are not an administrator."),
NOT_ENOUGH_CARDS("nec", "You must add card sets containing at least "
+ Game.MINIMUM_BLACK_CARDS + " black cards and " + Game.MINIMUM_WHITE_CARDS_PER_PLAYER
+ " times the player limit white cards."),
NOT_ENOUGH_PLAYERS("nep", "There are not enough players to start the game."),
NOT_GAME_HOST("ngh", "Only the game host can do that."),
NOT_IN_THAT_GAME("nitg", "You are not in that game."),
NOT_JUDGE("nj", "You are not the judge."),
NOT_REGISTERED("nr", "Not registered. Refresh the page."),
NOT_YOUR_TURN("nyt", "It is not your turn to play a card."),
OP_NOT_SPECIFIED("ons", "Operation not specified."),
RESERVED_NICK("rn", "That nick is reserved."),
SERVER_ERROR("serr", "An error occured on the server."),
SESSION_EXPIRED("se", "Your session has expired. Refresh the page."),
TOO_FAST("tf", "You are chatting too fast. Wait a few seconds and try again."),
TOO_MANY_GAMES("tmg", "There are too many games already in progress. Either join " +
"an existing game, or wait for one to become available."),
TOO_MANY_USERS("tmu", "There are too many users connected. Either join another server, or " +
"wait for a user to disconnect."),
WRONG_PASSWORD("wp", "That password is incorrect.");
private final String code;
private final String message;
/**
* @param code
* Error code to send over the wire to the client.
* @param message
* Message the client should display for the error code.
*/
ErrorCode(final String code, final String message) {
this.code = code;
this.message = message;
}
ErrorCode(final Enum<?> code, final String message) {
this.code = code.toString();
this.message = message;
}
@Override
public String toString() {
return code;
}
@Override
public String getString() {
return message;
}
}
/**
* Events that can be returned in a long poll response.
*/
public enum LongPollEvent {
@DuplicationAllowed
BANNED(DisconnectReason.BANNED),
@DuplicationAllowed
CARDCAST_ADD_CARDSET(AjaxOperation.CARDCAST_ADD_CARDSET),
@DuplicationAllowed
CARDCAST_REMOVE_CARDSET(AjaxOperation.CARDCAST_REMOVE_CARDSET),
@DuplicationAllowed
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_SPECTATOR_JOIN("gvj"),
GAME_SPECTATOR_LEAVE("gvl"),
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
* the client that we have nothing to inform them so the client doesn't think we went away.
*/
NOOP("_"),
PLAYER_LEAVE("pl");
private final String event;
LongPollEvent(final String event) {
this.event = event;
}
LongPollEvent(final Enum<?> event) {
this.event = event.toString();
}
@Override
public String toString() {
return event;
}
}
/**
* Data keys that can be in a long poll response.
*/
public enum LongPollResponse implements ReturnableData {
@DuplicationAllowed
BLACK_CARD(AjaxResponse.BLACK_CARD),
CARDCAST_DECK_INFO("cdi"),
@DuplicationAllowed
EMOTE(AjaxRequest.EMOTE),
@DuplicationAllowed
ERROR(AjaxResponse.ERROR),
@DuplicationAllowed
ERROR_CODE(AjaxResponse.ERROR_CODE),
EVENT("E"),
/**
* Player a chat message is from.
*/
FROM("f"),
/**
* A chat message is from an admin. This is going to be done with IP addresses for now.
*/
FROM_ADMIN("fa"),
@DuplicationAllowed
GAME_ID(AjaxResponse.GAME_ID),
@DuplicationAllowed
GAME_INFO(AjaxResponse.GAME_INFO),
GAME_STATE("gs"),
@DuplicationAllowed
HAND(AjaxResponse.HAND),
/**
* The delay until the next game round begins.
*/
INTERMISSION("i"),
@DuplicationAllowed
MESSAGE(AjaxRequest.MESSAGE),
@DuplicationAllowed
NICKNAME(AjaxRequest.NICKNAME),
PLAY_TIMER("Pt"),
@DuplicationAllowed
PLAYER_INFO(AjaxResponse.PLAYER_INFO),
/**
* Reason why a player disconnected.
*/
REASON("qr"),
ROUND_WINNER("rw"),
TIMESTAMP("ts"),
@DuplicationAllowed
WALL(AjaxRequest.WALL),
@DuplicationAllowed
WHITE_CARDS(AjaxResponse.WHITE_CARDS),
WINNING_CARD("WC");
private final String field;
LongPollResponse(final String field) {
this.field = field;
}
LongPollResponse(final Enum<?> field) {
this.field = field.toString();
}
@Override
public String toString() {
return field;
}
}
/**
* Data fields for white cards.
*/
public enum WhiteCardData {
@DuplicationAllowed
ID(AjaxRequest.CARD_ID),
TEXT("T"),
WATERMARK("W"),
WRITE_IN("wi");
private final String key;
WhiteCardData(final String key) {
this.key = key;
}
WhiteCardData(final Enum<?> key) {
this.key = key.toString();
}
@Override
public String toString() {
return key;
}
}
/**
* Data fields for black cards.
*/
public enum BlackCardData {
DRAW("D"),
@DuplicationAllowed
ID(WhiteCardData.ID),
PICK("PK"),
@DuplicationAllowed
TEXT(WhiteCardData.TEXT),
@DuplicationAllowed
WATERMARK(WhiteCardData.WATERMARK);
private final String key;
BlackCardData(final String key) {
this.key = key;
}
BlackCardData(final Enum<?> key) {
this.key = key.toString();
}
@Override
public String toString() {
return key;
}
}
/**
* Data fields for card sets.
*/
public enum CardSetData {
BASE_DECK("bd"),
BLACK_CARDS_IN_DECK("bcid"),
CARD_SET_DESCRIPTION("csd"),
CARD_SET_NAME("csn"),
@DuplicationAllowed
ID(WhiteCardData.ID),
WEIGHT("w"),
WHITE_CARDS_IN_DECK("wcid");
private final String key;
CardSetData(final String key) {
this.key = key;
}
CardSetData(final Enum<?> key) {
this.key = key.toString();
}
@Override
public String toString() {
return key;
}
}
/**
* A game's current state.
*/
public enum GameState implements Localizable {
DEALING("d", "In Progress"),
JUDGING("j", "In Progress"),
LOBBY("l", "Not Started"),
PLAYING("p", "In Progress"),
ROUND_OVER("ro", "In Progress");
private final String state;
private final String message;
GameState(final String state, final String message) {
this.state = state;
this.message = message;
}
@Override
public String toString() {
return state;
}
@Override
public String getString() {
return message;
}
}
/**
* Fields for information about a game.
*/
public enum GameInfo {
HOST("H"),
@DuplicationAllowed
ID(AjaxRequest.GAME_ID),
@DuplicationAllowed
GAME_OPTIONS(AjaxRequest.GAME_OPTIONS),
HAS_PASSWORD("hp"),
PLAYERS("P"),
SPECTATORS("V"),
STATE("S");
private final String key;
GameInfo(final String key) {
this.key = key;
}
GameInfo(final Enum<?> key) {
this.key = key.toString();
}
@Override
public String toString() {
return key;
}
}
/**
* 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"),
TIMER_MULTIPLIER("tm");
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.
*/
public enum GamePlayerInfo {
NAME("N"),
SCORE("sc"),
STATUS("st");
private final String key;
GamePlayerInfo(final String key) {
this.key = key;
}
@Override
public String toString() {
return key;
}
}
/**
* States that a player in a game can be in. The first client string is displayed in the
* scoreboard, and the second one is displayed in a banner at the top, telling the user what to
* do.
*/
public enum GamePlayerStatus implements DoubleLocalizable {
HOST("sh", "Host", "Wait for players then click Start Game."),
IDLE("si", "", "Waiting for players..."),
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!"),
SPECTATOR("sv", "Spectator", "You are just spectating.");
private final String status;
private final String message;
private final String message2;
GamePlayerStatus(final String status, final String message, final String message2) {
this.status = status;
this.message = message;
this.message2 = message2;
}
@Override
public String toString() {
return status;
}
@Override
public String getString() {
return message;
}
@Override
public String getString2() {
return message2;
}
}
/**
* Attributes stored in a client session.
*/
public class SessionAttribute {
public static final String USER = "user";
}
/**
* Mark an enum value as being allowed to be the same as another enum value. Should only be used
* when another enum's value is directly used as the value. This will prevent the test from
* flagging it as an invalid reuse.
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface DuplicationAllowed {
}
}