Search in sources :

Example 31 with Connection

use of soc.server.genericServer.Connection in project JSettlers2 by jdmonin.

the class SOCGameHandler method forceEndGameTurn.

/**
 * Try to force-end the current player's turn in this game.
 * Alter game state and send messages to players.
 * Will call {@link #endGameTurn(SOCGame, SOCPlayer, boolean)} if appropriate.
 * Will send gameState and current player (turn) to clients.
 *<P>
 * If the current player has lost connection, send the {@link SOCLeaveGame LEAVEGAME}
 * message out <b>before</b> calling this method.
 *<P>
 * Assumes, as {@link #endGameTurn(SOCGame, SOCPlayer, boolean)} does:
 * <UL>
 * <LI> ga.canEndTurn already called, returned false
 * <LI> ga.takeMonitor already called (not the same as {@link SOCGameList#takeMonitorForGame(String)})
 * <LI> gamelist.takeMonitorForGame is NOT called, we do NOT have that monitor
 * </UL>
 * @param ga Game to force end turn
 * @param plName Current player's name. Needed because if they have been disconnected by
 *               {@link #leaveGame(SOCGame, Connection)},
 *               their name within game object is already null.
 * @return true if the turn was ended and game is still active;
 *          false if we find that all players have left and
 *          the gamestate has been changed here to {@link SOCGame#OVER}.
 *
 * @see #endGameTurnOrForce(SOCGame, int, String, Connection, boolean)
 * @see SOCGame#forceEndTurn()
 */
private final boolean forceEndGameTurn(SOCGame ga, final String plName) {
    final String gaName = ga.getName();
    final int cpn = ga.getCurrentPlayerNumber();
    final int endFromGameState = ga.getGameState();
    SOCPlayer cp = ga.getPlayer(cpn);
    if (cp.hasAskedSpecialBuild()) {
        cp.setAskedSpecialBuild(false);
        srv.messageToGame(gaName, new SOCPlayerElement(gaName, cpn, SOCPlayerElement.SET, SOCPlayerElement.ASK_SPECIAL_BUILD, 0));
    }
    final SOCForceEndTurnResult res = ga.forceEndTurn();
    // also could be initial placement (START1A or START2A or START3A).
    if (SOCGame.OVER == ga.getGameState())
        // <--- Early return: All players have left ---
        return false;
    /**
     * Report any resources lost or gained.
     * See also forceGamePlayerDiscardOrGain for same reporting code.
     */
    SOCResourceSet resGainLoss = res.getResourcesGainedLost();
    if (resGainLoss != null) {
        /**
         * If gold hex or returning resources to player (not discarding), report actual types/amounts.
         * For discard, tell the discarding player's client that they discarded the resources,
         * tell everyone else that the player discarded unknown resources.
         */
        if (!res.isLoss()) {
            if ((endFromGameState == SOCGame.WAITING_FOR_PICK_GOLD_RESOURCE) || (endFromGameState == SOCGame.STARTS_WAITING_FOR_PICK_GOLD_RESOURCE)) {
                // Send SOCPlayerElement messages, "gains" text
                reportRsrcGainGold(ga, cp, cpn, resGainLoss, true, false);
            } else {
                // Send SOCPlayerElement messages
                reportRsrcGainLoss(gaName, resGainLoss, false, false, cpn, -1, null, null);
            }
        } else {
            Connection c = srv.getConnection(plName);
            if ((c != null) && c.isConnected())
                reportRsrcGainLoss(gaName, resGainLoss, true, true, cpn, -1, null, c);
            int totalRes = resGainLoss.getTotal();
            srv.messageToGameExcept(gaName, c, new SOCPlayerElement(gaName, cpn, SOCPlayerElement.LOSE, SOCPlayerElement.UNKNOWN, totalRes, true), true);
            // "{0} discarded {1} resources."
            srv.messageToGameKeyed(ga, true, "action.discarded", plName, totalRes);
        }
    }
    /**
     * report any dev-card or item returned to player's hand
     */
    final SOCInventoryItem itemCard = res.getReturnedInvItem();
    SOCInventoryItemAction retItemActionMsg = null;
    if (itemCard != null) {
        Connection c = srv.getConnection(plName);
        if ((c != null) && c.isConnected()) {
            if (itemCard instanceof SOCDevCard) {
                int card = itemCard.itype;
                if ((card == SOCDevCardConstants.KNIGHT) && (c.getVersion() < SOCDevCardConstants.VERSION_FOR_NEW_TYPES))
                    card = SOCDevCardConstants.KNIGHT_FOR_VERS_1_X;
                srv.messageToPlayer(c, new SOCDevCardAction(gaName, cpn, SOCDevCardAction.ADD_OLD, card));
            } else {
                retItemActionMsg = new SOCInventoryItemAction(gaName, cpn, (itemCard.isPlayable() ? SOCInventoryItemAction.ADD_PLAYABLE : SOCInventoryItemAction.ADD_OTHER), itemCard.itype, itemCard.isKept(), itemCard.isVPItem(), itemCard.canCancelPlay);
                srv.messageToPlayer(c, retItemActionMsg);
            }
        }
        // Announce item to game with same retItemActionMsg sent to player?
        boolean announceAsInvItemAction = false;
        // Announce this item to game as an unknown dev card type?
        boolean announceAsUnknown = true;
        if (!(itemCard instanceof SOCDevCard)) {
            // SOCInventoryItem: Add any new kinds here, to announce to all players.
            // If it needs a special message, do so and set announceAsUnknown = false
            // If it's private and doesn't need a special message, set handled = true and let it announce as unknown
            boolean handled = false;
            if (ga.isGameOptionSet(SOCGameOption.K_SC_FTRI)) {
                // endFromGameState is PLACING_INV_ITEM.
                // "Gift port" item details are public, send return message to whole game:
                handled = true;
                announceAsInvItemAction = true;
                announceAsUnknown = false;
            }
            // Fallback:
            if (!handled)
                System.err.println("forceEndGameTurn: Unhandled inventory item type " + itemCard.itype + " class " + itemCard.getClass());
        }
        if (announceAsInvItemAction) {
            srv.messageToGameExcept(gaName, c, retItemActionMsg, true);
        } else if (announceAsUnknown) {
            if (ga.clientVersionLowest >= SOCDevCardConstants.VERSION_FOR_NEW_TYPES) {
                srv.messageToGameExcept(gaName, c, new SOCDevCardAction(gaName, cpn, SOCDevCardAction.ADD_OLD, SOCDevCardConstants.UNKNOWN), true);
            } else {
                srv.messageToGameForVersionsExcept(ga, -1, SOCDevCardConstants.VERSION_FOR_NEW_TYPES - 1, c, new SOCDevCardAction(gaName, cpn, SOCDevCardAction.ADD_OLD, SOCDevCardConstants.UNKNOWN_FOR_VERS_1_X), true);
                srv.messageToGameForVersionsExcept(ga, SOCDevCardConstants.VERSION_FOR_NEW_TYPES, Integer.MAX_VALUE, c, new SOCDevCardAction(gaName, cpn, SOCDevCardAction.ADD_OLD, SOCDevCardConstants.UNKNOWN), true);
            }
            srv.messageToGameKeyed(ga, true, "forceend.devcard.returned", plName);
        // "{0}''s just-played development card was returned."
        }
    }
    /**
     * For initial placements, we don't end turns as normal.
     * (Player number may go forward or backwards, new state isn't ROLL_OR_CARD, etc.)
     * Update clients' gamestate, but don't call endGameTurn.
     */
    final int forceRes = res.getResult();
    if ((forceRes == SOCForceEndTurnResult.FORCE_ENDTURN_SKIP_START_ADV) || (forceRes == SOCForceEndTurnResult.FORCE_ENDTURN_SKIP_START_ADVBACK)) {
        if (res.didUpdateFP() || res.didUpdateLP()) {
            final int fpn = ga.getFirstPlayer();
            final SOCMessage msg = (ga.clientVersionLowest >= SOCGameElements.MIN_VERSION) ? new SOCGameElements(gaName, SOCGameElements.FIRST_PLAYER, fpn) : new SOCFirstPlayer(gaName, fpn);
            // will cause clients to recalculate lastPlayer too
            srv.messageToGame(gaName, msg);
        }
        sendTurn(ga, false);
        // <--- Early return ---
        return true;
    }
    /**
     * If the turn can now end, proceed as if player requested it.
     * Otherwise, send current gamestate.  We'll all wait for other
     * players to send discard messages, and afterwards this turn can end.
     */
    if (ga.canEndTurn(cpn))
        // could force gamestate to OVER, if a client leaves
        endGameTurn(ga, null, true);
    else
        sendGameState(ga, false, false);
    return (ga.getGameState() != SOCGame.OVER);
}
Also used : SOCMessage(soc.message.SOCMessage) SOCInventoryItemAction(soc.message.SOCInventoryItemAction) Connection(soc.server.genericServer.Connection) SOCPlayerElement(soc.message.SOCPlayerElement) SOCGameElements(soc.message.SOCGameElements) SOCFirstPlayer(soc.message.SOCFirstPlayer) SOCDevCardAction(soc.message.SOCDevCardAction)

Example 32 with Connection

use of soc.server.genericServer.Connection in project JSettlers2 by jdmonin.

the class SOCGameHandler method leaveGame.

// javadoc inherited from GameHandler. Return true if game is empty and should be ended.
public boolean leaveGame(SOCGame ga, Connection c) {
    final String gm = ga.getName();
    // Retain name, since will become null within game obj.
    final String plName = c.getData();
    boolean gameHasHumanPlayer = false;
    boolean gameHasObserver = false;
    @SuppressWarnings("unused") boolean // TODO checks/messages; added in v1.1.01, TODO not used yet
    gameVotingActiveDuringStart = false;
    final int gameState = ga.getGameState();
    boolean isPlayer = false;
    // removing this player number
    int playerNumber;
    for (playerNumber = 0; playerNumber < ga.maxPlayers; playerNumber++) {
        SOCPlayer player = ga.getPlayer(playerNumber);
        if ((player != null) && (player.getName() != null) && (player.getName().equals(plName))) {
            isPlayer = true;
            /**
             * About to remove this player from the game. Before doing so:
             * If a board-reset vote is in progress, they cannot vote
             * once they have left. So to keep the game moving,
             * fabricate their response: vote No.
             */
            if (ga.getResetVoteActive()) {
                if (gameState <= SOCGame.STARTS_WAITING_FOR_PICK_GOLD_RESOURCE)
                    gameVotingActiveDuringStart = true;
                if (ga.getResetPlayerVote(playerNumber) == SOCGame.VOTE_NONE) {
                    srv.gameList.releaseMonitorForGame(gm);
                    ga.takeMonitor();
                    srv.resetBoardVoteNotifyOne(ga, playerNumber, plName, false);
                    ga.releaseMonitor();
                    srv.gameList.takeMonitorForGame(gm);
                }
            }
            /**
             * Remove the player.
             */
            // player obj name becomes null
            ga.removePlayer(plName);
            // broadcastGameStats(cg);
            break;
        }
    }
    SOCLeaveGame leaveMessage = new SOCLeaveGame(plName, "-", gm);
    srv.messageToGameWithMon(gm, leaveMessage);
    srv.recordGameEvent(gm, leaveMessage);
    if (D.ebugOn)
        D.ebugPrintln("*** " + plName + " left the game " + gm + " at " + DateFormat.getTimeInstance(DateFormat.SHORT).format(new Date()));
    // "{0} left the game"
    srv.messageToGameKeyed(ga, false, "member.left.game", plName);
    /**
     * check if there is at least one person playing the game
     */
    for (int pn = 0; pn < ga.maxPlayers; pn++) {
        SOCPlayer player = ga.getPlayer(pn);
        if ((player != null) && (player.getName() != null) && (!ga.isSeatVacant(pn)) && (!player.isRobot())) {
            gameHasHumanPlayer = true;
            break;
        }
    }
    /**
     * if no human players, check if there is at least one person watching the game (observing).
     * Even with observers, end it unless ga.isBotsOnly or PROP_JSETTLERS_BOTS_BOTGAMES_TOTAL != 0.
     */
    if ((!gameHasHumanPlayer) && !srv.gameList.isGameEmpty(gm)) {
        Enumeration<Connection> membersEnum = srv.gameList.getMembers(gm).elements();
        while (membersEnum.hasMoreElements()) {
            Connection member = membersEnum.nextElement();
            // D.ebugPrintln("*** "+member.data+" is a member of "+gm);
            boolean nameMatch = false;
            for (int pn = 0; pn < ga.maxPlayers; pn++) {
                SOCPlayer player = ga.getPlayer(pn);
                if ((player != null) && (player.getName() != null) && (player.getName().equals(member.getData()))) {
                    nameMatch = true;
                    break;
                }
            }
            if (!nameMatch) {
                gameHasObserver = true;
                break;
            }
        }
        if (gameHasObserver && !ga.isBotsOnly) {
            if (0 == srv.getConfigIntProperty(SOCServer.PROP_JSETTLERS_BOTS_BOTGAMES_TOTAL, 0))
                gameHasObserver = false;
        }
    }
    /**
     * if the leaving member was playing the game, and
     * the game isn't over, then decide:
     * - Do we need to force-end the current turn?
     * - Do we need to cancel their initial settlement placement?
     * - Should we replace the leaving player with a robot?
     */
    if (isPlayer && (gameHasHumanPlayer || gameHasObserver) && ((ga.getPlayer(playerNumber).getPublicVP() > 0) || (gameState == SOCGame.START1A) || (gameState == SOCGame.START1B)) && (gameState < SOCGame.OVER) && !(gameState < SOCGame.START1A)) {
        boolean foundNoRobots;
        if (ga.getPlayer(playerNumber).isRobot()) {
            /**
             * don't replace bot with bot; force end-turn instead.
             */
            foundNoRobots = true;
        } else {
            /**
             * get a robot to replace this human player;
             * just in case, check game-version vs robots-version,
             * like at new-game (readyGameAskRobotsJoin).
             */
            foundNoRobots = !findRobotAskJoinGame(ga, Integer.valueOf(playerNumber), true);
        }
        /**
         * What to do if no robot was found to fill their spot?
         * Must keep the game going, might need to force-end current turn.
         */
        if (foundNoRobots) {
            final boolean stillActive = endGameTurnOrForce(ga, playerNumber, plName, c, true);
            if (!stillActive) {
                // force game destruction below
                gameHasHumanPlayer = false;
                gameHasObserver = false;
            }
        }
    } else {
        // observer leaving: If game is bot-only, don't end the game despite no human players/observers
        if (ga.isBotsOnly && (ga.getGameState() < SOCGame.OVER))
            gameHasObserver = true;
    }
    return !(gameHasHumanPlayer || gameHasObserver);
}
Also used : SOCLeaveGame(soc.message.SOCLeaveGame) Connection(soc.server.genericServer.Connection) Date(java.util.Date)

Example 33 with Connection

use of soc.server.genericServer.Connection in project JSettlers2 by jdmonin.

the class SOCGameHandler method sendGameStateOVER.

/**
 *  If game is OVER, send messages reporting winner, final score,
 *  and each player's victory-point cards.
 *  Also give stats on game length, and on each player's connect time.
 *  If player has finished more than 1 game since connecting, send their win-loss count.
 *<P>
 *  Increments server stats' numberOfGamesFinished.
 *  If db is active, calls {@link SOCServer#storeGameScores(SOCGame)} to save game stats.
 *<P>
 *  If {@link SOCGame#isBotsOnly}, calls {@link SOCServer#destroyGameAndBroadcast(String, String)} to make room
 *  for more games to run: The bots don't know on their own to leave, it's easier for the
 *  server to dismiss them within {@code destroyGame}.
 *<P>
 *  Make sure {@link SOCGameState}({@link SOCGame#OVER OVER}) is sent before calling this method.
 *
 * @param ga This game is over; state should be OVER
 * @since 1.1.00
 */
private void sendGameStateOVER(SOCGame ga) {
    final String gname = ga.getName();
    /**
     * Find and announce the winner
     * (the real "found winner" code is in SOCGame.checkForWinner;
     *  that was already called before sendGameStateOVER.)
     */
    SOCPlayer winPl = ga.getPlayer(ga.getCurrentPlayerNumber());
    if ((winPl.getTotalVP() < ga.vp_winner) && !ga.hasScenarioWinCondition) {
        // This is fallback code.
        for (int i = 0; i < ga.maxPlayers; i++) {
            if (winPl.getTotalVP() >= ga.vp_winner) {
                winPl = ga.getPlayer(i);
                break;
            }
        }
    }
    srv.messageToGameKeyed(ga, true, "stats.game.winner.withpoints", winPl.getName(), winPl.getTotalVP());
    // "{0} has won the game with {1,number} points."
    // / send a message with the revealed final scores
    {
        int[] scores = new int[ga.maxPlayers];
        boolean[] isRobot = new boolean[ga.maxPlayers];
        for (int i = 0; i < ga.maxPlayers; ++i) {
            scores[i] = ga.getPlayer(i).getTotalVP();
            isRobot[i] = ga.getPlayer(i).isRobot();
        }
        srv.messageToGame(gname, new SOCGameStats(gname, scores, isRobot));
    }
    // /
    for (int i = 0; i < ga.maxPlayers; i++) {
        SOCPlayer pl = ga.getPlayer(i);
        List<SOCInventoryItem> vpCards = pl.getInventory().getByState(SOCInventory.KEPT);
        if (!vpCards.isEmpty())
            srv.messageToGameKeyedSpecial(ga, true, "endgame.player.has.vpcards", pl.getName(), vpCards);
    // "Joe has a Gov.House (+1VP) and a Market (+1VP)" ["{0} has {1,dcards}."]
    }
    // for each player
    /**
     * send game-length and connect-length messages, possibly win-loss count.
     */
    {
        Date now = new Date();
        Date gstart = ga.getStartTime();
        if (gstart != null) {
            final int gameRounds = ga.getRoundCount();
            long gameSeconds = ((now.getTime() - gstart.getTime()) + 500L) / 1000L;
            final long gameMinutes = gameSeconds / 60L;
            gameSeconds = gameSeconds % 60L;
            if (gameSeconds == 0)
                srv.messageToGameKeyed(ga, true, "stats.game.was.roundsminutes", gameRounds, gameMinutes);
            else
                // "This game was # rounds, and took # minutes."
                srv.messageToGameKeyed(ga, true, "stats.game.was.roundsminutessec", gameRounds, gameMinutes, gameSeconds);
        // "This game was # rounds, and took # minutes # seconds." [or 1 second.]
        // Ignore possible "1 minutes"; that game is too short to worry about.
        }
        /**
         * Update each player's win-loss count for this session.
         * Tell each player their resource roll totals.
         * Tell each player how long they've been connected.
         * (Robot players aren't told this, it's not necessary.)
         */
        final String connMsgKey;
        if (ga.isPractice)
            // "You have been practicing # minutes."
            connMsgKey = "stats.cli.connected.minutes.prac";
        else
            // "You have been connected # minutes."
            connMsgKey = "stats.cli.connected.minutes";
        for (int i = 0; i < ga.maxPlayers; i++) {
            if (ga.isSeatVacant(i))
                continue;
            SOCPlayer pl = ga.getPlayer(i);
            Connection plConn = srv.getConnection(pl.getName());
            SOCClientData cd;
            if (plConn != null) {
                // Update win-loss count, even for robots
                cd = (SOCClientData) plConn.getAppData();
                if (pl == winPl)
                    cd.wonGame();
                else
                    cd.lostGame();
            } else {
                // To satisfy compiler warning
                cd = null;
            }
            if (pl.isRobot())
                // <-- Don't bother to send any stats text to robots --
                continue;
            if (plConn != null) {
                if (plConn.getVersion() >= SOCPlayerStats.VERSION_FOR_RES_ROLL) {
                    // Send total resources rolled
                    srv.messageToPlayer(plConn, new SOCPlayerStats(pl, SOCPlayerStats.STYPE_RES_ROLL));
                }
                final long connTime = plConn.getConnectTime().getTime();
                final long connMinutes = (((now.getTime() - connTime)) + 30000L) / 60000L;
                // "You have been connected # minutes."
                srv.messageToPlayerKeyed(plConn, gname, connMsgKey, connMinutes);
                // Send client's win-loss count for this session,
                // if more than 1 game has been played
                {
                    int wins = cd.getWins();
                    int losses = cd.getLosses();
                    if (wins + losses < 2)
                        // Only 1 game played so far
                        continue;
                    if (wins > 0) {
                        if (losses == 0)
                            srv.messageToPlayerKeyed(plConn, gname, "stats.cli.winloss.won", wins);
                        else
                            // "You have won {0,choice, 1#1 game|1<{0,number} games} since connecting."
                            srv.messageToPlayerKeyed(plConn, gname, "stats.cli.winloss.wonlost", wins, losses);
                    // "You have won {0,choice, 1#1 game|1<{0,number} games} and lost {1,choice, 1#1 game|1<{1,number} games} since connecting."
                    } else {
                        srv.messageToPlayerKeyed(plConn, gname, "stats.cli.winloss.lost", losses);
                    // "You have lost {0,choice, 1#1 game|1<{0,number} games} since connecting."
                    }
                }
            }
        }
    // for each player
    }
    // send game timing stats, win-loss stats
    srv.gameOverIncrGamesFinishedCount();
    srv.storeGameScores(ga);
    if (ga.isBotsOnly) {
        srv.destroyGameAndBroadcast(gname, "sendGameStateOVER");
    }
// Server structure more or less ensures sendGameStateOVER is called only once.
// TODO consider refactor to be completely sure, especially for storeGameScores.
}
Also used : Connection(soc.server.genericServer.Connection) SOCPlayerStats(soc.message.SOCPlayerStats) Date(java.util.Date) SOCGameStats(soc.message.SOCGameStats)

