parent
e64978a0da
commit
565c17b338
|
@ -1,13 +1,23 @@
|
|||
package net.socialgamer.cah;
|
||||
|
||||
import net.socialgamer.cah.data.GameManager;
|
||||
import net.socialgamer.cah.data.GameManager.GameId;
|
||||
import net.socialgamer.cah.data.GameManager.MaxGames;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.Provides;
|
||||
|
||||
|
||||
public class CahModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(Server.class).in(Singleton.class);
|
||||
bind(Integer.class).annotatedWith(GameId.class).toProvider(GameManager.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@MaxGames
|
||||
Integer provideMaxGames() {
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,28 @@
|
|||
package net.socialgamer.cah;
|
||||
|
||||
import net.socialgamer.cah.data.ConnectedUsers;
|
||||
import net.socialgamer.cah.data.GameManager;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
|
||||
@Singleton
|
||||
public class Server {
|
||||
private final ConnectedUsers users;
|
||||
private final GameManager gameManager;
|
||||
|
||||
public Server() {
|
||||
users = new ConnectedUsers();
|
||||
@Inject
|
||||
public Server(final ConnectedUsers connectedUsers, final GameManager gameManager) {
|
||||
users = connectedUsers;
|
||||
this.gameManager = gameManager;
|
||||
}
|
||||
|
||||
// TODO figure out if I can just get this to inject directly
|
||||
public ConnectedUsers getConnectedUsers() {
|
||||
return this.users;
|
||||
}
|
||||
|
||||
public GameManager getGames() {
|
||||
return this.gameManager;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,6 @@ public class StartupUtils extends GuiceServletContextListener {
|
|||
|
||||
@Override
|
||||
protected Injector getInjector() {
|
||||
return Guice.createInjector();
|
||||
return Guice.createInjector(new CahModule());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ public class UserPing extends TimerTask {
|
|||
private final ConnectedUsers users;
|
||||
|
||||
@Inject
|
||||
public UserPing(final Server server) {
|
||||
users = server.getConnectedUsers();
|
||||
public UserPing(final ConnectedUsers users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -11,14 +11,16 @@ import net.socialgamer.cah.Constants.LongPollResponse;
|
|||
import net.socialgamer.cah.Constants.ReturnableData;
|
||||
import net.socialgamer.cah.data.QueuedMessage.MessageType;
|
||||
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
|
||||
/**
|
||||
* Class that holds all users connected to the server, and provides functions to operate on said
|
||||
* list.
|
||||
*
|
||||
* @author ajanata
|
||||
*
|
||||
*/
|
||||
@Singleton
|
||||
public class ConnectedUsers {
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,8 +7,11 @@ import java.util.List;
|
|||
|
||||
import net.socialgamer.cah.Constants.LongPollResponse;
|
||||
import net.socialgamer.cah.Constants.ReturnableData;
|
||||
import net.socialgamer.cah.data.GameManager.GameId;
|
||||
import net.socialgamer.cah.data.QueuedMessage.MessageType;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
|
||||
public class Game {
|
||||
private final int id;
|
||||
|
@ -25,7 +28,8 @@ public class Game {
|
|||
* @param id
|
||||
* @param connectedUsers
|
||||
*/
|
||||
public Game(final int id, final ConnectedUsers connectedUsers) {
|
||||
@Inject
|
||||
public Game(@GameId final Integer id, final ConnectedUsers connectedUsers) {
|
||||
this.id = id;
|
||||
this.connectedUsers = connectedUsers;
|
||||
}
|
||||
|
@ -93,6 +97,10 @@ public class Game {
|
|||
return playersToUsers();
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private List<User> playersToUsers() {
|
||||
final List<User> users;
|
||||
synchronized (players) {
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
package net.socialgamer.cah.data;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.socialgamer.cah.data.GameManager.GameId;
|
||||
|
||||
import com.google.inject.BindingAnnotation;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
|
||||
/**
|
||||
* Manage games for the server.
|
||||
*
|
||||
* This is also a Guice provider for game ids.
|
||||
*
|
||||
* @author ajanata
|
||||
*/
|
||||
@Singleton
|
||||
@GameId
|
||||
public class GameManager implements Provider<Integer> {
|
||||
|
||||
private final int maxGames;
|
||||
private final Map<Integer, Game> games = new TreeMap<Integer, Game>();
|
||||
private final Provider<Game> gameProvider;
|
||||
/**
|
||||
* Potential next game id.
|
||||
*/
|
||||
private int nextId = 0;
|
||||
|
||||
@Inject
|
||||
public GameManager(final Provider<Game> gameProvider, @MaxGames final Integer maxGames) {
|
||||
this.gameProvider = gameProvider;
|
||||
this.maxGames = maxGames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new game, if there are free game slots. Returns 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.
|
||||
*/
|
||||
public Game createGame() {
|
||||
synchronized (games) {
|
||||
if (games.size() >= maxGames) {
|
||||
return null;
|
||||
}
|
||||
final Game game = gameProvider.get();
|
||||
assert (game.getId() >= 0);
|
||||
return game;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public void destroyGame(final int gameId) {
|
||||
synchronized (games) {
|
||||
final Game game = games.remove(gameId);
|
||||
if (game == null) {
|
||||
return;
|
||||
}
|
||||
// if the prospective next id isn't valid, set it to the id we just removed
|
||||
if (nextId == -1 || games.containsKey(nextId)) {
|
||||
nextId = gameId;
|
||||
}
|
||||
// TODO remove the players from the game
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 -1 if the maximum number of games are in progress.
|
||||
*/
|
||||
@Override
|
||||
public Integer get() {
|
||||
synchronized (games) {
|
||||
if (games.size() >= maxGames) {
|
||||
return -1;
|
||||
}
|
||||
if (!games.containsKey(nextId) && nextId >= 0) {
|
||||
final int ret = nextId;
|
||||
nextId = candidateGameId(ret);
|
||||
return ret;
|
||||
} else {
|
||||
final int ret = candidateGameId();
|
||||
nextId = candidateGameId(ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int candidateGameId() {
|
||||
return candidateGameId(-1);
|
||||
}
|
||||
|
||||
private int candidateGameId(final int skip) {
|
||||
synchronized (games) {
|
||||
if (games.size() >= maxGames) {
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < maxGames; i++) {
|
||||
if (i == skip) {
|
||||
continue;
|
||||
}
|
||||
if (!games.containsKey(i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
Map<Integer, Game> getGames() {
|
||||
return games;
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface GameId {
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface MaxGames {
|
||||
}
|
||||
}
|
|
@ -13,7 +13,6 @@ import net.socialgamer.cah.Constants.LongPollResponse;
|
|||
import net.socialgamer.cah.Constants.ReturnableData;
|
||||
import net.socialgamer.cah.Constants.SessionAttribute;
|
||||
import net.socialgamer.cah.RequestWrapper;
|
||||
import net.socialgamer.cah.Server;
|
||||
import net.socialgamer.cah.data.ConnectedUsers;
|
||||
import net.socialgamer.cah.data.QueuedMessage.MessageType;
|
||||
import net.socialgamer.cah.data.User;
|
||||
|
@ -28,8 +27,8 @@ public class ChatHandler extends Handler {
|
|||
private final ConnectedUsers users;
|
||||
|
||||
@Inject
|
||||
public ChatHandler(final Server server) {
|
||||
this.users = server.getConnectedUsers();
|
||||
public ChatHandler(final ConnectedUsers users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -10,7 +10,6 @@ import net.socialgamer.cah.Constants.DisconnectReason;
|
|||
import net.socialgamer.cah.Constants.ReturnableData;
|
||||
import net.socialgamer.cah.Constants.SessionAttribute;
|
||||
import net.socialgamer.cah.RequestWrapper;
|
||||
import net.socialgamer.cah.Server;
|
||||
import net.socialgamer.cah.data.ConnectedUsers;
|
||||
import net.socialgamer.cah.data.User;
|
||||
|
||||
|
@ -24,8 +23,8 @@ public class LogoutHandler extends Handler {
|
|||
private final ConnectedUsers users;
|
||||
|
||||
@Inject
|
||||
public LogoutHandler(final Server server) {
|
||||
this.users = server.getConnectedUsers();
|
||||
public LogoutHandler(final ConnectedUsers users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -12,7 +12,6 @@ import net.socialgamer.cah.Constants.AjaxOperation;
|
|||
import net.socialgamer.cah.Constants.AjaxResponse;
|
||||
import net.socialgamer.cah.Constants.ReturnableData;
|
||||
import net.socialgamer.cah.RequestWrapper;
|
||||
import net.socialgamer.cah.Server;
|
||||
import net.socialgamer.cah.data.ConnectedUsers;
|
||||
import net.socialgamer.cah.data.User;
|
||||
|
||||
|
@ -26,8 +25,8 @@ public class NamesHandler extends Handler {
|
|||
private final ConnectedUsers users;
|
||||
|
||||
@Inject
|
||||
public NamesHandler(final Server server) {
|
||||
this.users = server.getConnectedUsers();
|
||||
public NamesHandler(final ConnectedUsers users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -13,7 +13,6 @@ import net.socialgamer.cah.Constants.ErrorCode;
|
|||
import net.socialgamer.cah.Constants.ReturnableData;
|
||||
import net.socialgamer.cah.Constants.SessionAttribute;
|
||||
import net.socialgamer.cah.RequestWrapper;
|
||||
import net.socialgamer.cah.Server;
|
||||
import net.socialgamer.cah.data.ConnectedUsers;
|
||||
import net.socialgamer.cah.data.User;
|
||||
|
||||
|
@ -35,8 +34,8 @@ public class RegisterHandler extends Handler {
|
|||
* @param server
|
||||
*/
|
||||
@Inject
|
||||
public RegisterHandler(final Server server) {
|
||||
this.users = server.getConnectedUsers();
|
||||
public RegisterHandler(final ConnectedUsers users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
package net.socialgamer.cah.data;
|
||||
|
||||
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 net.socialgamer.cah.data.GameManager.GameId;
|
||||
import net.socialgamer.cah.data.GameManager.MaxGames;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Provides;
|
||||
|
||||
|
||||
public class GameManagerTest {
|
||||
|
||||
private Injector injector;
|
||||
private GameManager gameManager;
|
||||
private ConnectedUsers cmMock;
|
||||
private int gameId;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
injector = Guice.createInjector(new AbstractModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
// pass
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Provides
|
||||
@MaxGames
|
||||
Integer provideMaxGames() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Provides
|
||||
@GameId
|
||||
Integer provideGameId() {
|
||||
return gameId;
|
||||
}
|
||||
});
|
||||
|
||||
gameManager = injector.getInstance(GameManager.class);
|
||||
cmMock = createMock(ConnectedUsers.class);
|
||||
replay(cmMock);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
verify(cmMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAndDestroyGame() {
|
||||
// fill it up with 3 games
|
||||
assertEquals(0, gameManager.get().intValue());
|
||||
gameManager.getGames().put(0, new Game(0, cmMock));
|
||||
assertEquals(1, gameManager.get().intValue());
|
||||
gameManager.getGames().put(1, new Game(1, cmMock));
|
||||
assertEquals(2, gameManager.get().intValue());
|
||||
gameManager.getGames().put(2, new Game(2, cmMock));
|
||||
// make sure it says it can't make any more
|
||||
assertEquals(-1, gameManager.get().intValue());
|
||||
|
||||
// remove game 1 using its own method -- this should be how it always happens in production
|
||||
gameManager.destroyGame(1);
|
||||
// make sure it re-uses that id
|
||||
assertEquals(1, gameManager.get().intValue());
|
||||
gameManager.getGames().put(1, new Game(1, cmMock));
|
||||
assertEquals(-1, gameManager.get().intValue());
|
||||
|
||||
// remove game 1 out from under it, to make sure it'll fix itself
|
||||
gameManager.getGames().remove(1);
|
||||
assertEquals(1, gameManager.get().intValue());
|
||||
gameManager.getGames().put(1, new Game(1, cmMock));
|
||||
assertEquals(-1, gameManager.get().intValue());
|
||||
|
||||
gameManager.destroyGame(2);
|
||||
gameManager.destroyGame(0);
|
||||
assertEquals(2, gameManager.get().intValue());
|
||||
}
|
||||
}
|
|
@ -50,6 +50,5 @@ public class GameTest {
|
|||
assertEquals(null, game.getHost());
|
||||
|
||||
verify(cmMock);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue