diff --git a/WebContent/cardsets.jsp b/WebContent/cardsets.jsp
index 99b7396..6fe2d04 100644
--- a/WebContent/cardsets.jsp
+++ b/WebContent/cardsets.jsp
@@ -242,6 +242,7 @@ select {
Delete |
Edit |
Weight |
+ Active |
@@ -253,6 +254,7 @@ select {
Delete |
Edit |
<%=cardSet.getWeight()%> |
+ <%=cardSet.isActive()%> |
<%
}
diff --git a/WebContent/game.jsp b/WebContent/game.jsp
index d048f02..20d688f 100644
--- a/WebContent/game.jsp
+++ b/WebContent/game.jsp
@@ -86,27 +86,16 @@ HttpSession hSession = request.getSession(true);
If this is your first time playing, you may wish to read the changelog and list of
known issues.
- Most recent update: 21 February 2015:
+ Most recent update: 3 May 2015:
- - Servers now run in Amazon Web Services. This is going to cost me more, but at least it
- should be more stable and not take down my other stuff when it does go down...
- - I am still tweaking server settings in AWS. It likely is going to be unstable for another
- week or two while I fine-tune cost and performance.
- - Card set filters are fixed.
- /removecardcast
is fixed.
- - Connect and disconnect notices are disabled server-wide. This was a major source of
- bandwidth and processing time.
- - You can start a game without using any local card sets.You must have at
- least 50 black cards and (20 times player limit) white cards to be able to start a game.
- - Several other back-end performance and code maintainability improvements.
- - Custom card sets will be removed from local storage in the near future.You
- will have to use Cardcast to use custom card sets. If a card set you want is not already in
- Cardcast, you can attempt to extract it from
- the last
- version of the database dump which contains them and add it to Cardcast yourself; I will be
- unable to provide help in doing so.
- - At roughly the same time, all officially released Cards Against Humanity sets which are not
- already in the system will be added as local decks.
+ - The game list automatically updates once per minute now, instead of several times per
+ second. You can still click the Refresh Games button in the top left corner at any time.
+ - Chat flood protection has been made more strict.
+ - Other back-end changes to attempt to get the AWS bill in control.
+ - All locally-stored custom card sets have been removed. You must use
+ Cardcast for custom card sets now.
+ - The 5th and 6th Expansions, PAX Prime 2014 Panel, Ten Days or Whatever of Kwanzaa,
+ and Science packs have all been added.
- Remaining known issues and high priority features:
- Leaving a game as a spectator doesn't work right.
- Game owners still can't kick players from their game.
diff --git a/WebContent/index.jsp b/WebContent/index.jsp
index 7cacb56..06bd0df 100644
--- a/WebContent/index.jsp
+++ b/WebContent/index.jsp
@@ -54,6 +54,16 @@ to, for instance, display the number of connected players.
Recent Changes:
+ - 3 May 2015:
+ - The game list automatically updates once per minute now, instead of several times per
+ second. You can still click the Refresh Games button in the top left corner at any time.
+ - Chat flood protection has been made more strict.
+ - Other back-end changes to attempt to get the AWS bill in control.
+ - All locally-stored custom card sets have been removed. You must use
+ Cardcast for custom card sets now.
+ - The 5th and 6th Expansions, PAX Prime 2014 Panel, 10 Days or Whatever of Kwanzaa,
+ and Science packs have all been added.
+
- 21 February 2015:
- Servers now run in Amazon Web Services. This is going to cost me more, but at least it
should be more stable and not take down my other stuff when it does go down...
diff --git a/src/main/java/net/socialgamer/cah/Constants.java b/src/main/java/net/socialgamer/cah/Constants.java
index ec22bf3..e6c9720 100644
--- a/src/main/java/net/socialgamer/cah/Constants.java
+++ b/src/main/java/net/socialgamer/cah/Constants.java
@@ -44,8 +44,8 @@ import net.socialgamer.cah.data.Game;
*/
public class Constants {
- public static final int CHAT_FLOOD_MESSAGE_COUNT = 5;
- public static final int CHAT_FLOOD_TIME = 10 * 1000;
+ public static final int CHAT_FLOOD_MESSAGE_COUNT = 6;
+ public static final int CHAT_FLOOD_TIME = 30 * 1000;
public static final int CHAT_MAX_LENGTH = 200;
@SuppressWarnings("serial")
diff --git a/src/main/java/net/socialgamer/cah/StartupUtils.java b/src/main/java/net/socialgamer/cah/StartupUtils.java
index e0e6aaa..6f74aae 100644
--- a/src/main/java/net/socialgamer/cah/StartupUtils.java
+++ b/src/main/java/net/socialgamer/cah/StartupUtils.java
@@ -35,6 +35,8 @@ import javax.servlet.ServletContextEvent;
import net.socialgamer.cah.cardcast.CardcastModule;
import net.socialgamer.cah.cardcast.CardcastService;
+import net.socialgamer.cah.task.BroadcastGameListUpdateTask;
+import net.socialgamer.cah.task.UserPingTask;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
@@ -70,6 +72,16 @@ public class StartupUtils extends GuiceServletContextListener {
*/
private static final long PING_CHECK_DELAY = 5 * 1000;
+ /**
+ * Delay before the "update game list" broadcast timer is started, in milliseconds.
+ */
+ private static final long BROADCAST_UPDATE_START_DELAY = TimeUnit.SECONDS.toMillis(60);
+
+ /**
+ * Delay between invocations of the "update game list" broadcast timer, in milliseconds.
+ */
+ private static final long BROADCAST_UPDATE_DELAY = TimeUnit.SECONDS.toMillis(60);
+
/**
* Context attribute key name for the time the server was started.
*/
@@ -104,10 +116,18 @@ public class StartupUtils extends GuiceServletContextListener {
public void contextInitialized(final ServletContextEvent contextEvent) {
final ServletContext context = contextEvent.getServletContext();
final Injector injector = getInjector();
- final UserPing ping = injector.getInstance(UserPing.class);
+
final ScheduledThreadPoolExecutor timer = injector
.getInstance(ScheduledThreadPoolExecutor.class);
+
+ final UserPingTask ping = injector.getInstance(UserPingTask.class);
timer.scheduleAtFixedRate(ping, PING_START_DELAY, PING_CHECK_DELAY, TimeUnit.MILLISECONDS);
+
+ final BroadcastGameListUpdateTask broadcastUpdate = injector
+ .getInstance(BroadcastGameListUpdateTask.class);
+ timer.scheduleAtFixedRate(broadcastUpdate, BROADCAST_UPDATE_START_DELAY,
+ BROADCAST_UPDATE_DELAY, TimeUnit.MILLISECONDS);
+
serverStarted = new Date();
context.setAttribute(INJECTOR, injector);
context.setAttribute(DATE_NAME, serverStarted);
diff --git a/src/main/java/net/socialgamer/cah/data/Game.java b/src/main/java/net/socialgamer/cah/data/Game.java
index 504d9cf..6abf203 100644
--- a/src/main/java/net/socialgamer/cah/data/Game.java
+++ b/src/main/java/net/socialgamer/cah/data/Game.java
@@ -50,11 +50,11 @@ 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.SafeTimerTask;
import net.socialgamer.cah.cardcast.CardcastDeck;
import net.socialgamer.cah.cardcast.CardcastService;
import net.socialgamer.cah.data.GameManager.GameId;
import net.socialgamer.cah.data.QueuedMessage.MessageType;
+import net.socialgamer.cah.task.SafeTimerTask;
import org.apache.log4j.Logger;
import org.hibernate.Session;
diff --git a/src/main/java/net/socialgamer/cah/data/GameManager.java b/src/main/java/net/socialgamer/cah/data/GameManager.java
index 51b25d1..8ddfb13 100644
--- a/src/main/java/net/socialgamer/cah/data/GameManager.java
+++ b/src/main/java/net/socialgamer/cah/data/GameManager.java
@@ -1,16 +1,16 @@
/**
* 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
@@ -27,17 +27,13 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
-import net.socialgamer.cah.Constants.LongPollEvent;
-import net.socialgamer.cah.Constants.LongPollResponse;
-import net.socialgamer.cah.Constants.ReturnableData;
import net.socialgamer.cah.data.Game.TooManyPlayersException;
import net.socialgamer.cah.data.GameManager.GameId;
-import net.socialgamer.cah.data.QueuedMessage.MessageType;
+import net.socialgamer.cah.task.BroadcastGameListUpdateTask;
import org.apache.log4j.Logger;
@@ -49,9 +45,9 @@ import com.google.inject.Singleton;
/**
* Manage games for the server.
- *
+ *
* This is also a Guice provider for game ids.
- *
+ *
* @author Andy Janata (ajanata@socialgamer.net)
*/
@Singleton
@@ -62,7 +58,8 @@ public class GameManager implements Provider {
private final Provider maxGamesProvider;
private final Map games = new TreeMap();
private final Provider gameProvider;
- private final ConnectedUsers users;
+ private final BroadcastGameListUpdateTask broadcastUpdate;
+
/**
* Potential next game id.
*/
@@ -70,7 +67,7 @@ public class GameManager implements Provider {
/**
* Create a new game manager.
- *
+ *
* @param gameProvider
* Provider for new {@code Game} instances.
* @param maxGamesProvider
@@ -81,10 +78,10 @@ public class GameManager implements Provider {
@Inject
public GameManager(final Provider gameProvider,
@MaxGames final Provider maxGamesProvider,
- final ConnectedUsers users) {
+ final BroadcastGameListUpdateTask broadcastUpdate) {
this.gameProvider = gameProvider;
this.maxGamesProvider = maxGamesProvider;
- this.users = users;
+ this.broadcastUpdate = broadcastUpdate;
}
private int getMaxGames() {
@@ -94,7 +91,7 @@ public class GameManager implements Provider {
/**
* Creates a new game, if there are free game slots. Returns {@code null} if there are already the
* maximum number of games in progress.
- *
+ *
* @return Newly created game, or {@code null} if the maximum number of games are in progress.
*/
private Game createGame() {
@@ -114,11 +111,11 @@ public class GameManager implements Provider {
/**
* Creates a new game and puts the specified user into the game, if there are free game slots.
* Returns {@code null} if there are already the maximum number of games in progress.
- *
+ *
* Creating the game and adding the user are done atomically with respect to another game getting
* created, or even getting the list of active games. It is impossible for another user to join
* the game before the requesting user.
- *
+ *
* @param user
* User to place into the game.
* @return Newly created game, or {@code null} if the maximum number of games are in progress.
@@ -150,10 +147,10 @@ public class GameManager implements Provider {
/**
* This probably will not be used very often in the server: Games should normally be deleted when
* all players leave it. I'm putting this in if only to help with testing.
- *
+ *
* Destroys a game immediately. This will almost certainly cause errors on the client for any
* players left in the game. If {@code gameId} isn't valid, this method silently returns.
- *
+ *
* @param gameId
* ID of game to destroy.
*/
@@ -183,17 +180,15 @@ public class GameManager implements Provider {
* Broadcast an event to all users that they should refresh the game list.
*/
public void broadcastGameListRefresh() {
- final HashMap broadcastData = new HashMap();
- broadcastData.put(LongPollResponse.EVENT, LongPollEvent.GAME_LIST_REFRESH.toString());
- users.broadcastToAll(MessageType.GAME_EVENT, broadcastData);
+ broadcastUpdate.needsUpdate();
}
/**
* Get an unused game ID, or -1 if the maximum number of games are in progress. This should not be
* called in such a case, though!
- *
+ *
* TODO: make this not suck
- *
+ *
* @return Next game id, or {@code -1} if the maximum number of games are in progress.
*/
@Override
@@ -220,7 +215,7 @@ public class GameManager implements Provider {
/**
* Try to guess a good candidate for the next game id.
- *
+ *
* @param skip
* An id to skip over.
* @return A guess for the next game id.
@@ -255,7 +250,7 @@ public class GameManager implements Provider {
/**
* Gets the game with the specified id, or {@code null} if there is no game with that id.
- *
+ *
* @param id
* Id of game to retrieve.
* @return The Game, or {@code null} if there is no game with that id.
diff --git a/src/main/java/net/socialgamer/cah/task/BroadcastGameListUpdateTask.java b/src/main/java/net/socialgamer/cah/task/BroadcastGameListUpdateTask.java
new file mode 100644
index 0000000..7e363e5
--- /dev/null
+++ b/src/main/java/net/socialgamer/cah/task/BroadcastGameListUpdateTask.java
@@ -0,0 +1,39 @@
+package net.socialgamer.cah.task;
+
+import java.util.HashMap;
+
+import net.socialgamer.cah.Constants.LongPollEvent;
+import net.socialgamer.cah.Constants.LongPollResponse;
+import net.socialgamer.cah.Constants.ReturnableData;
+import net.socialgamer.cah.data.ConnectedUsers;
+import net.socialgamer.cah.data.QueuedMessage.MessageType;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+
+@Singleton
+public class BroadcastGameListUpdateTask extends SafeTimerTask {
+
+ private volatile boolean needsUpdate = false;
+ private final ConnectedUsers users;
+
+ @Inject
+ public BroadcastGameListUpdateTask(final ConnectedUsers users) {
+ this.users = users;
+ }
+
+ public void needsUpdate() {
+ needsUpdate = true;
+ }
+
+ @Override
+ public void process() {
+ if (needsUpdate) {
+ final HashMap broadcastData = new HashMap();
+ broadcastData.put(LongPollResponse.EVENT, LongPollEvent.GAME_LIST_REFRESH.toString());
+ users.broadcastToAll(MessageType.GAME_EVENT, broadcastData);
+ needsUpdate = false;
+ }
+ }
+}
diff --git a/src/main/java/net/socialgamer/cah/SafeTimerTask.java b/src/main/java/net/socialgamer/cah/task/SafeTimerTask.java
similarity index 91%
rename from src/main/java/net/socialgamer/cah/SafeTimerTask.java
rename to src/main/java/net/socialgamer/cah/task/SafeTimerTask.java
index c4c95c5..b8ec8e7 100644
--- a/src/main/java/net/socialgamer/cah/SafeTimerTask.java
+++ b/src/main/java/net/socialgamer/cah/task/SafeTimerTask.java
@@ -1,4 +1,4 @@
-package net.socialgamer.cah;
+package net.socialgamer.cah.task;
import org.apache.log4j.Logger;
diff --git a/src/main/java/net/socialgamer/cah/UserPing.java b/src/main/java/net/socialgamer/cah/task/UserPingTask.java
similarity index 88%
rename from src/main/java/net/socialgamer/cah/UserPing.java
rename to src/main/java/net/socialgamer/cah/task/UserPingTask.java
index fadb117..06fb3dc 100644
--- a/src/main/java/net/socialgamer/cah/UserPing.java
+++ b/src/main/java/net/socialgamer/cah/task/UserPingTask.java
@@ -1,16 +1,16 @@
/**
* 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
@@ -21,27 +21,29 @@
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package net.socialgamer.cah;
+package net.socialgamer.cah.task;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import net.socialgamer.cah.data.ConnectedUsers;
import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* Timer task to check for disconnected and idle clients.
- *
+ *
* @author Andy Janata (ajanata@gmail.com)
*/
-public class UserPing extends SafeTimerTask {
+@Singleton
+public class UserPingTask extends SafeTimerTask {
private final ConnectedUsers users;
private final ScheduledThreadPoolExecutor globalTimer;
@Inject
- public UserPing(final ConnectedUsers users, final ScheduledThreadPoolExecutor globalTimer) {
+ public UserPingTask(final ConnectedUsers users, final ScheduledThreadPoolExecutor globalTimer) {
this.users = users;
this.globalTimer = globalTimer;
}
diff --git a/src/test/java/net/socialgamer/cah/data/GameManagerTest.java b/src/test/java/net/socialgamer/cah/data/GameManagerTest.java
index 7296355..6c2e266 100644
--- a/src/test/java/net/socialgamer/cah/data/GameManagerTest.java
+++ b/src/test/java/net/socialgamer/cah/data/GameManagerTest.java
@@ -1,16 +1,16 @@
/**
* 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
@@ -58,7 +58,7 @@ import com.google.inject.Provides;
/**
* Tests for {@code GameManager}.
- *
+ *
* @author Andy Janata (ajanata@socialgamer.net)
*/
public class GameManagerTest {
@@ -129,11 +129,8 @@ public class GameManagerTest {
verify(userMock);
}
- @SuppressWarnings("unchecked")
@Test
public void testGetAndDestroyGame() {
- cuMock.broadcastToAll(eq(MessageType.GAME_EVENT), anyObject(HashMap.class));
- expectLastCall().times(3);
replay(cuMock);
replay(userMock);
@@ -168,8 +165,6 @@ public class GameManagerTest {
@SuppressWarnings("unchecked")
@Test
public void testCreateGame() {
- cuMock.broadcastToAll(eq(MessageType.GAME_EVENT), anyObject(HashMap.class));
- expectLastCall().times(3);
cuMock.broadcastToList(anyObject(Collection.class), eq(MessageType.GAME_PLAYER_EVENT),
anyObject(HashMap.class));
expectLastCall().times(3);