Merge latest upstream code.

This commit is contained in:
Gavin Lambert 2013-10-02 22:22:20 +13:00
commit cdfba970f9
13 changed files with 10840 additions and 5657 deletions

4
README
View File

@ -1 +1,3 @@
Pretend You're Xyzzy, a Cards Against Humanity clone, server and web client. See WebContent/license.html for full details.
Pretend You're Xyzzy, a Cards Against Humanity clone, server and web client. See WebContent/license.html for full details.
Note: This project has only been tested in Tomcat 7 and is known to not work in Tomcat 6 without some finagling. Currently, the only automated way to build is is using the Eclipse project.

View File

@ -6,38 +6,6 @@
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>AjaxServlet</servlet-name>
<servlet-class>net.socialgamer.cah.servlets.AjaxServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>JavascriptConfigServlet</servlet-name>
<servlet-class>net.socialgamer.cah.servlets.JavascriptConfigServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>LongPollServlet</servlet-name>
<servlet-class>net.socialgamer.cah.servlets.LongPollServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Schema</servlet-name>
<servlet-class>net.socialgamer.cah.servlets.Schema</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AjaxServlet</servlet-name>
<url-pattern>/AjaxServlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>JavascriptConfigServlet</servlet-name>
<url-pattern>/js/cah.config.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>LongPollServlet</servlet-name>
<url-pattern>/LongPollServlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Schema</servlet-name>
<url-pattern>/Schema</url-pattern>
</servlet-mapping>
<listener>
<listener-class>net.socialgamer.cah.StartupUtils</listener-class>
</listener>

View File

