com.ahsgaming.valleyofbones.network.GameServer.java Source code

Java tutorial

Introduction

Here is the source code for com.ahsgaming.valleyofbones.network.GameServer.java

Source

/**
 * Copyright 2012 Jami Couch
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * This project uses:
 * 
 * LibGDX
 * Copyright 2011 see LibGDX AUTHORS file
 * Licensed under Apache License, Version 2.0 (see above).
 * 
 */
package com.ahsgaming.valleyofbones.network;

import java.io.IOException;
import java.util.HashMap;

import com.ahsgaming.valleyofbones.*;
import com.ahsgaming.valleyofbones.ai.AIPlayer;
import com.ahsgaming.valleyofbones.ai.FSMAIPlayer;
import com.ahsgaming.valleyofbones.network.KryoCommon.AddAIPlayer;
import com.ahsgaming.valleyofbones.network.KryoCommon.RegisterPlayer;
import com.ahsgaming.valleyofbones.network.KryoCommon.RegisteredPlayer;
import com.ahsgaming.valleyofbones.network.KryoCommon.RemovePlayer;
import com.ahsgaming.valleyofbones.network.KryoCommon.StartGame;
import com.ahsgaming.valleyofbones.screens.GameSetupConfig;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Net;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.net.HttpParametersUtils;
import com.badlogic.gdx.utils.*;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.kryonet.Server;

/**
 * @author jami
 *
 */
public class GameServer implements NetController {
    public String LOG = "GameServer";
    public static String globalServerUrl = (VOBGame.DEBUG_GLOBAL_SERVER ? "http://localhost/jami/vob-api-v1/"
            : "http://api.jamicouch.com/v1/");

    final VOBGame game;

    Server server, broadcastServer;
    GameController controller;

    GameSetupConfig gameConfig;

    Array<RegisteredPlayer> registeredPlayers = new Array<RegisteredPlayer>();
    HashMap<Integer, String> playerIdKeyMap = new HashMap<Integer, String>();
    Array<RegisteredPlayer> registeredSpectators = new Array<RegisteredPlayer>();

    ObjectMap<Connection, Integer> connMap = new ObjectMap<Connection, Integer>();

    Array<Player> players = new Array<Player>();
    Array<Command> recdCommands = new Array<Command>();

    int nextPlayerId = 0;
    int hostId = -1;

    boolean loadGame = false;

    boolean stopServer = false;

    boolean gameStarted = false;

    GameResult gameResult;

    boolean endTurnRecd;

    int publicServerId = -1;

    float serverPing = 30;
    float serverPingTimeout = 0;

    float awaitReconnectCountdown = 0;
    float awaitReconnectTime = 30;
    boolean awaitReconnect = false;

    int maxPlayers = 2;
    int firstPid = -1;

    /**
     * 
     */
    public GameServer(final VOBGame game, GameSetupConfig cfg) {
        this.game = game;
        gameConfig = cfg;

        init();
    }

    public void reset() {
        if (server != null)
            server.close();
        if (broadcastServer != null)
            broadcastServer.close();
        controller = null;
        loadGame = false;
        gameStarted = false;
        nextPlayerId = 0;
        players = new Array<Player>();
        recdCommands = new Array<Command>();
        //        spectators = new HashMap<Integer, KryoCommon.Spectator>();
        //        connMap = new ObjectMap<Connection, NetPlayer>();
        registeredPlayers = new Array<RegisteredPlayer>();
        registeredSpectators = new Array<RegisteredPlayer>();
        playerIdKeyMap = new HashMap<Integer, String>();
        connMap = new ObjectMap<Connection, Integer>();
        hostId = -1;
        stopServer = false;
        gameResult = null;
        awaitReconnectCountdown = 0;
        awaitReconnectTime = 0;
        awaitReconnect = false;

        removePublicServer();

        init();
    }

