Package net.yura.lobby.server

Source Code of net.yura.lobby.server.GameLobby

package net.yura.lobby.server;

import java.io.File;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.logging.Level;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import net.yura.lobby.client.LobbyCom;
import net.yura.lobby.client.ProtoAccess;
import net.yura.lobby.database.Database;
import net.yura.lobby.database.GameRoom;
import net.yura.lobby.database.GameTypeRoom;
import net.yura.lobby.database.User;
import net.yura.lobby.database.impl.JPADatabase;
import net.yura.lobby.gen.ProtoLobby;
import net.yura.lobby.model.Game;
import net.yura.lobby.model.GameType;
import net.yura.lobby.model.Message;
import net.yura.lobby.model.Player;

/**
* @author Yura Mamyrin
*/
public class GameLobby implements LobbyServerMXBean {

    final Database database;

    // this is loaded at the start and never changes
    private final Map<Integer,GameType> classLoaders = new HashMap();

    private final ConcurrentMap<Integer,Collection<LobbySession>> gameTypeRooms = new ConcurrentHashMap();
    private final Map<Integer,ServerGame> runningGames = new ConcurrentHashMap();

    private boolean doAndroidSend;
    private boolean saveOnShutdown=true;
    private String welcomeMessage,newHost;

    final SocketServer server;

