/*
* Copyright (c) 2012, Andy Janata
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Class to manage the game interface.
*
* @author Andy Janata (ajanata@socialgamer.net)
* @param {Number}
* id The game id.
* @constructor
*/
cah.Game = function(id) {
/**
* The game id.
*
* @type {Number}
* @private
*/
this.id_ = id;
/**
* The element for this game lobby.
*
* @type {HTMLDivElement}
* @private
*/
this.element_ = $("#game_template").clone()[0];
this.element_.id = "game_" + id;
$(this.element_).removeClass("hide");
/**
* The element for the scoreboard for this game.
*
* @type {HTMLDivElement}
* @private
*/
this.scoreboardElement_ = $("#scoreboard_template").clone()[0];
this.scoreboardElement_.id = "scoreboard_" + id;
$(this.scoreboardElement_).removeClass("hide");
/**
* The element for the chat room for this game
*
* @type {HTMLDivElement}
* @private
*/
this.chatElement_ = $("#tab-global").clone()[0];
this.chatElement_.id = "tab-chat-game_" + this.id_;
$(".chat_submit", this.chatElement_).click(chatsubmit_click(this.id_, this.chatElement_));
$(".chat", this.chatElement_).keyup(chat_keyup($(".chat_submit", this.chatElement_)));
// TODO make it not even copy this in the first place
$(".log", this.chatElement_).empty();
/**
* The element for the game options for this game.
*
* @type {HTMLDivElement}
* @private
*/
this.optionsElement_ = $("#game_options_template").clone()[0];
this.optionsElement_.id = "game_options_" + id;
// TODO: It looks like I'm not changing the id on the label elements...
$("#score_limit_template_label", this.optionsElement_).attr("for", "score_limit_" + id);
$("#player_limit_template_label", this.optionsElement_).attr("for", "player_limit_" + id);
$("#card_set_template_label", this.optionsElement_).attr("for", "card_set_" + id);
$("#game_password_template_label", this.optionsElement_).attr("for", "game_password_" + id);
$("#game_hide_password_template_label", this.optionsElement_).attr("for",
"game_hide_password_" + id);
$("#use_timer_template_label", this.optionsElement_).attr("for", "use_timer_" + id);
$("#score_limit_template", this.optionsElement_).attr("id", "score_limit_" + id);
$("#player_limit_template", this.optionsElement_).attr("id", "player_limit_" + id);
$("#card_set_template", this.optionsElement_).attr("id", "card_set_" + id);
$("#game_password_template", this.optionsElement_).attr("id", "game_password_" + id);
$("#game_fake_password_template", this.optionsElement_).attr("id", "game_fake_password_" + id);
$("#game_hide_password_template", this.optionsElement_).attr("id", "game_hide_password_" + id);
$("#use_timer_template", this.optionsElement_).attr("id", "use_timer_" + id);
for ( var key in cah.CardSet.list) {
/** @type {cah.CardSet} */
var cardSet = cah.CardSet.list[key];
var cardSetElementId = 'card_set_' + this.id_ + '_' + cardSet.getId();
var title = cardSet.getDescription() + ' ' + cardSet.getBlackCardCount() + ' black card'
+ (cardSet.getBlackCardCount() == 1 ? '' : 's') + ', ' + cardSet.getWhiteCardCount()
+ ' white card' + (cardSet.getWhiteCardCount() == 1 ? '' : 's') + '.';
var aria_label = cardSet.getName() + '. ' + title;
// that space at the beginning matters
var html = ' ';
if (cardSet.isBaseDeck()) {
$(".base_card_sets", this.optionsElement_).append(html);
} else {
$(".extra_card_sets", this.optionsElement_).append(html);
}
}
$("label", this.optionsElement_).removeAttr("id");
$(".game_options", this.element_).replaceWith(this.optionsElement_);
/**
* The nickname of the host of this game.
*
* @type {String}
* @private
*/
this.host_ = "";
/**
* User->value mapping of scorecards in the scoreboard.
*
* @type {Object}
* @private
*/
this.scoreCards_ = {};
/**
* The cards in the player's hand.
*
* @type {Array}
* @private
*/
this.hand_ = Array();
/**
* Map of id to card object arrays for round cards.
*
* @type {Object}
* @private
*/
this.roundCards_ = {};
/**
* The game's state.
*
* @type {cah.$.GameState}
* @private
*/
this.state_ = cah.$.GameState.LOBBY;
/**
* The black card for the current round.
*
* @type {cah.card.BlackCard}
* @private
*/
this.blackCard_ = null;
/**
* The black card for the previous round.
*
* @type {cah.card.BlackCard}
* @private
*/
this.lastBlackCard_ = null;
/**
* Selected card from the player's hand.
*
* @type {cah.card.WhiteCard}
* @private;
*/
this.handSelectedCard_ = null;
/**
* Selected card from the round's white cards.
*
* @type {cah.card.WhiteCard}
* @private;
*/
this.roundSelectedCard_ = null;
/**
* The name of the judge of the current round.
*
* @type {String}
* @private
*/
this.judge_ = null;
/**
* Scale factor for hand cards when zoomed out.
*
* @type {Number}
* @private
*/
this.handCardSmallScale_ = .35;
/**
* Scale factor for hand cards when zoomed in.
*
* @type {Number}
* @private
*/
this.handCardLargeScale_ = .6;
/**
* Size for hand cards when zoomed out.
*
* @type {Number}
* @private
*/
this.handCardSmallSize_ = 83;
/**
* Size for hand cards when zoomed in.
*
* @type {Number}
* @private
*/
this.handCardLargeSize_ = 142;
/**
* Scale factor for round cards when zoomed out.
*
* @type {Number}
* @private
*/
this.roundCardSmallScale_ = 1;
/**
* Scale factor for round cards when zoomed in.
*
* @type {Number}
* @private
*/
this.roundCardLargeScale_ = 1;
/**
* Size for round cards when zoomed out.
*
* @type {Number}
* @private
*/
this.roundCardSmallSize_ = 236;
/**
* Size for round cards when zoomed in.
*
* @type {Number}
* @private
*/
this.roundCardLargeSize_ = 236;
/**
* Whether we are showing the result of the last round.
*
* @type {Boolean}
* @private
*/
this.showingLastRound_ = false;
/**
* Whether we are showing the options or the game.
*
* @type {Boolean}
* @private
*/
this.showingOptions_ = true;
$("#leave_game").click(cah.bind(this, this.leaveGameClick_));
$("#start_game").click(cah.bind(this, this.startGameClick_));
$(".confirm_card", this.element_).click(cah.bind(this, this.confirmClick_));
$(".game_show_last_round", this.element_).click(cah.bind(this, this.showLastRoundClick_));
$(".game_show_options", this.element_).click(cah.bind(this, this.showOptionsClick_));
$("select", this.optionsElement_).change(cah.bind(this, this.optionChanged_));
$("input", this.optionsElement_).blur(cah.bind(this, this.optionChanged_));
$(".use_timer", this.optionsElement_).change(cah.bind(this, this.optionChanged_));
$(".card_set", this.optionsElement_).change(cah.bind(this, this.optionChanged_));
$(".game_hide_password", this.optionsElement_).click(cah.bind(this, this.showOrHidePassword_));
$(window).on("resize.game_" + this.id_, cah.bind(this, this.windowResize_));
};
/**
* Load game data from the server and display the game lobby.
*
* TODO reload round win state
*
* @param {Number}
* gameId The game id.
*/
cah.Game.joinGame = function(gameId) {
cah.Ajax.build(cah.$.AjaxOperation.GET_GAME_INFO).withGameId(gameId).run();
cah.Ajax.build(cah.$.AjaxOperation.GET_CARDS).withGameId(gameId).run();
cah.GameList.instance.hide();
var game = new cah.Game(gameId);
cah.currentGames[gameId] = game;
game.insertIntoDocument();
};
/**
* Toggle showing the previous round result.
*
* @private
*/
cah.Game.prototype.showLastRoundClick_ = function() {
if (this.showingLastRound_) {
$(".game_show_last_round", this.element_).attr("value", "Show Last Round");
$(".game_black_card_round_indicator", this.element_).text("this round is");
$(".game_black_card", this.element_).empty().append(this.blackCard_.getElement());
$(".game_white_card_wrapper", this.element_).removeClass("hide");
$(".game_last_round", this.element_).addClass("hide");
} else {
$(".game_show_last_round", this.element_).attr("value", "Show Current Round");
$(".game_black_card_round_indicator", this.element_).text("last round was");
$(".game_black_card", this.element_).empty().append(this.lastBlackCard_.getElement());
$(".game_white_card_wrapper", this.element_).addClass("hide");
$(".game_last_round", this.element_).removeClass("hide");
}
this.showingLastRound_ = !this.showingLastRound_;
};
/**
* Toggle showing the game's options.
*
* @private
*/
cah.Game.prototype.showOptionsClick_ = function() {
if (this.showingOptions_) {
this.showOptions_();
} else {
this.hideOptions_();
}
this.showingOptions_ = !this.showingOptions_;
};
/**
* Show or hide the game's password, based on the value of the checkbox.
*
* @private
*/
cah.Game.prototype.showOrHidePassword_ = function() {
if ($(".game_hide_password", this.optionsElement_).attr("checked")) {
$(".game_password", this.optionsElement_).hide();
$(".game_fake_password", this.optionsElement_).show();
$(".game_fake_password", this.optionsElement_).attr("value",
$(".game_password", this.optionsElement_).attr("value"));
$(".game_fake_password", this.optionsElement_).attr("disabled", "disabled");
} else {
$(".game_password", this.optionsElement_).show();
$(".game_fake_password", this.optionsElement_).hide();
}
};
/**
* @return {HTMLDivElement} This object's element.
*/
cah.Game.prototype.getElement = function() {
return this.element_;
};
/**
* Set the black card on display.
*
* @param {Object}
* card Black card data from server.
*/
cah.Game.prototype.setBlackCard = function(card) {
this.blackCard_ = new cah.card.BlackCard(true, card[cah.$.BlackCardData.ID]);
this.blackCard_.setText(card[cah.$.BlackCardData.TEXT]);
this.blackCard_.setWatermark(card[cah.$.BlackCardData.WATERMARK]);
this.blackCard_.setDraw(card[cah.$.BlackCardData.DRAW]);
this.blackCard_.setPick(card[cah.$.BlackCardData.PICK]);
if (1 != card[cah.$.BlackCardData.PICK] && this.judge_ != cah.nickname) {
cah.log.status_with_game(this, "Play " + card[cah.$.BlackCardData.PICK]
+ " cards, in the order you wish them to be judged.");
}
if (!this.showingLastRound_) {
$(".game_black_card", this.element_).empty().append(this.blackCard_.getElement());
}
};
/**
* Add multiple cards to the player's hand.
*
* @param {Array}
* cards The array of card objects sent from the server.
*/
cah.Game.prototype.dealtCards = function(cards) {
for ( var index in cards) {
var thisCard = cards[index];
var card = new cah.card.WhiteCard(true, thisCard[cah.$.WhiteCardData.ID]);
card.setText(thisCard[cah.$.WhiteCardData.TEXT]);
card.setWatermark(thisCard[cah.$.WhiteCardData.WATERMARK]);
this.dealtCard(card);
}
};
/**
* Add a card to the player's hand.
*
* @param {cah.card.WhiteCard}
* card Card to add to hand.
*/
cah.Game.prototype.dealtCard = function(card) {
this.hand_.push(card);
var element = card.getElement();
$(".game_hand_cards", this.element_).append(element);
$(element).css("transform-origin", "0 0");
var data = {
card : card,
};
$(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_)).on("keypress.hand", data,
cah.bind(this, this.handCardKeypress_));
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);
}
$(card.getElement(), $("game_hand_cards", this.element_)).remove();
this.resizeHandCards_();
};
/**
* Set the round white cards.
*
* @param {Array}
* cardSets Array of arrays of cah.$.WhiteCardData to display.
*/
cah.Game.prototype.setRoundWhiteCards = function(cardSets) {
for ( var setIndex in cardSets) {
var thisSet = Array();
for ( var index in cardSets[setIndex]) {
var cardData = cardSets[setIndex][index];
var card;
var id = cardData[cah.$.WhiteCardData.ID];
if (id >= 0) {
card = new cah.card.WhiteCard(true, id);
card.setText(cardData[cah.$.WhiteCardData.TEXT]);
card.setWatermark(cardData[cah.$.WhiteCardData.WATERMARK]);
} else {
card = new cah.card.WhiteCard();
}
thisSet.push(card);
}
this.addRoundWhiteCard_(thisSet);
}
};
/**
* Add a white card to the round white cards area.
*
* @param {Array}
* cards Array of cah.card.WhiteCard to add to area.
* @private
*/
cah.Game.prototype.addRoundWhiteCard_ = function(cards) {
var parentElem;
if (cards.length > 1) {
parentElem = $("#game_white_cards_binder_template").clone()[0];
parentElem.id = "";
$(parentElem).removeClass("hide");
$(".game_white_cards", this.element_).append(parentElem);
} else {
parentElem = $(".game_white_cards", this.element_)[0];
}
for ( var index in cards) {
var card = cards[index];
var element = card.getElement();
$(parentElem).append(element);
$(element).css("transform-origin", "0 0");
var data = {
card : card,
};
$(element).on("mouseenter.round", data, cah.bind(this, this.roundCardMouseEnter_)).on(
"mouseleave.round", data, cah.bind(this, this.roundCardMouseLeave_)).on("click.round",
data, cah.bind(this, this.roundCardClick_)).on("keypress.round", data,
cah.bind(this, this.roundCardKeypress_));
}
this.roundCards_[cards[0].getServerId()] = cards;
this.resizeRoundCards_();
};
/**
* Event handler for hand card mouse enter.
*
* @param e
* @private
*/
cah.Game.prototype.handCardMouseEnter_ = function(e) {
$(e.data.card.getElement()).css("z-index", "2").animate({
scale : this.handCardLargeScale_,
width : this.handCardLargeSize_,
}, {
duration : 200,
queue : false,
});
};
/**
* Event handler for hand card mouse leave.
*
* @param e
* @private
*/
cah.Game.prototype.handCardMouseLeave_ = function(e) {
$(e.data.card.getElement()).animate({
scale : this.handCardSmallScale_,
"z-index" : 1,
width : this.handCardSmallSize_,
}, {
duration : 200,
queue : false,
});
};
/**
* Event handler for round card mouse enter.
*
* @param e
* @private
*/
cah.Game.prototype.roundCardMouseEnter_ = function(e) {
$(e.data.card.getElement()).css("z-index", "201").animate({
scale : this.roundCardLargeScale_,
width : this.roundCardLargeSize_,
}, {
duration : 200,
queue : false,
});
};
/**
* Event handler for round card mouse leave.
*
* @param e
* @private
*/
cah.Game.prototype.roundCardMouseLeave_ = function(e) {
$(e.data.card.getElement()).animate({
scale : this.roundCardSmallScale_,
"z-index" : 200,
width : this.roundCardSmallSize_,
}, {
duration : 200,
queue : false,
});
};
/**
* Event handler for window resize.
*
* @private
*/
cah.Game.prototype.windowResize_ = function() {
this.resizeHandCards_();
this.resizeRoundCards_();
};
/**
* Resize cards in hand to fit window size and hand size.
*
* @private
*/
cah.Game.prototype.resizeHandCards_ = function() {
var data = {
class : ".game_hand_cards",
cardSmallSize : this.handCardSmallSize_,
cardLargeSize : this.handCardLargeSize_,
cardSmallScale : this.handCardSmallScale_,
cardLargeScale : this.handCardLargeScale_,
maxSmallSize : 150,
minSmallSize : 66,
smallSize : function() {
return ($(".game_hand_cards", this.element_).width() - 20)
/ ($(".game_hand_cards .card_holder", this.element_).length + 1);
},
};
this.resizeCardHelper_(data);
this.handCardSmallSize_ = data.cardSmallSize;
this.handCardLargeSize_ = data.cardLargeSize;
this.handCardSmallScale_ = data.cardSmallScale;
this.handCardLargeScale_ = data.cardLargeScale;
};
/**
* Resize cards in the round white cards are to fit window size and number of players.
*
* TODO This will need some more consideration when there are multiple cards played per player,
* though it seems to mostly work.
*
* @private
*/
cah.Game.prototype.resizeRoundCards_ = function() {
$(".game_right_side", this.element_).width(
$(window).width() - $(".game_left_side", this.element_).width() - 30);
var data = {
class : ".game_white_cards",
cardSmallSize : this.roundCardSmallSize_,
cardLargeSize : this.roundCardLargeSize_,
cardSmallScale : this.roundCardSmallScale_,
cardLargeScale : this.roundCardLargeScale_,
maxSmallSize : 236,
minSmallSize : 118,
smallSize : function() {
return ($(window).width() - $(".game_left_side", this.element_).width() - 60)
/ $(".game_white_cards .card_holder", this.element_).length;
},
};
this.resizeCardHelper_(data);
this.roundCardSmallSize_ = data.cardSmallSize;
this.roundCardLargeSize_ = data.cardLargeSize;
this.roundCardSmallScale_ = data.cardSmallScale;
this.roundCardLargeScale_ = data.cardLargeScale;
};
/**
* Helper for resizing cards, so the logic only needs to be in one place.
*
* @param {Object}
* data In/out. Scale, size, and callback helper.
* @private
*/
cah.Game.prototype.resizeCardHelper_ = function(data) {
var elems = $(data.class + " .card_holder", this.element_);
data.cardSmallSize = data.smallSize();
if (data.cardSmallSize > data.maxSmallSize) {
data.cardSmallSize = data.maxSmallSize;
}
if (data.cardSmallSize < data.minSmallSize) {
data.cardSmallSize = data.minSmallSize;
}
var maxScale = 236 / data.cardSmallSize;
var scale = maxScale < 1.8 ? maxScale : 1.8;
data.cardLargeSize = data.cardSmallSize * scale;
if (data.cardLargeSize > 236) {
data.cardLargeSize = 236;
}
data.cardSmallScale = data.cardSmallSize / 236;
data.cardLargeScale = data.cardSmallScale * scale;
if (data.cardLargeScale > maxScale) {
data.cardLargeScale = maxScale;
}
elems.width(data.cardSmallSize).height(data.cardSmallSize).animate({
scale : data.cardSmallScale,
}, {
duration : 0,
});
};
/**
* Insert this game into the document.
*/
cah.Game.prototype.insertIntoDocument = function() {
$("#main_holder").empty().append(this.element_);
$("#info_area").empty().append(this.scoreboardElement_);
$("#leave_game").show();
var linkToChatArea = $("");
this.gameChatTab_ = $("