- add game list ajax call

- skeleton for game lobby list
- fix ajax handlers to use the constants instead of literals for the ops
This commit is contained in:
Andy Janata 2012-01-17 17:48:21 -08:00
parent 565c17b338
commit 79f2f59716
15 changed files with 382 additions and 19 deletions

View File

@ -1,3 +1,20 @@
#menubar {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 25px;
border: 1px solid black;
}
#menubar_left {
float: left;
}
#menubar_right {
float: right;
}
#nickbox {
border: 1px solid black;
display: inline;
@ -8,7 +25,7 @@
*/
}
#canvass {
#canvas {
/* * /
display: none;
/* */
@ -19,6 +36,51 @@
border: 1px solid red;
}
#main {
position: absolute;
width: 100%;
top: 26px;
height: 506px;
border: 1px solid black;
}
#game_list {
height: 100%;
width: 100%;
background: #c0c0c0;
overflow: auto;
}
.gamelist_lobby {
width: 32%;
height: 100px;
float: left;
border: 1px solid black;
margin: 4px;
}
.gamelist_lobby_left {
width: 74%;
height: 100%;
float: left;
}
.gamelist_lobby_right {
border-left: 1px solid black;
width: 25%;
height: 100%;
float: right;
}
.gamelist_lobby_join {
width: 100%;
height: 100%;
}
.gamelist_lobby_status {
float: right;
}
#chat_area {
width: 500px;
height: 215px;
@ -49,12 +111,6 @@
padding: 0px;
}
#logout {
position: absolute;
top: 0px;
right: 0px;
}
#chat_submit {
width: 50px;
height: 19px;

View File

@ -10,8 +10,10 @@
<script type="text/javascript" src="js/cah.js"></script>
<%-- cah must be first, ajax must be before app. app probably has to be last. --%>
<%-- TODO make this be dynamic with looking at the filesystem and using jquery --%>
<%-- except that is nontrivial thanks to dependency ordering -_- --%>
<script type="text/javascript" src="js/cah.constants.js"></script>
<script type="text/javascript" src="js/cah.log.js"></script>
<script type="text/javascript" src="js/cah.gamelist.js"></script>
<script type="text/javascript" src="js/cah.longpoll.js"></script>
<script type="text/javascript" src="js/cah.longpoll.handlers.js"></script>
<script type="text/javascript" src="js/cah.ajax.js"></script>
@ -21,6 +23,7 @@
<link rel="stylesheet" type="text/css" href="cah.css" media="screen" />
</head>
<body>
<%-- Ensure a session exists for the user. --%>
<% HttpSession hSession = request.getSession(true); %>
<%--
< % = new net.socialgamer.cah.data.WhiteDeck().getNextCard().toString() % >
@ -32,8 +35,19 @@
<span id="nickbox_error" class="error"></span>
</div>
<div id="canvass">
<input type="button" id="logout" value="Log out" />
<div id="canvas">
<div id="menubar">
<div id="menubar_left">
<input type="button" id="refresh_games" value="Refresh Games" />
</div>
<div id="menubar_right">
<input type="button" id="logout" value="Log out" />
</div>
</div>
<div id="main">
<div id="game_list">
</div>
</div>
<div id="chat_area">
<div id="log"></div>
<input type="text" id="chat" maxlength="200" />
@ -41,5 +55,19 @@
</div>
</div>
<div id="gamelist_lobby_template" class="gamelist_lobby">
<div class="gamelist_lobby_left">
Game <span class="gamelist_lobby_id">###</span>
<span class="gamelist_lobby_status">status</span>
<br/>
Host: <span class="gamelist_lobby_host">host</span>
<br/>
Players: <span class="gamelist_lobby_players">host, player1, player2</span>
</div>
<div class="gamelist_lobby_right">
<input type="button" class="gamelist_lobby_join" value="Join" />
</div>
</div>
</body>
</html>

View File