Example 34 with Connection

use of soc.server.genericServer.Connection in project JSettlers2 by jdmonin.

the class SOCServer method messageToGameForVersionsExcept.

/**
 * Send a message to all the connections in a game in a certain version range, excluding one.
 * Used for backwards compatibility.
 *
 * @param ga  the game
 * @param vmin  Minimum version to send to, or -1.  Same format as
 *                {@link Version#versionNumber()} and {@link Connection#getVersion()}.
 * @param vmax  Maximum version to send to, or {@link Integer#MAX_VALUE}
 * @param ex  the excluded connection, or null
 * @param mes  the message
 * @param takeMon Should this method take and release
 *                game's monitor via {@link SOCGameList#takeMonitorForGame(String)} ?
 *                If the game's clients are all older than <tt>vmin</tt> or
 *                newer than <tt>vmax</tt>, nothing happens and the monitor isn't taken.
 * @since 1.1.19
 * @see #messageToGameExcept(String, Connection, SOCMessage, boolean)
 */
public final void messageToGameForVersionsExcept(final SOCGame ga, final int vmin, final int vmax, final Connection ex, final SOCMessage mes, final boolean takeMon) {
    if ((ga.clientVersionLowest > vmax) || (ga.clientVersionHighest < vmin))
        // <--- All clients too old or too new ---
        return;
    final String gn = ga.getName();
    if (takeMon)
        gameList.takeMonitorForGame(gn);
    try {
        Vector<Connection> v = gameList.getMembers(gn);
        if (v != null) {
            // lazy init, will be mes.toCmd()
            String mesCmd = null;
            Enumeration<Connection> menum = v.elements();
            while (menum.hasMoreElements()) {
                Connection con = menum.nextElement();
                if ((con == null) || (con == ex))
                    continue;
                final int cv = con.getVersion();
                if ((cv < vmin) || (cv > vmax))
                    continue;
                // currentGameEventRecord.addMessageOut(new SOCMessageRecord(mes, "SERVER", con.getData()));
                if (mesCmd == null)
                    mesCmd = mes.toCmd();
                con.put(mesCmd);
            }
        }
    } catch (Exception e) {
        D.ebugPrintStackTrace(e, "Exception in messageToGameForVersions");
    }
    if (takeMon)
        gameList.releaseMonitorForGame(gn);
}
Also used : StringConnection(soc.server.genericServer.StringConnection) Connection(soc.server.genericServer.Connection) MissingResourceException(java.util.MissingResourceException) EOFException(java.io.EOFException) DBSettingMismatchException(soc.server.database.DBSettingMismatchException) SocketException(java.net.SocketException) SQLException(java.sql.SQLException) IOException(java.io.IOException)