@ -387,6 +387,10 @@ span.debug, span.admin {
width: 700px;
}
.game_options {
z-index: 500;
}
.game_right_side_box {
height: 60%;
margin: 10px;

View File

@ -95,12 +95,14 @@ HttpSession hSession = request.getSession(true);
If this is your first time playing, you may wish to read <a href="/">the changelog and list of
known issues</a>.
</p>
<p tabindex="0">Most recent update: 28 April 2013:</p>
<p tabindex="0">Most recent update: 7 August 2013:</p>
<ul>
<li tabindex="0">Version 1.3 of the base Cards Against Humanity game.</li>
<li tabindex="0">Card sets are grouped by official or custom.</li>
<li tabindex="0">You can <a href="viewcards.jsp">view all of the cards in the game</a>,
including searching by text and filtering card set.</li>
<li tabindex="0"><strong>The game list will not automatically update all the time now.</strong>
You will need to start using the Refresh Games button. The game list will automatically update
for new games, removed games, when games become passworded, or when you leave a game.</li>
<li tabindex="0">A lot of custom card sets have been added.</li>
<li tabindex="0">Cleaned up some error handling.</li>
<li tabindex="0">Tried to fix some more of the server crashes.</li>
</ul>
<div id="nickbox">
Nickname:

View File

@ -54,6 +54,14 @@ to, for instance, display the number of connected players.
</p>
<p>Recent Changes:</p>
<ul>
<li>7 August 2013:<ul>
<li tabindex="0"><strong>The game list will not automatically update all the time now.</strong>
You will need to start using the Refresh Games button. The game list will automatically update
for new games, removed games, when games become passworded, or when you leave a game.</li>
<li tabindex="0">A lot of custom card sets have been added.</li>
<li tabindex="0">Cleaned up some error handling.</li>
<li tabindex="0">Tried to fix some more of the server crashes.</li>
</ul></li>
<li>28 April 2013:<ul>
<li tabindex="0">Version 1.3 of the base Cards Against Humanity game.</li>
<li tabindex="0">Card sets are grouped by official or custom.</li>

File diff suppressed because it is too large Load Diff

View File

@ -60,7 +60,7 @@ public class CahModule extends AbstractModule {
@Provides
@MaxGames
Integer provideMaxGames() {
return 200;
return 250;
}
/**

View File

@ -30,6 +30,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
@ -58,7 +59,7 @@ public class ConnectedUsers {
/**
* Duration of a ping timeout, in nanoseconds.
*/
public static final long PING_TIMEOUT = 45L * 1000L * 1000000L;
public static final long PING_TIMEOUT = TimeUnit.SECONDS.toNanos(45);
/**
* Key (username) must be stored in lower-case to facilitate case-insensitivity in nicks.

View File

@ -197,7 +197,8 @@ public class Game {
data.put(LongPollResponse.NICKNAME, user.getNickname());
broadcastToPlayers(MessageType.GAME_PLAYER_EVENT, data);
gameManager.broadcastGameListRefresh();
// Don't do this anymore, it was driving up a crazy amount of traffic.
// gameManager.broadcastGameListRefresh();
}
/**
@ -265,7 +266,8 @@ public class Game {
data.put(LongPollResponse.NICKNAME, user.getNickname());
broadcastToPlayers(MessageType.GAME_PLAYER_EVENT, data);
gameManager.broadcastGameListRefresh();
// Don't do this anymore, it was driving up a crazy amount of traffic.
// gameManager.broadcastGameListRefresh();
if (host == player) {
if (players.size() > 0) {
@ -357,7 +359,8 @@ public class Game {
data.put(LongPollResponse.NICKNAME, user.getNickname());
broadcastToPlayers(MessageType.GAME_PLAYER_EVENT, data);
gameManager.broadcastGameListRefresh();
// Don't do this anymore, it was driving up a crazy amount of traffic.
// gameManager.broadcastGameListRefresh();
}
/**
@ -491,20 +494,21 @@ public class Game {
info.put(GameInfo.PASSWORD, password);
}
info.put(GameInfo.HAS_PASSWORD, password != null && !password.equals(""));
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);
final Player[] playersCopy = players.toArray(new Player[players.size()]);
final List<String> playerNames = new ArrayList<String>(playersCopy.length);
for (final Player player : playersCopy) {
playerNames.add(player.toString());
}
synchronized (spectators) {
final List<String> spectatorNames = new ArrayList<String>(spectators.size());
for (final User spectator : spectators) {
spectatorNames.add(spectator.toString());
}
info.put(GameInfo.SPECTATORS, spectatorNames);
info.put(GameInfo.PLAYERS, playerNames);
final User[] spectatorsCopy = spectators.toArray(new User[spectators.size()]);
final List<String> spectatorNames = new ArrayList<String>(spectatorsCopy.length);
for (final User spectator : spectatorsCopy) {
spectatorNames.add(spectator.toString());
}
info.put(GameInfo.SPECTATORS, spectatorNames);
return info;
}
@ -514,12 +518,11 @@ public class Game {
*/
public List<Map<GamePlayerInfo, Object>> getAllPlayerInfo() {
final List<Map<GamePlayerInfo, Object>> info;
synchronized (players) {
info = new ArrayList<Map<GamePlayerInfo, Object>>(players.size());
for (final Player player : players) {
final Map<GamePlayerInfo, Object> playerInfo = getPlayerInfo(player);
info.add(playerInfo);
}
final Player[] playersCopy = players.toArray(new Player[players.size()]);
info = new ArrayList<Map<GamePlayerInfo, Object>>(playersCopy.length);
for (final Player player : playersCopy) {
final Map<GamePlayerInfo, Object> playerInfo = getPlayerInfo(player);
info.add(playerInfo);
}
return info;
}
@ -619,14 +622,13 @@ public class Game {
return false;
}
boolean started;
synchronized (players) {
if (players.size() >= 3) {
// Pick a random start judge, though the "next" judge will actually go first.
judgeIndex = (int) (Math.random() * players.size());
started = true;
} else {
started = false;
}
final int numPlayers = players.size();
if (numPlayers >= 3) {
// Pick a random start judge, though the "next" judge will actually go first.
judgeIndex = (int) (Math.random() * numPlayers);
started = true;
} else {
started = false;
}
if (started) {
logger.info(String.format("Starting game %d.", id));
@ -657,21 +659,19 @@ public class Game {
* Move the game into the {@code DEALING} state, and deal cards. The game immediately then moves
* into the {@code PLAYING} state.
* <br/>
* Synchronizes on {@link #players}.
*/
private void dealState() {
state = GameState.DEALING;
synchronized (players) {
for (final Player player : players) {
final List<WhiteCard> hand = player.getHand();
final List<WhiteCard> newCards = new LinkedList<WhiteCard>();
while (hand.size() < 10) {
final WhiteCard card = getNextWhiteCard();
hand.add(card);
newCards.add(card);
}
sendCardsToPlayer(player, newCards);
final Player[] playersCopy = players.toArray(new Player[players.size()]);
for (final Player player : playersCopy) {
final List<WhiteCard> hand = player.getHand();
final List<WhiteCard> newCards = new LinkedList<WhiteCard>();
while (hand.size() < 10) {
final WhiteCard card = getNextWhiteCard();
hand.add(card);
newCards.add(card);
}
sendCardsToPlayer(player, newCards);
}
playingState();
}
@ -831,15 +831,14 @@ public class Game {
logger.info(String.format("Skipping idle player %s in game %d.",
player.getUser().toString(), id));
player.skipped();
final HashMap<ReturnableData, Object> data = getEventMap();
final HashMap<ReturnableData, Object> data = getEventMap();
data.put(LongPollResponse.NICKNAME, player.getUser().getNickname());
if (player.getSkipCount() >= MAX_SKIPS_BEFORE_KICK || playedCards.size() < 2) {
data.put(LongPollResponse.EVENT, LongPollEvent.GAME_PLAYER_KICKED_IDLE.toString());
data.put(LongPollResponse.NICKNAME, player.getUser().getNickname());
playersToRemove.add(player.getUser());
} else {
data.put(LongPollResponse.EVENT, LongPollEvent.GAME_PLAYER_SKIPPED.toString());
data.put(LongPollResponse.NICKNAME, player.getUser().getNickname());
playersToUpdateStatus.add(player);
}
broadcastToPlayers(MessageType.GAME_EVENT, data);
@ -983,6 +982,8 @@ public class Game {
data.put(LongPollResponse.PLAYER_INFO, getPlayerInfo(judge));
broadcastToPlayers(MessageType.GAME_PLAYER_EVENT, data);
}
gameManager.broadcastGameListRefresh();
}
/**
@ -1085,19 +1086,16 @@ public class Game {
/**
* Get the {@code Player} object for a given {@code User} object.
*
* Synchronizes on {@link #players}.
*
* @param user
* @return The {@code Player} object representing {@code user} in this game, or {@code null} if
* {@code user} is not in this game.
*/
@Nullable
private Player getPlayerForUser(final User user) {
synchronized (players) {
for (final Player player : players) {
if (player.getUser() == user) {
return player;
}
final Player[] playersCopy = players.toArray(new Player[players.size()]);
for (final Player player : playersCopy) {
if (player.getUser() == user) {
return player;
}
}
return null;
@ -1229,11 +1227,10 @@ public class Game {
*/
private List<User> playersToUsers() {
final List<User> users;
synchronized (players) {
users = new ArrayList<User>(players.size());
for (final Player player : players) {
users.add(player.getUser());
}
final Player[] playersCopy = players.toArray(new Player[players.size()]);
users = new ArrayList<User>(playersCopy.length);
for (final Player player : playersCopy) {
users.add(player.getUser());
}
synchronized (spectators) {
users.addAll(spectators);

View File

@ -52,6 +52,7 @@ public class ChangeGameOptionHandler extends GameWithPlayerHandler {
Integer.parseInt(cardSetId)));
}
}
final String oldPassword = game.getPassword();
String password = request.getParameter(AjaxRequest.PASSWORD);
if (password == null) {
password = "";
@ -64,11 +65,16 @@ public class ChangeGameOptionHandler extends GameWithPlayerHandler {
useTimer = Boolean.valueOf(useTimerString);
}
game.updateGameSettings(scoreLimit, playerLimit, spectatorLimit, cardSets, password, useTimer);
// only broadcast an update if the password state has changed, because it needs to change
// the text on the join button and the sort order
if (!password.equals(oldPassword)) {
gameManager.broadcastGameListRefresh();
}
} catch (final NumberFormatException nfe) {
return error(ErrorCode.BAD_REQUEST);
}
gameManager.broadcastGameListRefresh();
return data;
}
}

View File

@ -74,7 +74,6 @@ public class JoinGameHandler extends GameHandler {
} catch (final TooManyPlayersException e) {
return error(ErrorCode.GAME_FULL);
}
gameManager.broadcastGameListRefresh();
return data;
}
}

View File

@ -5,11 +5,13 @@ import java.io.PrintWriter;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/js/cah.config.js")
public class JavascriptConfigServlet extends HttpServlet {
private static final long serialVersionUID = 4287566906479434127L;

View File

@ -30,6 +30,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
@ -60,28 +61,28 @@ public class LongPollServlet extends CahServlet {
/**
* Minimum amount of time before timing out and returning a no-op, in nanoseconds.
*/
private static final long TIMEOUT_BASE = 20 * 1000 * 1000;
private static final long TIMEOUT_BASE = TimeUnit.SECONDS.toNanos(20);
// private static final long TIMEOUT_BASE = 10 * 1000 * 1000;
/**
* Randomness factor added to minimum timeout duration, in nanoseconds. The maximum timeout delay
* will be TIMEOUT_BASE + TIMEOUT_RANDOMNESS - 1.
*/
private static final double TIMEOUT_RANDOMNESS = 5 * 1000 * 1000;
private static final double TIMEOUT_RANDOMNESS = TimeUnit.SECONDS.toNanos(5);
// private static final double TIMEOUT_RANDOMNESS = 0;
/**
* The maximum number of messages which will be returned to a client during a single poll
* operation.
*/
private static final int MAX_MESSAGES_PER_POLL = 10;
private static final int MAX_MESSAGES_PER_POLL = 20;
/**
* An amount of milliseconds to wait after being notified that the user has at least one message
* to deliver, before we actually deliver messages. This will allow multiple messages that arrive
* in close proximity to each other to actually be delivered in the same client request.
*/
private static final int WAIT_FOR_MORE_DELAY = 30;
private static final int WAIT_FOR_MORE_DELAY = 50;
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
@ -99,9 +100,9 @@ public class LongPollServlet extends CahServlet {
final User user = (User) hSession.getAttribute(SessionAttribute.USER);
assert (user != null);
user.contactedServer();
while (!(user.hasQueuedMessages()) && System.nanoTime() < end) {
while (!(user.hasQueuedMessages()) && System.nanoTime() - end < 0) {
try {
user.waitForNewMessageNotification((end - System.nanoTime()) / 1000);
user.waitForNewMessageNotification(TimeUnit.NANOSECONDS.toMillis(end - System.nanoTime()));
} catch (final InterruptedException ie) {
// pass
}