package net.sf.colossus.webcommon; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import net.sf.colossus.common.Constants; import net.sf.colossus.common.Options; import net.sf.colossus.util.Glob; import net.sf.colossus.util.Split; import net.sf.colossus.webclient.WebClient; /** * One object of this this class represents a game for which players/users * have enrolled to play it together. * It starts in state "PROPOSED" as type either instantly or scheduled. * Then its state will change along the sequence of states * PROPOSED, DUE, ACTIVATED, STARTING, READY_TO_CONNECT, RUNNING, ENDING * as denoted in the GameState enum. * * The actual running/starting of the game will be handled by different * classes, namely GameOnServer and (to be done) GameOnClient. * * The same class is also used at client side, but only part of the data * is used there (e.g. the user has only a name, not a socket). * * @author Clemens Katzer */ public class GameInfo { private static final Logger LOGGER = Logger.getLogger(GameInfo.class .getName()); private static int nextFreeGameId = 1; private String gameId; private GameType type; private GameState state; /** temporary backup during startingAttempt */ private GameState oldState; private User startingUser = null; private int portNr = -1; private String hostingHost = ""; private IGameRunner gameRunner; private String initiator; private String variant; private String viewmode; private final boolean autosave = true; // those 3 should still be added at client side: private String eventExpiring; private boolean unlimitedMulligans; private boolean balancedTowers; private boolean autoSansLordBattles; private boolean inactivityTimeout; private boolean probabilityBasedBattleHits; private boolean noFirstTurnT2TTeleportOpt; private boolean noFirstTurnTeleportOpt; private boolean towerToTowerTeleportOnlyOpt; private boolean noTowerTeleportOpt; private boolean noTitanTeleportOpt; private boolean noFirstTurnWarlockRecruitOpt; private int min; private int target; private int max; private int onlineCount; private String resumeFromFilename; // next three only needed for scheduled games private long startTime = 0; private int duration = 0; private String summary = ""; // private String infoText = ""; private int enrolledPlayers; private ArrayList<User> players = null; // used on server side, to create a game proposed by client private GameInfo(GameType type) { this.gameId = String.valueOf(getNextFreeGameId()); this.type = type; this.state = GameState.PROPOSED; this.enrolledPlayers = 0; this.players = new ArrayList<User>(); } /** Server calls this to set it high enough that existing directories * in games work directory are not overwritten * * @param id Next games should have higher number than given id */ public static void setNextFreeGameId(int id) { nextFreeGameId = id; } private static int getNextFreeGameId() { return nextFreeGameId++; } public static boolean wouldBeInstantGame(long startTime) { return makeTypeFromStarttime(startTime).equals(GameType.INSTANT); } private static GameType makeTypeFromStarttime(long startTime) { return (startTime == -1 ? GameType.INSTANT : GameType.SCHEDULED); } public GameInfo(String initiator, String variant, String viewmode, long startTime, int duration, String summary, String expire, List<String> gameOptions, List<String> teleportOptions, int min, int target, int max) { this(makeTypeFromStarttime(startTime)); parseExtraOptions(gameOptions); parseExtraOptions(teleportOptions); this.initiator = initiator; this.variant = variant; this.viewmode = viewmode; this.eventExpiring = expire; this.min = min; this.target = target; this.max = max; this.onlineCount = 0; this.startTime = startTime; this.duration = duration; this.summary = summary; this.enrolledPlayers = 0; this.players = new ArrayList<User>(); this.resumeFromFilename = null; LOGGER.log(Level.FINEST, "A new potential game was created!! - variant " + variant + " viewmode " + viewmode); } /* * go through list containing the extraOptions, set the corresponding * boolean values to true */ private void parseExtraOptions(List<String> extraOptions) { if (extraOptions.size() == 0) { LOGGER.finest("Extra options list is totally empty! '"); return; } if (extraOptions.size() == 1 && extraOptions.get(0).equals("")) { LOGGER.finest("Extra options is one empty element!"); return; } LOGGER.finest("Extra options size: " + extraOptions.size()); for (Iterator<String> iterator = extraOptions.iterator(); iterator .hasNext();) { String string = iterator.next(); LOGGER.finest("Handling option string '" + string + "'"); if (string.equals(Options.sansLordAutoBattle)) { this.autoSansLordBattles = true; } else if (string.equals(Options.inactivityTimeout)) { this.inactivityTimeout = true; } else if (string.equals(Options.unlimitedMulligans)) { this.unlimitedMulligans = true; } else if (string.equals(Options.balancedTowers)) { this.balancedTowers = true; } else if (string.equals(Options.pbBattleHits)) { this.probabilityBasedBattleHits = true; } else if (string.equals(Options.noFirstTurnT2TTeleport)) { this.noFirstTurnT2TTeleportOpt = true; } else if (string.equals(Options.noFirstTurnTeleport)) { this.noFirstTurnTeleportOpt = true; } else if (string.equals(Options.towerToTowerTeleportOnly)) { this.towerToTowerTeleportOnlyOpt = true; } else if (string.equals(Options.noTowerTeleport)) { this.noTowerTeleportOpt = true; } else if (string.equals(Options.noTitanTeleport)) { this.noTitanTeleportOpt = true; } else if (string.equals(Options.noFirstTurnWarlockRecruit)) { this.noFirstTurnWarlockRecruitOpt = true; } else { LOGGER.severe("Unexpected option string '" + string + "' when parsing extraOptions !"); } } } // ================= now the stuff for the client side =============== // used on client side, to restore a proposed game sent by server public GameInfo(String gameId, boolean onServer) { this.gameId = gameId; if (onServer) { int intGameId = Integer.parseInt(gameId); if (nextFreeGameId <= intGameId) { nextFreeGameId = intGameId + 1; } } this.players = new ArrayList<User>(); } public static GameInfo fromString(String[] tokens, HashMap<String, GameInfo> games, boolean fromFile) { GameInfo gi; // tokens[0] is the command String gameId = tokens[1]; String key = gameId; if (games.containsKey(gameId)) { // use the object webclient has created earlier LOGGER.finest("Found already, updating"); gi = games.get(key); } else { gi = new GameInfo(gameId, fromFile); LOGGER.finest("Creating a new GameInfo"); games.put(key, gi); } int j = 2; gi.type = GameType.valueOf(tokens[j++]); gi.state = GameState.valueOf(tokens[j++]); gi.initiator = tokens[j++]; gi.variant = tokens[j++]; gi.viewmode = tokens[j++]; gi.startTime = Long.parseLong(tokens[j++]); gi.duration = Integer.parseInt(tokens[j++]); gi.summary = tokens[j++]; gi.eventExpiring = tokens[j++]; String token8 = tokens[j++]; String token9 = tokens[j++]; if (token8.equalsIgnoreCase("true") || token8.equalsIgnoreCase("false")) { gi.unlimitedMulligans = Boolean.valueOf(token8).booleanValue(); gi.balancedTowers = Boolean.valueOf(token9).booleanValue(); } else { if (token8.length() > 0) { List<String> extraOptions = Split.split(Glob.sep, token8); LOGGER.finest("Game extra options string is '" + token8 + "'"); gi.parseExtraOptions(extraOptions); } else { LOGGER.finest("Empty 'game' extra-options string - ok!"); } if (token9.length() > 0) { LOGGER.finest("Teleport extra options string is '" + token9 + "'"); List<String> teleportOptions = Split.split(Glob.sep, token9); gi.parseExtraOptions(teleportOptions); } else { LOGGER.finest("Empty 'teleport' extra-options string - ok!"); } } gi.min = Integer.parseInt(tokens[j++]); gi.target = Integer.parseInt(tokens[j++]); gi.max = Integer.parseInt(tokens[j++]); gi.onlineCount = Integer.parseInt(tokens[j++]); int lastIndex = j; gi.enrolledPlayers = Integer.parseInt(tokens[lastIndex]); ArrayList<User> players = new ArrayList<User>(); int i = 1; while (i <= gi.enrolledPlayers) { String name = tokens[lastIndex + i]; User user = new User(name); players.add(user); i++; } gi.players = players; return gi; } public String toStringCheckClientVersion(String username, int clientVersion, String sep) { String giString; if (clientVersion >= WebClient.WC_VERSION_DELETE_SUSPENDED_GAME) { LOGGER .info("Sending GameInfo (can handle deleted game) to client " + username); giString = toString(sep); } else if (clientVersion >= WebClient.WC_VERSION_SUPPORTS_EXTRA_OPTIONS) { LOGGER.info("Sending GameInfo (new style) to client " + username); boolean noSuspend = clientVersion <= WebClient.WC_VERSION_RESUME; boolean noDelete = clientVersion < WebClient.WC_VERSION_DELETE_SUSPENDED_GAME; giString = toStringFixState(sep, noSuspend, noDelete); } else { LOGGER.info("Sending LegacyGameInfo to client " + username); giString = toStringLegacy(sep); } return giString; } public String toStringLegacy(String sep) { StringBuilder playerList = new StringBuilder(); Iterator<User> it = players.iterator(); while (it.hasNext()) { playerList.append(sep); User user = it.next(); playerList.append(user.getName()); } String summary2 = summary; int count = 0; count = (this.autoSansLordBattles ? 1 : 0) + (this.inactivityTimeout ? 1 : 0) + (this.probabilityBasedBattleHits ? 1 : 0); if (count > 0) { summary2 = "NOTE! " + count + " extra options are set! | " + summary; } GameState modifiedState = state; if (modifiedState.equals(GameState.DELETED) || modifiedState.equals(GameState.SUSPENDED)) { modifiedState = GameState.ENDING; } String message = gameId + sep + type.toString() + sep + modifiedState.toString() + sep + initiator + sep + variant + sep + viewmode + sep + startTime + sep + duration + sep + summary2 + sep + eventExpiring + sep + unlimitedMulligans + sep + balancedTowers + sep + min + sep + target + sep + max + sep + onlineCount + sep + enrolledPlayers + playerList.toString(); return message; } public String toString(String sep) { StringBuilder playerList = new StringBuilder(); Iterator<User> it = players.iterator(); while (it.hasNext()) { playerList.append(sep); User user = it.next(); playerList.append(user.getName()); } String message = gameId + sep + type.toString() + sep + state.toString() + sep + initiator + sep + variant + sep + viewmode + sep + startTime + sep + duration + sep + summary + sep + eventExpiring + sep + getExtraOptionsAsString() + sep + getTeleportOptionsAsString() + sep + min + sep + target + sep + max + sep + onlineCount + sep + enrolledPlayers + playerList.toString(); return message; } /** * If webclients that cannot handle it receive a DELETED or * SUSPENDED state, they throw exception and disconnect :-( * So we give them ENDING instead... * @param sep * @param noSuspend TODO * @param noDelete TODO * @return */ public String toStringFixState(String sep, boolean noSuspend, boolean noDelete) { StringBuilder playerList = new StringBuilder(); Iterator<User> it = players.iterator(); while (it.hasNext()) { playerList.append(sep); User user = it.next(); playerList.append(user.getName()); } GameState modifiedState = state; if (noDelete && state.equals(GameState.DELETED)) { modifiedState = GameState.ENDING; } if (noSuspend && state.equals(GameState.SUSPENDED)) { modifiedState = GameState.ENDING; } String message = gameId + sep + type.toString() + sep + modifiedState.toString() + sep + initiator + sep + variant + sep + viewmode + sep + startTime + sep + duration + sep + summary + sep + eventExpiring + sep + getExtraOptionsAsString() + sep + getTeleportOptionsAsString() + sep + min + sep + target + sep + max + sep + onlineCount + sep + enrolledPlayers + playerList.toString(); return message; } public String getExtraOptionsAsString() { return Glob.glob(getExtraOptions()); } public String getTeleportOptionsAsString() { return Glob.glob(getTeleportOptions()); } public List<String> getExtraOptions() { List<String> extraOptions = new ArrayList<String>(); if (this.autoSansLordBattles) { extraOptions.add(Options.sansLordAutoBattle); } if (this.inactivityTimeout) { extraOptions.add(Options.inactivityTimeout); } if (this.unlimitedMulligans) { extraOptions.add(Options.unlimitedMulligans); } if (this.balancedTowers) { extraOptions.add(Options.balancedTowers); } if (this.probabilityBasedBattleHits) { extraOptions.add(Options.pbBattleHits); } return extraOptions; } public List<String> getTeleportOptions() { List<String> teleportOptions = new ArrayList<String>(); if (this.noFirstTurnT2TTeleportOpt) { teleportOptions.add(Options.noFirstTurnT2TTeleport); } if (this.noFirstTurnTeleportOpt) { teleportOptions.add(Options.noFirstTurnTeleport); } if (this.towerToTowerTeleportOnlyOpt) { teleportOptions.add(Options.towerToTowerTeleportOnly); } if (this.noTowerTeleportOpt) { teleportOptions.add(Options.noTowerTeleport); } if (this.noTitanTeleportOpt) { teleportOptions.add(Options.noTitanTeleport); } if (this.noFirstTurnWarlockRecruitOpt) { teleportOptions.add(Options.noFirstTurnWarlockRecruit); } return teleportOptions; } public void setState(GameState state) { this.state = state; } public GameState getGameState() { return this.state; } public boolean isScheduledGame() { boolean isSch = (this.type == GameType.SCHEDULED); return isSch; } public String getStateString() { return this.state.toString(); } public String getGameId() { return this.gameId; } public void setGameId(String val) { this.gameId = val; } public void setGameRunner(IGameRunner gr) { this.gameRunner = gr; } public IGameRunner getGameRunner() { return gameRunner; } public int getPort() { return this.portNr; } public void setPort(int nr) { this.portNr = nr; } public void setHostingHost(String host) { hostingHost = host; } public String getHostingHost() { return hostingHost; } public String getInitiator() { return initiator; } public void setInitiator(String val) { initiator = val; } public Long getStartTime() { return Long.valueOf(startTime); } public void setStartTime(String val) { startTime = Long.parseLong(val); } public Integer getDuration() { return Integer.valueOf(duration); } public void setDuration(String val) { duration = Integer.parseInt(val); } public String getSummary() { return summary; } public void setSummary(String val) { summary = val; } public String getVariant() { return variant; } public void setVariant(String val) { variant = val; } public String getViewmode() { return viewmode; } public boolean getAutosave() { return autosave; } public void setViewmode(String val) { viewmode = val; } public String getEventExpiring() { return eventExpiring; } public void setEventExpiring(String val) { eventExpiring = val; } public boolean getUnlimitedMulligans() { return unlimitedMulligans; } public void setUnlimitedMulligans(boolean val) { unlimitedMulligans = val; } public boolean getBalancedTowers() { return balancedTowers; } public void setBalancedTowers(boolean val) { balancedTowers = val; } public boolean getAutoSansLordBattles() { return autoSansLordBattles; } public void setAutoSansLordBattles(boolean val) { autoSansLordBattles = val; } public boolean getInactivityTimeout() { return inactivityTimeout; } public void setInactivityTimeout(boolean val) { inactivityTimeout = val; } public boolean getProbabilityBasedBattleHits() { return probabilityBasedBattleHits; } public void setProbabilityBasedBattleHits(boolean val) { probabilityBasedBattleHits = val; } public String getGameOptionsFlagsString() { String s = (this.unlimitedMulligans ? "U" : "-") + (this.balancedTowers ? "B" : "-") + (this.autoSansLordBattles ? "N" : "-") + (this.probabilityBasedBattleHits ? "P" : "-") + (this.inactivityTimeout ? "I" : "-"); return s; } public String GetOptionsTooltipText() { String ttText = (this.unlimitedMulligans ? Options.unlimitedMulligans : "-") + ", " + (this.balancedTowers ? Options.balancedTowers : "-") + ", " + (this.autoSansLordBattles ? Options.sansLordAutoBattle : "-") + ", " + (this.probabilityBasedBattleHits ? Options.pbBattleHits : "-") + ", " + (this.inactivityTimeout ? Options.inactivityTimeout : "-"); return ttText; } public String getTeleportOptionsFlagsString() { String s = (this.noFirstTurnT2TTeleportOpt ? "2" : "-") + (this.noFirstTurnTeleportOpt ? "1" : "-") + (this.towerToTowerTeleportOnlyOpt ? "T" : "-") + (this.noTowerTeleportOpt ? "w" : "-") + (this.noTitanTeleportOpt ? "t" : "-") + (this.noFirstTurnWarlockRecruitOpt ? "W" : "-"); return s; } public String GetTeleportOptionsTooltipText() { String ttText = (this.noFirstTurnT2TTeleportOpt ? Options.noFirstTurnT2TTeleport : "-") + ", " + (this.noFirstTurnTeleportOpt ? Options.noFirstTurnTeleport : "-") + ", " + (this.towerToTowerTeleportOnlyOpt ? Options.towerToTowerTeleportOnly : "-") + ", " + (this.noTowerTeleportOpt ? Options.noTowerTeleport : "-") + ", " + (this.noTitanTeleportOpt ? Options.noTitanTeleport : "-") + ", " + (this.noFirstTurnWarlockRecruitOpt ? Options.noFirstTurnWarlockRecruit : "-"); return ttText; } /** * Have enough players enrolled (at least "min") * @return true or false whether enough (at least 'min') players are * already enrolled to this game */ public boolean hasEnoughPlayers() { return enrolledPlayers >= min; } /** * Have enough players enrolled (at least "min") * @return true or false whether all enrolled players are online */ public boolean allEnrolledOnline() { return onlineCount >= enrolledPlayers; } /** * Has the scheduled time come? * @return true if the game can be started according to schedule */ public boolean isDue() { long now = new Date().getTime(); return now > startTime; } public Integer getMin() { return Integer.valueOf(min); } public void setMin(Integer val) { min = val.intValue(); } public Integer getTargetInteger() { return Integer.valueOf(target); } public int getTarget() { return target; } public void setTarget(Integer val) { target = val.intValue(); } public Integer getMax() { return Integer.valueOf(max); } public void setMax(Integer val) { max = val.intValue(); } public Integer getEnrolledCount() { return Integer.valueOf(enrolledPlayers); } public boolean enoughPlayersEnrolled() { return enrolledPlayers >= min; } public int getOnlineCount() { return onlineCount; } public void setOnlineCount(int count) { onlineCount = count; } public void setEnrolledCount(Integer val) { enrolledPlayers = val.intValue(); } public void setResumeFromFilename(String filename) { this.resumeFromFilename = filename; } public String getResumeFromFilename() { return this.resumeFromFilename; } public ArrayList<User> getPlayers() { return this.players; } public String getPlayerListAsString() { if (players == null) { LOGGER.warning("Tried to get player list as string for gameId " + gameId + ", but player list == null"); return "<none>"; } StringBuilder playerList = new StringBuilder(""); if (players.size() == 0) { return "<none>"; } for (User u : players) { if (playerList.length() != 0) { playerList.append(", "); } playerList.append(u.getName()); } return playerList.substring(0); } public boolean isFirstInEnrolledList(String name) { if (players.size() > 0 && players.get(0).getName().equals(name)) { return true; } return false; } public boolean reEnrollIfNecessary(User newUser) { String newName = newUser.getName(); boolean found = removeIfEnrolled(newName); if (found) { players.add(newUser); enrolledPlayers = players.size(); LOGGER.finest("Re-Enrolled user " + newName + " to game " + getGameId()); LOGGER.finest("Players now: " + getPlayerListAsString()); } else { LOGGER.finest("User " + newName + " not in game " + getGameId() + " - nothing to do."); } return found; } /** * TODO remove overlap with isEnrolled * * If user with name "newName" is found, remove it from game, so that * it can be safely enrolled again. * E.g. after user reconnected or accidental double click to enroll button * @param newName * @return Whether user was found */ public boolean removeIfEnrolled(String newName) { Iterator<User> it = players.iterator(); boolean found = false; while (!found && it.hasNext()) { User user = it.next(); String name = user.getName(); if (newName.equalsIgnoreCase(name)) { it.remove(); enrolledPlayers = players.size(); found = true; } } return found; } public boolean isEnrolled(String searchName) { boolean found = false; for (User u : players) { if (searchName.equalsIgnoreCase(u.getName())) { found = true; break; } } return found; } public void setPlayerList(ArrayList<User> playerlist) { players = playerlist; } public boolean updateOnlineCount(int newCount) { if (newCount != onlineCount) { onlineCount = newCount; return true; } return false; } /* * return reason why fail, or null if ok */ public String enroll(User user) { String reason = null; // Just in case, to avoid duplicates (e.g. bounce/double click) if (variant.equals("DinoTitan")) { IWebClient client = user.getWebserverClient(); if (client.getClientVersion() < WebClient.WC_VERSION_DINO_OK) { reason = "Client does not support this variant, please upgrade!"; return reason; } } removeIfEnrolled(user.getName()); if (enrolledPlayers < max) { synchronized (players) { players.add(user); enrolledPlayers++; } } else { reason = "Game is full"; } return reason; } public String unenroll(User user) { String reason = null; synchronized (players) { int index = players.indexOf(user); if (index != -1) { players.remove(index); enrolledPlayers--; if (players.size() != enrolledPlayers) { LOGGER.log(Level.SEVERE, "players.size() != enrolledPlayers!!"); } } else { reason = "Player " + user.getName() + " to unenroll not found in game " + gameId; } } return reason; } public void storeToOptionsObject(Options gameOptions, String localPlayerName, boolean noAIs) { if (this.portNr == -1) { this.portNr = Constants.defaultPort; } // XXX get port from gameinfo gameOptions.setOption(Options.serveAtPort, this.portNr); // XXX get host from gameinfo // XXX set host in gameinfo, send to server/client gameOptions.setOption(Options.variant, getVariant()); gameOptions.setOption(Options.viewMode, getViewmode()); gameOptions.setOption(Options.autosave, getAutosave()); gameOptions.setOption(Options.eventExpiring, getEventExpiring()); gameOptions.setOption(Options.unlimitedMulligans, getUnlimitedMulligans()); gameOptions.setOption(Options.balancedTowers, getBalancedTowers()); gameOptions.setOption(Options.sansLordAutoBattle, getAutoSansLordBattles()); gameOptions.setOption(Options.pbBattleHits, getProbabilityBasedBattleHits()); gameOptions.setOption(Options.inactivityTimeout, getInactivityTimeout()); gameOptions.setOption(Options.noFirstTurnTeleport, this.noFirstTurnTeleportOpt); gameOptions.setOption(Options.noFirstTurnTeleport, this.noFirstTurnTeleportOpt); gameOptions.setOption(Options.towerToTowerTeleportOnly, this.towerToTowerTeleportOnlyOpt); gameOptions .setOption(Options.noTowerTeleport, this.noTowerTeleportOpt); gameOptions .setOption(Options.noTitanTeleport, this.noTitanTeleportOpt); gameOptions.setOption(Options.noFirstTurnWarlockRecruit, this.noFirstTurnWarlockRecruitOpt); // gameOptions.setOption(Options.autoQuit, true); String name; String type; Iterator<User> it = getPlayers().iterator(); int numPlayers = getTarget(); if (noAIs) { numPlayers = getPlayers().size(); } for (int i = 0; i < numPlayers; i++) { if (it.hasNext()) { User u = it.next(); name = u.getName(); // TODO is ignorecase necessary here? // Added just in case when fixing another case where it matters ... if (localPlayerName != null && name.equalsIgnoreCase(localPlayerName)) { type = Constants.human; // use user real name; } else { type = Constants.network; name = Constants.byClient; } } else { type = Constants.anyAI; name = Constants.byColor; } gameOptions.setOption(Options.playerName + i, name); gameOptions.setOption(Options.playerType + i, type); } gameOptions.setOption(Options.autoStop, true); return; } public boolean relevantForSaving() { return state.equals(GameState.PROPOSED) || state.equals(GameState.DUE) || state.equals(GameState.SUSPENDED); } public boolean isStartable() { return state.equals(GameState.PROPOSED) || state.equals(GameState.DUE) || state.equals(GameState.SUSPENDED); } public boolean isRunning() { return state.equals(GameState.RUNNING); } public boolean wasAlreadyStarted() { return !(state.equals(GameState.PROPOSED) || state .equals(GameState.DUE)); } /** if game is proposed or due, re-enroll a player that just logs in; * otherwise not * @return true if game is either proposed or due */ public boolean isProposedOrDue() { return state.equals(GameState.PROPOSED) || state.equals(GameState.DUE); } public void markStarting(User starter) { this.startingUser = starter; this.oldState = state; this.state = GameState.ACTIVATED; } public boolean isStarting() { return startingUser != null; } public void cancelStarting() { this.state = oldState; this.startingUser = null; } public User getStartingUser() { return startingUser; } public boolean wantsDetailedLogging() { return isEnrolled("Sir Volander") || isEnrolled("dlmartin"); } /** * Enum for the possible TYPES of a game * (scheduled or instant, perhaps later also template?) */ public static enum GameType { SCHEDULED, INSTANT; // TEMPLATE ? } /** * Enum for the possible states of a game: */ public static enum GameState { PROPOSED, DUE, ACTIVATED, STARTING, READY_TO_CONNECT, RUNNING, ENDING, SUSPENDED, DELETED; } }