Example 35 with Connection

use of soc.server.genericServer.Connection in project JSettlers2 by jdmonin.

the class SOCServer method resetBoardAndNotify.

/**
 * Reset the board, to a copy with same players but new layout.
 * Here's the general outline; step 1 and 2 are done immediately here,
 * steps 3 through n are done (after robots are dismissed) within
 * {@link #resetBoardAndNotify_finish(SOCGameBoardReset, SOCGame)}.
 *<OL>
 * <LI value=1> Reset the board, remember player positions.
 *              If there are robots, set game state to
 *              {@link SOCGame#READY_RESET_WAIT_ROBOT_DISMISS}.
 * <LI value=2a> Send ResetBoardAuth to each client (like sending JoinGameAuth at new game)
 *    Humans will reset their copy of the game.
 *    Robots will leave the game, and soon be requested to re-join.
 *    (This simplifies the robot client.)
 *    If the game was in initial placement or was already over at reset time, different robots will
 *    be randomly chosen to join the reset game.
 * <LI value=2b> If there were robots, wait for them all to leave the old game.
 *    Otherwise, (race condition) they may leave the new game as it is forming.
 *    Set {@link SOCGame#boardResetOngoingInfo}.
 *    Wait for them to leave the old game before continuing.
 *    The call will be made from {@link SOCServerMessageHandler#handleLEAVEGAME_maybeGameReset_oldRobot(String)}.
 * <LI value=2c> If no robots, immediately call {@link #resetBoardAndNotify_finish(SOCGameBoardReset, SOCGame)}.
 *   <P>
 *    <b>This ends this method.</b>  Step 3 and the rest are in
 *    {@link #resetBoardAndNotify_finish(SOCGameBoardReset, SOCGame)}.
 * <LI value=3> Send messages as if each human player has clicked "join" (except JoinGameAuth)
 * <LI value=4> Send as if each human player has clicked "sit here"
 * <LI value=5a> If no robots, send to game as if someone else has
 *              clicked "start game", and set up state to begin game play.
 * <LI value=5b>  If there are robots, set up wait-request
 *     queue (robotJoinRequests). Game will wait for robots to send
 *     JOINGAME and SITDOWN, as they do when joining a newly created game.
 *     Once all robots have re-joined, the game will begin.
 *</OL>
 * @since 1.1.00
 */
