deal cards to players when the game starts

This commit is contained in:
Andy Janata 2012-01-23 15:06:20 -08:00
parent 44dcc45c48
commit c73ce88646
12 changed files with 205 additions and 31 deletions

View File

@ -159,7 +159,7 @@ span.debug {
bottom: 0px;
*/
font-family: Arial, Verdana, san-serif;
font-size: 16pt;
font-size: 18pt;
float: left;
-webkit-user-select: none;

View File

@ -37,7 +37,7 @@
<span id="nickbox_error" class="error"></span>
</div>
<div id="canvas">
<div id="canvas" class="hide">
<div id="menubar">
<div id="menubar_left">
<input type="button" id="refresh_games" class="hide" value="Refresh Games" />

View File

@ -54,6 +54,7 @@ cah.ajax.ErrorHandlers[cah.$.AjaxOperation.FIRST_LOAD] = function(data) {
*/
cah.ajax.after_registered = function() {
cah.log.debug("done registering");
$("#canvas").show();
// TODO once there are channels, this needs to specify the global channel
cah.Ajax.build(cah.$.AjaxOperation.NAMES).run();
cah.GameList.instance.show();
@ -100,3 +101,7 @@ cah.ajax.SuccessHandlers[cah.$.AjaxOperation.LEAVE_GAME] = function(data) {
// This will get updated when the server fires a refresh event
cah.GameList.instance.show();
};
cah.ajax.SuccessHandlers[cah.$.AjaxOperation.START_GAME] = function(data) {
// pass
};

View File

@ -216,16 +216,3 @@ cah.card.WhiteCard.prototype.getFaceUp_ = function() {
$(temp).removeClass("hide");
return temp;
};
// $(document).ready(function() {
// var card = new cah.card.BlackCard();
// $("#canvas").append(card.getElement());
//
// var card2 = new cah.card.BlackCard(true);
// // card2.setText("black card");
// $("#canvas").append(card2.getElement());
//
// var card3 = new cah.card.WhiteCard(true);
// card3.setText("white card");
// $("#canvas").append(card3.getElement());
// });

View File

@ -6,6 +6,7 @@ cah.$.AjaxOperation = function() {
// pass
};
cah.$.AjaxOperation.prototype.dummy = undefined;
cah.$.AjaxOperation.START_GAME = "start_game";
cah.$.AjaxOperation.FIRST_LOAD = "firstload";
cah.$.AjaxOperation.LOG_OUT = "logout";
cah.$.AjaxOperation.GAME_LIST = "games";
@ -58,6 +59,7 @@ cah.$.ErrorCode = function() {
cah.$.ErrorCode.prototype.dummy = undefined;
cah.$.ErrorCode.TOO_MANY_GAMES = "too_many_games";
cah.$.ErrorCode.INVALID_NICK = "invalid_nick";
cah.$.ErrorCode.NOT_GAME_HOST = "not_game_host";
cah.$.ErrorCode.BAD_REQUEST = "bad_req";
cah.$.ErrorCode.CANNOT_JOIN_ANOTHER_GAME = "cannot_join_another_game";
cah.$.ErrorCode.NO_GAME_SPECIFIED = "no_game_spec";
@ -66,6 +68,7 @@ cah.$.ErrorCode.MESSAGE_TOO_LONG = "msg_too_long";
cah.$.ErrorCode.BAD_OP = "bad_op";
cah.$.ErrorCode.NO_SESSION = "no_session";
cah.$.ErrorCode.NOT_REGISTERED = "not_registered";
cah.$.ErrorCode.NOT_ENOUGH_PLAYERS = "not_enough_players";
cah.$.ErrorCode.INVALID_GAME = "invalid_game";
cah.$.ErrorCode.OP_NOT_SPECIFIED = "op_not_spec";
cah.$.ErrorCode.NO_MSG_SPECIFIED = "no_msg_spec";
@ -76,6 +79,7 @@ cah.$.ErrorCode_msg = {};
cah.$.ErrorCode_msg['bad_op'] = "Invalid operation.";
cah.$.ErrorCode_msg['not_registered'] = "Not registered. Refresh the page.";
cah.$.ErrorCode_msg['msg_too_long'] = "Messages cannot be longer than 200 characters.";
cah.$.ErrorCode_msg['not_enough_players'] = "There are not enough players to start the game.";
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.";
@ -85,6 +89,7 @@ cah.$.ErrorCode_msg['too_many_games'] = "There are too many games already in pro
cah.$.ErrorCode_msg['no_nick_spec'] = "No nickname specified.";
cah.$.ErrorCode_msg['no_session'] = "Session not detected. Make sure you have cookies enabled.";
cah.$.ErrorCode_msg['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['op_not_spec'] = "Operation not specified.";
@ -127,9 +132,13 @@ cah.$.GameState = function() {
// pass
};
cah.$.GameState.prototype.dummy = undefined;
cah.$.GameState.PLAYING = "playing";
cah.$.GameState.LOBBY = "lobby";
cah.$.GameState.JUDGING = "judging";
cah.$.GameState.DEALING = "dealing";
cah.$.GameState_msg = {};
cah.$.GameState_msg['playing'] = "In Progress";
cah.$.GameState_msg['judging'] = "In Progress";
cah.$.GameState_msg['lobby'] = "Joinable (Not Started)";
cah.$.GameState_msg['dealing'] = "Dealing";
@ -143,6 +152,7 @@ cah.$.LongPollEvent.GAME_PLAYER_LEAVE = "game_player_leave";
cah.$.LongPollEvent.NEW_PLAYER = "new_player";
cah.$.LongPollEvent.PLAYER_LEAVE = "player_leave";
cah.$.LongPollEvent.GAME_PLAYER_JOIN = "game_player_join";
cah.$.LongPollEvent.HAND_DEAL = "hand_deal";
cah.$.LongPollEvent.CHAT = "chat";
cah.$.LongPollResponse = function() {
@ -152,6 +162,7 @@ cah.$.LongPollResponse.prototype.dummy = undefined;
cah.$.LongPollResponse.MESSAGE = "message";
cah.$.LongPollResponse.REASON = "reason";
cah.$.LongPollResponse.GAME_ID = "game_id";
cah.$.LongPollResponse.HAND = "hand";
cah.$.LongPollResponse.FROM = "from";
cah.$.LongPollResponse.EVENT = "event";
cah.$.LongPollResponse.ERROR = "error";
@ -166,3 +177,10 @@ cah.$.ReconnectNextAction.prototype.dummy = undefined;
cah.$.ReconnectNextAction.GAME = "game";
cah.$.ReconnectNextAction.NONE = "none";
cah.$.WhiteCardData = function() {
// pass
};
cah.$.WhiteCardData.prototype.dummy = undefined;
cah.$.WhiteCardData.TEXT = "text";
cah.$.WhiteCardData.ID = "id";

View File

@ -28,6 +28,7 @@ cah.Game = function(id) {
* @private
*/
this.element_ = $("#game_template").clone()[0];
this.element_.id = "game_" + id;
$(this.element_).removeClass("hide");
/**
@ -37,6 +38,7 @@ cah.Game = function(id) {
* @private
*/
this.scoreboardElement_ = $("#scoreboard_template").clone()[0];
this.scoreboardElement_.id = "scoreboard_" + id;
$(this.scoreboardElement_).removeClass("hide");
/**
@ -80,6 +82,21 @@ cah.Game.prototype.getElement = function() {
return this.element_;
};
/**
* 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]);
this.dealtCard(card);
}
};
/**
* Add a card to the player's hand.
*
@ -150,6 +167,8 @@ cah.Game.prototype.updateGameStatus = function(data) {
* @private
*/
cah.Game.prototype.leaveGameClick_ = function() {
// TODO make sure everything cleans up right, I got an error when I tried to start a different
// game after leaving one
cah.Ajax.build(cah.$.AjaxOperation.LEAVE_GAME).withGameId(this.id_).run();
};
@ -159,7 +178,7 @@ cah.Game.prototype.leaveGameClick_ = function() {
* @private
*/
cah.Game.prototype.startGameClick_ = function() {
// TODO
cah.Ajax.build(cah.$.AjaxOperation.START_GAME).withGameId(this.id_).run();
};
/**
@ -289,14 +308,3 @@ cah.GameScorePanel.prototype.update = function(score, status) {
jQuery(".scorecard_score", this.element_).text(score);
jQuery(".scorecard_status", this.element_).text(cah.$.GamePlayerStatus_msg[status]);
};
// $(document).ready(function() {
// var game = new cah.Game(0);
// $("#main_holder").append(game.getElement());
//
// for ( var i = 0; i < 10; i++) {
// var card = new cah.card.WhiteCard(true);
// card.setText("This is card " + i);
// game.dealtCard(card);
// }
// });

View File

@ -69,3 +69,13 @@ cah.longpoll.EventHandlers[cah.$.LongPollEvent.GAME_PLAYER_LEAVE] = function(dat
cah.log.error("Received player leave event for unknown game id " + gameId);
}
};
cah.longpoll.EventHandlers[cah.$.LongPollEvent.HAND_DEAL] = function(data) {
var gameId = data[cah.$.LongPollResponse.GAME_ID];
var game = cah.currentGames[gameId];
if (game) {
game.dealtCards(data[cah.$.LongPollResponse.HAND]);
} else {
cah.log.error("Received dealt cards for unknown game id " + gameId);
}
};

View File

@ -61,7 +61,8 @@ public class Constants {
LEAVE_GAME("leave_game"),
LOG_OUT("logout"),
NAMES("names"),
REGISTER("register");
REGISTER("register"),
START_GAME("start_game");
private final String op;
@ -135,6 +136,8 @@ public class Constants {
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_REGISTERED("not_registered", "Not registered. Refresh the page."),
OP_NOT_SPECIFIED("op_not_spec", "Operation not specified."),
SESSION_EXPIRED("session_expired", "Your session has expired. Refresh the page."),
@ -171,6 +174,7 @@ public class Constants {
GAME_PLAYER_JOIN("game_player_join"),
GAME_PLAYER_LEAVE("game_player_leave"),
GAME_REFRESH("game_refresh"),
HAND_DEAL("hand_deal"),
NEW_PLAYER("new_player"),
NOOP("noop"),
PLAYER_LEAVE("player_leave");
@ -193,6 +197,7 @@ public class Constants {
EVENT("event"),
FROM("from"),
GAME_ID("game_id"),
HAND("hand"),
MESSAGE("message"),
NICKNAME("nickname"),
REASON("reason"),
@ -214,9 +219,27 @@ public class Constants {
public static final String USER = "user";
}
public enum WhiteCardData {
ID("id"),
TEXT("text");
private final String key;
WhiteCardData(final String key) {
this.key = key;
}
@Override
public String toString() {
return key;
}
}
public enum GameState implements Localizable {
DEALING("dealing", "Dealing"),
LOBBY("lobby", "Joinable (Not Started)");
JUDGING("judging", "In Progress"),
LOBBY("lobby", "Joinable (Not Started)"),
PLAYING("playing", "In Progress");
private final String state;
private final String message;

View File

@ -3,6 +3,7 @@ package net.socialgamer.cah.data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@ -13,12 +14,34 @@ import net.socialgamer.cah.Constants.GameState;
import net.socialgamer.cah.Constants.LongPollEvent;
import net.socialgamer.cah.Constants.LongPollResponse;
import net.socialgamer.cah.Constants.ReturnableData;
import net.socialgamer.cah.Constants.WhiteCardData;
import net.socialgamer.cah.data.GameManager.GameId;
import net.socialgamer.cah.data.QueuedMessage.MessageType;
import net.socialgamer.cah.db.WhiteCard;
import com.google.inject.Inject;
/**
* Game data and logic class. Games are simple finite state machines, with 3 states that wait for
* user input, and 3 transient states that it quickly passes through on the way back to a waiting
* state:
*
* ......Lobby.----------->.Dealing.(transient).-------->.Playing
* .......^........................^.........................|....................
* .......|.v----.Win.(transient).<+------.Judging.<---------+....................
* .....Reset.(transient)
*
* Lobby is the default state. When the game host sends a start game event, the game moves to the
* Dealing state, where it deals out cards to every player and automatically moves into the Playing
* state. After all players have played a card, the game moves to Judging and waits for the judge to
* pick a card. The game either moves to Win, if a player reached the win goal, or Dealing
* otherwise. Win moves through Reset to reset the game back to default state. The game also
* immediately moves through Reset at any point there are fewer than 3 players in the game.
*
*
* @author ajanata
*/
public class Game {
private final int id;
private final List<Player> players = new ArrayList<Player>(10);
@ -28,6 +51,8 @@ public class Game {
private BlackDeck blackDeck;
private WhiteDeck whiteDeck;
private GameState state;
// TODO make this work with "draw x" cards. probably will not actually be done here.
private final int currentHandSize = 10;
// TODO make this host-configurable
private final int maxPlayers = 10;
@ -174,9 +199,59 @@ public class Game {
return info;
}
public void start() {
/**
* Start the game, if there are at least 3 players present. This does not do any access checking!
*
* @return True if the game is started. Would only be false if there aren't enough players, or the
* game is already started, but hopefully clients would prevent that from happening!
*/
public boolean start() {
if (state != GameState.LOBBY) {
return false;
}
synchronized (players) {
if (players.size() >= 3) {
blackDeck = new BlackDeck();
whiteDeck = new WhiteDeck();
dealState();
return true;
} else {
return false;
}
}
}
private void dealState() {
state = GameState.DEALING;
// TODO deal
synchronized (players) {
for (final Player player : players) {
final List<WhiteCard> hand = player.getHand();
final List<WhiteCard> newCards = new LinkedList<WhiteCard>();
while (hand.size() < currentHandSize) {
final WhiteCard card = whiteDeck.getNextCard();
hand.add(card);
newCards.add(card);
}
sendDealtCardsToPlayer(player, newCards);
}
}
}
private void sendDealtCardsToPlayer(final Player player, final List<WhiteCard> cards) {
final Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>();
data.put(LongPollResponse.EVENT, LongPollEvent.HAND_DEAL.toString());
data.put(LongPollResponse.GAME_ID, id);
final List<Map<WhiteCardData, Object>> cardData =
new ArrayList<Map<WhiteCardData, Object>>(cards.size());
for (final WhiteCard card : cards) {
final Map<WhiteCardData, Object> thisCard = new HashMap<WhiteCardData, Object>();
thisCard.put(WhiteCardData.ID, card.getId());
thisCard.put(WhiteCardData.TEXT, card.getText());
cardData.add(thisCard);
}
data.put(LongPollResponse.HAND, cardData);
final QueuedMessage qm = new QueuedMessage(MessageType.GAME_EVENT, data);
player.getUser().enqueueMessage(qm);
}
private List<User> playersToUsers() {

View File

@ -28,6 +28,9 @@ public class Player {
score++;
}
/**
* @return The backing object for the player's hand (i.e., it can be modified).
*/
public List<WhiteCard> getHand() {
return hand;
}

View File

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

View File

@ -0,0 +1,44 @@
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.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 StartGameHandler extends GameHandler {
public static final String OP = AjaxOperation.START_GAME.toString();
@Inject
public StartGameHandler(final GameManager gameManager) {
super(gameManager);
}
@Override
public Map<ReturnableData, Object> handle(final RequestWrapper request,
final HttpSession session, final User user,
final Game game) {
final Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>();
if (game.getHost() != user) {
return error(ErrorCode.NOT_GAME_HOST);
}
if (!game.start()) {
return error(ErrorCode.NOT_ENOUGH_PLAYERS);
}
return data;
}
}