    public void init() {
        // setup the KryoNet server
        server = new Server();
        KryoCommon.register(server);

        broadcastServer = new Server();

        try {
            server.bind(gameConfig.hostPort);
            server.start();
        } catch (Exception ex) {
            Gdx.app.log(LOG, "Server failed to start");
            Gdx.app.log(LOG, ex.getMessage());
            stopServer = true;
            return;
        }

        try {
            broadcastServer.bind(0, KryoCommon.udpPort);
            broadcastServer.start();
        } catch (IOException ex) {
            Gdx.app.log(LOG, ex.getMessage());
        }

        // ai vs ai
        //        addAIPlayer(0);
        //        addAIPlayer(1);
        //        loadGame = true;

        server.addListener(new Listener() {

            public void received(Connection c, Object obj) {
                if (obj instanceof RegisterPlayer) {
                    RegisterPlayer rp = (RegisterPlayer) obj;

                    if (!rp.version.equals(VOBGame.VERSION)) {
                        // wrong version!
                        server.sendToTCP(c.getID(), new KryoCommon.VersionError());
                        return;
                    }

                    boolean playerFound = false;
                    if (gameStarted) {
                        synchronized (players) {
                            for (Player p : players) {
                                //                                Gdx.app.log(LOG, "" + p);
                                //                                Gdx.app.log(LOG, p.getPlayerName() + ": " + connMap.findKey(p.getPlayerId(), true).isConnected());
                                if (!(p instanceof AIPlayer) && p.getPlayerName().equals(rp.name)
                                        && !connMap.findKey(p.getPlayerId(), true).isConnected()) {
                                    connMap.remove(connMap.findKey(p.getPlayerId(), true));
                                    connMap.put(c, p.getPlayerId());
                                    Unpause up = new Unpause();
                                    up.owner = -1;
                                    up.turn = controller.getGameTurn();
                                    Gdx.app.log(LOG, "Queuing unpause");
                                    controller.queueCommand(up);
                                    sendCommand(up);
                                    awaitReconnect = false;
                                    playerFound = true;
                                }
                            }

                            if (!playerFound) {
                                if (!gameConfig.allowSpectate) {
                                    server.sendToTCP(c.getID(), new KryoCommon.GameFullError());
                                    return;
                                }
                                RegisteredPlayer reg = new RegisteredPlayer();
                                reg.id = getNextPlayerId();
                                reg.spectator = true;
                                reg.name = rp.name;
                                reg.race = rp.race;
                                registeredSpectators.add(reg);

                                connMap.put(c, reg.id);
                                server.sendToTCP(c.getID(), reg);

                                sendPlayerList();
                                sendSetupInfo();

                                StartGame startGame = new StartGame();
                                startGame.currentPlayer = firstPid;
                                startGame.spawnType = gameConfig.spawnType;

                                server.sendToTCP(c.getID(), startGame);
                                Command[] cmds = new Command[controller.getCommandHistory().size];
                                for (int i = 0; i < cmds.length; i++)
                                    cmds[i] = controller.getCommandHistory().get(i);
                                server.sendToTCP(c.getID(), cmds);

                                KryoCommon.GameUpdate gameUpdate = new KryoCommon.GameUpdate();
                                gameUpdate.currentPlayer = controller.getCurrentPlayer().getPlayerId();
                                gameUpdate.turn = controller.getGameTurn();
                                gameUpdate.timer = controller.getTurnTimer();
                                server.sendToTCP(c.getID(), gameUpdate);
                            }
                        }
                        return;
                    }

                    int id = getNextPlayerId();
                    Color use = new Color(1, 1, 1, 1);
                    RegisteredPlayer reg = new RegisteredPlayer();
                    reg.id = id;
                    reg.name = rp.name;
                    reg.race = rp.race;
                    if (registeredPlayers.size < maxPlayers && !rp.spectator) {
                        Gdx.app.log(LOG, "player joined");
                        reg.spectator = false;
                        boolean found = false;
                        boolean[] colorsUsed = new boolean[Player.AUTOCOLORS.length];
                        for (RegisteredPlayer p : registeredPlayers) {
                            colorsUsed[p.color] = true;
                        }
                        if (rp.prefColor < Player.AUTOCOLORS.length && rp.prefColor >= 0
                                && !colorsUsed[rp.prefColor]) {
                            reg.color = rp.prefColor;
                        } else {
                            int i = 0;
                            while (i < Player.AUTOCOLORS.length && colorsUsed[i]) {
                                i++;
                            }
                            i = Math.min(i, Player.AUTOCOLORS.length - 1);
                            reg.color = i;
                        }
                    } else {
                        Gdx.app.log(LOG, "player --> spectator");
                        reg.spectator = true;
                    }

                    if (reg.spectator && !gameConfig.allowSpectate) {
                        Gdx.app.log(LOG, "No spectators!");
                        server.sendToTCP(c.getID(), new KryoCommon.GameFullError());
                        return;
                    }

                    if (hostId == -1)
                        hostId = reg.id;

                    reg.host = (reg.id == hostId);

                    if (reg.spectator) {
                        registeredSpectators.add(reg);
                    } else {
                        registeredPlayers.add(reg);
                    }

                    if (rp.key != null)
                        playerIdKeyMap.put(reg.id, rp.key);
                    connMap.put(c, reg.id);
                    server.sendToTCP(c.getID(), reg);

                    sendPlayerList();
                    sendSetupInfo();
                }

                if (obj instanceof KryoCommon.UpdatePlayer) {
                    KryoCommon.UpdatePlayer update = (KryoCommon.UpdatePlayer) obj;
                    if (connMap.get(c) != update.id && connMap.get(c) != hostId)
                        return;

                    RegisteredPlayer player = null;
                    for (RegisteredPlayer rp : registeredPlayers) {
                        if (rp.id == update.id) {
                            player = rp;
                        }
                    }

                    if (player != null && (connMap.get(c) == update.id || player.isAI)) {
                        player.color = update.color;
                        player.race = update.race;
                        player.ready = update.ready;
                    }

                    sendPlayerList();
                }

                if (obj instanceof AddAIPlayer) {
                    // TODO implement teams/player limits more robustly
                    // make sure this is from the host
                    if (connMap.get(c) != hostId)
                        return;

                    addAIPlayer();

                    sendPlayerList();
                }

                if (obj instanceof RemovePlayer) {
                    // make sure this is from the host
                    if (connMap.get(c) != hostId)
                        return;

                    removePlayer(((RemovePlayer) obj).id);

                    sendPlayerList();
                }

                if (obj instanceof KryoCommon.GameDetails) {
                    if (connMap.get(c) != hostId)
                        return;

                    if (controller == null) {
                        KryoCommon.GameDetails details = (KryoCommon.GameDetails) obj;
                        gameConfig.setDetails(details);

                        Json json = new Json();
                        System.out.println(json.prettyPrint(json.toJson(details, KryoCommon.GameDetails.class)));
                        sendSetupInfo();

                        if (!gameConfig.allowSpectate && registeredSpectators.size > 0) {
                            // remove all spectators

                            for (int i = 0; i < registeredSpectators.size; i++) {
                                Connection conn = connMap.findKey(registeredSpectators.get(i).id, true);
                                if (conn != null && conn.isConnected())
                                    conn.close();
                            }
                            registeredSpectators.clear();

                            sendPlayerList();
                        }
                    }
                }

                if (obj instanceof KryoCommon.ChatMessage) {
                    server.sendToAllTCP(obj);
                }

                // TODO need to check this for validity
                if (controller != null) {
                    // the player represented by this connection has finished loading
                    if (obj instanceof StartGame) {
                        int id = connMap.get(c);
                        Player player = null;
                        for (Player p : players) {
                            if (p.getPlayerId() == id)
                                player = p;
                        }
                        if (player != null)
                            player.setLoaded(true);

                    } else if (obj instanceof Command) {
                        Command cmd = (Command) obj;
                        if (cmd.owner != connMap.get(c))
                            cmd.owner = connMap.get(c);

                        recdCommands.add((Command) obj);
                    }
                } else {
                    if (obj instanceof StartGame) {
                        if (connMap.get(c) != hostId)
                            return;
                        for (RegisteredPlayer rp : registeredPlayers) {
                            if (!(rp.ready || rp.isAI)) {
                                return;
                            }
                        }
                        loadGame = true;
                    }
                }
            }

            public void connected(Connection c) {
            }

            public void disconnected(Connection c) {
                if (gameStarted) {
                    Player player = (connMap.containsKey(c) ? findPlayerById(connMap.get(c)) : null);

                    if (player == null)
                        return;
                    // wait for reconnect
                    Pause p = new Pause();
                    p.isAuto = true;
                    p.owner = -1;
                    p.turn = controller.getGameTurn();
                    controller.queueCommand(p);
                    sendCommand(p);
                    awaitReconnectCountdown = awaitReconnectTime;
                    awaitReconnect = true;
                    if (controller.getGameResult() == null)
                        sendPlayerList();
                } else if (connMap.containsKey(c)) {
                    int id = connMap.get(c);

                    RegisteredPlayer player = null;
                    for (RegisteredPlayer rp : registeredPlayers) {
                        if (rp.id == id)
                            player = rp;
                    }
                    if (player != null) {
                        registeredPlayers.removeValue(player, true);
                    } else {
                        for (RegisteredPlayer rp : registeredSpectators) {
                            if (rp.id == id) {
                                player = rp;
                            }
                        }

                        if (player != null) {
                            registeredSpectators.removeValue(player, true);
                        }
                    }

                    connMap.remove(c);

                    if (hostId == id) {
                        // find a new host
                        hostId = -1;

                        for (RegisteredPlayer rp : registeredPlayers) {
                            if (!rp.isAI) {
                                player = rp;
                                hostId = rp.id;
                                break;
                            }
                        }

                        if (hostId == -1) {
                            for (RegisteredPlayer rp : registeredSpectators) {
                                if (!rp.isAI) {
                                    player = rp;
                                    hostId = rp.id;
                                    break;
                                }
                            }
                        }

                        if (hostId == -1) {
                            reset();
                            return;
                        }

                        player.host = true;
                        server.sendToTCP(connMap.findKey(player.id, true).getID(), player);
                    }
                    sendPlayerList();
                }
            }
        });

        if (gameConfig.isPublic) {
            registerPublicServer();
            serverPingTimeout = serverPing;
        }
    }