void resetBoardAndNotify(final String gaName, final int requestingPlayer) {
    /**
     * 1. Reset the board, remember player positions.
     *    Takes the monitorForGame and (when reset is ready) releases it.
     *    If robots, resetBoard will also set gamestate
     *    and boardResetOngoingInfo field.
     */
    SOCGameBoardReset reBoard = gameList.resetBoard(gaName);
    if (reBoard == null) {
        final SOCGame ga = gameList.getGameData(gaName);
        if (ga != null)
            messageToGameKeyed(ga, true, "resetboard.doit.interror", gaName);
        // <---- Early return: reset failed ----
        return;
    }
    SOCGame reGame = reBoard.newGame;
    // Announce who asked for this reset
    {
        String plName = reGame.getPlayer(requestingPlayer).getName();
        final String key = (plName != null) ? // ">>> Game {0} board reset by {1}"
        "resetboard.doit.announce.requester" : // ">>> Game {0} board reset by a player who left"
        "resetboard.doit.announce.playerwholeft";
        messageToGameKeyed(reGame, true, key, gaName, plName);
    }
    // If game is still initial-placing or was over, we'll shuffle the robots
    final boolean resetWithShuffledBots = (reBoard.oldGameState < SOCGame.ROLL_OR_CARD) || (reBoard.oldGameState == SOCGame.OVER);
    /**
     * Player connection data:
     * - Humans are copied from old to new game
     * - Robots aren't copied to new game, must re-join
     */
    Connection[] huConns = reBoard.humanConns;
    Connection[] roConns = reBoard.robotConns;
    /**
     * Notify old game's players. (Humans and robots)
     *
     * 2a. Send ResetBoardAuth to each (like sending JoinGameAuth at new game).
     *    Humans will reset their copy of the game.
     *    Robots will leave the game, and soon will be requested to re-join.
     */
    for (int pn = 0; pn < reGame.maxPlayers; ++pn) {
        SOCResetBoardAuth resetMsg = new SOCResetBoardAuth(gaName, pn, requestingPlayer);
        if (huConns[pn] != null)
            messageToPlayer(huConns[pn], resetMsg);
        else if (roConns[pn] != null) {
            if (!resetWithShuffledBots)
                // same robot will rejoin
                messageToPlayer(roConns[pn], resetMsg);
            else
                // could be different bot
                messageToPlayer(roConns[pn], new SOCRobotDismiss(gaName));
        }
    }
    if (!reBoard.hadRobots)
        resetBoardAndNotify_finish(reBoard, reGame);
// else
// gameState is READY_RESET_WAIT_ROBOT_DISMISS,
// and once the last robot leaves this game,
// SOCServerMessageHandler.handleLEAVEGAME will take care of the reset,
// by calling resetBoardAndNotify_finish.
}
Also used : SOCGameBoardReset(soc.util.SOCGameBoardReset) StringConnection(soc.server.genericServer.StringConnection) Connection(soc.server.genericServer.Connection)

Aggregations

Connection (soc.server.genericServer.Connection)44 StringConnection (soc.server.genericServer.StringConnection)24 SQLException (java.sql.SQLException)8 MissingResourceException (java.util.MissingResourceException)8 SOCGame (soc.game.SOCGame)7 EOFException (java.io.EOFException)5 IOException (java.io.IOException)5 SocketException (java.net.SocketException)5 ArrayList (java.util.ArrayList)5 SOCPlayerElement (soc.message.SOCPlayerElement)5 DBSettingMismatchException (soc.server.database.DBSettingMismatchException)5 SOCPlayer (soc.game.SOCPlayer)4 Date (java.util.Date)3 SOCGameElements (soc.message.SOCGameElements)3 HashSet (java.util.HashSet)2 Hashtable (java.util.Hashtable)2 TimerTask (java.util.TimerTask)2 Vector (java.util.Vector)2 SOCDevCardAction (soc.message.SOCDevCardAction)2 SOCInventoryItemAction (soc.message.SOCInventoryItemAction)2