From 7600ec27dec212dc4725ca59156ccbdd28684520 Mon Sep 17 00:00:00 2001 From: Andy Janata Date: Thu, 26 Jan 2012 18:07:39 -0800 Subject: [PATCH] - refactor game handlers a bit to add another abstract class that ensures the user requesting is in the game in question - add play card handler. need to send the white card count down to users that refresh the page. - ajax event handlers get the request object as well as the result object so the server doesn't have to send the game id back in a response --- WebContent/cah.css | 8 +- WebContent/js/cah.ajax.builder.js | 11 ++ WebContent/js/cah.ajax.handlers.js | 34 ++-- WebContent/js/cah.ajax.js | 2 +- WebContent/js/cah.constants.js | 29 +++- WebContent/js/cah.game.js | 148 +++++++++++++----- WebContent/js/cah.longpoll.handlers.js | 8 +- src/net/socialgamer/cah/Constants.java | 10 +- .../socialgamer/cah/UpdateHandlerList.java | 3 +- src/net/socialgamer/cah/data/Game.java | 94 +++++++++-- .../cah/handlers/GameWithPlayerHandler.java | 34 ++++ .../cah/handlers/GetCardsHandler.java | 4 +- .../cah/handlers/GetGameInfoHandler.java | 4 +- .../socialgamer/cah/handlers/Handlers.java | 1 + .../cah/handlers/JoinGameHandler.java | 8 - .../cah/handlers/LeaveGameHandler.java | 8 +- .../cah/handlers/PlayCardHandler.java | 52 ++++++ .../cah/handlers/StartGameHandler.java | 6 +- 18 files changed, 361 insertions(+), 103 deletions(-) create mode 100644 src/net/socialgamer/cah/handlers/GameWithPlayerHandler.java create mode 100644 src/net/socialgamer/cah/handlers/PlayCardHandler.java diff --git a/WebContent/cah.css b/WebContent/cah.css index 3715849..9c4c6e0 100644 --- a/WebContent/cah.css +++ b/WebContent/cah.css @@ -212,7 +212,7 @@ span.debug { color: black; } -.game_hand_cards .cah { +.cah { position: absolute; bottom: 0px; left: 0px; @@ -272,12 +272,6 @@ span.debug { margin-top: 15px; } -.game_black_card .cah { - position: absolute; - bottom: 15px; - left: 15px; -} - .confirm_card { float: left; margin-top: 10px; diff --git a/WebContent/js/cah.ajax.builder.js b/WebContent/js/cah.ajax.builder.js index 60a931f..211ff2a 100644 --- a/WebContent/js/cah.ajax.builder.js +++ b/WebContent/js/cah.ajax.builder.js @@ -103,6 +103,17 @@ cah.ajax.Builder.prototype.withGameId = function(gameId) { return this; }; +/** + * @param {number} + * cardId Card id field to use in the request. + * @returns {cah.ajax.Builder} This object. + */ +cah.ajax.Builder.prototype.withCardId = function(cardId) { + this.assertNotExecuted(); + this.data[cah.$.AjaxRequest.CARD_ID] = cardId; + return this; +}; + cah.ajax.Builder.prototype.assertNotExecuted = function() { if (this.run_) { throw "Request already executed."; diff --git a/WebContent/js/cah.ajax.handlers.js b/WebContent/js/cah.ajax.handlers.js index 90f5e1a..9ceb50d 100644 --- a/WebContent/js/cah.ajax.handlers.js +++ b/WebContent/js/cah.ajax.handlers.js @@ -80,38 +80,40 @@ cah.ajax.SuccessHandlers[cah.$.AjaxOperation.GAME_LIST] = function(data) { cah.GameList.instance.processUpdate(data); }; -cah.ajax.SuccessHandlers[cah.$.AjaxOperation.JOIN_GAME] = function(data) { +cah.ajax.SuccessHandlers[cah.$.AjaxOperation.JOIN_GAME] = function(data, req) { + cah.Game.joinGame(req[cah.$.AjaxRequest.GAME_ID]); +}; + +cah.ajax.SuccessHandlers[cah.$.AjaxOperation.CREATE_GAME] = function(data) { cah.Game.joinGame(data[cah.$.AjaxResponse.GAME_ID]); }; -cah.ajax.SuccessHandlers[cah.$.AjaxOperation.CREATE_GAME] = cah.ajax.SuccessHandlers[cah.$.AjaxOperation.JOIN_GAME]; - -cah.ajax.SuccessHandlers[cah.$.AjaxOperation.GET_GAME_INFO] = function(data) { - var game = cah.currentGames[data[cah.$.AjaxResponse.GAME_INFO][cah.$.GameInfo.ID]]; +cah.ajax.SuccessHandlers[cah.$.AjaxOperation.GET_GAME_INFO] = function(data, req) { + var game = cah.currentGames[req[cah.$.AjaxRequest.GAME_ID]]; if (game) { game.updateGameStatus(data); } }; -cah.ajax.SuccessHandlers[cah.$.AjaxOperation.LEAVE_GAME] = function(data) { - var game = cah.currentGames[data[cah.$.AjaxResponse.GAME_ID]]; +cah.ajax.SuccessHandlers[cah.$.AjaxOperation.LEAVE_GAME] = function(data, req) { + var game = cah.currentGames[req[cah.$.AjaxRequest.GAME_ID]]; if (game) { game.dispose(); - delete cah.currentGames[data[cah.$.AjaxResponse.GAME_ID]]; + delete cah.currentGames[req[cah.$.AjaxRequest.GAME_ID]]; } cah.GameList.instance.update(); cah.GameList.instance.show(); }; -cah.ajax.SuccessHandlers[cah.$.AjaxOperation.START_GAME] = function(data) { - var game = cah.currentGames[data[cah.$.AjaxResponse.GAME_ID]]; +cah.ajax.SuccessHandlers[cah.$.AjaxOperation.START_GAME] = function(data, req) { + var game = cah.currentGames[data[cah.$.AjaxRequest.GAME_ID]]; if (game) { game.startGameComplete(); } }; -cah.ajax.SuccessHandlers[cah.$.AjaxOperation.GET_CARDS] = function(data) { - var gameId = data[cah.$.AjaxResponse.GAME_ID]; +cah.ajax.SuccessHandlers[cah.$.AjaxOperation.GET_CARDS] = function(data, req) { + var gameId = req[cah.$.AjaxRequest.GAME_ID]; var game = cah.currentGames[gameId]; if (game) { game.dealtCards(data[cah.$.AjaxResponse.HAND]); @@ -122,3 +124,11 @@ cah.ajax.SuccessHandlers[cah.$.AjaxOperation.GET_CARDS] = function(data) { cah.log.error("Received hand for unknown game id " + gameId); } }; + +cah.ajax.SuccessHandlers[cah.$.AjaxOperation.PLAY_CARD] = function(data, req) { + var gameId = req[cah.$.AjaxRequest.GAME_ID]; + var game = cah.currentGames[gameId]; + if (game) { + game.playCardComplete(); + } +}; diff --git a/WebContent/js/cah.ajax.js b/WebContent/js/cah.ajax.js index 7264658..f9933ea 100644 --- a/WebContent/js/cah.ajax.js +++ b/WebContent/js/cah.ajax.js @@ -80,7 +80,7 @@ cah.Ajax.prototype.done = function(data) { } else { var req = this.pendingRequests[data[cah.$.AjaxResponse.SERIAL]]; if (req && cah.ajax.SuccessHandlers[req.getOp()]) { - cah.ajax.SuccessHandlers[req.getOp()](data); + cah.ajax.SuccessHandlers[req.getOp()](data, req.data); } else if (req) { cah.log.error("Unhandled response for op " + req.getOp()); } else { diff --git a/WebContent/js/cah.constants.js b/WebContent/js/cah.constants.js index e302d9a..0f466ca 100644 --- a/WebContent/js/cah.constants.js +++ b/WebContent/js/cah.constants.js @@ -6,24 +6,26 @@ cah.$.AjaxOperation = function() { // Dummy constructor to make Eclipse auto-complete. }; cah.$.AjaxOperation.prototype.dummyForAutocomplete = undefined; -cah.$.AjaxOperation.START_GAME = "start_game"; cah.$.AjaxOperation.FIRST_LOAD = "firstload"; +cah.$.AjaxOperation.START_GAME = "start_game"; cah.$.AjaxOperation.LOG_OUT = "logout"; -cah.$.AjaxOperation.GET_CARDS = "get_cards"; cah.$.AjaxOperation.GAME_LIST = "games"; -cah.$.AjaxOperation.JOIN_GAME = "join_game"; cah.$.AjaxOperation.GET_GAME_INFO = "get_game_info"; -cah.$.AjaxOperation.REGISTER = "register"; +cah.$.AjaxOperation.PLAY_CARD = "play_card"; cah.$.AjaxOperation.CREATE_GAME = "create_game"; +cah.$.AjaxOperation.GET_CARDS = "get_cards"; +cah.$.AjaxOperation.JOIN_GAME = "join_game"; +cah.$.AjaxOperation.REGISTER = "register"; cah.$.AjaxOperation.CHAT = "chat"; -cah.$.AjaxOperation.NAMES = "names"; cah.$.AjaxOperation.LEAVE_GAME = "leave_game"; +cah.$.AjaxOperation.NAMES = "names"; cah.$.AjaxRequest = function() { // Dummy constructor to make Eclipse auto-complete. }; cah.$.AjaxRequest.prototype.dummyForAutocomplete = undefined; cah.$.AjaxRequest.MESSAGE = "message"; +cah.$.AjaxRequest.CARD_ID = "card_id"; cah.$.AjaxRequest.GAME_ID = "game_id"; cah.$.AjaxRequest.SERIAL = "serial"; cah.$.AjaxRequest.OP = "op"; @@ -40,6 +42,7 @@ cah.$.AjaxResponse.BLACK_CARD = "black_card"; cah.$.AjaxResponse.IN_PROGRESS = "in_progress"; cah.$.AjaxResponse.GAMES = "games"; cah.$.AjaxResponse.NICKNAME = "nickname"; +cah.$.AjaxResponse.CARD_ID = "card_id"; cah.$.AjaxResponse.NEXT = "next"; cah.$.AjaxResponse.GAME_INFO = "game_info"; cah.$.AjaxResponse.ERROR = "error"; @@ -70,11 +73,15 @@ cah.$.ErrorCode = function() { }; cah.$.ErrorCode.prototype.dummyForAutocomplete = undefined; cah.$.ErrorCode.TOO_MANY_GAMES = "too_many_games"; +cah.$.ErrorCode.NO_CARD_SPECIFIED = "no_card_spec"; +cah.$.ErrorCode.NOT_YOUR_TURN = "not_your_turn"; cah.$.ErrorCode.INVALID_NICK = "invalid_nick"; cah.$.ErrorCode.NOT_GAME_HOST = "not_game_host"; cah.$.ErrorCode.ALREADY_STARTED = "already_started"; cah.$.ErrorCode.BAD_REQUEST = "bad_req"; cah.$.ErrorCode.CANNOT_JOIN_ANOTHER_GAME = "cannot_join_another_game"; +cah.$.ErrorCode.INVALID_CARD = "invalid_card"; +cah.$.ErrorCode.DO_NOT_HAVE_CARD = "do_not_have_card"; cah.$.ErrorCode.NO_GAME_SPECIFIED = "no_game_spec"; cah.$.ErrorCode.SESSION_EXPIRED = "session_expired"; cah.$.ErrorCode.MESSAGE_TOO_LONG = "msg_too_long"; @@ -84,30 +91,36 @@ cah.$.ErrorCode.NOT_REGISTERED = "not_registered"; cah.$.ErrorCode.NOT_ENOUGH_PLAYERS = "not_enough_players"; cah.$.ErrorCode.INVALID_GAME = "invalid_game"; cah.$.ErrorCode.OP_NOT_SPECIFIED = "op_not_spec"; +cah.$.ErrorCode.NOT_IN_THAT_GAME = "not_in_that_game"; cah.$.ErrorCode.NO_MSG_SPECIFIED = "no_msg_spec"; cah.$.ErrorCode.NICK_IN_USE = "nick_in_use"; -cah.$.ErrorCode.GAME_FULL = "game_full"; cah.$.ErrorCode.NO_NICK_SPECIFIED = "no_nick_spec"; +cah.$.ErrorCode.GAME_FULL = "game_full"; cah.$.ErrorCode_msg = {}; cah.$.ErrorCode_msg['bad_op'] = "Invalid operation."; cah.$.ErrorCode_msg['not_registered'] = "Not registered. Refresh the page."; cah.$.ErrorCode_msg['not_enough_players'] = "There are not enough players to start the game."; +cah.$.ErrorCode_msg['do_not_have_card'] = "You don't have that card."; cah.$.ErrorCode_msg['msg_too_long'] = "Messages cannot be longer than 200 characters."; cah.$.ErrorCode_msg['session_expired'] = "Your session has expired. Refresh the page."; cah.$.ErrorCode_msg['invalid_game'] = "Invalid game specified."; cah.$.ErrorCode_msg['no_game_spec'] = "No game specified."; +cah.$.ErrorCode_msg['no_card_spec'] = "No card specified."; cah.$.ErrorCode_msg['game_full'] = "That game is full. Join another."; 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['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['no_nick_spec'] = "No nickname specified."; +cah.$.ErrorCode_msg['not_your_turn'] = "It is not your turn to play a card."; cah.$.ErrorCode_msg['no_session'] = "Session not detected. Make sure you have cookies enabled."; +cah.$.ErrorCode_msg['no_nick_spec'] = "No nickname specified."; cah.$.ErrorCode_msg['nick_in_use'] = "Nickname is already in use."; cah.$.ErrorCode_msg['not_game_host'] = "Only the game host can do that."; cah.$.ErrorCode_msg['bad_req'] = "Bad request."; cah.$.ErrorCode_msg['cannot_join_another_game'] = "You cannot join another game."; +cah.$.ErrorCode_msg['invalid_card'] = "Invalid card specified."; cah.$.ErrorCode_msg['op_not_spec'] = "Operation not specified."; -cah.$.ErrorCode_msg['already_started'] = "The game has already started."; cah.$.ErrorCode_msg['no_msg_spec'] = "No message specified."; +cah.$.ErrorCode_msg['already_started'] = "The game has already started."; +cah.$.ErrorCode_msg['not_in_that_game'] = "You are not in that game."; cah.$.GameInfo = function() { // Dummy constructor to make Eclipse auto-complete. diff --git a/WebContent/js/cah.game.js b/WebContent/js/cah.game.js index 59cf137..3d75472 100644 --- a/WebContent/js/cah.game.js +++ b/WebContent/js/cah.game.js @@ -81,14 +81,24 @@ cah.Game = function(id) { */ this.handSelectedCard_ = null; + /** + * Card the player played this round. + * + * TODO make this an array when we support the multiple play blacks + * + * @type {cah.card.WhiteCard} + * @private + */ + this.myPlayedCard_ = null; + /** * The judge of the current round. * * @type {String} * @private */ - this.judge_ = null; + this.judge_ = null; /** * Scale factor for hand cards when zoomed out. * @@ -205,9 +215,33 @@ cah.Game.prototype.dealtCard = function(card) { var data = { card : card, }; - $(element).mouseenter(data, cah.bind(this, this.handCardMouseEnter_)).mouseleave(data, - cah.bind(this, this.handCardMouseLeave_)).click(data, cah.bind(this, this.handCardClick_)); - this.windowResize_(); + $(element).on("mouseenter.hand", data, cah.bind(this, this.handCardMouseEnter_)).on( + "mouseleave.hand", data, cah.bind(this, this.handCardMouseLeave_)).on("click.hand", data, + cah.bind(this, this.handCardClick_)); + + this.resizeHandCards_(); +}; + +/** + * Remove a card from the hand. + * + * @param {cah.card.WhiteCard} + * card Card to remove. + */ +cah.Game.prototype.removeCardFromHand = function(card) { + var cardIndex = -1; + for ( var index in this.hand_) { + if (this.hand_[index] == card) { + cardIndex = index; + break; + } + } + if (cardIndex != -1) { + $(card.getElement()).css("width", "").css("height", "").css("transform-origin", "").css( + "z-index", "").css("-moz-transform", "").css("-ms-transform", "").css("-webkit-transform", + "").css("-o-transform", "").off(".hand"); + this.hand_.splice(cardIndex, 1); + } }; /** @@ -299,33 +333,54 @@ cah.Game.prototype.updateGameStatus = function(data) { var playerInfos = data[cah.$.AjaxResponse.PLAYER_INFO]; for ( var index in playerInfos) { - var thisInfo = playerInfos[index]; - var playerName = thisInfo[cah.$.GamePlayerInfo.NAME]; - var playerStatus = thisInfo[cah.$.GamePlayerInfo.STATUS]; - var panel = this.scoreCards_[playerName]; - if (!panel) { - // new score panel - panel = new cah.GameScorePanel(playerName); - $(this.scoreboardElement_).append(panel.getElement()); - this.scoreCards_[playerName] = panel; - } - panel.update(thisInfo[cah.$.GamePlayerInfo.SCORE], playerStatus); + this.updateUserStatus(playerInfos[index]); + } +}; +/** + * Update a single player's info. + * + * @param {Object} + * playerInfo The PlayerInfo from the server. + */ +cah.Game.prototype.updateUserStatus = function(playerInfo) { + var playerName = playerInfo[cah.$.GamePlayerInfo.NAME]; + var playerStatus = playerInfo[cah.$.GamePlayerInfo.STATUS]; + var panel = this.scoreCards_[playerName]; + if (!panel) { + // new score panel + panel = new cah.GameScorePanel(playerName); + $(this.scoreboardElement_).append(panel.getElement()); + this.scoreCards_[playerName] = panel; + } + var oldStatus = panel.getStatus(); + panel.update(playerInfo[cah.$.GamePlayerInfo.SCORE], playerStatus); + + if (playerName == cah.nickname) { + $(".game_message", this.element_).text(cah.$.GamePlayerStatus_msg_2[playerStatus]); + if (playerStatus == cah.$.GamePlayerStatus.PLAYING) { + $(".confirm_card", this.element_).removeAttr("disabled"); + } else { + this.handSelectedCard_ = null; + $(".selected", $(".game_hand", this.element_)).removeClass("selected"); + $(".confirm_card", this.element_).attr("disabled", "disabled"); + } + } + + if (playerStatus == cah.$.GamePlayerStatus.JUDGE + || playerStatus == cah.$.GamePlayerStatus.JUDGING) { + this.judge_ = playerName; + } + + if (oldStatus == cah.$.GamePlayerStatus.PLAYING && playerStatus == cah.$.GamePlayerStatus.IDLE) { + // this player played a card. display a face-down white card in the area, or our card. + var displayCard; if (playerName == cah.nickname) { - $(".game_message", this.element_).text(cah.$.GamePlayerStatus_msg_2[playerStatus]); - if (playerStatus == cah.$.GamePlayerStatus.PLAYING) { - $(".confirm_card", this.element_).removeAttr("disabled"); - } else { - this.handSelectedCard_ = null; - $(".selected", $(".game_hand", this.element_)).removeClass("selected"); - $(".confirm_card", this.element_).attr("disabled", "disabled"); - } - } - - if (playerStatus == cah.$.GamePlayerStatus.JUDGE - || playerStatus == cah.$.GamePlayerStatus.JUDGING) { - this.judge_ = playerName; + displayCard = this.myPlayedCard_; + } else { + displayCard = new cah.card.WhiteCard(); } + $(".game_white_cards", this.element_).append(displayCard.getElement()); } }; @@ -335,7 +390,14 @@ cah.Game.prototype.updateGameStatus = function(data) { * @private */ cah.Game.prototype.confirmClick_ = function() { - // TODO + if (this.judge_ == cah.nickname) { + // TODO + } else { + if (this.handSelectedCard_ != null) { + cah.Ajax.build(cah.$.AjaxOperation.PLAY_CARD).withGameId(this.id_).withCardId( + this.handSelectedCard_.getServerId()).run(); + } + } }; /** @@ -393,6 +455,17 @@ cah.Game.prototype.startGameComplete = function() { $("#start_game").hide(); }; +cah.Game.prototype.playCardComplete = function() { + if (this.handSelectedCard_) { + $(".card", this.handSelectedCard_.getElement()).removeClass("selected"); + // TODO support for multiple play + this.myPlayedCard_ = this.handSelectedCard_; + this.removeCardFromHand(this.handSelectedCard_); + this.handSelectedCard_ = null; + } + $(".confirm_card", this.element_).attr("disabled", "disabled"); +}; + /** * Free resources used by this game and remove from the document. */ @@ -472,16 +545,6 @@ cah.Game.prototype.stateChange = function(data) { } }; -// /** -// * Remove a card from the hand. -// * -// * @param {number|cah.card.WhiteCard} -// * card If number, index of card to remove. If cah.card.WhiteCard, card instance to remove. -// */ -// cah.Game.prototype.removeCard = function(card) { -// -// }; - // /////////////////////////////////////////////// /** @@ -548,6 +611,13 @@ cah.GameScorePanel.prototype.update = function(score, status) { jQuery(".scorecard_status", this.element_).text(cah.$.GamePlayerStatus_msg[status]); }; +/** + * @returns {cah.$.GamePlayerStatus} The status of the player represented by this panel. + */ +cah.GameScorePanel.prototype.getStatus = function() { + return this.status_; +}; + // $(document).ready(function() { // var game = new cah.Game(0); // $("#main_holder").append(game.getElement()); diff --git a/WebContent/js/cah.longpoll.handlers.js b/WebContent/js/cah.longpoll.handlers.js index 3ec77f0..55c1a73 100644 --- a/WebContent/js/cah.longpoll.handlers.js +++ b/WebContent/js/cah.longpoll.handlers.js @@ -52,7 +52,8 @@ cah.longpoll.EventHandlers[cah.$.LongPollEvent.GAME_LIST_REFRESH] = function(dat cah.longpoll.EventHandlers[cah.$.LongPollEvent.GAME_PLAYER_JOIN] = function(data) { cah.longpoll.EventHandlers.__gameEvent(data, cah.Game.prototype.playerJoin, - data[cah.$.LongPollResponse.NICKNAME], "player join"); + data[cah.$.LongPollResponse.NICKNAME], + "player join (if you just joined a game this may be OK)"); }; cah.longpoll.EventHandlers[cah.$.LongPollEvent.GAME_PLAYER_LEAVE] = function(data) { @@ -70,6 +71,11 @@ cah.longpoll.EventHandlers[cah.$.LongPollEvent.GAME_STATE_CHANGE] = function(dat .__gameEvent(data, cah.Game.prototype.stateChange, data, "state change"); }; +cah.longpoll.EventHandlers[cah.$.LongPollEvent.GAME_PLAYER_INFO_CHANGE] = function(data) { + cah.longpoll.EventHandlers.__gameEvent(data, cah.Game.prototype.updateUserStatus, + data[cah.$.LongPollResponse.PLAYER_INFO], "player info change"); +}; + /** * Helper for event handlers for game events. * diff --git a/src/net/socialgamer/cah/Constants.java b/src/net/socialgamer/cah/Constants.java index 558a0e6..d252ea6 100644 --- a/src/net/socialgamer/cah/Constants.java +++ b/src/net/socialgamer/cah/Constants.java @@ -73,6 +73,7 @@ public class Constants { LEAVE_GAME("leave_game"), LOG_OUT("logout"), NAMES("names"), + PLAY_CARD("play_card"), REGISTER("register"), START_GAME("start_game"); @@ -89,6 +90,7 @@ public class Constants { } public enum AjaxRequest { + CARD_ID("card_id"), GAME_ID("game_id"), MESSAGE("message"), NICKNAME("nickname"), @@ -109,6 +111,7 @@ public class Constants { public enum AjaxResponse implements ReturnableData { BLACK_CARD("black_card"), + CARD_ID(AjaxRequest.CARD_ID.toString()), ERROR("error"), ERROR_CODE("error_code"), GAME_ID("game_id"), @@ -119,7 +122,7 @@ public class Constants { MAX_GAMES("max_games"), NAMES("names"), NEXT("next"), - NICKNAME("nickname"), + NICKNAME(AjaxRequest.NICKNAME.toString()), PLAYER_INFO("player_info"), SERIAL(AjaxRequest.SERIAL.toString()); @@ -140,20 +143,25 @@ public class Constants { BAD_OP("bad_op", "Invalid operation."), BAD_REQUEST("bad_req", "Bad request."), CANNOT_JOIN_ANOTHER_GAME("cannot_join_another_game", "You cannot join another game."), + DO_NOT_HAVE_CARD("do_not_have_card", "You don't have that card."), GAME_FULL("game_full", "That game is full. Join another."), + INVALID_CARD("invalid_card", "Invalid card specified."), INVALID_GAME("invalid_game", "Invalid game specified."), 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."), MESSAGE_TOO_LONG("msg_too_long", "Messages cannot be longer than 200 characters."), NICK_IN_USE("nick_in_use", "Nickname is already in use."), + NO_CARD_SPECIFIED("no_card_spec", "No card specified."), NO_GAME_SPECIFIED("no_game_spec", "No game specified."), NO_MSG_SPECIFIED("no_msg_spec", "No message specified."), NO_NICK_SPECIFIED("no_nick_spec", "No nickname specified."), NO_SESSION("no_session", "Session not detected. Make sure you have cookies enabled."), NOT_ENOUGH_PLAYERS("not_enough_players", "There are not enough players to start the game."), NOT_GAME_HOST("not_game_host", "Only the game host can do that."), + NOT_IN_THAT_GAME("not_in_that_game", "You are not in that game."), NOT_REGISTERED("not_registered", "Not registered. Refresh the page."), + NOT_YOUR_TURN("not_your_turn", "It is not your turn to play a card."), OP_NOT_SPECIFIED("op_not_spec", "Operation not specified."), 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 " + diff --git a/src/net/socialgamer/cah/UpdateHandlerList.java b/src/net/socialgamer/cah/UpdateHandlerList.java index 22c128a..33a90b5 100644 --- a/src/net/socialgamer/cah/UpdateHandlerList.java +++ b/src/net/socialgamer/cah/UpdateHandlerList.java @@ -8,7 +8,8 @@ import java.util.List; public class UpdateHandlerList { - private static final List EXCLUDE = Arrays.asList("GameHandler", "Handler", "Handlers"); + private static final List EXCLUDE = Arrays.asList("GameHandler", "GameWithPlayerHandler", + "Handler", "Handlers"); /** * @param args diff --git a/src/net/socialgamer/cah/data/Game.java b/src/net/socialgamer/cah/data/Game.java index 9a05638..6010999 100644 --- a/src/net/socialgamer/cah/data/Game.java +++ b/src/net/socialgamer/cah/data/Game.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Map; import net.socialgamer.cah.Constants.BlackCardData; +import net.socialgamer.cah.Constants.ErrorCode; import net.socialgamer.cah.Constants.GameInfo; import net.socialgamer.cah.Constants.GamePlayerInfo; import net.socialgamer.cah.Constants.GamePlayerStatus; @@ -47,6 +48,8 @@ import com.google.inject.Inject; public class Game { private final int id; private final List players = new ArrayList(10); + // TODO make this Map> once we support the multiple play black cards + private final Map playedCards = new HashMap(); private final ConnectedUsers connectedUsers; private final GameManager gameManager; private Player host; @@ -207,8 +210,13 @@ public class Game { final Map playerInfo = new HashMap(); playerInfo.put(GamePlayerInfo.NAME, player.getUser().getNickname()); playerInfo.put(GamePlayerInfo.SCORE, player.getScore()); - // TODO fix this once we actually have gameplay logic + playerInfo.put(GamePlayerInfo.STATUS, getPlayerStatus(player).toString()); + return playerInfo; + } + + private GamePlayerStatus getPlayerStatus(final Player player) { + // TODO fix this once we actually have gameplay logic final GamePlayerStatus playerStatus; switch (state) { @@ -220,15 +228,20 @@ public class Game { } break; case PLAYING: - if (players.get(judgeIndex) == player) { + if (getJudge() == player) { playerStatus = GamePlayerStatus.JUDGE; } else { - // TODO check if they have played a card - playerStatus = GamePlayerStatus.PLAYING; + synchronized (playedCards) { + if (playedCards.containsKey(player)) { + playerStatus = GamePlayerStatus.IDLE; + } else { + playerStatus = GamePlayerStatus.PLAYING; + } + } } break; case JUDGING: - if (players.get(judgeIndex) == player) { + if (getJudge() == player) { playerStatus = GamePlayerStatus.JUDGING; } else { playerStatus = GamePlayerStatus.IDLE; @@ -237,10 +250,7 @@ public class Game { default: throw new IllegalStateException("Unknown GameState " + state.toString()); } - - playerInfo.put(GamePlayerInfo.STATUS, playerStatus.toString()); - - return playerInfo; + return playerStatus; } /** @@ -297,7 +307,7 @@ public class Game { data.put(LongPollResponse.GAME_ID, id); data.put(LongPollResponse.BLACK_CARD, getBlackCard()); data.put(LongPollResponse.GAME_STATE, GameState.PLAYING.toString()); - data.put(LongPollResponse.JUDGE, players.get(judgeIndex).getUser().getNickname()); + data.put(LongPollResponse.JUDGE, getJudge().getUser().getNickname()); broadcastToPlayers(MessageType.GAME_EVENT, data); } @@ -363,10 +373,22 @@ public class Game { } public List> getHand(final User user) { + final Player player = getPlayerForUser(user); + if (player != null) { + final List hand = player.getHand(); + synchronized (hand) { + return handSubsetToClient(player.getHand()); + } + } else { + return null; + } + } + + private Player getPlayerForUser(final User user) { synchronized (players) { for (final Player player : players) { if (player.getUser() == user) { - return handSubsetToClient(player.getHand()); + return player; } } } @@ -399,6 +421,56 @@ public class Game { return users; } + private Player getJudge() { + return players.get(judgeIndex); + } + + public ErrorCode playCard(final User user, final int cardId) { + final Player player = getPlayerForUser(user); + if (player != null) { + if (getJudge() == player) { + return ErrorCode.NOT_YOUR_TURN; + } + final List hand = player.getHand(); + WhiteCard playCard = null; + synchronized (hand) { + final Iterator iter = hand.iterator(); + while (iter.hasNext()) { + final WhiteCard card = iter.next(); + if (card.getId() == cardId) { + playCard = card; + // 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(); + break; + } + } + } + if (playCard != null) { + synchronized (playedCards) { + playedCards.put(player, playCard); + + final HashMap data = new HashMap(); + data.put(LongPollResponse.EVENT, LongPollEvent.GAME_PLAYER_INFO_CHANGE.toString()); + data.put(LongPollResponse.GAME_ID, id); + data.put(LongPollResponse.PLAYER_INFO, getPlayerInfo(player)); + broadcastToPlayers(MessageType.GAME_PLAYER_EVENT, data); + + // TODO make this check that everybody has played proper number of cards when we support + // multiple play blacks + if (playedCards.size() == players.size() - 1) { + judgingState(); + } + } + return null; + } else { + return ErrorCode.DO_NOT_HAVE_CARD; + } + } else { + return null; + } + } + public class TooManyPlayersException extends Exception { private static final long serialVersionUID = -6603422097641992017L; } diff --git a/src/net/socialgamer/cah/handlers/GameWithPlayerHandler.java b/src/net/socialgamer/cah/handlers/GameWithPlayerHandler.java new file mode 100644 index 0000000..59de173 --- /dev/null +++ b/src/net/socialgamer/cah/handlers/GameWithPlayerHandler.java @@ -0,0 +1,34 @@ +package net.socialgamer.cah.handlers; + +import java.util.Map; + +import javax.servlet.http.HttpSession; + +import net.socialgamer.cah.Constants.ErrorCode; +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.User; + + +public abstract class GameWithPlayerHandler extends GameHandler { + + public GameWithPlayerHandler(final GameManager gameManager) { + super(gameManager); + } + + @Override + public final Map handle(final RequestWrapper request, + final HttpSession session, final User user, final Game game) { + // TODO when multiple games per user are supported, we need to change this. + if (user.getGame() != game) { + return error(ErrorCode.NOT_IN_THAT_GAME); + } else { + return handleWithUserInGame(request, session, user, game); + } + } + + public abstract Map handleWithUserInGame(final RequestWrapper request, + final HttpSession session, final User user, final Game game); +} diff --git a/src/net/socialgamer/cah/handlers/GetCardsHandler.java b/src/net/socialgamer/cah/handlers/GetCardsHandler.java index d90402c..8773928 100644 --- a/src/net/socialgamer/cah/handlers/GetCardsHandler.java +++ b/src/net/socialgamer/cah/handlers/GetCardsHandler.java @@ -19,7 +19,7 @@ import net.socialgamer.cah.data.User; import com.google.inject.Inject; -public class GetCardsHandler extends GameHandler { +public class GetCardsHandler extends GameWithPlayerHandler { @Inject public GetCardsHandler(final GameManager gameManager) { @@ -30,7 +30,7 @@ public class GetCardsHandler extends GameHandler { @SuppressWarnings("unchecked") @Override - public Map handle(final RequestWrapper request, + public Map handleWithUserInGame(final RequestWrapper request, final HttpSession session, final User user, final Game game) { final Map data = new HashMap(); diff --git a/src/net/socialgamer/cah/handlers/GetGameInfoHandler.java b/src/net/socialgamer/cah/handlers/GetGameInfoHandler.java index 2943580..3af9de9 100644 --- a/src/net/socialgamer/cah/handlers/GetGameInfoHandler.java +++ b/src/net/socialgamer/cah/handlers/GetGameInfoHandler.java @@ -16,7 +16,7 @@ import net.socialgamer.cah.data.User; import com.google.inject.Inject; -public class GetGameInfoHandler extends GameHandler { +public class GetGameInfoHandler extends GameWithPlayerHandler { public static final String OP = AjaxOperation.GET_GAME_INFO.toString(); @@ -26,7 +26,7 @@ public class GetGameInfoHandler extends GameHandler { } @Override - public Map handle(final RequestWrapper request, + public Map handleWithUserInGame(final RequestWrapper request, final HttpSession session, final User user, final Game game) { final Map data = new HashMap(); diff --git a/src/net/socialgamer/cah/handlers/Handlers.java b/src/net/socialgamer/cah/handlers/Handlers.java index fe60153..442195b 100644 --- a/src/net/socialgamer/cah/handlers/Handlers.java +++ b/src/net/socialgamer/cah/handlers/Handlers.java @@ -20,6 +20,7 @@ public class Handlers { LIST.put(LeaveGameHandler.OP, LeaveGameHandler.class); LIST.put(LogoutHandler.OP, LogoutHandler.class); LIST.put(NamesHandler.OP, NamesHandler.class); + LIST.put(PlayCardHandler.OP, PlayCardHandler.class); LIST.put(RegisterHandler.OP, RegisterHandler.class); LIST.put(StartGameHandler.OP, StartGameHandler.class); } diff --git a/src/net/socialgamer/cah/handlers/JoinGameHandler.java b/src/net/socialgamer/cah/handlers/JoinGameHandler.java index b3e49e6..79789cc 100644 --- a/src/net/socialgamer/cah/handlers/JoinGameHandler.java +++ b/src/net/socialgamer/cah/handlers/JoinGameHandler.java @@ -6,7 +6,6 @@ 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.RequestWrapper; @@ -39,14 +38,7 @@ public class JoinGameHandler extends GameHandler { } catch (final TooManyPlayersException e) { return error(ErrorCode.GAME_FULL); } - - // return the game id as a positive result to the client, which will then make another request - // to actually get game data - data.put(AjaxResponse.GAME_ID, game.getId()); - gameManager.broadcastGameListRefresh(); - return data; } - } diff --git a/src/net/socialgamer/cah/handlers/LeaveGameHandler.java b/src/net/socialgamer/cah/handlers/LeaveGameHandler.java index 1b1edc0..cb4a0e8 100644 --- a/src/net/socialgamer/cah/handlers/LeaveGameHandler.java +++ b/src/net/socialgamer/cah/handlers/LeaveGameHandler.java @@ -6,7 +6,6 @@ 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.ReturnableData; import net.socialgamer.cah.RequestWrapper; import net.socialgamer.cah.data.Game; @@ -16,7 +15,7 @@ import net.socialgamer.cah.data.User; import com.google.inject.Inject; -public class LeaveGameHandler extends GameHandler { +public class LeaveGameHandler extends GameWithPlayerHandler { public static final String OP = AjaxOperation.LEAVE_GAME.toString(); @@ -26,14 +25,11 @@ public class LeaveGameHandler extends GameHandler { } @Override - public Map handle(final RequestWrapper request, + public Map handleWithUserInGame(final RequestWrapper request, final HttpSession session, final User user, final Game game) { final Map data = new HashMap(); game.removePlayer(user); - // Return the game ID back to the client. It should in theory be able to figure out which leave - // was successfull but whatever. - data.put(AjaxResponse.GAME_ID, game.getId()); return data; } } diff --git a/src/net/socialgamer/cah/handlers/PlayCardHandler.java b/src/net/socialgamer/cah/handlers/PlayCardHandler.java new file mode 100644 index 0000000..14e2693 --- /dev/null +++ b/src/net/socialgamer/cah/handlers/PlayCardHandler.java @@ -0,0 +1,52 @@ +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.AjaxRequest; +import net.socialgamer.cah.Constants.ErrorCode; +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.User; + +import com.google.inject.Inject; + + +public class PlayCardHandler extends GameWithPlayerHandler { + + public static final String OP = AjaxOperation.PLAY_CARD.toString(); + + @Inject + public PlayCardHandler(final GameManager gameManager) { + super(gameManager); + } + + @Override + public Map handleWithUserInGame(final RequestWrapper request, + final HttpSession session, final User user, final Game game) { + final Map data = new HashMap(); + + final int cardId; + + if (request.getParameter(AjaxRequest.CARD_ID) == null) { + return error(ErrorCode.NO_CARD_SPECIFIED); + } + try { + cardId = Integer.parseInt(request.getParameter(AjaxRequest.CARD_ID)); + } catch (final NumberFormatException nfe) { + return error(ErrorCode.INVALID_CARD); + } + + final ErrorCode ec = game.playCard(user, cardId); + if (ec != null) { + return error(ec); + } else { + return data; + } + } +} diff --git a/src/net/socialgamer/cah/handlers/StartGameHandler.java b/src/net/socialgamer/cah/handlers/StartGameHandler.java index b1abebd..5cf02d0 100644 --- a/src/net/socialgamer/cah/handlers/StartGameHandler.java +++ b/src/net/socialgamer/cah/handlers/StartGameHandler.java @@ -6,7 +6,6 @@ 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.GameState; import net.socialgamer.cah.Constants.ReturnableData; @@ -18,7 +17,7 @@ import net.socialgamer.cah.data.User; import com.google.inject.Inject; -public class StartGameHandler extends GameHandler { +public class StartGameHandler extends GameWithPlayerHandler { public static final String OP = AjaxOperation.START_GAME.toString(); @@ -28,7 +27,7 @@ public class StartGameHandler extends GameHandler { } @Override - public Map handle(final RequestWrapper request, + public Map handleWithUserInGame(final RequestWrapper request, final HttpSession session, final User user, final Game game) { final Map data = new HashMap(); @@ -39,7 +38,6 @@ public class StartGameHandler extends GameHandler { } else if (!game.start()) { return error(ErrorCode.NOT_ENOUGH_PLAYERS); } else { - data.put(AjaxResponse.GAME_ID, game.getId()); return data; } }