    public Player findPlayerById(int id) {
        for (Player p : players) {
            if (p.getPlayerId() == id)
                return p;
        }
        return null;
    }

    public void startGame() {
        // the host clicked startGame --> send out the StartGame message so that the clients load and report
        for (RegisteredPlayer rp : registeredPlayers) {
            if (rp.isAI) {
                players.add(new FSMAIPlayer(this, rp.id, rp.name, Player.AUTOCOLORS[rp.color]));
                players.get(players.size - 1).setRace(rp.race);
            } else {
                if (playerIdKeyMap.containsKey(rp.id)) {
                    players.add(new Player(rp.id, rp.name, Player.AUTOCOLORS[rp.color], rp.race,
                            playerIdKeyMap.get(rp.id)));
                } else {
                    players.add(new Player(rp.id, rp.name, Player.AUTOCOLORS[rp.color], rp.race));
                }
            }
        }

        gameConfig.spawnType = (gameConfig.spawnType == GameSetupConfig.SpawnTypes.SPAWN_RANDOM.ordinal()
                ? MathUtils.random(0, GameSetupConfig.SpawnTypes.values().length - 2)
                : gameConfig.spawnType);

        controller = new GameController(gameConfig, players);
        controller.LOG = controller.LOG + "#Server";

        switch (GameSetupConfig.FirstMoveType.values()[gameConfig.firstMove]) {
        case MOVE_RANDOM:
            controller.setCurrentPlayer(players.get(MathUtils.random(players.size - 1)));
            break;
        case MOVE_P1:
            controller.setCurrentPlayer(players.get(0));
            break;
        case MOVE_P2:
            controller.setCurrentPlayer(players.get(1));
            break;
        }
        loadGame = false;

        // don't need to broadcast on UDP anymore - thats just confusing
        broadcastServer.close();

        sendStartGame();
    }