@ -6,7 +6,7 @@
* @author ajanata
*/
cah.ajax.SuccessHandlers.register = function(data) {
cah.ajax.SuccessHandlers[cah.$.AjaxOperation.REGISTER] = function(data) {
cah.nickname = data['nickname'];
cah.log.status("You are connected as " + cah.nickname);
$("#nickbox").hide();
@ -15,12 +15,12 @@ cah.ajax.SuccessHandlers.register = function(data) {
cah.ajax.after_registered();
};
cah.ajax.ErrorHandlers.register = function(data) {
cah.ajax.ErrorHandlers[cah.$.AjaxOperation.REGISTER] = function(data) {
$("#nickbox_error").text(cah.$.ErrorCode_msg[data.error_code]);
$("#nickname").focus();
};
cah.ajax.SuccessHandlers.firstload = function(data) {
cah.ajax.SuccessHandlers[cah.$.AjaxOperation.FIRST_LOAD] = function(data) {
if (data.in_progress) {
// TODO reload data. see what 'next' is and go from there.
// for now just load the nickname
@ -32,7 +32,7 @@ cah.ajax.SuccessHandlers.firstload = function(data) {
}
};
cah.ajax.ErrorHandlers.firstload = function(data) {
cah.ajax.ErrorHandlers[cah.$.AjaxOperation.FIRST_LOAD] = function(data) {
// TODO dunno what to do here, if anything
};
@ -44,19 +44,24 @@ cah.ajax.after_registered = function() {
cah.log.debug("done registering");
// TODO once there are channels, this needs to specify the global channel
cah.Ajax.build(cah.$.AjaxOperation.NAMES).run();
cah.Ajax.build(cah.$.AjaxOperation.GAME_LIST).run();
cah.longpoll.longPoll();
};
cah.ajax.SuccessHandlers.chat = function(data) {
cah.ajax.SuccessHandlers[cah.$.AjaxOperation.CHAT] = function(data) {
// pass
};
cah.ajax.SuccessHandlers.logout = function(data) {
cah.ajax.SuccessHandlers[cah.$.AjaxOperation.LOG_OUT] = function(data) {
window.location.reload();
};
cah.ajax.ErrorHandlers.logout = cah.ajax.SuccessHandlers.logout;
cah.ajax.ErrorHandlers[cah.$.AjaxOperation.LOG_OUT] = cah.ajax.SuccessHandlers.logout;
cah.ajax.SuccessHandlers.names = function(data) {
cah.ajax.SuccessHandlers[cah.$.AjaxOperation.NAMES] = function(data) {
cah.log.status("Currently connected: " + data.names.join(", "));
};
cah.ajax.SuccessHandlers[cah.$.AjaxOperation.GAME_LIST] = function(data) {
cah.GameList.instance.update(data);
};

View File

@ -20,6 +20,8 @@ $(document).ready(function() {
// have not expressed an interest in being cleared out yet.
// $(window).bind("beforeunload", window_beforeunload);
$("#logout").click(logout_click);
$("#refresh_games").click(refreshgames_click);
});
function nickbox_keyup(e) {
@ -53,3 +55,7 @@ function chatsubmit_click(e) {
function logout_click(e) {
cah.Ajax.build(cah.$.AjaxOperation.LOG_OUT).run();
}
function refreshgames_click(e) {
cah.Ajax.build(cah.$.AjaxOperation.GAME_LIST).run();
}

View File

@ -8,6 +8,7 @@ cah.$.AjaxOperation = function() {
cah.$.AjaxOperation.prototype.dummy = undefined;
cah.$.AjaxOperation.FIRST_LOAD = "firstload";
cah.$.AjaxOperation.LOG_OUT = "logout";
cah.$.AjaxOperation.GAME_LIST = "games";
cah.$.AjaxOperation.REGISTER = "register";
cah.$.AjaxOperation.CHAT = "chat";
cah.$.AjaxOperation.NAMES = "names";
@ -29,7 +30,9 @@ cah.$.AjaxResponse.NEXT = "next";
cah.$.AjaxResponse.ERROR = "error";
cah.$.AjaxResponse.ERROR_CODE = "error_code";
cah.$.AjaxResponse.SERIAL = "serial";
cah.$.AjaxResponse.MAX_GAMES = "max_games";
cah.$.AjaxResponse.IN_PROGRESS = "in_progress";
cah.$.AjaxResponse.GAMES = "games";
cah.$.AjaxResponse.NICKNAME = "nickname";
cah.$.AjaxResponse.NAMES = "names";
@ -69,6 +72,22 @@ cah.$.ErrorCode_msg['invalid_nick'] = "Nickname must contain only upper and lowe
cah.$.ErrorCode_msg['no_session'] = "Session not detected. Make sure you have cookies enabled.";
cah.$.ErrorCode_msg['no_nick_spec'] = "No nickname specified.";
cah.$.GameInfo = function() {
// pass
};
cah.$.GameInfo.prototype.dummy = undefined;
cah.$.GameInfo.HOST = "host";
cah.$.GameInfo.STATE = "state";
cah.$.GameInfo.PLAYERS = "players";
cah.$.GameInfo.ID = "id";
cah.$.GameState = function() {
// pass
};
cah.$.GameState.prototype.dummy = undefined;
cah.$.GameState.LOBBY = "lobby";
cah.$.GameState.DEALING = "dealing";
cah.$.LongPollEvent = function() {
// pass
};

View File

@ -0,0 +1,55 @@
/**
* Display the list of games on the server.
*
* @author ajanata
*/
/**
* @constructor
*/
cah.GameList = function() {
/**
* The game list DOM element.
*
* @type {HTMLDivElement}
* @private
*/
this.element_ = $("#game_list")[0];
var foo = new cah.GameListLobby(0).getElement();
for ( var i = 0; i < 50; i++) {
this.element_.appendChild(new cah.GameListLobby(i).getElement());
}
};
$(document).ready(function() {
cah.GameList.instance = new cah.GameList();
});
/**
* Update the list of games.
*
* @param {Object}
* gameData The game data returned by the server.
*/
cah.GameList.prototype.update = function(gameData) {
// TODO clear existing display
};
/**
* A single entry in the game list.
*
* @param {number}
* id This game's id.
* @constructor
*/
cah.GameListLobby = function(id) {
this.id_ = id;
this.element_ = $("#gamelist_lobby_template").clone()[0];
this.element_.id = "gamelist_lobby_" + id;
};
cah.GameListLobby.prototype.getElement = function() {
return this.element_;
};

View File

@ -28,6 +28,7 @@ public class Constants {
public enum AjaxOperation {
CHAT("chat"),
FIRST_LOAD("firstload"),
GAME_LIST("games"),
LOG_OUT("logout"),
NAMES("names"),
REGISTER("register");
@ -65,7 +66,9 @@ public class Constants {
public enum AjaxResponse implements ReturnableData {
ERROR("error"),
ERROR_CODE("error_code"),
GAMES("games"),
IN_PROGRESS("in_progress"),
MAX_GAMES("max_games"),
NAMES("names"),
NEXT("next"),
NICKNAME("nickname"),
@ -167,4 +170,38 @@ public class Constants {
public class SessionAttribute {
public static final String USER = "user";
}
public enum GameState {
DEALING("dealing"),
LOBBY("lobby");
private final String state;
GameState(final String state) {
this.state = state;
}
@Override
public String toString() {
return state;
}
}
public enum GameInfo {
HOST("host"),
ID("id"),
PLAYERS("players"),
STATE("state");
private final String key;
GameInfo(final String key) {
this.key = key;
}
@Override
public String toString() {
return key;
}
}
}

View File

@ -22,7 +22,7 @@ public class Server {
return this.users;
}
public GameManager getGames() {
public GameManager getGameManager() {
return this.gameManager;
}
}

View File

@ -4,7 +4,10 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.socialgamer.cah.Constants.GameInfo;
import net.socialgamer.cah.Constants.GameState;
import net.socialgamer.cah.Constants.LongPollResponse;
import net.socialgamer.cah.Constants.ReturnableData;
import net.socialgamer.cah.data.GameManager.GameId;
@ -20,6 +23,7 @@ public class Game {
private Player host;
private BlackDeck blackDeck;
private WhiteDeck whiteDeck;
private GameState state;
/**
* TODO Injection here would be much nicer, but that would need a Provider for the id... Too much
@ -32,6 +36,7 @@ public class Game {
public Game(@GameId final Integer id, final ConnectedUsers connectedUsers) {
this.id = id;
this.connectedUsers = connectedUsers;
state = GameState.LOBBY;
}
public void addPlayer(final User user) {
@ -41,6 +46,7 @@ public class Game {
if (host == null) {
host = player;
}
user.joinGame(this);
}
final HashMap<ReturnableData, Object> data = new HashMap<ReturnableData, Object>();
@ -62,6 +68,7 @@ public class Game {
final Player player = iterator.next();
if (player.getUser() == user) {
iterator.remove();
user.leaveGame(this);
final HashMap<ReturnableData, Object> data = new HashMap<ReturnableData, Object>();
data.put(LongPollResponse.EVENT, "game_player_leave");
data.put(LongPollResponse.GAME_ID, id);
@ -101,6 +108,26 @@ public class Game {
return id;
}
public Map<GameInfo, Object> getInfo() {
final Map<GameInfo, Object> info = new HashMap<GameInfo, Object>();
info.put(GameInfo.ID, id);
info.put(GameInfo.HOST, host.toString());
info.put(GameInfo.STATE, state.toString());
synchronized (players) {
final List<String> playerNames = new ArrayList<String>(players.size());
for (final Player player : players) {
playerNames.add(player.toString());
}
info.put(GameInfo.PLAYERS, playerNames);
}
return info;
}
public void start() {
state = GameState.DEALING;
// TODO deal
}
private List<User> playersToUsers() {
final List<User> users;
synchronized (players) {

View File

@ -2,6 +2,8 @@ package net.socialgamer.cah.data;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.TreeMap;
@ -50,7 +52,10 @@ public class GameManager implements Provider<Integer> {
return null;
}
final Game game = gameProvider.get();
assert (game.getId() >= 0);
if (game.getId() < 0) {
return null;
}
games.put(game.getId(), game);
return game;
}
}
@ -123,6 +128,13 @@ public class GameManager implements Provider<Integer> {
}
}
public Collection<Game> getGameList() {
synchronized (games) {
// return a copy
return new ArrayList<Game>(games.values());
}
}
Map<Integer, Game> getGames() {
return games;
}

View File

@ -10,4 +10,9 @@ public class Player {
public User getUser() {
return user;
}
@Override
public String toString() {
return user.toString();
}
}

View File

@ -15,6 +15,8 @@ public class User {
private long lastHeardFrom = 0;
private Game currentGame;
/**
* Reset when this user object is no longer valid, most likely because it pinged out.
*/
@ -77,6 +79,11 @@ public class User {
return nickname;
}
@Override
public String toString() {
return getNickname();
}
/**
* Update the timestamp that we have last heard from this user to the current time.
*/
@ -104,4 +111,42 @@ public class User {
public void noLongerVaild() {
valid = false;
}
/**
* @return The current game in which this user is participating.
*/
public Game getGame() {
return currentGame;
}
/**
* Marks a given game as this user's active game.
*
* This should only be called from Game itself.
*
* @param game
* Game in which this user is playing.
* @throws IllegalStateException
* Thrown if this user is already in another game.
*/
void joinGame(final Game game) throws IllegalStateException {
if (currentGame != null && currentGame != game) {
throw new IllegalStateException("User is already in a game.");
}
currentGame = game;
}
/**
* Marks the user as no longer participating in a game.
*
* This should only be called from Game itself.
*
* @param game
* Game from which to remove the user.
*/
void leaveGame(final Game game) {
if (currentGame == game) {
currentGame = null;
}
}
}

View File

@ -0,0 +1,50 @@
package net.socialgamer.cah.handlers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
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.GameInfo;
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.GameManager.MaxGames;
import com.google.inject.Inject;
public class GameListHandler extends Handler {
public static final String OP = AjaxOperation.GAME_LIST.toString();
private final GameManager gameManager;
private final int maxGames;
@Inject
public GameListHandler(final GameManager gameManager, @MaxGames final Integer maxGames) {
this.gameManager = gameManager;
this.maxGames = maxGames;
}
@Override
public Map<ReturnableData, Object> handle(final RequestWrapper request,
final HttpSession session) {
final Map<ReturnableData, Object> ret = new HashMap<ReturnableData, Object>();
final Collection<Game> games = gameManager.getGameList();
final List<Map<GameInfo, Object>> gameInfos =
new ArrayList<Map<GameInfo, Object>>(games.size());
for (final Game game : games) {
gameInfos.add(game.getInfo());
}
ret.put(AjaxResponse.GAMES, gameInfos);
ret.put(AjaxResponse.MAX_GAMES, maxGames);
return ret;
}
}

View File

@ -12,6 +12,7 @@ public class Handlers {
LIST = new HashMap<String, Class<? extends Handler>>();
LIST.put(ChatHandler.OP, ChatHandler.class);
LIST.put(FirstLoadHandler.OP, FirstLoadHandler.class);
LIST.put(GameListHandler.OP, GameListHandler.class);
LIST.put(LogoutHandler.OP, LogoutHandler.class);
LIST.put(NamesHandler.OP, NamesHandler.class);
LIST.put(RegisterHandler.OP, RegisterHandler.class);

View File

@ -4,6 +4,8 @@ import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import net.socialgamer.cah.data.GameManager.GameId;
import net.socialgamer.cah.data.GameManager.MaxGames;
@ -86,4 +88,19 @@ public class GameManagerTest {
gameManager.destroyGame(0);
assertEquals(2, gameManager.get().intValue());
}
@Test
public void testCreateGame() {
Game game = gameManager.createGame();
assertNotNull(game);
gameId = 1;
game = gameManager.createGame();
assertNotNull(game);
gameId = 2;
game = gameManager.createGame();
assertNotNull(game);
gameId = -1;
game = gameManager.createGame();
assertNull(game);
}
}