    public GameLobby(SocketServer sserver) {
        this.server = sserver;

        try {
            MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
            mbs.registerMBean(this, new ObjectName("net.yura.lobby:type=LobbyServer") );
        }
        catch (Exception ex) {
            throw new RuntimeException(ex);
        }

        //database = new net.yura.lobby.database.impl.MemoryDatabase();
        database = new JPADatabase();

        TurnBasedGame.scheduler.pause();
        loadFromDB();
        TurnBasedGame.scheduler.resume();

        // add shutdown hook only if we manage to create a DB
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                if (saveOnShutdown) {
                    saveToDB();
                }
            }
        });

        doAndroidSend = true;
    }

    public void loadFromDB() {
        Collection<Integer> gameIds = new ArrayList();
        try {
            database.startTransaction();
            Collection<GameTypeRoom> list = database.getGameTypes();
            for (GameTypeRoom gtr:list) {
                classLoaders.put(gtr.getId(), gtr.getClassLoader() );
            }
            Collection<GameRoom> games = database.getGameRooms();
            for (GameRoom game:games) {
                gameIds.add( game.getId() );
            }
        }
        finally {
            database.endTransaction();
        }

        int count=0;
        for (int gameId:gameIds) {
            count++;
            LobbyServer.logger.info(count+"/"+gameIds.size()+" loading "+gameId);

            long start = System.currentTimeMillis();
            long db1;
            GameRoom game;
            byte[] data;
            try {
                database.startTransaction();
                game = database.getGame(gameId);
                db1 = System.currentTimeMillis();
                data = game.getGameData();
            }
            finally {
                database.endTransaction();
            }

            long db2 = System.currentTimeMillis();
            if (data!=null) {
                try {
                    ServerGame sg = createServerGame(game, null);
                    sg.loadGame(data);
                }
                catch (Exception ex) {
                    LobbyServer.logger.log(Level.WARNING, "failed to load game from DB "+game, ex);
                }
            }
            long load = System.currentTimeMillis();

            LobbyServer.logger.info(count+"/"+gameIds.size()+" done "+gameId+" took GET_GAME:"+(db1-start)+" GET_DATA:"+(db2-db1)+
                    (data == null ? " no data" : " DATA:"+data.length+" LOAD:"+(load-db2)));
        }
    }

    @Override
    public void saveToDB() {
        LobbyServer.logger.info("going to save games to DB");

        int priority = Thread.currentThread().getPriority();
        Thread.currentThread().setPriority(Thread.MAX_PRIORITY);

        Collection<ServerGame> games = new ArrayList( runningGames.values() );
        int count = 0;
        for (ServerGame game:games) {
            count++;
            try {
                if (game.isFinished()) {
                    LobbyServer.logger.info(count+"/"+games.size()+" removing "+game.getId());
                    removeGame( game.getId() );
                }
                else {
                    LobbyServer.logger.info(count+"/"+games.size()+" saving "+game.getId());
                    saveGame( game.getId() );
                }
            }
            catch (Exception ex) {
          LobbyServer.logger.log(Level.WARNING, "failed to save/remove game to DB "+game.getId(), ex);
            }
        }

        Thread.currentThread().setPriority(priority);

        LobbyServer.logger.info("DONE saving games to DB");
    }

    @Override
    public void saveGame(int id) {
        try {
            database.startTransaction();

            GameRoom gameRoom = getGame(id);
            ServerGame serverGame = runningGames.get(id);

            if (serverGame!=null) {
                gameRoom.setGameData( serverGame.saveGameState() );
            }

            database.saveGame(gameRoom);
        }
        finally {
            database.endTransaction();
        }
    }

    public void connected(LobbySession session,String uuid,byte[] key) {

        if (newHost!=null) {
            Map responce = new HashMap();
            responce.put("newHost", newHost );
            send( session,ProtoAccess.COMMAND_KEY,responce );
        }
        else {
            try {
                database.startTransaction();
                // just in case the client does not have one
                //if (uuid!=null) { uuid = UUID.randomUUID().toString(); }

                User client = database.getUUIDGuestName(uuid);
                if (client==null) {
                    client = new User();
                    client.setUid(uuid);
                    client.setType(Player.PLAYER_GUEST);
                    database.saveUser(client);
                }

                Map responce = new HashMap();
                responce.put("key", key );
                responce.put("username", client.getName() );
                responce.put("usertype", client.getType() );

                session.setUser( client.getName() );
                session.setClientUID( uuid );

                send( session,ProtoAccess.COMMAND_KEY,responce );

                if (welcomeMessage!=null) {
                    Map map = new HashMap();
                    map.put("message", welcomeMessage);
                    send(session, ProtoAccess.COMMAND_CHAT_MESSAGE, map);
                }
            }
            finally {
                database.endTransaction();
            }
        }
    }

    public void disconnected(LobbySession session) {
        try {
            if (session.currentGameType!=-1) {
                getPlayers(session.currentGameType).remove(session);
                session.currentGameType=-1;
            }
            // make copy of list to avoid ConcurrentModificationException
            List<ServerGame> games = new ArrayList( session.playingOrWatchingGame );
            for (ServerGame gm: games) {
                try {
                    // can throw game not found exception, if a game has been killed from jmx but user was in it
                    stopPlayOrWatch(session,gm.getId());
                }
                catch (Exception ex) {
                    LobbyServer.logger.log(Level.WARNING, "error stopping play of game "+session+" "+gm.getId(), ex);
                }
            }

            //if (ch.getPlayerType() == Player.PLAYER_GUEST && ch.getGames().isEmpty()) {
                // TODO delete this guest account
            //}
        }
        catch (Exception ex) {
            LobbyServer.logger.log(Level.WARNING, "error in disconnected "+session, ex);
        }
    }

    public void setNick(LobbySession session, String username) {
        try {
            setNick(session.getUsername(), username);
        }
        catch (IllegalArgumentException ex) {
            send( session,ProtoAccess.COMMAND_LOGIN_ERROR, String.valueOf( ex.getMessage() ) );
        }
    }

    @Override
    public void setNick(String oldname, String username) {

        User user;

        try {
            database.startTransaction();

            // maybe this should not "fix" the errors but throw an exception!
            // then again we send the username back to the client, so is prob ok
            username = LobbyCom.checkUsername(username);

            if (oldname.equals(username) || (oldname.toLowerCase().startsWith("guest") && username.length()==0)) {
                // should never happen
                throw new IllegalArgumentException("new name same as old name: "+oldname);
            }
            if (username.toLowerCase().startsWith("guest")) {
                throw new IllegalArgumentException("can not start your nick with guest: "+username );
            }
            if (trimToLetters(username.toLowerCase()).endsWith("bot")) {
                throw new IllegalArgumentException("can not end your nick with bot: "+username );
            }
            if (username.toLowerCase().endsWith("resigned")) {
                throw new IllegalArgumentException("can not end your nick with resigned: "+username );
            }
            if (username.toLowerCase().equals("null")) {
                throw new IllegalArgumentException("you can not call yourself: "+username );
            }
            if (username.indexOf('\n') >= 0 || username.indexOf('\r') >= 0 || username.indexOf('\t') >= 0) {
                throw new IllegalArgumentException("you can not have a that symbol in your name: "+username );
            }
            if (username.length()!=0 && username.length() < 4) {
                throw new IllegalArgumentException("username must be at least 4 chars: "+username);
            }
            if (username.length()!=0 && username.length() > 45) { // 45 is length of field in JPADB
                throw new IllegalArgumentException("username is too long, must be less then 45 chars: "+username);
            }
            if (username.length()!=0 && database.getUser(username)!=null) {
                // error: an account is already registered with this name
                // error: or a guest has joined a game with this name
                // error: a guest is connected with this name
                throw new IllegalArgumentException("this user is already in the database: "+username);
            }

            user = database.getUser(oldname);
            if (user==null) {
                throw new IllegalArgumentException("user with name \""+oldname+"\" not found");
            }
            user.setName(username.length()==0?null:username);
            database.saveUser(user);
        }
        finally {
            database.endTransaction();
        }

        // everything went ok with renaming, now we need to send out 3 commands
        // 1) COMMAND_LOGIN_OK/COMMAND_LOGOUT_OK
        // 2) COMMAND_ADD_OR_UPDATE_GAME and COMMAND_RENAME_PLAYER
        // and we need to make sure it is in this order otherise the client game action buttons may get wrong option

        Map responce = new HashMap();
        responce.put("username", user.getName() ); // username sent server->client not encrypted
        responce.put("userType", user.getType() );

        Collection<LobbySession> sessions = server.getLobbySession(oldname);
        for (LobbySession session:sessions) {
            session.setUser(user.getName());

            // TODO what exactly is the difference between these 2 commands???
            if (username.length()==0) {
                send( session,ProtoAccess.COMMAND_LOGOUT_OK, responce );
            }
            else {
                send( session,ProtoAccess.COMMAND_LOGIN_OK, responce );
            }
        }

        // these are the games that the user is currently in or is watching
        Set<LobbySession> others = new HashSet();
        // this is the games that the user has joined
        for (GameRoom gm: user.getGames() ) {
            ServerGame serverGame = runningGames.get(gm.getId());
            if (serverGame!=null) {
                serverGame.midgameLogin( oldname, user.getName() );
                others.addAll( serverGame.getAllClients() );
            }
            gameChanged(gm);
        }
        for (LobbySession session:sessions) for (ServerGame gm: session.playingOrWatchingGame ) {
            // put together a list of everyone watching any of this users games
            others.addAll( gm.getAllClients() );
        }
        Map send = new HashMap();
        send.put("oldName", oldname );
        send.put("newName", user.getName() );
        send.put("userType", user.getType() );
        for (LobbySession other: others) {
            // we need to send this even to ourselves as we need to be renamed there too
            send(other, ProtoAccess.COMMAND_RENAME_PLAYER, send);
        }
    }

    public static String trimToLetters(String in) {
        int start = 0;
        int end = in.length();
        for (int c=0;c<in.length();c++) {
            if (!Character.isLetter(in.charAt(c))) {
                start = c+1;
            }
            else {
                break;
            }
        }
        if (start != end) {
            for (int c=in.length()-1;c>=0;c--) {
                if (!Character.isLetter(in.charAt(c))) {
                    end = c;
                }
                else {
                    break;
                }
            }
        }
        return in.substring(start, end);
    }

    public void sendGameTypes(LobbySession session) {
        if (newHost!=null) {
            // should never normally get here, unless its a old version of the app
            throw new IllegalStateException("please update your app");
        }

        try {
            database.startTransaction();

            Collection<GameTypeRoom> c = database.getGameTypes();
            List<GameType> gametypes = new ArrayList();
            for (GameTypeRoom gtr:c) {
                gametypes.add( gtr.getGameType() );
            }
            Collections.sort(gametypes);
            send( session,ProtoAccess.COMMAND_ALL_GAMETYPES,gametypes );
        }
        finally {
            database.endTransaction();
        }
    }

    @Override
    public int getNumberRunningGames() {
        return runningGames.size();
    }

    @Override
    public void androidSend(String username,String msg, Integer gameId) {
        LobbyServer.logger.info("AndroidSend to "+doAndroidSend+" "+username+" "+msg+" "+gameId);
        if (doAndroidSend) {
            AndroidGCM.send( this, database, username, msg, gameId);
        }
    }

    @Override
    public void sendChatRoomMessage(int game_id, String message) {
        sendChatMessage(game_id, null, message);
    }
    @Override
    public void sendSystemMessage(String message) {
        sendChatMessage(null, null, message);
    }

    @Override
    public void kickUser(String name) {
        Collection<LobbySession> sessions = server.getLobbySession(name);

        LobbyServer.logger.log(Level.INFO, "going to kick \""+name+"\" = "+sessions);

        for (LobbySession s:sessions) {
            server.kick(s);
        }
    }

    @Override
    public void setSaveOnShutdown(boolean b) {
        saveOnShutdown = b;
    }
    @Override
    public boolean getSaveOnShutdown() {
        return saveOnShutdown;
    }
    @Override
    public void setDoAndroidSend(boolean b) {
        doAndroidSend = b;
    }
    @Override
    public boolean getDoAndroidSend() {
        return doAndroidSend;
    }

    @Override
    public void setWelcomeMessage(String msg) {
        welcomeMessage="".equals(msg)?null:msg;
    }

    @Override
    public String getWelcomeMessage() {
        return welcomeMessage;
    }

    @Override
    public void setNewHost(String host) {
        newHost="".equals(host)?null:host;
    }

    @Override
    public String getNewHost() {
        return newHost;
    }

    @Override
    public Map<String, Integer> getClientVersions() {
        Map<String, Integer> result = new HashMap();
        Collection<LobbySession> towho = server.getLobbySession(null);
        for (LobbySession name: towho ) {
            String version = name.getClientVersion();
            Integer count = result.get(version);
            result.put(version, count==null?1:count+1);
        }
        return result;
    }

    @Override
    public void resignFromAllGames(String username) {
        List<Integer> games = new ArrayList();

        try {
            database.startTransaction();
            User user = database.getUser(username);
            for (GameRoom game:user.getGames()) {
                games.add(game.getId());
            }
        }
        finally {
            database.endTransaction();
        }

        for (int game:games) {
            try {
                leaveGame(username, game);
            }
            catch(Exception ex) {
                LobbyServer.logger.log(Level.WARNING, "resignFromAllGames error leaving game "+game, ex);
            }
        }
    }

    public void sendChatMessage(Integer game_id,String sender,String message) {
        Collection<LobbySession> towho;
        if (game_id==null) {
            towho = server.getLobbySession(null);
        }
        else {
            ServerGame sg = runningGames.get( game_id );
            if (sg==null) {
                throw new IllegalArgumentException("can not send "+sender+":\""+message+"\" can not find server game with id: "+game_id);
            }
            towho = sg.getAllClients();
        }
        Map map = new HashMap();
        if (game_id!=null) {
            map.put("room_id", game_id );
        }
        if (sender!=null) {
            map.put("sender", sender);
        }
        map.put("message", message);
        for (LobbySession name: towho ) {
            send(name, ProtoAccess.COMMAND_CHAT_MESSAGE, map);
        }
    }

    private User getUser(LobbySession session) {
        User user = database.getUUIDGuestName( session.getClientUID() );
        if (user==null) {
            throw new IllegalArgumentException("no user for "+session.getClientUID()+" "+session.getUsername());
        }
        return user;
    }
    private GameRoom getGame(int id) {
        GameRoom gameRoom = database.getGame( id );
        if (gameRoom==null) {
            throw new IllegalArgumentException("can not find game with id: "+id);
        }
        return gameRoom;
    }

    public GameRoom createGame(Game game) {
        if (newHost!=null) {
            // should never normally get here, unless its a old version of the app
            throw new IllegalStateException("please update your app");
        }

        try {
            database.startTransaction();

            GameRoom gameRoom = new GameRoom(game);
            GameTypeRoom gameTypeRoom = database.getGameType( game.getType().getId() );
            gameTypeRoom.createNewGame(gameRoom);
            database.saveGame(gameRoom);
            return gameRoom;
        }
        finally {
            database.endTransaction();
        }
    }

    public void setCurrentGameType(LobbySession session,int game_type_id) {
        try {
            database.startTransaction();

            // remove from current one
            if (session.currentGameType!=-1) {
                getPlayers(session.currentGameType).remove(session);
            }

            getPlayers(game_type_id).add(session);

            session.currentGameType = game_type_id;

            List<GameRoom> games = new ArrayList(database.getGameType(game_type_id).getGameRooms());

            Collections.sort(games);

            for (GameRoom gr: games) {
                sendGame(session, gr.getGame());
            }
        }
        finally {
            database.endTransaction();
        }
    }

    /**
     * give me all the sessions that are watching this gameType
     */
    private Collection<LobbySession> getPlayers(int gameTypeId) {
        // add to new list
        Collection<LobbySession> players = gameTypeRooms.get(gameTypeId);
        if (players==null) {
            // we need to use something Concurrent or we will get ConcurrentModificationException
            gameTypeRooms.putIfAbsent(gameTypeId, new ConcurrentSkipListSet() );
            players = gameTypeRooms.get(gameTypeId);
        }
        return players;
    }

    void gameChanged(GameRoom gr) {
        Collection<LobbySession> players = getPlayers(gr.getGameTypeRoom().getId());
        for (LobbySession session:players) {
            sendGame(session,gr.getGame());
        }
    }

    private void sendGame(LobbySession session, Game game) {
        ServerGame sgame = runningGames.get(game.getId());
        if (sgame!=null) {
            game.setWhosTurn( sgame.getWhosTurn() );
            game.setInGame( sgame.getAllClients().size() );
        }
        send( session , ProtoAccess.COMMAND_ADD_OR_UPDATE_GAME , game );
    }

    @Override
    public void joinGame(String username,int id) {
        GameRoom game = joinGame(username, id, null);
        gameChanged(game);
    }

    public GameRoom joinGame(String username,int id, LobbySession creator) {

        ServerGame serverGame = runningGames.get(id);
        GameRoom game;

        if (serverGame!=null) {
            serverGame.playerJoined( username ); // TODO case may be wrong!!
        }

        try {
            database.startTransaction();
            game = getGame( id );
            User user = database.getUser(username);

            // as this game is already started and serverGame.playerJoined was successfull
            // we need to increase the number of available places in this game
            // to allow this new player to be added to it with game.joinGame
            if (serverGame!=null) {
                game.setMaxPlayers( game.getMaxPlayers()+1 );
            }
            game.joinGame( user );

            database.saveGame(game);
        }
        finally {
            database.endTransaction();
        }

        if (game.getNumOfPlayers() == game.getMaxPlayers() && serverGame==null) {
            serverGame = createServerGame(game, creator);
            Set<User> players = game.getUsers();
            String[] playerNames = new String[players.size()];
            Iterator<User> it = players.iterator();
            for (int c=0;it.hasNext();c++) {
                playerNames[c] = it.next().getName();
            }
            serverGame.startGame(game.getOptions(), playerNames );
        }

        return game;
    }

    @Override
    public void leaveGame(String username,int gameId) {

        ServerGame serverGame = runningGames.get(gameId);
        GameRoom game;
        boolean gameRemoved=false;

        // we want to remove the player from the DB first otherwise things can be happeing in 2 threads at the same time
        // and this will cause potentially incorrect information to be sent to the client
        // e.g. both here and game thread send updated Game to client, but the client gets the game with the removed player
        // after the game without the removed player.
        try {
            database.startTransaction();
            game = getGame( gameId );

            if (game.getNumOfPlayers() == game.getMaxPlayers() && serverGame==null) {
                throw new IllegalStateException("game has not been initialised yet");
            }

            User user = database.getUser(username);
            username = user.getName(); // as the case may have not been exactly correct when this method was called

            game.leaveGame( user );

            if (serverGame==null) {
                gameRemoved = destroyGameCheck(serverGame,gameId);
            }
            else {
                game.setMaxPlayers( game.getMaxPlayers()-1 );
            }

            if (!gameRemoved) {
                database.saveGame(game);
            }
        }
        finally {
            database.endTransaction();
        }

        if (!gameRemoved) {
            // now that we have finished everything DB related, now call resign on the live object
            if (serverGame!=null) {
                serverGame.playerResigned( username ); // this may call gameFinished -> destroyGameCheck
            }
            gameChanged(game);
        }
    }

    public void playOrWatch(LobbySession session,int game_id) {
        try {
            database.startTransaction();

            ServerGame serverGame = runningGames.get( game_id );

            if (serverGame==null) {
                throw new IllegalArgumentException("can not find running game for id "+game_id);
            }
            if (serverGame.getAllClients().contains(session)) {
                throw new IllegalStateException("session is already playing/watching this game "+game_id+" "+session);
            }
            if (!server.isConnected(session)) {
                throw new IllegalStateException("session is not connected");
            }

            // this will now send the game object to the client
            serverGame.clientEntered( session );
            session.playingOrWatchingGame.add(serverGame);

            // while we were having all this fun, we may have lost the client connection
            if (!server.isConnected(session)) {
                serverGame.clientLeaves( session );
                session.playingOrWatchingGame.remove(serverGame);
                throw new IllegalStateException("session is not connected, and we have had to remove it from the game");
            }

            Set<Player> users = getPlayersAndSpectators( serverGame.getAllClients() );
            for (Player player:users) {
                Map message = new HashMap();
                message.put("room_id", serverGame.getId() );
                message.put("player", player );
                send( session ,ProtoAccess.COMMAND_PLAYER_ADDED, message );
            }

            // TODO what if the person joins is already there with another session?

            // tell everyone else we have joined, except us, as we already know
            Map message = new HashMap();
            message.put("room_id", serverGame.getId() );
            message.put("player", getUser(session) );
            for (LobbySession user: serverGame.getAllClients() ) {
                if (user != session) {
                    send( user,ProtoAccess.COMMAND_PLAYER_ADDED, message );
                }
            }
        }
        finally {
            database.endTransaction();
        }
    }

    public void stopPlayOrWatch(LobbySession session,int game_id) {
        try {
            database.startTransaction();

            ServerGame serverGame = runningGames.get( game_id );

            if (serverGame==null) {
                throw new IllegalArgumentException("can not find running game for id "+game_id);
            }
            if (!serverGame.getAllClients().contains(session)) {
                throw new IllegalStateException("session is not playing/watching this game "+game_id+" "+session);
            }

            // this will now send the game object to the client
            serverGame.clientLeaves( session );
            session.playingOrWatchingGame.remove(serverGame);

            // TODO in the game, get the current players and remove all of them from our current session

            // tell everyone else we have left
            Map message = new HashMap();
            message.put("room_id", serverGame.getId() );
            message.put("playerName", session.getUsername() );
            for (LobbySession user: serverGame.getAllClients() ) {
                // TODO do not send to us
                send( user,ProtoAccess.COMMAND_PLAYER_REMOVE, message );
            }

            destroyGameCheck(serverGame,serverGame.getId());
        }
        finally {
            database.endTransaction();
        }
    }

    public void messageForGame(int gameId,String username, Object message) {
        ServerGame serverGame = runningGames.get( gameId );
        serverGame.messageFromUser(username, message );
    }

    public Set<Player> getPlayersAndSpectators(Collection<LobbySession> sessions) {
        Set<Player> set = new HashSet();
        for (LobbySession session:sessions) {
            set.add( getUser(session) );
        }
        return set;
    }

    private boolean destroyGameCheck(ServerGame serverGame,int id) {
        // checks the game is not just been created
        GameRoom game = getGame(id);
        if ((serverGame==null || serverGame.getAllClients().isEmpty()) && ((serverGame!=null && serverGame.isFinished()) || game.getUsers().isEmpty())) {
                removeGame(game);
                return true;
        }
        return false;
    }

    private void removeGame(GameRoom gameRoom) {
        int id = gameRoom.getId();

        ServerGame serverGame = runningGames.get(id);
        if (serverGame!=null) {

            // TODO what if someone is watching this game, what do?

            serverGame.destroyServerGame();
            runningGames.remove( id );
        }

        int gameTypeId = gameRoom.getGameTypeRoom().getId();
        gameRoom.getGameTypeRoom().getGameRooms().remove(gameRoom);
        database.removeGame(gameRoom);

        Map map = new HashMap();
        map.put("game_id", id );

        Collection<LobbySession> players = getPlayers(gameTypeId);
        for (LobbySession session:players) {
            send( session , ProtoAccess.COMMAND_REMOVE_GAME , map );
        }
    }

    @Override
    public void removeGame(int gameId) {
        try {
            database.startTransaction();
            removeGame( getGame(gameId) );
        }
        finally {
            database.endTransaction();
        }
    }

    @Override
    public boolean isFinished(int gameId) {
        ServerGame serverGame = runningGames.get( gameId );
        if (serverGame==null) {
            throw new IllegalArgumentException("can not find running game for id "+gameId);
        }
        return serverGame.isFinished();
    }

    @Override
    public int countFinishedGames() {
        int count = 0;
        for (ServerGame game:runningGames.values()) {
            if (game.isFinished()) {
                count++;
            }
        }
        return count;
    }

    private ServerGame createServerGame(final GameRoom game, final LobbySession creator) {
          try {
                GameType gameType = classLoaders.get( game.getGameTypeRoom().getId() );
                ClassLoader loader = gameType.getClassLoader( new File(".").toURI().toASCIIString() );
                ServerGame serverGame = (ServerGame) Class.forName( gameType.getClassName(), true, loader ).newInstance();
                serverGame.setId(game.getId());
                serverGame.setTimeout(game.getTimeout());
                runningGames.put(game.getId(), serverGame);

                final String gameName = game.getName();
                final ServerGame sg = serverGame;
                serverGame.addServerGameListener(new ServerGameListener() {
                    @Override
                    public void messageFromGame(Object message, Collection<LobbySession> towho) {
                        Map map = new HashMap();
                        map.put("game_id", sg.getId() );
                        map.put("message", message);
                        for (LobbySession who:towho) {
                            send(who, ProtoAccess.COMMAND_GAME_MESSAGE, map);
                        }
                    }

                    @Override
                    public void gameStarted() {
                        if (creator != null) {
                            Map map = new HashMap();
                            map.put("game_id", sg.getId() );
                            send(creator, ProtoAccess.COMMAND_GAME_STARTED, map);
                        }
                    }
                   
                    @Override
                    public void gameFinished() {
                        try {
                            database.startTransaction();

                            destroyGameCheck(sg,sg.getId());
                        }
                        finally {
                            database.endTransaction();
                        }
                    }
                    @Override
                    public void sendChatroomMessage(String message) {
                        sendChatRoomMessage(sg.getId(),message);
                    }
                    @Override
                    public void needInputFrom(String username) {

                        boolean doAndroidSend=false;
                        try {
                            database.startTransaction();

                            LobbySession found=null;
                            Game game = getGame( sg.getId() ).getGame();
                            Collection<LobbySession> sessions = sg.getAllClients();
                            for (LobbySession session:sessions) {
                                sendGame(session, game);
                                if (session.getUsername().equals(username)) {
                                    found = session;
                                }
                            }

                            if (username!=null && found==null) {
                                Collection<LobbySession> session = server.getLobbySession(username);
                                if (!session.isEmpty()) {
                                    for (LobbySession s:session) {
                                        sendGame(s, game);
                                    }
                                }
                                else {
                                    // if they are not even connected
                                    doAndroidSend = true;
                                }
                            }
                        }
                        finally {
                            database.endTransaction();
                        }

                        if (doAndroidSend) {
                            androidSend(username, "It is your go: "+gameName, sg.getId());
                        }
                    }
                    @Override
                    public void resignPlayer(String username) {
                        leaveGame(username, sg.getId());
                    }
                    @Override
                    public void logGameMove(String user, String move) { }
                });
                return serverGame;
            }
            catch (Exception ex) {
                throw new RuntimeException(ex);
            }
    }

    public void registerGCM(LobbySession session,String key) {
        try {
            database.startTransaction();

            User user = getUser(session);
            user.setAndroidId( key );
            database.saveUser(user);
            send(session, ProtoLobby.COMMAND_ANDROID_REGISTER_DONE, null);
        }
        finally {
            database.endTransaction();
        }
    }

    public void unregisterGCM(LobbySession session,String key) {
        try {
            database.startTransaction();

            User user = getUser(session);
            if (LobbyServer.equals(user.getAndroidId(), key)) {
                user.setAndroidId(null);
                database.saveUser(user);
                send(session, ProtoLobby.COMMAND_ANDROID_UNREGISTER_DONE, null);
            }
            else {
                throw new IllegalStateException("android "+user.getAndroidId()+" "+key);
            }
        }
        finally {
            database.endTransaction();
        }
    }

    public GameType getGameType(int gameTypeId) {
        return classLoaders.get( gameTypeId );
    }

    public void send(LobbySession session, String command, Object param) {
        Message message = new Message();
        message.setCommand(command);
        message.setParam(param);
        LobbyServer.logger.info("Sending "+session+" "+message);
        server.send(session, message);
    }

    @Override
    public void removeCurrentPlayerNotInGameGames() {
        Collection<Integer> badGames = new ArrayList();
        try {
            database.startTransaction();
            Collection<GameRoom> games = database.getGameRooms();
            for (GameRoom game:games) {
                ServerGame serverGame = runningGames.get(game.getId());
                if (serverGame!=null) {
                    String whosTurn = serverGame.getWhosTurn();
                    if (whosTurn != null && !"".equals(whosTurn) && !game.hasUser(whosTurn)) {
                        LobbyServer.logger.log(Level.WARNING, "current player not in game \""+whosTurn+"\" players: "+game.getUsers());
                        badGames.add(serverGame.getId());
                    }
                }
            }
        }
        finally {
            database.endTransaction();
        }

        for (int id : badGames) {
            removeGame(id);
        }
    }
   
    @Override
    public void removeRunningGameNotFoundGames() {
        Collection<Integer> badGames = new ArrayList();
        try {
            database.startTransaction();
            Collection<GameRoom> games = database.getGameRooms();
            for (GameRoom game:games) {
                if (game.getNumOfPlayers() == game.getMaxPlayers() && runningGames.get(game.getId()) == null) {
                    LobbyServer.logger.log(Level.WARNING, "no running game found for game: "+game);
                    badGames.add( game.getId() );
                }
            }
        }
        finally {
            database.endTransaction();
        }
       
        for (int id : badGames) {
            removeGame(id);
        }
    }
}
TOP

Related Classes of net.yura.lobby.server.GameLobby

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.