    public void endGame() {
        // the controller has a game result --> broadcast it to everybody and close the server
        GameResult result = controller.getGameResult();
        controller.setState(GameStates.GAMEOVER);
        //        game.setGameResult(result);
        gameResult = result;

        Gdx.app.log(LOG,
                String.format("GameResult: winner: %d; Losers: (%d)", result.winner, result.losers.length));

        server.sendToAllTCP(result);
        server.close();

        if (gameConfig.isPublic) {
            sendPublicServerResult();
        }
    }

    public void stop() {
        server.close();

        removePublicServer();
    }

    public boolean update(float delta) {

        if (!gameStarted && gameConfig.isPublic) {
            serverPingTimeout -= delta;
            if (serverPingTimeout <= 0) {
                serverPingTimeout = serverPing;
                sendPublicServerUpdate();
            }
        }

        if ((controller == null))
            return true;

        // once the controller exists, we need to check to make sure all players are ready
        if (!gameStarted) {
            boolean allReady = true;
            for (int p = 0; p < players.size; p++) {
                if (!players.get(p).isLoaded()) {
                    allReady = false;
                }
            }

            // once all are ready, we need to unpause the game
            if (allReady) {
                Unpause up = new Unpause();
                up.owner = -1;
                up.turn = controller.getGameTurn();
                // important to both send the command out and add to the local queue!
                server.sendToAllTCP(up);
                controller.queueCommand(up);
                gameStarted = true;
                endTurnRecd = false;
                if (gameConfig.isPublic)
                    sendPublicServerUpdate();
            }
        }

        if (gameStarted) {

            // moved this logic here to avoid "nested" iterator problem, which I think occurred because of the call to validate on another thread
            synchronized (recdCommands) {
                for (Command cmd : recdCommands) {
                    // discard past, future, invalid, or duplicated commands
                    if (cmd.turn == controller.getGameTurn() && !controller.getCommandQueue().contains(cmd, false)
                            && cmd.validate(controller)) {
                        if (cmd instanceof EndTurn) {
                            endTurnRecd = true;
                        } else {
                            controller.queueCommand(cmd);
                            server.sendToAllTCP(cmd);
                        }
                    }
                }
                recdCommands.clear();
            }

            controller.update(delta);

            if (awaitReconnect) {
                awaitReconnectCountdown -= delta;
                if (awaitReconnectCountdown < 0) {
                    for (int p = 0; p < players.size; p++) {
                        if (players.get(p) instanceof AIPlayer
                                || connMap.findKey(players.get(p).getPlayerId(), true).isConnected()) {
                            controller.declareWinner(players.get(p));
                            awaitReconnect = false;
                        }
                    }
                }
            }

            if (isEndTurn()) {
                EndTurn endTurn = new EndTurn(controller.getCommandQueue());
                endTurn.turn = controller.getGameTurn();
                endTurn.owner = controller.getCurrentPlayer().getPlayerId();
                server.sendToAllTCP(endTurn);
                controller.setNextTurn(true);
                controller.doTurn();
                endTurnRecd = false;
            }
        }

        if (controller.getGameResult() != null) {
            endGame();
            return false;
        }

        return true;
    }

