Package net.yura.lobby.server

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

/*   
    (C) 2007-2013 yura.net
   This file is part of Lobby.

    Lobby is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License

    Lobby is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package net.yura.lobby.server;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import net.yura.util.PausableScheduledThreadPoolExecutor;

/**
* @author Yura
*/
public abstract class TurnBasedGame implements ServerGame {

        public static final int MAX_SKIPS = 5;
   
        public static final PausableScheduledThreadPoolExecutor scheduler = new PausableScheduledThreadPoolExecutor(1);
        static {
            // JAVA 7 ONLY
            scheduler.setRemoveOnCancelPolicy(true);
        }
   
        private Future future;
       
      private ServerGameListener listoner;

  private Collection<LobbySession> spectators = new ConcurrentSkipListSet();

        protected boolean finished,startGameCalled;
  String whoiwantinputfrom;
        int timeout;
        Map<String,Integer> skips = new HashMap();
       
  // ################################### abstract methods ####################################

  // life cycle of a game
  @Override public abstract void startGame(String startGameOptions, String[] players);
        @Override public abstract void loadGame(byte[] gameData);
  public abstract void destroyGame();

        public abstract void playerJoins(String username);
  public abstract void playerResigns(String username);
  public abstract void renamePlayer(String oldser,String newuser);

  //comunication
  public abstract void clientHasJoined(String username);
  public abstract void stringFromPlayer(String username, String message);
       
  // called by this if the player has left or they have timmed out
  public abstract void doBasicGo(String username);
       
  // ################################### methods callable by the game ####################################

  // called by the game when it comes to an end
  // set things up for another game
  public final void gameFinished(String winner) {
                killFuture();
                whoiwantinputfrom = null;
                finished=true;
    sendStringToAllClient("LOBBY_GAMEOVER"); // stops the timeout clock on the client, IF it has been implemented

    listoner.sendChatroomMessage("Game over! "+winner+" has won!");
    listoner.gameFinished();
  }

  public final void getInputFromClient(String username) {
                String oldPlayer = whoiwantinputfrom;
                whoiwantinputfrom = username;
                if (!LobbyServer.equals(oldPlayer, whoiwantinputfrom)) {

                    // the first time we need input from a user we know the game has finished setup
                    // is now started/ready to be opened
                    if (whoiwantinputfrom!=null && !startGameCalled) {
                        listoner.gameStarted();
                        startGameCalled = true;
                    }

                    // if whoiwantinputfrom is null we send it to tell the client
                    // that we do not need input from any human player
                    listoner.needInputFrom(whoiwantinputfrom);
                }
                killFuture();
                if (whoiwantinputfrom!=null) {
                    future = scheduler.schedule(new Runnable() {
                        @Override
                        public void run() {
                            final String username = whoiwantinputfrom;
                            try {
                                if (username==null) {
                                    throw new IllegalStateException("username is null");
                                }
                               
                                // if 100 turns have been skipped, this game is dead
                                int skip = skips.get(username)==null?1:skips.get(username)+1;
                                skips.put(username, skip);

                                if (skip >= MAX_SKIPS) {
                                    listoner.sendChatroomMessage(username+" has timed out "+MAX_SKIPS+" times and has been resigned from the game.");
                                    listoner.resignPlayer(username);
                                }
                                else {
                                    listoner.sendChatroomMessage(username+" has timed out on their turn");
                                    doBasicGo(username);
                                }
                            }
                            catch(Throwable th) {
                                LobbyServer.logger.log(Level.WARNING, "error in timeout for game: "+id+" for user "+username, th);
                            }
                        }
                    }, timeout + 10, TimeUnit.SECONDS); // add 10 seconds for bad ping
                }
  }

        private Collection<LobbySession> usernameToLobbySessionArray(String username) {
            List<LobbySession> sessions = new ArrayList();
            for (LobbySession session:spectators) {
                if (session.getUsername().equals( username )) {
                    sessions.add(session);
                }
            }
            return sessions;
        }
       
  public final void sendStringToClient(String a, String username) {
            listoner.messageFromGame(a, usernameToLobbySessionArray(username) );
  }

  public final void sendStringToAllClient(String a) {
            listoner.messageFromGame(a, spectators );
  }

