package controllers; import com.fasterxml.jackson.databind.JsonNode; import models.Game; import models.event.WaitingForOpponent; import models.player.ConnectedPlayer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import play.mvc.Controller; import play.mvc.Result; import play.mvc.WebSocket; import views.html.index; import javax.annotation.concurrent.ThreadSafe; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; @ThreadSafe public class Application extends Controller { private static final Logger log = LoggerFactory.getLogger(Application.class); /** * {@link Queue} of players waiting to be paired. */ private static final Queue<ConnectedPlayer> pendingPlayers = new ConcurrentLinkedQueue<>(); /** * {@link Map} of active {@link Game}s keyed by game ids. */ private static final Map<String, Game> games = new ConcurrentHashMap<>(); public static Queue<ConnectedPlayer> getPendingPlayers() { return pendingPlayers; } public static Map<String, Game> getGames() { return games; } public static Result index() { int gameCount = games.size(); int playerCount = pendingPlayers.size() + 2 * gameCount; return ok(index.render(gameCount, playerCount)); } /** * Accepts incoming join requests. * * Function either pairs the connection with a pending player, or pushes * it to the pending players queue. Requests are handled in parallel, no * synchronization is necessary. */ public static WebSocket<JsonNode> join() { return new WebSocket<JsonNode>() { @Override public void onReady(In<JsonNode> in, Out<JsonNode> out) { ConnectedPlayer lowerPlayer = new ConnectedPlayer(in, out); log.trace("Incoming {}.", lowerPlayer); // Tell the player that we are trying to find a pair. new WaitingForOpponent(lowerPlayer.getId()).write(out); // Try to pair the incoming player with another player. ConnectedPlayer upperPlayer = pendingPlayers.poll(); if (upperPlayer != null) { Game game = new Game( upperPlayer, lowerPlayer, new Game.ShutdownListener() { @Override public void onGameShutdown(String gameId) { Application.onGameShutdown(gameId); } }); game.start(); games.put(game.getId(), game); log.trace("Started {} with {} and {}.", game, upperPlayer, lowerPlayer); } // Else, queue the player into the waiting list. else { pendingPlayers.add(lowerPlayer); log.trace("Queued {}.", lowerPlayer); } } }; } /** * Closes player connections and cleans up data structures. */ public static void onGameShutdown(String gameId) { Game game = games.remove(gameId); for (ConnectedPlayer player : game.getPlayers()) player.getOutputSocket().close(); log.trace("Closed {}.", game); } }