    public void sendPlayerList() {
        RegisteredPlayer[] list = null;
        if (!gameStarted) {
            list = new RegisteredPlayer[registeredPlayers.size + registeredSpectators.size];
            for (int p = 0; p < list.length; p++) {
                if (p < registeredPlayers.size) {
                    list[p] = registeredPlayers.get(p);
                } else {
                    list[p] = registeredSpectators.get(p - registeredPlayers.size);
                }
            }

        } else {
            list = new RegisteredPlayer[players.size];
            for (int p = 0; p < players.size; p++) {
                RegisteredPlayer rp = new RegisteredPlayer();
                Player pl = players.get(p);
                rp.id = pl.getPlayerId();
                rp.name = pl.getPlayerName();
                rp.race = pl.getRace();
                int c = 0;
                while (!pl.getPlayerColor().equals(Player.AUTOCOLORS[c]) && c < Player.AUTOCOLORS.length - 1)
                    c++;
                rp.color = c;
                list[p] = rp;
            }
        }
        if (gameConfig.isPublic)
            sendPublicServerUpdate();
        server.sendToAllTCP(list);
    }

    public void sendSetupInfo() {
        KryoCommon.GameDetails si = gameConfig.getDetails();
        si.hostId = hostId;
        server.sendToAllTCP(si);
    }

    protected int getNextPlayerId() {
        int id = nextPlayerId;
        nextPlayerId += 1;
        return id;
    }

    public boolean isEndTurn() {
        if (controller != null) {
            if (controller.getTurnTimer() <= 0)
                return true;
        }

        return endTurnRecd;
    }

    @Override
    public void setGameController(GameController controller) {
        // TODO Auto-generated method stub

    }

    @Override
    public GameController getGameController() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void sendStartGame() {
        StartGame startGame = new StartGame();
        startGame.currentPlayer = controller.getCurrentPlayer().getPlayerId();
        firstPid = controller.getCurrentPlayer().getPlayerId();
        startGame.spawnType = gameConfig.spawnType;
        server.sendToAllTCP(startGame);
    }

