- 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
This commit is contained in:
Andy Janata 2012-01-26 18:07:39 -08:00
parent 6ed6bdb365
commit 7600ec27de
18 changed files with 361 additions and 103 deletions

View File

@ -212,7 +212,7 @@ span.debug {
color: black; color: black;
} }
.game_hand_cards .cah { .cah {
position: absolute; position: absolute;
bottom: 0px; bottom: 0px;
left: 0px; left: 0px;
@ -272,12 +272,6 @@ span.debug {
margin-top: 15px; margin-top: 15px;
} }
.game_black_card .cah {
position: absolute;
bottom: 15px;
left: 15px;
}
.confirm_card { .confirm_card {
float: left; float: left;
margin-top: 10px; margin-top: 10px;

View File

@ -103,6 +103,17 @@ cah.ajax.Builder.prototype.withGameId = function(gameId) {
return this; 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() { cah.ajax.Builder.prototype.assertNotExecuted = function() {
if (this.run_) { if (this.run_) {
throw "Request already executed."; throw "Request already executed.";

View File

@ -80,38 +80,40 @@ cah.ajax.SuccessHandlers[cah.$.AjaxOperation.GAME_LIST] = function(data) {
cah.GameList.instance.processUpdate(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.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, req) {
var game = cah.currentGames[req[cah.$.AjaxRequest.GAME_ID]];
cah.ajax.SuccessHandlers[cah.$.AjaxOperation.GET_GAME_INFO] = function(data) {
var game = cah.currentGames[data[cah.$.AjaxResponse.GAME_INFO][cah.$.GameInfo.ID]];
if (game) { if (game) {
game.updateGameStatus(data); game.updateGameStatus(data);
} }
}; };
cah.ajax.SuccessHandlers[cah.$.AjaxOperation.LEAVE_GAME] = function(data) { cah.ajax.SuccessHandlers[cah.$.AjaxOperation.LEAVE_GAME] = function(data, req) {
var game = cah.currentGames[data[cah.$.AjaxResponse.GAME_ID]]; var game = cah.currentGames[req[cah.$.AjaxRequest.GAME_ID]];
if (game) { if (game) {
game.dispose(); 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.update();
cah.GameList.instance.show(); cah.GameList.instance.show();
}; };
cah.ajax.SuccessHandlers[cah.$.AjaxOperation.START_GAME] = function(data) { cah.ajax.SuccessHandlers[cah.$.AjaxOperation.START_GAME] = function(data, req) {
var game = cah.currentGames[data[cah.$.AjaxResponse.GAME_ID]]; var game = cah.currentGames[data[cah.$.AjaxRequest.GAME_ID]];
if (game) { if (game) {
game.startGameComplete(); game.startGameComplete();
} }
}; };
cah.ajax.SuccessHandlers[cah.$.AjaxOperation.GET_CARDS] = function(data) { cah.ajax.SuccessHandlers[cah.$.AjaxOperation.GET_CARDS] = function(data, req) {
var gameId = data[cah.$.AjaxResponse.GAME_ID]; var gameId = req[cah.$.AjaxRequest.GAME_ID];
var game = cah.currentGames[gameId]; var game = cah.currentGames[gameId];
if (game) { if (game) {
game.dealtCards(data[cah.$.AjaxResponse.HAND]); 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.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();
}
};

View File

@ -80,7 +80,7 @@ cah.Ajax.prototype.done = function(data) {
} else { } else {
var req = this.pendingRequests[data[cah.$.AjaxResponse.SERIAL]]; var req = this.pendingRequests[data[cah.$.AjaxResponse.SERIAL]];
if (req && cah.ajax.SuccessHandlers[req.getOp()]) { if (req && cah.ajax.SuccessHandlers[req.getOp()]) {
cah.ajax.SuccessHandlers[req.getOp()](data); cah.ajax.SuccessHandlers[req.getOp()](data, req.data);
} else if (req) { } else if (req) {
cah.log.error("Unhandled response for op " + req.getOp()); cah.log.error("Unhandled response for op " + req.getOp());
} else { } else {

View File

@ -6,24 +6,26 @@ cah.$.AjaxOperation = function() {
// Dummy constructor to make Eclipse auto-complete. // Dummy constructor to make Eclipse auto-complete.
}; };
cah.$.AjaxOperation.prototype.dummyForAutocomplete = undefined; cah.$.AjaxOperation.prototype.dummyForAutocomplete = undefined;
cah.$.AjaxOperation.START_GAME = "start_game";
cah.$.AjaxOperation.FIRST_LOAD = "firstload"; cah.$.AjaxOperation.FIRST_LOAD = "firstload";
cah.$.AjaxOperation.START_GAME = "start_game";
cah.$.AjaxOperation.LOG_OUT = "logout"; cah.$.AjaxOperation.LOG_OUT = "logout";
cah.$.AjaxOperation.GET_CARDS = "get_cards";
cah.$.AjaxOperation.GAME_LIST = "games"; cah.$.AjaxOperation.GAME_LIST = "games";
cah.$.AjaxOperation.JOIN_GAME = "join_game";
cah.$.AjaxOperation.GET_GAME_INFO = "get_game_info"; 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.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.CHAT = "chat";
cah.$.AjaxOperation.NAMES = "names";
cah.$.AjaxOperation.LEAVE_GAME = "leave_game"; cah.$.AjaxOperation.LEAVE_GAME = "leave_game";
cah.$.AjaxOperation.NAMES = "names";
cah.$.AjaxRequest = function() { cah.$.AjaxRequest = function() {
// Dummy constructor to make Eclipse auto-complete. // Dummy constructor to make Eclipse auto-complete.
}; };
cah.$.AjaxRequest.prototype.dummyForAutocomplete = undefined; cah.$.AjaxRequest.prototype.dummyForAutocomplete = undefined;
cah.$.AjaxRequest.MESSAGE = "message"; cah.$.AjaxRequest.MESSAGE = "message";
cah.$.AjaxRequest.CARD_ID = "card_id";
cah.$.AjaxRequest.GAME_ID = "game_id"; cah.$.AjaxRequest.GAME_ID = "game_id";
cah.$.AjaxRequest.SERIAL = "serial"; cah.$.AjaxRequest.SERIAL = "serial";
cah.$.AjaxRequest.OP = "op"; cah.$.AjaxRequest.OP = "op";
@ -40,6 +42,7 @@ cah.$.AjaxResponse.BLACK_CARD = "black_card";
cah.$.AjaxResponse.IN_PROGRESS = "in_progress"; cah.$.AjaxResponse.IN_PROGRESS = "in_progress";
cah.$.AjaxResponse.GAMES = "games"; cah.$.AjaxResponse.GAMES = "games";
cah.$.AjaxResponse.NICKNAME = "nickname"; cah.$.AjaxResponse.NICKNAME = "nickname";
cah.$.AjaxResponse.CARD_ID = "card_id";
cah.$.AjaxResponse.NEXT = "next"; cah.$.AjaxResponse.NEXT = "next";
cah.$.AjaxResponse.GAME_INFO = "game_info"; cah.$.AjaxResponse.GAME_INFO = "game_info";
cah.$.AjaxResponse.ERROR = "error"; cah.$.AjaxResponse.ERROR = "error";
@ -70,11 +73,15 @@ cah.$.ErrorCode = function() {
}; };
cah.$.ErrorCode.prototype.dummyForAutocomplete = undefined; cah.$.ErrorCode.prototype.dummyForAutocomplete = undefined;
cah.$.ErrorCode.TOO_MANY_GAMES = "too_many_games"; 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.INVALID_NICK = "invalid_nick";
cah.$.ErrorCode.NOT_GAME_HOST = "not_game_host"; cah.$.ErrorCode.NOT_GAME_HOST = "not_game_host";
cah.$.ErrorCode.ALREADY_STARTED = "already_started"; cah.$.ErrorCode.ALREADY_STARTED = "already_started";
cah.$.ErrorCode.BAD_REQUEST = "bad_req"; cah.$.ErrorCode.BAD_REQUEST = "bad_req";
cah.$.ErrorCode.CANNOT_JOIN_ANOTHER_GAME = "cannot_join_another_game"; 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.NO_GAME_SPECIFIED = "no_game_spec";
cah.$.ErrorCode.SESSION_EXPIRED = "session_expired"; cah.$.ErrorCode.SESSION_EXPIRED = "session_expired";
cah.$.ErrorCode.MESSAGE_TOO_LONG = "msg_too_long"; 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.NOT_ENOUGH_PLAYERS = "not_enough_players";
cah.$.ErrorCode.INVALID_GAME = "invalid_game"; cah.$.ErrorCode.INVALID_GAME = "invalid_game";
cah.$.ErrorCode.OP_NOT_SPECIFIED = "op_not_spec"; 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.NO_MSG_SPECIFIED = "no_msg_spec";
cah.$.ErrorCode.NICK_IN_USE = "nick_in_use"; cah.$.ErrorCode.NICK_IN_USE = "nick_in_use";
cah.$.ErrorCode.GAME_FULL = "game_full";
cah.$.ErrorCode.NO_NICK_SPECIFIED = "no_nick_spec"; cah.$.ErrorCode.NO_NICK_SPECIFIED = "no_nick_spec";
cah.$.ErrorCode.GAME_FULL = "game_full";
cah.$.ErrorCode_msg = {}; cah.$.ErrorCode_msg = {};
cah.$.ErrorCode_msg['bad_op'] = "Invalid operation."; cah.$.ErrorCode_msg['bad_op'] = "Invalid operation.";
cah.$.ErrorCode_msg['not_registered'] = "Not registered. Refresh the page."; 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['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['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['session_expired'] = "Your session has expired. Refresh the page.";
cah.$.ErrorCode_msg['invalid_game'] = "Invalid game specified."; cah.$.ErrorCode_msg['invalid_game'] = "Invalid game specified.";
cah.$.ErrorCode_msg['no_game_spec'] = "No 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['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['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['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_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['nick_in_use'] = "Nickname is already in use.";
cah.$.ErrorCode_msg['not_game_host'] = "Only the game host can do that."; cah.$.ErrorCode_msg['not_game_host'] = "Only the game host can do that.";
cah.$.ErrorCode_msg['bad_req'] = "Bad request."; cah.$.ErrorCode_msg['bad_req'] = "Bad request.";
cah.$.ErrorCode_msg['cannot_join_another_game'] = "You cannot join another game."; 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['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['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() { cah.$.GameInfo = function() {
// Dummy constructor to make Eclipse auto-complete. // Dummy constructor to make Eclipse auto-complete.

View File

@ -81,14 +81,24 @@ cah.Game = function(id) {
*/ */
this.handSelectedCard_ = null; 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. * The judge of the current round.
* *
* @type {String} * @type {String}
* @private * @private
*/ */
this.judge_ = null;
this.judge_ = null;
/** /**
* Scale factor for hand cards when zoomed out. * Scale factor for hand cards when zoomed out.
* *
@ -205,9 +215,33 @@ cah.Game.prototype.dealtCard = function(card) {
var data = { var data = {
card : card, card : card,
}; };
$(element).mouseenter(data, cah.bind(this, this.handCardMouseEnter_)).mouseleave(data, $(element).on("mouseenter.hand", data, cah.bind(this, this.handCardMouseEnter_)).on(
cah.bind(this, this.handCardMouseLeave_)).click(data, cah.bind(this, this.handCardClick_)); "mouseleave.hand", data, cah.bind(this, this.handCardMouseLeave_)).on("click.hand", data,
this.windowResize_(); 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]; var playerInfos = data[cah.$.AjaxResponse.PLAYER_INFO];
for ( var index in playerInfos) { for ( var index in playerInfos) {
var thisInfo = playerInfos[index]; this.updateUserStatus(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);
/**
* 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) { if (playerName == cah.nickname) {
$(".game_message", this.element_).text(cah.$.GamePlayerStatus_msg_2[playerStatus]); displayCard = this.myPlayedCard_;
if (playerStatus == cah.$.GamePlayerStatus.PLAYING) { } else {
$(".confirm_card", this.element_).removeAttr("disabled"); displayCard = new cah.card.WhiteCard();
} 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;
} }
$(".game_white_cards", this.element_).append(displayCard.getElement());
} }
}; };
@ -335,7 +390,14 @@ cah.Game.prototype.updateGameStatus = function(data) {
* @private * @private
*/ */
cah.Game.prototype.confirmClick_ = function() { 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(); $("#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. * 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]); 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() { // $(document).ready(function() {
// var game = new cah.Game(0); // var game = new cah.Game(0);
// $("#main_holder").append(game.getElement()); // $("#main_holder").append(game.getElement());

View File

@ -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[cah.$.LongPollEvent.GAME_PLAYER_JOIN] = function(data) {
cah.longpoll.EventHandlers.__gameEvent(data, cah.Game.prototype.playerJoin, 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) { 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"); .__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. * Helper for event handlers for game events.
* *

View File

@ -73,6 +73,7 @@ public class Constants {
LEAVE_GAME("leave_game"), LEAVE_GAME("leave_game"),
LOG_OUT("logout"), LOG_OUT("logout"),
NAMES("names"), NAMES("names"),
PLAY_CARD("play_card"),
REGISTER("register"), REGISTER("register"),
START_GAME("start_game"); START_GAME("start_game");
@ -89,6 +90,7 @@ public class Constants {
} }
public enum AjaxRequest { public enum AjaxRequest {
CARD_ID("card_id"),
GAME_ID("game_id"), GAME_ID("game_id"),
MESSAGE("message"), MESSAGE("message"),
NICKNAME("nickname"), NICKNAME("nickname"),
@ -109,6 +111,7 @@ public class Constants {
public enum AjaxResponse implements ReturnableData { public enum AjaxResponse implements ReturnableData {
BLACK_CARD("black_card"), BLACK_CARD("black_card"),
CARD_ID(AjaxRequest.CARD_ID.toString()),
ERROR("error"), ERROR("error"),
ERROR_CODE("error_code"), ERROR_CODE("error_code"),
GAME_ID("game_id"), GAME_ID("game_id"),
@ -119,7 +122,7 @@ public class Constants {
MAX_GAMES("max_games"), MAX_GAMES("max_games"),
NAMES("names"), NAMES("names"),
NEXT("next"), NEXT("next"),
NICKNAME("nickname"), NICKNAME(AjaxRequest.NICKNAME.toString()),
PLAYER_INFO("player_info"), PLAYER_INFO("player_info"),
SERIAL(AjaxRequest.SERIAL.toString()); SERIAL(AjaxRequest.SERIAL.toString());
@ -140,20 +143,25 @@ public class Constants {
BAD_OP("bad_op", "Invalid operation."), BAD_OP("bad_op", "Invalid operation."),
BAD_REQUEST("bad_req", "Bad request."), BAD_REQUEST("bad_req", "Bad request."),
CANNOT_JOIN_ANOTHER_GAME("cannot_join_another_game", "You cannot join another game."), 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."), 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_GAME("invalid_game", "Invalid game specified."),
INVALID_NICK("invalid_nick", "Nickname must contain only upper and lower case letters, " + 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 " + "numbers, or underscores, must be 3 to 30 characters long, and must not start with a " +
"number."), "number."),
MESSAGE_TOO_LONG("msg_too_long", "Messages cannot be longer than 200 characters."), MESSAGE_TOO_LONG("msg_too_long", "Messages cannot be longer than 200 characters."),
NICK_IN_USE("nick_in_use", "Nickname is already in use."), 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_GAME_SPECIFIED("no_game_spec", "No game specified."),
NO_MSG_SPECIFIED("no_msg_spec", "No message specified."), NO_MSG_SPECIFIED("no_msg_spec", "No message specified."),
NO_NICK_SPECIFIED("no_nick_spec", "No nickname specified."), NO_NICK_SPECIFIED("no_nick_spec", "No nickname specified."),
NO_SESSION("no_session", "Session not detected. Make sure you have cookies enabled."), 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_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_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_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."), 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 " + TOO_MANY_GAMES("too_many_games", "There are too many games already in progress. Either join " +

View File

@ -8,7 +8,8 @@ import java.util.List;
public class UpdateHandlerList { public class UpdateHandlerList {
private static final List<String> EXCLUDE = Arrays.asList("GameHandler", "Handler", "Handlers"); private static final List<String> EXCLUDE = Arrays.asList("GameHandler", "GameWithPlayerHandler",
"Handler", "Handlers");
/** /**
* @param args * @param args

View File

@ -8,6 +8,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import net.socialgamer.cah.Constants.BlackCardData; import net.socialgamer.cah.Constants.BlackCardData;
import net.socialgamer.cah.Constants.ErrorCode;
import net.socialgamer.cah.Constants.GameInfo; import net.socialgamer.cah.Constants.GameInfo;
import net.socialgamer.cah.Constants.GamePlayerInfo; import net.socialgamer.cah.Constants.GamePlayerInfo;
import net.socialgamer.cah.Constants.GamePlayerStatus; import net.socialgamer.cah.Constants.GamePlayerStatus;
@ -47,6 +48,8 @@ import com.google.inject.Inject;
public class Game { public class Game {
private final int id; private final int id;
private final List<Player> players = new ArrayList<Player>(10); private final List<Player> players = new ArrayList<Player>(10);
// TODO make this Map<Player, List<WhiteCard>> once we support the multiple play black cards
private final Map<Player, WhiteCard> playedCards = new HashMap<Player, WhiteCard>();
private final ConnectedUsers connectedUsers; private final ConnectedUsers connectedUsers;
private final GameManager gameManager; private final GameManager gameManager;
private Player host; private Player host;
@ -207,8 +210,13 @@ public class Game {
final Map<GamePlayerInfo, Object> playerInfo = new HashMap<GamePlayerInfo, Object>(); final Map<GamePlayerInfo, Object> playerInfo = new HashMap<GamePlayerInfo, Object>();
playerInfo.put(GamePlayerInfo.NAME, player.getUser().getNickname()); playerInfo.put(GamePlayerInfo.NAME, player.getUser().getNickname());
playerInfo.put(GamePlayerInfo.SCORE, player.getScore()); 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; final GamePlayerStatus playerStatus;
switch (state) { switch (state) {
@ -220,15 +228,20 @@ public class Game {
} }
break; break;
case PLAYING: case PLAYING:
if (players.get(judgeIndex) == player) { if (getJudge() == player) {
playerStatus = GamePlayerStatus.JUDGE; playerStatus = GamePlayerStatus.JUDGE;
} else { } else {
// TODO check if they have played a card synchronized (playedCards) {
playerStatus = GamePlayerStatus.PLAYING; if (playedCards.containsKey(player)) {
playerStatus = GamePlayerStatus.IDLE;
} else {
playerStatus = GamePlayerStatus.PLAYING;
}
}
} }
break; break;
case JUDGING: case JUDGING:
if (players.get(judgeIndex) == player) { if (getJudge() == player) {
playerStatus = GamePlayerStatus.JUDGING; playerStatus = GamePlayerStatus.JUDGING;
} else { } else {
playerStatus = GamePlayerStatus.IDLE; playerStatus = GamePlayerStatus.IDLE;
@ -237,10 +250,7 @@ public class Game {
default: default:
throw new IllegalStateException("Unknown GameState " + state.toString()); throw new IllegalStateException("Unknown GameState " + state.toString());
} }
return playerStatus;
playerInfo.put(GamePlayerInfo.STATUS, playerStatus.toString());
return playerInfo;
} }
/** /**
@ -297,7 +307,7 @@ public class Game {
data.put(LongPollResponse.GAME_ID, id); data.put(LongPollResponse.GAME_ID, id);
data.put(LongPollResponse.BLACK_CARD, getBlackCard()); data.put(LongPollResponse.BLACK_CARD, getBlackCard());
data.put(LongPollResponse.GAME_STATE, GameState.PLAYING.toString()); 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); broadcastToPlayers(MessageType.GAME_EVENT, data);
} }
@ -363,10 +373,22 @@ public class Game {
} }
public List<Map<WhiteCardData, Object>> getHand(final User user) { public List<Map<WhiteCardData, Object>> getHand(final User user) {
final Player player = getPlayerForUser(user);
if (player != null) {
final List<WhiteCard> hand = player.getHand();
synchronized (hand) {
return handSubsetToClient(player.getHand());
}
} else {
return null;
}
}
private Player getPlayerForUser(final User user) {
synchronized (players) { synchronized (players) {
for (final Player player : players) { for (final Player player : players) {
if (player.getUser() == user) { if (player.getUser() == user) {
return handSubsetToClient(player.getHand()); return player;
} }
} }
} }
@ -399,6 +421,56 @@ public class Game {
return users; 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<WhiteCard> hand = player.getHand();
WhiteCard playCard = null;
synchronized (hand) {
final Iterator<WhiteCard> 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<ReturnableData, Object> data = new HashMap<ReturnableData, Object>();
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 { public class TooManyPlayersException extends Exception {
private static final long serialVersionUID = -6603422097641992017L; private static final long serialVersionUID = -6603422097641992017L;
} }

View File

@ -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<ReturnableData, Object> 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<ReturnableData, Object> handleWithUserInGame(final RequestWrapper request,
final HttpSession session, final User user, final Game game);
}

View File

@ -19,7 +19,7 @@ import net.socialgamer.cah.data.User;
import com.google.inject.Inject; import com.google.inject.Inject;
public class GetCardsHandler extends GameHandler { public class GetCardsHandler extends GameWithPlayerHandler {
@Inject @Inject
public GetCardsHandler(final GameManager gameManager) { public GetCardsHandler(final GameManager gameManager) {
@ -30,7 +30,7 @@ public class GetCardsHandler extends GameHandler {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public Map<ReturnableData, Object> handle(final RequestWrapper request, public Map<ReturnableData, Object> handleWithUserInGame(final RequestWrapper request,
final HttpSession session, final User user, final Game game) { final HttpSession session, final User user, final Game game) {
final Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>(); final Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>();

View File

@ -16,7 +16,7 @@ import net.socialgamer.cah.data.User;
import com.google.inject.Inject; 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(); public static final String OP = AjaxOperation.GET_GAME_INFO.toString();
@ -26,7 +26,7 @@ public class GetGameInfoHandler extends GameHandler {
} }
@Override @Override
public Map<ReturnableData, Object> handle(final RequestWrapper request, public Map<ReturnableData, Object> handleWithUserInGame(final RequestWrapper request,
final HttpSession session, final User user, final Game game) { final HttpSession session, final User user, final Game game) {
final Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>(); final Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>();

View File

@ -20,6 +20,7 @@ public class Handlers {
LIST.put(LeaveGameHandler.OP, LeaveGameHandler.class); LIST.put(LeaveGameHandler.OP, LeaveGameHandler.class);
LIST.put(LogoutHandler.OP, LogoutHandler.class); LIST.put(LogoutHandler.OP, LogoutHandler.class);
LIST.put(NamesHandler.OP, NamesHandler.class); LIST.put(NamesHandler.OP, NamesHandler.class);
LIST.put(PlayCardHandler.OP, PlayCardHandler.class);
LIST.put(RegisterHandler.OP, RegisterHandler.class); LIST.put(RegisterHandler.OP, RegisterHandler.class);
LIST.put(StartGameHandler.OP, StartGameHandler.class); LIST.put(StartGameHandler.OP, StartGameHandler.class);
} }

View File

@ -6,7 +6,6 @@ import java.util.Map;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import net.socialgamer.cah.Constants.AjaxOperation; import net.socialgamer.cah.Constants.AjaxOperation;
import net.socialgamer.cah.Constants.AjaxResponse;
import net.socialgamer.cah.Constants.ErrorCode; import net.socialgamer.cah.Constants.ErrorCode;
import net.socialgamer.cah.Constants.ReturnableData; import net.socialgamer.cah.Constants.ReturnableData;
import net.socialgamer.cah.RequestWrapper; import net.socialgamer.cah.RequestWrapper;
@ -39,14 +38,7 @@ public class JoinGameHandler extends GameHandler {
} catch (final TooManyPlayersException e) { } catch (final TooManyPlayersException e) {
return error(ErrorCode.GAME_FULL); 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(); gameManager.broadcastGameListRefresh();
return data; return data;
} }
} }

View File

@ -6,7 +6,6 @@ import java.util.Map;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import net.socialgamer.cah.Constants.AjaxOperation; import net.socialgamer.cah.Constants.AjaxOperation;
import net.socialgamer.cah.Constants.AjaxResponse;
import net.socialgamer.cah.Constants.ReturnableData; import net.socialgamer.cah.Constants.ReturnableData;
import net.socialgamer.cah.RequestWrapper; import net.socialgamer.cah.RequestWrapper;
import net.socialgamer.cah.data.Game; import net.socialgamer.cah.data.Game;
@ -16,7 +15,7 @@ import net.socialgamer.cah.data.User;
import com.google.inject.Inject; import com.google.inject.Inject;
public class LeaveGameHandler extends GameHandler { public class LeaveGameHandler extends GameWithPlayerHandler {
public static final String OP = AjaxOperation.LEAVE_GAME.toString(); public static final String OP = AjaxOperation.LEAVE_GAME.toString();
@ -26,14 +25,11 @@ public class LeaveGameHandler extends GameHandler {
} }
@Override @Override
public Map<ReturnableData, Object> handle(final RequestWrapper request, public Map<ReturnableData, Object> handleWithUserInGame(final RequestWrapper request,
final HttpSession session, final User user, final Game game) { final HttpSession session, final User user, final Game game) {
final Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>(); final Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>();
game.removePlayer(user); 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; return data;
} }
} }

View File

@ -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<ReturnableData, Object> handleWithUserInGame(final RequestWrapper request,
final HttpSession session, final User user, final Game game) {
final Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>();
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;
}
}
}

View File

@ -6,7 +6,6 @@ import java.util.Map;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import net.socialgamer.cah.Constants.AjaxOperation; import net.socialgamer.cah.Constants.AjaxOperation;
import net.socialgamer.cah.Constants.AjaxResponse;
import net.socialgamer.cah.Constants.ErrorCode; import net.socialgamer.cah.Constants.ErrorCode;
import net.socialgamer.cah.Constants.GameState; import net.socialgamer.cah.Constants.GameState;
import net.socialgamer.cah.Constants.ReturnableData; import net.socialgamer.cah.Constants.ReturnableData;
@ -18,7 +17,7 @@ import net.socialgamer.cah.data.User;
import com.google.inject.Inject; import com.google.inject.Inject;
public class StartGameHandler extends GameHandler { public class StartGameHandler extends GameWithPlayerHandler {
public static final String OP = AjaxOperation.START_GAME.toString(); public static final String OP = AjaxOperation.START_GAME.toString();
@ -28,7 +27,7 @@ public class StartGameHandler extends GameHandler {
} }
@Override @Override
public Map<ReturnableData, Object> handle(final RequestWrapper request, public Map<ReturnableData, Object> handleWithUserInGame(final RequestWrapper request,
final HttpSession session, final User user, final Game game) { final HttpSession session, final User user, final Game game) {
final Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>(); final Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>();
@ -39,7 +38,6 @@ public class StartGameHandler extends GameHandler {
} else if (!game.start()) { } else if (!game.start()) {
return error(ErrorCode.NOT_ENOUGH_PLAYERS); return error(ErrorCode.NOT_ENOUGH_PLAYERS);
} else { } else {
data.put(AjaxResponse.GAME_ID, game.getId());
return data; return data;
} }
} }