  public final void sendObjectToClient(Serializable a,String username) {
            listoner.messageFromGame( serializableToByteArray(a) , usernameToLobbySessionArray(username) );
  }

  public final void sendObjectToAllClient(Serializable a) {
            listoner.messageFromGame( serializableToByteArray(a), spectators );
  }

        private static byte[] serializableToByteArray(Serializable a) {
            try {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                ObjectOutputStream oout = new ObjectOutputStream(out);
                oout.writeObject(a);
                oout.flush();
                return out.toByteArray();
            }
            catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        }
       
  // ################################### implementation of ServerGame ####################################

        int id;
        @Override
        public void setId(int id) {
            this.id=id;
        }

        @Override
        public int getId() {
            return id;
        }

        @Override
        public void setTimeout(int timeout) {
            this.timeout = timeout;
           
            if (this.timeout <= 0) {
                this.timeout = 60; // a turn based game MUST have a timeout to give players a time to take there turn
            }
        }

        @Override
  public final void addServerGameListener(ServerGameListener l) {
            listoner = l;
  }

        @Override
  public final void removeServerGameListener(ServerGameListener l) {
            if( listoner.equals(l)) {
                    listoner = null;
            }
  }

        @Override
        public Collection<LobbySession> getAllClients() {
            return spectators;
        }
       
        @Override
  public final void clientEntered(final LobbySession session) {

                // we tell everyone currently in the game this person is joining
                listoner.sendChatroomMessage(session.getUsername()+" has entered the game");
               
                // we add this new player as a spectator to make sure we do not miss any messages
                // from the game that can happen between clientHasJoined() and spectators.add()
                spectators.add(session);

    // this is here so if the clientHasJoined sends anything to the client
    // the game object is already created on the client and ready
//    new Thread() {
//
//      public void run() {
//
//        try { Thread.sleep(1000); }
//        catch(InterruptedException e){}
//
                                // TODO we send game object to every session with this username
                                // would be better if just sent it to the session we needed to
                                clientHasJoined( session.getUsername() );
//
//      }
//
//    }.start();

  }


  // game will finish if someone leaves
        @Override
  public final void clientLeaves(LobbySession username) {
            spectators.remove(username);
            listoner.sendChatroomMessage(username.getUsername()+" has left the game");
  }

        @Override
        public void playerResigned(String player) {
            // TODO what if player clicks resign twice, there is not check if we are already resigned here
            if (player.equals(whoiwantinputfrom)) {
                killFuture();
                whoiwantinputfrom = null;
            }
            listoner.sendChatroomMessage( player+" has resigned from the game");
            // even though a error may happen in the resign, that would lead to us not being resigned
            // we still have to call the sendChatroomMessage method first, as it will make more sense
            // in the log (player X resigned, game over) and the game may get deleted by the resign
            // command, and calling sendChatroomMessage on a deleted game will throw a nullpointer
            playerResigns( player ); // this may lead to the game being deleted (gameFinished called)
        }

        @Override
        public void playerJoined(String player) {
            playerJoins( player );
            listoner.sendChatroomMessage( player+" has joined the game");
        }

        @Override
  public final void messageFromUser(String username, Object object) {
            skips.put(username, 0); // if someone does something reset skips counter

            if (username.equals(whoiwantinputfrom)) {
                killFuture();
            }
            stringFromPlayer(username, (String)object );
        }

        // TODO should be synchronized with something?
        @Override
  public final void midgameLogin(String oldser,String newuser) {
            renamePlayer( oldser , newuser );

            Integer skip = skips.remove(oldser);
            if (skip!=null) {
                skips.put(newuser, skip);
            }
            if (oldser.equals(whoiwantinputfrom)) {
                whoiwantinputfrom = newuser;
            }

            listoner.sendChatroomMessage(oldser+" has logged in as "+newuser);
  }

        @Override
        public boolean isFinished() {
            return finished;
        }

        @Override
        public String getWhosTurn() {
            return whoiwantinputfrom;
        }
       
        @Override
        public void destroyServerGame() {
            killFuture();
            destroyGame();
        }
       
        void killFuture() {
            Future f = future;
            if (f!=null && !f.isDone() && !f.isCancelled()) {
                f.cancel(true);
            }
        }
}
TOP

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

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.