    @Override
    public void addAIPlayer() {
        if (registeredPlayers.size < maxPlayers) {
            RegisteredPlayer reg = new RegisteredPlayer();
            reg.id = getNextPlayerId();
            reg.name = "AI Player";
            reg.color = Player.getUnusedColorId(registeredPlayers);
            reg.race = "terran";
            reg.isAI = true;
            reg.host = false;
            reg.ready = true;
            reg.spectator = false;
            registeredPlayers.add(reg);
            sendPlayerList();
        }
    }

    @Override
    public void removePlayer(int playerId) {
        if (!gameStarted) {
            Array<RegisteredPlayer> remove = new Array<RegisteredPlayer>();
            for (RegisteredPlayer p : registeredPlayers) {
                if (p.id == playerId) {
                    remove.add(p);
                }
            }

            for (RegisteredPlayer p : registeredSpectators) {
                if (p.id == playerId)
                    remove.add(p);
            }

            for (RegisteredPlayer rp : remove) {
                if (!rp.isAI && connMap.containsValue(rp.id, true)) {
                    connMap.findKey(rp.id, true).close();
                }
            }
            registeredPlayers.removeAll(remove, true);
        }
    }

    @Override
    public Array<Player> getPlayers() {
        return this.players;
    }

    public Array<RegisteredPlayer> getRegisteredPlayers() {
        return registeredPlayers;
    }

    @Override
    public Player getPlayer() {
        return null;
    }

    @Override
    public void sendCommand(Command cmd) {
        server.sendToAllTCP(cmd);
    }

    @Override
    public void sendAICommand(Command cmd) {
        if (!controller.getCommandQueue().contains(cmd, false) && cmd.validate(controller)) {
            if (cmd instanceof EndTurn) {
                endTurnRecd = true;
            } else {
                controller.queueCommand(cmd);
                server.sendToAllTCP(cmd);
            }
        }
    }

    @Override
    public boolean isConnected() {
        return false;
    }

    @Override
    public boolean isConnecting() {
        return false;
    }

    public boolean isLoadGame() {
        return loadGame;
    }

    public boolean isGameStarted() {
        return gameStarted;
    }

    public boolean isStopServer() {
        return stopServer;
    }

    public void setStopServer(boolean stop) {
        stopServer = stop;
    }

    public GameResult getGameResult() {
        return gameResult;
    }

    public GameSetupConfig getGameConfig() {
        return gameConfig;
    }

    public void registerPublicServer() {
        Net.HttpRequest httpGet = new Net.HttpRequest(Net.HttpMethods.POST);
        httpGet.setUrl(String.format("%s/servers", globalServerUrl));

        String content = String.format("{ \"name\": \"%s\", \"port\": \"%s\", \"version\": \"%s\" }",
                gameConfig.hostName, Integer.toString(gameConfig.hostPort), VOBGame.VERSION);

        //        HashMap parameters = new HashMap();
        //        parameters.put("name", gameConfig.hostName);
        //        parameters.put("port", Integer.toString(gameConfig.hostPort));
        ////        parameters.put("ip", Utils.getIPAddress(true));
        httpGet.setContent(content);
        //        System.out.println(HttpParametersUtils.convertHttpParameters(parameters));

        Gdx.net.sendHttpRequest(httpGet, new Net.HttpResponseListener() {
            @Override
            public void handleHttpResponse(Net.HttpResponse httpResponse) {
                String response = httpResponse.getResultAsString();
                switch (httpResponse.getStatus().getStatusCode()) {
                case 400:
                case 404:
                    Gdx.app.log(LOG, "Failed to register with global server");
                    Gdx.app.log(LOG, response);
                    break;
                case 200:
                    Gdx.app.log(LOG, "Registered with global server");
                    Gdx.app.log(LOG, response);
                    JsonReader reader = new JsonReader();
                    JsonValue result = reader.parse(response);
                    if (result != null)
                        publicServerId = result.getInt("id", -1);
                    Gdx.app.log(LOG, Integer.toString(publicServerId));
                    break;
                default:
                    Gdx.app.log(LOG, String.format("Unknown HTTP Status Code: %d",
                            httpResponse.getStatus().getStatusCode()));
                    Gdx.app.log(LOG, response);
                }
            }

            @Override
            public void failed(Throwable t) {
                Gdx.app.log(LOG, "Failed to register with global server");
                t.printStackTrace();
            }
        });
    }

