- create games
- list games - automatically refreshes list when game is created or destroyed - destroy games after removing all players (including at logout)
This commit is contained in:
parent
40de91b5ac
commit
979a9c4123
|
@ -39,6 +39,7 @@
|
|||
<div id="menubar">
|
||||
<div id="menubar_left">
|
||||
<input type="button" id="refresh_games" value="Refresh Games" />
|
||||
<input type="button" id="create_game" value="Create Game" />
|
||||
</div>
|
||||
<div id="menubar_right">
|
||||
<input type="button" id="logout" value="Log out" />
|
||||
|
|
|
@ -20,8 +20,6 @@ $(document).ready(function() {
|
|||
// have not expressed an interest in being cleared out yet.
|
||||
// $(window).bind("beforeunload", window_beforeunload);
|
||||
$("#logout").click(logout_click);
|
||||
|
||||
$("#refresh_games").click(refreshgames_click);
|
||||
});
|
||||
|
||||
function nickbox_keyup(e) {
|
||||
|
@ -55,7 +53,3 @@ function chatsubmit_click(e) {
|
|||
function logout_click(e) {
|
||||
cah.Ajax.build(cah.$.AjaxOperation.LOG_OUT).run();
|
||||
}
|
||||
|
||||
function refreshgames_click(e) {
|
||||
cah.Ajax.build(cah.$.AjaxOperation.GAME_LIST).run();
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ cah.$.AjaxOperation.FIRST_LOAD = "firstload";
|
|||
cah.$.AjaxOperation.LOG_OUT = "logout";
|
||||
cah.$.AjaxOperation.GAME_LIST = "games";
|
||||
cah.$.AjaxOperation.REGISTER = "register";
|
||||
cah.$.AjaxOperation.CREATE_GAME = "create_game";
|
||||
cah.$.AjaxOperation.CHAT = "chat";
|
||||
cah.$.AjaxOperation.NAMES = "names";
|
||||
|
||||
|
@ -27,6 +28,7 @@ cah.$.AjaxResponse = function() {
|
|||
};
|
||||
cah.$.AjaxResponse.prototype.dummy = undefined;
|
||||
cah.$.AjaxResponse.NEXT = "next";
|
||||
cah.$.AjaxResponse.GAME_ID = "game_id";
|
||||
cah.$.AjaxResponse.ERROR = "error";
|
||||
cah.$.AjaxResponse.ERROR_CODE = "error_code";
|
||||
cah.$.AjaxResponse.SERIAL = "serial";
|
||||
|
@ -48,29 +50,33 @@ cah.$.ErrorCode = function() {
|
|||
// pass
|
||||
};
|
||||
cah.$.ErrorCode.prototype.dummy = undefined;
|
||||
cah.$.ErrorCode.NO_SESSION = "no_session";
|
||||
cah.$.ErrorCode.NOT_REGISTERED = "not_registered";
|
||||
cah.$.ErrorCode.TOO_MANY_GAMES = "too_many_games";
|
||||
cah.$.ErrorCode.INVALID_NICK = "invalid_nick";
|
||||
cah.$.ErrorCode.BAD_REQUEST = "bad_req";
|
||||
cah.$.ErrorCode.SESSION_EXPIRED = "session_expired";
|
||||
cah.$.ErrorCode.MESSAGE_TOO_LONG = "msg_too_long";
|
||||
cah.$.ErrorCode.BAD_OP = "bad_op";
|
||||
cah.$.ErrorCode.NO_SESSION = "no_session";
|
||||
cah.$.ErrorCode.NOT_REGISTERED = "not_registered";
|
||||
cah.$.ErrorCode.CANNOT_JOIN_GAME = "cannot_join_game";
|
||||
cah.$.ErrorCode.OP_NOT_SPECIFIED = "op_not_spec";
|
||||
cah.$.ErrorCode.NO_MSG_SPECIFIED = "no_msg_spec";
|
||||
cah.$.ErrorCode.NICK_IN_USE = "nick_in_use";
|
||||
cah.$.ErrorCode.SESSION_EXPIRED = "session_expired";
|
||||
cah.$.ErrorCode.MESSAGE_TOO_LONG = "msg_too_long";
|
||||
cah.$.ErrorCode.NO_NICK_SPECIFIED = "no_nick_spec";
|
||||
cah.$.ErrorCode.BAD_OP = "bad_op";
|
||||
cah.$.ErrorCode_msg = {};
|
||||
cah.$.ErrorCode_msg['nick_in_use'] = "Nickname is already in use.";
|
||||
cah.$.ErrorCode_msg['bad_op'] = "Invalid operation.";
|
||||
cah.$.ErrorCode_msg['not_registered'] = "Not registered. Refresh the page.";
|
||||
cah.$.ErrorCode_msg['bad_req'] = "Bad request.";
|
||||
cah.$.ErrorCode_msg['msg_too_long'] = "Messages cannot be longer than 200 characters.";
|
||||
cah.$.ErrorCode_msg['cannot_join_game'] = "You cannot join another game.";
|
||||
cah.$.ErrorCode_msg['session_expired'] = "Your session has expired. Refresh the page.";
|
||||
cah.$.ErrorCode_msg['invalid_nick'] = "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.";
|
||||
cah.$.ErrorCode_msg['no_nick_spec'] = "No nickname specified.";
|
||||
cah.$.ErrorCode_msg['no_session'] = "Session not detected. Make sure you have cookies enabled.";
|
||||
cah.$.ErrorCode_msg['too_many_games'] = "There are too many games already in progress. Either join an existing game, or wait for one to become available.";
|
||||
cah.$.ErrorCode_msg['nick_in_use'] = "Nickname is already in use.";
|
||||
cah.$.ErrorCode_msg['bad_req'] = "Bad request.";
|
||||
cah.$.ErrorCode_msg['op_not_spec'] = "Operation not specified.";
|
||||
cah.$.ErrorCode_msg['no_msg_spec'] = "No message specified.";
|
||||
cah.$.ErrorCode_msg['invalid_nick'] = "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.";
|
||||
cah.$.ErrorCode_msg['no_session'] = "Session not detected. Make sure you have cookies enabled.";
|
||||
cah.$.ErrorCode_msg['no_nick_spec'] = "No nickname specified.";
|
||||
|
||||
cah.$.GameInfo = function() {
|
||||
// pass
|
||||
|
@ -96,6 +102,7 @@ cah.$.LongPollEvent = function() {
|
|||
};
|
||||
cah.$.LongPollEvent.prototype.dummy = undefined;
|
||||
cah.$.LongPollEvent.NOOP = "noop";
|
||||
cah.$.LongPollEvent.GAME_REFRESH = "game_refresh";
|
||||
cah.$.LongPollEvent.NEW_PLAYER = "new_player";
|
||||
cah.$.LongPollEvent.PLAYER_LEAVE = "player_leave";
|
||||
cah.$.LongPollEvent.CHAT = "chat";
|
||||
|
|
|
@ -15,6 +15,17 @@ cah.GameList = function() {
|
|||
* @private
|
||||
*/
|
||||
this.element_ = $("#game_list")[0];
|
||||
|
||||
/**
|
||||
* Array of all game lobby objects.
|
||||
*
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
this.games_ = new Array();
|
||||
|
||||
$("#create_game").click(cah.bind(this, this.createGameClick_));
|
||||
$("#refresh_games").click(cah.bind(this, this.refreshGamesClick_));
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
|
@ -28,11 +39,40 @@ $(document).ready(function() {
|
|||
* gameData The game data returned by the server.
|
||||
*/
|
||||
cah.GameList.prototype.update = function(gameData) {
|
||||
// TODO clear existing display
|
||||
while (this.element_.hasChildNodes()) {
|
||||
this.element_.removeChild(this.element_.firstChild);
|
||||
}
|
||||
this.games_ = new Array();
|
||||
|
||||
for ( var key in gameData[cah.$.AjaxResponse.GAMES]) {
|
||||
var game = gameData[cah.$.AjaxResponse.GAMES][key];
|
||||
var lobby = new cah.GameListLobby(this.element_, game);
|
||||
this.games_.push(lobby);
|
||||
}
|
||||
|
||||
if (gameData[cah.$.AjaxResponse.GAMES].length < gameData[cah.$.AjaxResponse.MAX_GAMES]) {
|
||||
$("#create_game").removeAttr("disabled");
|
||||
} else {
|
||||
$("#create_game").attr("disabled", "disabled");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
cah.GameList.prototype.createGameClick_ = function(e) {
|
||||
cah.Ajax.build(cah.$.AjaxOperation.CREATE_GAME).run();
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
cah.GameList.prototype.refreshGamesClick_ = function(e) {
|
||||
this.refreshGames();
|
||||
};
|
||||
|
||||
cah.GameList.prototype.refreshGames = function() {
|
||||
cah.Ajax.build(cah.$.AjaxOperation.GAME_LIST).run();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -45,3 +45,7 @@ cah.longpoll.EventHandlers[cah.$.LongPollEvent.CHAT] = function(data) {
|
|||
cah.log.status("<" + data.from + "> " + data.message);
|
||||
}
|
||||
};
|
||||
|
||||
cah.longpoll.EventHandlers[cah.$.LongPollEvent.GAME_REFRESH] = function(data) {
|
||||
cah.GameList.instance.refreshGames();
|
||||
};
|
||||
|
|
|
@ -27,6 +27,7 @@ public class Constants {
|
|||
|
||||
public enum AjaxOperation {
|
||||
CHAT("chat"),
|
||||
CREATE_GAME("create_game"),
|
||||
FIRST_LOAD("firstload"),
|
||||
GAME_LIST("games"),
|
||||
LOG_OUT("logout"),
|
||||
|
@ -66,6 +67,7 @@ public class Constants {
|
|||
public enum AjaxResponse implements ReturnableData {
|
||||
ERROR("error"),
|
||||
ERROR_CODE("error_code"),
|
||||
GAME_ID("game_id"),
|
||||
GAMES("games"),
|
||||
IN_PROGRESS("in_progress"),
|
||||
MAX_GAMES("max_games"),
|
||||
|
@ -89,6 +91,7 @@ public class Constants {
|
|||
public enum ErrorCode implements Localizable {
|
||||
BAD_OP("bad_op", "Invalid operation."),
|
||||
BAD_REQUEST("bad_req", "Bad request."),
|
||||
CANNOT_JOIN_GAME("cannot_join_game", "You cannot join another game."),
|
||||
INVALID_NICK("invalid_nick", "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."),
|
||||
|
@ -99,7 +102,9 @@ public class Constants {
|
|||
NO_SESSION("no_session", "Session not detected. Make sure you have cookies enabled."),
|
||||
NOT_REGISTERED("not_registered", "Not registered. Refresh the page."),
|
||||
OP_NOT_SPECIFIED("op_not_spec", "Operation not specified."),
|
||||
SESSION_EXPIRED("session_expired", "Your session has expired. Refresh the page.");
|
||||
SESSION_EXPIRED("session_expired", "Your session has expired. Refresh the page."),
|
||||
TOO_MANY_GAMES("too_many_games", "There are too many games already in progress. Either join " +
|
||||
"an existing game, or wait for one to become available.");
|
||||
|
||||
private final String code;
|
||||
private final String message;
|
||||
|
@ -128,6 +133,7 @@ public class Constants {
|
|||
|
||||
public enum LongPollEvent {
|
||||
CHAT("chat"),
|
||||
GAME_REFRESH("game_refresh"),
|
||||
NEW_PLAYER("new_player"),
|
||||
NOOP("noop"),
|
||||
PLAYER_LEAVE("player_leave");
|
||||
|
|
|
@ -20,6 +20,7 @@ public class Game {
|
|||
private final int id;
|
||||
private final List<Player> players = new ArrayList<Player>(10);
|
||||
private final ConnectedUsers connectedUsers;
|
||||
private final GameManager gameManager;
|
||||
private Player host;
|
||||
private BlackDeck blackDeck;
|
||||
private WhiteDeck whiteDeck;
|
||||
|
@ -31,11 +32,14 @@ public class Game {
|
|||
*
|
||||
* @param id
|
||||
* @param connectedUsers
|
||||
* @param gameManager
|
||||
*/
|
||||
@Inject
|
||||
public Game(@GameId final Integer id, final ConnectedUsers connectedUsers) {
|
||||
public Game(@GameId final Integer id, final ConnectedUsers connectedUsers,
|
||||
final GameManager gameManager) {
|
||||
this.id = id;
|
||||
this.connectedUsers = connectedUsers;
|
||||
this.gameManager = gameManager;
|
||||
state = GameState.LOBBY;
|
||||
}
|
||||
|
||||
|
@ -84,6 +88,10 @@ public class Game {
|
|||
break;
|
||||
}
|
||||
}
|
||||
// this seems terrible
|
||||
if (players.size() == 0) {
|
||||
gameManager.destroyGame(id);
|
||||
}
|
||||
return players.size() == 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,15 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.socialgamer.cah.Constants.LongPollEvent;
|
||||
import net.socialgamer.cah.Constants.LongPollResponse;
|
||||
import net.socialgamer.cah.Constants.ReturnableData;
|
||||
import net.socialgamer.cah.data.GameManager.GameId;
|
||||
import net.socialgamer.cah.data.QueuedMessage.MessageType;
|
||||
|
||||
import com.google.inject.BindingAnnotation;
|
||||
import com.google.inject.Inject;
|
||||
|
@ -29,15 +34,18 @@ public class GameManager implements Provider<Integer> {
|
|||
private final int maxGames;
|
||||
private final Map<Integer, Game> games = new TreeMap<Integer, Game>();
|
||||
private final Provider<Game> gameProvider;
|
||||
private final ConnectedUsers users;
|
||||
/**
|
||||
* Potential next game id.
|
||||
*/
|
||||
private int nextId = 0;
|
||||
|
||||
@Inject
|
||||
public GameManager(final Provider<Game> gameProvider, @MaxGames final Integer maxGames) {
|
||||
public GameManager(final Provider<Game> gameProvider, @MaxGames final Integer maxGames,
|
||||
final ConnectedUsers users) {
|
||||
this.gameProvider = gameProvider;
|
||||
this.maxGames = maxGames;
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,6 +64,37 @@ public class GameManager implements Provider<Integer> {
|
|||
return null;
|
||||
}
|
||||
games.put(game.getId(), game);
|
||||
broadcastGameListRefresh();
|
||||
return game;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new game and puts the specified user into the game, if there are free game slots.
|
||||
* Returns null if there are already the maximum number of games in progress.
|
||||
*
|
||||
* Creating the game and adding the user are done atomically with respect to another game getting
|
||||
* created, or even getting the list of active games. It is impossible for another user to join
|
||||
* the game before the requesting user.
|
||||
*
|
||||
* @param user
|
||||
* User to place into the game.
|
||||
* @return Newly created game, or {@code null} if the maximum number of games are in progress.
|
||||
* @throws IllegalStateException
|
||||
* If the user is already in a game and cannot join another.
|
||||
*/
|
||||
public Game createGameWithPlayer(final User user) throws IllegalStateException {
|
||||
synchronized (games) {
|
||||
final Game game = createGame();
|
||||
if (game == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
game.addPlayer(user);
|
||||
} catch (final IllegalStateException ise) {
|
||||
destroyGame(game.getId());
|
||||
throw ise;
|
||||
}
|
||||
return game;
|
||||
}
|
||||
}
|
||||
|
@ -78,9 +117,17 @@ public class GameManager implements Provider<Integer> {
|
|||
nextId = gameId;
|
||||
}
|
||||
// TODO remove the players from the game
|
||||
|
||||
broadcastGameListRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastGameListRefresh() {
|
||||
final HashMap<ReturnableData, Object> broadcastData = new HashMap<ReturnableData, Object>();
|
||||
broadcastData.put(LongPollResponse.EVENT, LongPollEvent.GAME_REFRESH.toString());
|
||||
users.broadcastToAll(MessageType.GAME_EVENT, broadcastData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an unused game ID, or -1 if the maximum number of games are in progress. This should not be
|
||||
* called in such a case, though!
|
||||
|
|
|
@ -39,7 +39,7 @@ public class QueuedMessage implements Comparable<QueuedMessage> {
|
|||
* @author ajanata
|
||||
*/
|
||||
public enum MessageType {
|
||||
PLAYER_EVENT(3), GAME_PLAYER_EVENT(4), CHAT(5);
|
||||
PLAYER_EVENT(3), GAME_EVENT(3), GAME_PLAYER_EVENT(4), CHAT(5);
|
||||
|
||||
private final int weight;
|
||||
|
||||
|
|
|
@ -109,6 +109,9 @@ public class User {
|
|||
* Mark this user as no longer valid, probably because they pinged out.
|
||||
*/
|
||||
public void noLongerVaild() {
|
||||
if (currentGame != null) {
|
||||
currentGame.removePlayer(this);
|
||||
}
|
||||
valid = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package net.socialgamer.cah.handlers;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import net.socialgamer.cah.Constants.AjaxOperation;
|
||||
import net.socialgamer.cah.Constants.AjaxResponse;
|
||||
import net.socialgamer.cah.Constants.ErrorCode;
|
||||
import net.socialgamer.cah.Constants.ReturnableData;
|
||||
import net.socialgamer.cah.Constants.SessionAttribute;
|
||||
import net.socialgamer.cah.RequestWrapper;
|
||||
import net.socialgamer.cah.data.ConnectedUsers;
|
||||
import net.socialgamer.cah.data.Game;
|
||||
import net.socialgamer.cah.data.GameManager;
|
||||
import net.socialgamer.cah.data.User;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
|
||||
public class CreateGameHandler extends Handler {
|
||||
|
||||
public static final String OP = AjaxOperation.CREATE_GAME.toString();
|
||||
|
||||
private final GameManager gameManager;
|
||||
private final ConnectedUsers users;
|
||||
|
||||
@Inject
|
||||
public CreateGameHandler(final GameManager gameManager, final ConnectedUsers users) {
|
||||
this.gameManager = gameManager;
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<ReturnableData, Object> handle(final RequestWrapper request,
|
||||
final HttpSession session) {
|
||||
final Map<ReturnableData, Object> ret = new HashMap<ReturnableData, Object>();
|
||||
|
||||
final User user = (User) session.getAttribute(SessionAttribute.USER);
|
||||
assert (user != null);
|
||||
|
||||
Game game;
|
||||
try {
|
||||
game = gameManager.createGameWithPlayer(user);
|
||||
} catch (final IllegalStateException ise) {
|
||||
return error(ErrorCode.CANNOT_JOIN_GAME);
|
||||
}
|
||||
if (game == null) {
|
||||
return error(ErrorCode.TOO_MANY_GAMES);
|
||||
} else {
|
||||
ret.put(AjaxResponse.GAME_ID, game.getId());
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ public class Handlers {
|
|||
static {
|
||||
LIST = new HashMap<String, Class<? extends Handler>>();
|
||||
LIST.put(ChatHandler.OP, ChatHandler.class);
|
||||
LIST.put(CreateGameHandler.OP, CreateGameHandler.class);
|
||||
LIST.put(FirstLoadHandler.OP, FirstLoadHandler.class);
|
||||
LIST.put(GameListHandler.OP, GameListHandler.class);
|
||||
LIST.put(LogoutHandler.OP, LogoutHandler.class);
|
||||
|
|
Loading…
Reference in New Issue