    public void removePublicServer() {
        if (publicServerId >= 0) {
            Net.HttpRequest httpDelete = new Net.HttpRequest(Net.HttpMethods.DELETE);
            httpDelete.setUrl(String.format("%s/servers/%d", globalServerUrl, publicServerId));
            Gdx.net.sendHttpRequest(httpDelete, new Net.HttpResponseListener() {
                @Override
                public void handleHttpResponse(Net.HttpResponse httpResponse) {
                    Gdx.app.log(LOG, httpResponse.getResultAsString());
                }

                @Override
                public void failed(Throwable t) {
                    t.printStackTrace();
                }
            });
        }

        publicServerId = -1;
    }

    public void sendPublicServerResult() {
        if (publicServerId >= 0) {
            Net.HttpRequest httpPost = new Net.HttpRequest(Net.HttpMethods.POST);
            httpPost.setUrl(String.format("%s/games", globalServerUrl));
            String playersString = "{}";
            if (controller.getPlayers().size >= 2) {
                playersString = String.format("{ \"%d\": %s, \"%d\": %s }",
                        controller.getPlayers().get(0).getPlayerId(), controller.getPlayers().get(0).getKeyString(), //"\"" + controller.getPlayers().get(0).getPlayerName() + "\"",
                        controller.getPlayers().get(1).getPlayerId(),
                        controller.getPlayers().get(1).getKeyString());
            }
            String historyString = "[";
            boolean first = true;
            for (Command command : controller.getCommandHistory()) {
                if (first) {
                    first = false;
                } else {
                    historyString += ", ";
                }
                historyString += command.toJson();
            }
            historyString += "]";

            String content = String.format("{ \"game\": %s, \"version\": \"%s\", \"map\": \"%s\" }",
                    "{ \"history\": " + historyString + ", \"result\": " + gameResult.winner + ", \"map\": \""
                            + controller.getMapName() + "\", \"players\": " + playersString + "}",
                    VOBGame.VERSION, controller.getMapName());

            httpPost.setContent(content);

            Gdx.net.sendHttpRequest(httpPost, new Net.HttpResponseListener() {
                @Override
                public void handleHttpResponse(Net.HttpResponse httpResponse) {
                    String response = httpResponse.getResultAsString();

                    switch (httpResponse.getStatus().getStatusCode()) {
                    case 400:
                    case 404:
                        Gdx.app.log(LOG, "Failed to upload game result");
                        Gdx.app.log(LOG, response);
                        break;
                    case 200:
                        Gdx.app.log(LOG, "Uploaded game result");
                        Gdx.app.log(LOG, response);
                        break;
                    default:
                        Gdx.app.log(LOG, "Unknown response code " + httpResponse.getStatus().getStatusCode());
                        Gdx.app.log(LOG, response);
                    }
                }

                @Override
                public void failed(Throwable t) {
                    t.printStackTrace();
                }
            });

            Gdx.app.log(LOG, httpPost.getContent());
        }
    }

    public void sendPublicServerUpdate() {
        Net.HttpRequest req = new Net.HttpRequest(Net.HttpMethods.PUT);
        req.setUrl(String.format("%s/servers/%d", globalServerUrl, publicServerId));

        String content = String.format("{ \"players\": %d, \"status\": %d }",
                (gameStarted ? players.size : registeredPlayers.size), (gameStarted ? 1 : 0));

        Gdx.app.log("LOG", content);
        req.setContent(content);
        Gdx.net.sendHttpRequest(req, new Net.HttpResponseListener() {
            @Override
            public void handleHttpResponse(Net.HttpResponse httpResponse) {
                switch (httpResponse.getStatus().getStatusCode()) {
                default:
                case 404:
                    registerPublicServer();
                    serverPingTimeout = 60;
                    break;
                case 200:
                    Gdx.app.log(LOG, "Update success");
                    break;
                }
                Gdx.app.log(LOG, httpResponse.getResultAsString());
            }

            @Override
            public void failed(Throwable t) {
                Gdx.app.log(LOG, "Update failed");
            }
        });
    }

    @Override
    public HashMap<Integer, String> getSpectators() {
        HashMap<Integer, String> list = new HashMap<Integer, String>();
        for (RegisteredPlayer spectator : registeredSpectators) {
            list.put(spectator.id, spectator.name);
        }
        return list;
    }

    public void sendChat(String message) {
    }

    public Array<String> getChatLog() {
        return null;
    }
}