gt:             currentPlayerNumber = Math.abs(rand.nextInt() % maxPlayers);
context: /**
   * Java Settlers - An online multiplayer version of the game Settlers of Catan
   * Copyright (C) 2003  Robert S. Thomas <thomas@infolab.northwestern.edu>
   * Portions of this file Copyright (C) 2007-2022 Jeremy D Monin <jeremy@nand.net>
   * Portions of this file Copyright (C) 2012 Skylar Bolton <iiagrer@gmail.com>
   * Portions of this file Copyright (C) 2012 Paul Bilnoski <paul@bilnoski.net>
   * Portions of this file Copyright (C) 2017 Ruud Poutsma <rtimon@gmail.com>
   * Portions of this file Copyright (C) 2017-2018 Strategic Conversation (STAC Project) https://www.irit.fr/STAC/
   *
   * This program 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, or (at your option) any later version.
   *
   * This program 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/>.
   *
   * The maintainer of this program can be reached at jsettlers@nand.net
   **/
  package soc.game;
  
  import soc.disableDebug.D;
  
  import soc.message.SOCMessage;  // For static calls only; SOCGame does not interact with network messages
  import soc.server.SOCBoardAtServer;  // For calling server-only methods like distributeClothFromRoll
  import soc.util.DataUtils;
  import soc.util.IntPair;
  import soc.util.SOCFeatureSet;
  import soc.util.SOCGameBoardReset;
  
  import java.io.Serializable;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.NoSuchElementException;
  import java.util.Random;
  import java.util.Set;
  import java.util.Stack;
  import java.util.Vector;
  
  
  /**
   * A class for holding and manipulating game data.
   * Most methods are not implicitly thread-safe;
   * call {@link #takeMonitor()} and {@link #releaseMonitor()} around them.
   *<P>
   * The model in this client/server game is: The SOCGame at server contains the game's
   * complete state information, and game logic advances there.
   * Each client's local SOCGame contains only partial state (for instance, other
   * players' resources or devel cards may be of unknown type); and the server directly
   * updates clients' game state by sending messages such as
   * {@link soc.message.SOCGameState} and {@link soc.message.SOCSetPlayedDevCard}.
   * Within this package, you can check {@link #isAtServer}.
   *<P>
   * Many methods assume you've already checked whether the move is valid,
   * and won't check it a second time.  For example, {@link #canPlayKnight(int)}
   * should be called to check before calling {@link #playKnight()}.
   *<P>
   * For the board <b>coordinate system and terms</b> (hex, node, edge), see the
   * {@link SOCBoard} class javadoc.
   *<P>
   * Games are created at the server in {@link soc.server.SOCGameListAtServer} and given an
   * expiration time 120 minutes away
   * ({@link soc.server.SOCGameListAtServer#GAME_TIME_EXPIRE_MINUTES SOCGameListAtServer.GAME_TIME_EXPIRE_MINUTES}).
   * Players then choose their seats, optionally locking empty seats against joining robots,
   * and any player can click the Start Game button.
   *<P>
   * Game play begins with the server calling {@link #startGame()}, then sending messages to clients
   * with the starting game state and player data and a board layout.
   * After initial placement, normal play begins with the first player's turn, in state {@link #ROLL_OR_CARD};
   * {@link #updateAtGameFirstTurn()} is called for any work needed.
   *<P>
   * During game play, {@link #putPiece(SOCPlayingPiece)} and other game-action methods update {@code gameState}.
   * {@link #updateAtTurn()}, <tt>putPiece</tt> and some other game-action methods update {@link #lastActionTime}.
   *<P>
   * The game's current plays and actions are tracked through game states, such as
   * {@value #START1A} or {@link #WAITING_FOR_DISCARDS}.  A normal turn starts at {@link #ROLL_OR_CARD};
   * after dice are rolled, the turn will spend most of its time in {@link #PLAY1}.  If you need to
   * add a state, please see the instructions at {@link #NEW}.
   *<P>
   * The winner is the player who has {@link #vp_winner} or more victory points (typically 10)
   * on their own turn.  Some optional game scenarios have special win conditions, see {@link #checkForWinner()}.
   *<P>
   * The {@link SOCGame#hasSeaBoard large sea board} features scenario events.
   * To listen for these, call {@link #setGameEventListener(SOCGameEventListener)}.
   * If the game has a scenario, its {@link SOCGameOption}s will include {@code "SC"}, possibly along with
   * other options whose key names start with {@code "_SC_"}.
   *<P>
   * To persist a game's contents or save/load, use {@link soc.server.savegame.SavedGameModel}.
   *
   * @author Robert S. Thomas
   */
  public class SOCGame implements Serializable, Cloneable
  {
      /**
       * The main game class has a serialVersionUID; pieces and players don't.
       * To persist a game between versions, use {@link soc.server.savegame.SavedGameModel}.
       */
      private static final long serialVersionUID = 2500L;  // last structural change v2.5.00
  
      /**
       * Game states.  {@link #NEW} is a brand-new game, not yet ready to start playing.
       * Players are choosing where to sit, or have all sat but no one has yet clicked
       * the "start game" button. The board is empty sea, with no land hexes yet.
       * Next state from NEW is {@link #READY} if robots, or {@link #START1A} if only humans
       * are playing.
       *<P>
       * General assumptions for states and their numeric values:
       * <UL>
       * <LI> Any scenario-specific initial pieces, such as those in
       *      {@link SOCScenario#K_SC_PIRI SC_PIRI}, are sent while
       *      game state is still &lt; {@link #START1A}.
       * <LI> Active game states are >= {@link #START1A} and &lt; {@link #OVER}
       * <LI> Initial placement ends after {@link #START2B} or {@link #START3B}, going directly to {@link #ROLL_OR_CARD}
       * <LI> A Normal turn's "main phase" is {@link #PLAY1}, after dice-roll/card-play in {@link #ROLL_OR_CARD}
       * <LI> When the game is waiting for a player to react to something,
       *      state is > {@link #PLAY1}, &lt; {@link #OVER}; state name starts with
       *      PLACING_ or WAITING_
       * <LI> While reloading and resuming a saved game, state is {@link #LOADING}, barely &lt; {@link #OVER}.
       *      Some code may want to check state &lt; {@code LOADING} instead of &lt; {@code OVER}.
       * </UL>
       *<P>
       * The code reacts to (switches based on) game state in several places.
       * The main places to check, if you add a game state:
       *<UL>
       * <LI> {@link soc.client.SOCBoardPanel#updateMode()}
       * <LI> {@link soc.client.SOCBuildingPanel#updateButtonStatus()}
       * <LI> {@link soc.client.SOCPlayerInterface#updateAtGameState()}
       * <LI> {@link #putPiece(SOCPlayingPiece)}
       * <LI> {@link #advanceTurnStateAfterPutPiece()}
       * <LI> {@link #forceEndTurn()}
       * <LI> {@link soc.robot.SOCRobotBrain#run()}
       * <LI> {@link soc.server.SOCGameHandler#sendGameState(SOCGame)}
       * <LI> {@link soc.message.SOCGameState} javadoc list of states with related messages and client responses
       *</UL>
       * Also, if your state is similar to an existing state, do a where-used search
       * for that state, and decide where both states should be reacted to.
       *<P>
       * If your new state might be waiting for several players (not just the current player) to
       * respond with a choice (such as picking resources to discard or gain), also update
       * {@link soc.server.GameHandler#endTurnIfInactive(SOCGame, long)}.  Otherwise the robot will be
       * forced to lose its turn while waiting for human players.
       *<P>
       * Other places to check, if you add a game state:
       *<UL>
       * <LI> SOCBoardPanel.BoardPopupMenu.showBuild, showCancelBuild
       * <LI> SOCBoardPanel.drawBoard
       * <LI> SOCHandPanel.addPlayer, began, removePlayer, updateAtTurn, updateValue
       * <LI> SOCGame.addPlayer
       * <LI> SOCServerMessageHandler.handleSTARTGAME
       * <LI> Your game type's GameHandler.leaveGame, sitDown, GameMessageHandler.handleCANCELBUILDREQUEST, handlePUTPIECE
       * <LI> SOCPlayerClient.handleCANCELBUILDREQUEST, SOCDisplaylessPlayerClient.handleCANCELBUILDREQUEST
       *</UL>
       */
      public static final int NEW = 0; // Brand new game, players sitting down
  
      /**
       * Ready to start playing.  All humans have chosen a seat.
       * Wait for requested robots to sit down.
       * Once robots have joined the game (this happens in other threads, possibly in other
       * processes), gameState will become {@link #START1A}.
       * @see #READY_RESET_WAIT_ROBOT_DISMISS
       * @see #LOADING_RESUMING
       */
      public static final int READY = 1; // Ready to start playing
  
      /**
       * This game object has just been created by a reset, but the old game contains robot players,
       * so we must wait for them to leave before re-inviting anyone to continue the reset process.
       * Once they have all left, state becomes {@link #READY}.
       * See {@link #boardResetOngoingInfo} and (private) SOCServer.resetBoardAndNotify.
       * @since 1.1.07
       */
      public static final int READY_RESET_WAIT_ROBOT_DISMISS = 4;
  
      /**
       * Players place first settlement.  Proceed in order for each player; next state
       * is {@link #START1B} to place each player's 1st road.
       */
      public static final int START1A = 5; // Players place 1st stlmt
  
      /**
       * Players place first road.  Next state is {@link #START1A} to place next
       * player's 1st settlement, or if all have placed settlements,
       * {@link #START2A} to place 2nd settlement.
       */
      public static final int START1B = 6; // Players place 1st road
  
      /**
       * Players place second settlement.  Proceed in reverse order for each player;
       * next state is {@link #START2B} to place 2nd road.
       * If the settlement is placed on a Gold Hex, the next state
       * is {@link #STARTS_WAITING_FOR_PICK_GOLD_RESOURCE}.
       *<P>
       * If game scenario option {@link SOCGameOptionSet#K_SC_3IP _SC_3IP} is set, then instead of
       * this second settlement giving resources, a third round of placement will do that;
       * next game state after START2A remains {@link #START2B}.
       */
      public static final int START2A = 10; // Players place 2nd stlmt
  
      /**
       * Just placed an initial piece, waiting for current
       * player to choose which Gold Hex resources to receive.
       * This can happen after the second or third initial settlement,
       * or (with the fog scenario {@link SOCGameOptionSet#K_SC_FOG _SC_FOG})
       * when any initial road, settlement, or ship reveals a gold hex.
       *<P>
       * The next game state will be based on <tt>oldGameState</tt>,
       * which is the state whose placement led to {@link #STARTS_WAITING_FOR_PICK_GOLD_RESOURCE}.
       * For settlements not revealed from fog:
       * Next game state is {@link #START2B} to place 2nd road.
       * If game scenario option {@link SOCGameOptionSet#K_SC_3IP _SC_3IP} is set,
       * next game state can be {@link #START3B}.
       *<P>
       * Valid only when {@link #hasSeaBoard}, settlement adjacent to {@link SOCBoardLarge#GOLD_HEX},
       * or gold revealed from {@link SOCBoardLarge#FOG_HEX} by a placed road, ship, or settlement.
       *<P>
       * This is the highest-numbered possible starting state; value is {@link #ROLL_OR_CARD} - 1.
       *
       * @see #WAITING_FOR_PICK_GOLD_RESOURCE
       * @see #pickGoldHexResources(int, SOCResourceSet)
       * @since 2.0.00
       */
      public static final int STARTS_WAITING_FOR_PICK_GOLD_RESOURCE = 14;  // value must be 1 less than ROLL_OR_CARD
  
      /**
       * Players place second road.  Next state is {@link #START2A} to place previous
       * player's 2nd settlement (player changes in reverse order), or if all have placed
       * settlements, {@link #ROLL_OR_CARD} to begin first player's turn.
       *<P>
       * If game scenario option {@link SOCGameOptionSet#K_SC_3IP _SC_3IP} is set, then instead of
       * starting normal play, a third settlement and road are placed by each player,
       * with game state {@link #START3A}.
       */
      public static final int START2B = 11; // Players place 2nd road
  
      /**
       * (Game scenarios) Players place third settlement.  Proceed in normal order
       * for each player; next state is {@link #START3B} to place 3rd road.
       * If the settlement is placed on a Gold Hex, the next state
       * is {@link #STARTS_WAITING_FOR_PICK_GOLD_RESOURCE}.
       *<P>
       * Valid only when game scenario option {@link SOCGameOptionSet#K_SC_3IP _SC_3IP} is set.
       */
      public static final int START3A = 12;  // Players place 3rd settlement
  
      /**
       * Players place third road.  Next state is {@link #START3A} to place previous
       * player's 3rd settlement (player changes in normal order), or if all have placed
       * settlements, {@link #ROLL_OR_CARD} to begin first player's turn.
       *<P>
       * Valid only when game scenario option {@link SOCGameOptionSet#K_SC_3IP _SC_3IP} is set.
       */
      public static final int START3B = 13;
  
      /**
       * Start of a normal turn: Time to roll or play a card.
       * Next state depends on card or roll, but usually is {@link #PLAY1}.
       *<P>
       * If 7 is rolled, might be {@link #WAITING_FOR_DISCARDS} or {@link #WAITING_FOR_ROBBER_OR_PIRATE}
       *   or {@link #PLACING_ROBBER} or {@link #PLACING_PIRATE}.
       *<P>
       * If 7 is rolled with scenario option <tt>_SC_PIRI</tt>, there is no robber to move, but
       * the player will choose their robbery victim ({@link #WAITING_FOR_ROB_CHOOSE_PLAYER}) after any discards.
       *<P>
       * If the number rolled is on a gold hex, next state might be
       *   {@link #WAITING_FOR_PICK_GOLD_RESOURCE}.
       *<P>
       * <b>More special notes for scenario <tt>_SC_PIRI</tt>:</b> When the dice is rolled, the pirate fleet moves
       * along a path, and attacks the sole player with an adjacent settlement to the pirate hex, if any.
       * This is resolved before any of the normal dice-rolling actions (distributing resources, handling a 7, etc.)
       * If the player ties or loses (pirate fleet is stronger than player's fleet of warships), the roll is
       * handled as normal, as described above.  If the player wins, they get to pick a random resource.
       * Unless the roll is 7, this can be dealt with along with other gained resources (gold hexes).
       * So: <b>If the player wins and the roll is 7,</b> the player must pick their resource before any normal 7 discarding.
       * In that case only, the next state is {@link #WAITING_FOR_PICK_GOLD_RESOURCE}, which will be
       * followed by {@link #WAITING_FOR_DISCARDS} or {@link #WAITING_FOR_ROB_CHOOSE_PLAYER}.
       *<P>
       * Before v2.0.00 this state was named {@code PLAY}.
       */
      public static final int ROLL_OR_CARD = 15; // Play continues normally; time to roll or play card
  
      /**
       * Done rolling (or moving robber on 7).  Time for other turn actions,
       * such as building or buying or trading, or playing a card if not already done.
       * Next state depends on what's done, but usually is the next player's {@link #ROLL_OR_CARD}.
       */
      public static final int PLAY1 = 20; // Done rolling
  
      public static final int PLACING_ROAD = 30;
      public static final int PLACING_SETTLEMENT = 31;
      public static final int PLACING_CITY = 32;
  
      /**
       * Player is placing the robber on a new land hex.
       * May follow state {@link #WAITING_FOR_ROBBER_OR_PIRATE} if the game {@link #hasSeaBoard}.
       *<P>
       * Possible next game states:
       *<UL>
       * <LI> {@link #PLAY1}, after robbing no one or the single possible victim; from {@code oldGameState}
       * <LI> {@link #WAITING_FOR_ROB_CHOOSE_PLAYER} if multiple possible victims
       * <LI> In scenario {@link SOCGameOptionSet#K_SC_CLVI _SC_CLVI}, {@link #WAITING_FOR_ROB_CLOTH_OR_RESOURCE}
       *   if the victim has cloth and has resources
       * <LI> {@link #OVER}, if current player just won by gaining Largest Army
       *   (when there aren't multiple possible victims or another robber-related choice to make)
       *</UL>
       * @see #PLACING_PIRATE
       * @see #canMoveRobber(int, int)
       * @see #moveRobber(int, int)
       */
      public static final int PLACING_ROBBER = 33;
  
      /**
       * Player is placing the pirate ship on a new water hex,
       * in a game which {@link #hasSeaBoard}.
       * May follow state {@link #WAITING_FOR_ROBBER_OR_PIRATE}.
       * Has the same possible next game states as {@link #PLACING_ROBBER}.
       * @see #canMovePirate(int, int)
       * @see #movePirate(int, int)
       * @since 2.0.00
       */
      public static final int PLACING_PIRATE = 34;
  
      /**
       * This game {@link #hasSeaBoard}, and a player has bought and is placing a ship.
       * @since 2.0.00
       */
      public static final int PLACING_SHIP = 35;
  
      /**
       * Player is placing their first free road/ship.
       * If {@link #getCurrentDice()} == 0, the Road Building card was
       * played before rolling the dice.
       */
      public static final int PLACING_FREE_ROAD1 = 40;
  
      /**
       * Player is placing their second free road/ship.
       * If {@link #getCurrentDice()} == 0, the Road Building card was
       * played before rolling the dice.
       */
      public static final int PLACING_FREE_ROAD2 = 41;
  
      /**
       * Player is placing the special {@link SOCInventoryItem} held in {@link #getPlacingItem()}. For some kinds
       * of item, placement can sometimes be canceled by calling {@link #cancelPlaceInventoryItem(boolean)}.
       *<P>
       * The placement method depends on the scenario and item type; for example,
       * {@link SOCGameOptionSet#K_SC_FTRI _SC_FTRI} has trading port items and would
       * call {@link #placePort(int)}.
       *<P>
       * Placement requires its own game state (not {@code PLAY1}) because sometimes
       * it's triggered by the game after another action, not initiated by player request.
       *<P>
       * When setting this gameState: In case placement is canceled, set
       * {@code oldGameState} to {@link #PLAY1} or {@link #SPECIAL_BUILDING}.
       * @since 2.0.00
       */
      public static final int PLACING_INV_ITEM = 42;
  
      /**
       * Waiting for player(s) to discard, after 7 is rolled in {@link #rollDice()}.
       * Next game state is {@link #WAITING_FOR_DISCARDS}
       * (if other players still need to discard),
       * {@link #WAITING_FOR_ROBBER_OR_PIRATE},
       * or {@link #PLACING_ROBBER}.
       *<P>
       * In scenario option <tt>_SC_PIRI</tt>, there is no robber
       * to move, but the player will choose their robbery victim
       * ({@link #WAITING_FOR_ROB_CHOOSE_PLAYER}) after any discards.
       * If there are no possible victims, next state is {@link #PLAY1}.
       *
       * @see #discard(int, ResourceSet)
       */
      public static final int WAITING_FOR_DISCARDS = 50;
  
      /**
       * Waiting for player to choose a player to rob,
       * with the robber or pirate ship, after rolling 7 or
       * playing a Knight/Soldier card.
       * Next game state is {@link #PLAY1}, {@link #WAITING_FOR_ROB_CLOTH_OR_RESOURCE},
       * or {@link #OVER} if player just won by gaining Largest Army.
       *<P>
       * To see whether we're moving the robber or the pirate, use {@link #getRobberyPirateFlag()}.
       * To choose the player, call {@link #choosePlayerForRobbery(int)}.
       *<P>
       * In scenario option <tt>_SC_PIRI</tt>, there is no robber
       * to move, but the player will choose their robbery victim.
       * <tt>{@link #currentRoll}.sc_clvi_robPossibleVictims</tt>
       * holds the list of possible victims.  In that scenario,
       * the player also doesn't control the pirate ships, and
       * never has Knight cards to move the robber and steal.
       *<P>
       * So in that scenario, the only time the game state is {@code WAITING_FOR_ROB_CHOOSE_PLAYER}
       * is when the player must choose to steal from a possible victim, or choose to steal
       * from no one, after a 7 is rolled.  To choose the victim, call {@link #choosePlayerForRobbery(int)}.
       * To choose no one, call {@link #choosePlayerForRobbery(int) choosePlayerForRobbery(-1)}.
       *<P>
       * Before v2.0.00, this game state was called {@code WAITING_FOR_CHOICE}.
       *
       * @see #playKnight()
       * @see #canChoosePlayer(int)
       * @see #canChooseRobClothOrResource(int)
       * @see #stealFromPlayer(int, boolean)
       */
      public static final int WAITING_FOR_ROB_CHOOSE_PLAYER = 51;
  
      /**
       * Waiting for player to choose 2 resources (Discovery/Year of Plenty card)
       * Next game state is {@link #PLAY1}.
       */
      public static final int WAITING_FOR_DISCOVERY = 52;
  
      /**
       * Waiting for player to choose a resource (Monopoly card)
       * Next game state is {@link #PLAY1}.
       */
      public static final int WAITING_FOR_MONOPOLY = 53;
  
      /**
       * Waiting for player to choose the robber or the pirate ship,
       * after {@link #rollDice()} or {@link #playKnight()}.
       * Next game state is {@link #PLACING_ROBBER} or {@link #PLACING_PIRATE}.
       *<P>
       * Moving from {@code WAITING_FOR_ROBBER_OR_PIRATE} to those states preserves {@code oldGameState}.
       *
       * @see #canChooseMovePirate()
       * @see #chooseMovePirate(boolean)
       * @see #WAITING_FOR_DISCARDS
       * @since 2.0.00
       */
      public static final int WAITING_FOR_ROBBER_OR_PIRATE = 54;
  
      /**
       * Waiting for player to choose whether to rob cloth or rob a resource.
       * Previous game state is {@link #PLACING_PIRATE} or {@link #WAITING_FOR_ROB_CHOOSE_PLAYER}.
       * Next step: Call {@link #stealFromPlayer(int, boolean)} with result of that player's choice.
       *<P>
       * Next game state is {@link #PLAY1}, or {@link #OVER} if player just won by gaining Largest Army.
       *<P>
       * Used with scenario option {@link SOCGameOptionSet#K_SC_CLVI _SC_CLVI}.
       *
       * @see #movePirate(int, int)
       * @see #canChooseRobClothOrResource(int)
       * @since 2.0.00
       */
      public static final int WAITING_FOR_ROB_CLOTH_OR_RESOURCE = 55;
  
      /**
       * Waiting for player(s) to choose which Gold Hex resources to receive.
       * Next game state is usually {@link #PLAY1}, sometimes
       * {@link #PLACING_FREE_ROAD2} or {@link #SPECIAL_BUILDING}.
       * ({@link #oldGameState} holds the <b>next</b> state after this WAITING state.)
       *<P>
       * Valid only when {@link #hasSeaBoard}, settlements or cities
       * adjacent to {@link SOCBoardLarge#GOLD_HEX}.
       *<P>
       * When receiving this state from server, a client shouldn't immediately check their user player's
       * {@link SOCPlayer#getNeedToPickGoldHexResources()} or prompt the user to pick resources; those
       * players' clients will be sent a {@link soc.message.SOCSimpleRequest#PROMPT_PICK_RESOURCES} message shortly.
       *<P>
       * If scenario option {@link SOCGameOptionSet#K_SC_PIRI _SC_PIRI} is active,
       * this state is also used when a 7 is rolled and the player has won against a
       * pirate fleet attack.  They must choose a free resource.  {@link #oldGameState} is {@link #ROLL_OR_CARD}.
       * Then, the 7 is resolved as normal.  See {@link #ROLL_OR_CARD} javadoc for details.
       * That's the only time free resources are picked on rolling 7.
       *
       * @see #STARTS_WAITING_FOR_PICK_GOLD_RESOURCE
       * @see #pickGoldHexResources(int, SOCResourceSet)
       * @since 2.0.00
       */
      public static final int WAITING_FOR_PICK_GOLD_RESOURCE = 56;
  
      /**
       * The 6-player board's Special Building Phase.
       * Takes place at the end of any player's normal turn (roll, place, etc).
       * The Special Building Phase changes {@link #currentPlayerNumber}.
       * So, it begins by calling {@link #advanceTurn()} to
       * the next player, and continues clockwise until
       * {@link #currentPlayerNumber} == {@link #specialBuildPhase_afterPlayerNumber}.
       * At that point, the Special Building Phase is over,
       * and it's the next player's turn as usual.
       * @since 1.1.08
       */
      public static final int SPECIAL_BUILDING = 100;  // see advanceTurnToSpecialBuilding()
  
      /**
       * A saved game is being loaded. Its actual state is saved in field {@code oldGameState}.
       * Bots have joined to sit in seats which were bots in the saved game, and human seats
       * are currently unclaimed except for the requesting user if they were named as a player.
       * Before resuming play, server or user may need to satisfy conditions or constraints
       * (have a certain type of bot sit down at a given player number, etc).
       * If there are unclaimed non-vacant seats where bots will need to join,
       * next state is {@link #LOADING_RESUMING}, otherwise will resume at {@code oldGameState}.
       *<P>
       * This game state is higher-numbered than actively-playing states, slightly lower than {@link #OVER}
       * or {@link #LOADING_RESUMING}.
       *
       * @since 2.3.00
       */
      public static final int LOADING = 990;
  
      /**
       * A saved game was loaded and is about to resume, now waiting for some bots to rejoin.
       * Game's actual state is saved in field {@code oldGameState}.
       * Before we can resume play, server has requested some bots to fill unclaimed non-vacant seats,
       * which had humans when the game was saved. Server is waiting for the bots to join and sit down
       * (like state {@link #READY}). Once all bots have joined, gameState can resume at {@code oldGameState}.
       *<P>
       * This game state is higher-numbered than actively-playing states, slightly lower than {@link #OVER}
       * but higher than {@link #LOADING}.
       *
       * @since 2.3.00
       */
      public static final int LOADING_RESUMING = 992;
  
      /**
       * The game is over.  A player has accumulated enough ({@link #vp_winner}) victory points,
       * or all players have left the game.
       * The winning player, if any, is {@link #getPlayerWithWin()}.
       * @see #checkForWinner()
       */
      public static final int OVER = 1000; // The game is over
  
      /**
       * This game is an obsolete old copy of a new (reset) game with the same name.
       * To assist logic, numeric constant value is greater than {@link #OVER}.
       * @see #resetAsCopy()
       * @see #getOldGameState()
       * @since 1.1.00
       */
      public static final int RESET_OLD = 1001;
  
      /**
       * seat states
       */
      private static final int VACANT = 0, OCCUPIED = 1;
  
      /**
       * Seat state {@link #VACANT}, but {@link #removePlayer(String, boolean)} was called
       * with flag that another player will be seated here momentarily; used at server only.
       * See that method for threading assumption/race prevention.
       * @since 2.1.00
       */
      private static final int VACANT_PENDING_REPLACE = 2;
  
      // for seatLock states, see SeatLockState enum javadoc.
  
      /**
       * {@link #boardResetVotes} per-player states: no vote sent; yes; no.
       * @since 1.1.00
       */
      public static final int VOTE_NONE = 0;
      public static final int VOTE_YES  = 1;
      public static final int VOTE_NO   = 2;
  
      /**
       * Maximum number of players in a game, in this version.
       * Was 4 before 1.1.08, now is 6.
       * @see #maxPlayers
       * @see #MAXPLAYERS_STANDARD
       */
      public static final int MAXPLAYERS = 6;
  
      /**
       * maximum number of players in a standard game
       * without the 6-player extension.
       * @see #MAXPLAYERS
       * @see #maxPlayers
       * @since 1.1.08
       */
      public static final int MAXPLAYERS_STANDARD = 4;
  
      /**
       * minimum number of players in a game (was assumed =={@link #MAXPLAYERS} in standard 1.0.6).
       * Use {@link #isSeatVacant(int)} to determine if a player is present;
       * <tt>players[i]</tt> will be non-null although no player is there.
       * @since 1.1.00
       */
      public static final int MINPLAYERS = 2;
  
      /**
       * Default number of victory points (10) needed to win.
       * Per-game copy is {@link #vp_winner}, can be changed from 10 in
       * constructor with the <tt>"VP"</tt> {@link SOCGameOption}.
       *<P>
       * Before v1.1.14 this was {@code VP_WINNER}.
       * @since 1.1.00
       */
      public static final int VP_WINNER_STANDARD = 10;
  
      /**
       * Number of development cards (5) which are Victory Point cards.
       * Not used in scenario {@link SOCGameOptionSet#K_SC_PIRI _SC_PIRI}.
       * (If 4 or more players in that scenario, they become {@link SOCDevCardConstants#KNIGHT KNIGHT} cards.)
       * @see #NUM_DEVCARDS_STANDARD
       * @since 2.0.00
       */
      private static final int NUM_DEVCARDS_VP = 5;
  
      /**
       * Number of development cards (25) in the standard rules,
       * without the 6-player extension.
       * @see #NUM_DEVCARDS_6PLAYER
       * @since 1.1.08
       */
      private static final int NUM_DEVCARDS_STANDARD = 25;
  
      /**
       * Number of development cards (34) in the 6-player rules.
       * @see #NUM_DEVCARDS_STANDARD
       * @since 1.1.08
       */
      private static final int NUM_DEVCARDS_6PLAYER = 9 + NUM_DEVCARDS_STANDARD;
  
      /**
       * Minimum version (2.5.00) that supports canceling the second free road or ship placement
       * without ending player's turn.
       * @see #cancelBuildRoad(int)
       * @see #cancelBuildShip(int)
       * @see #VERSION_FOR_CANCEL_FREE_ROAD2
       * @since 2.5.00
       */
      public static final int VERSION_FOR_CANCEL_FREE_ROAD1 = 2500;
  
      /**
       * Minimum version (1.1.17) that supports canceling the second free road or ship placement.
       * @see #cancelBuildRoad(int)
       * @see #cancelBuildShip(int)
       * @see #VERSION_FOR_CANCEL_FREE_ROAD1
       * @since 1.1.17
       */
      public static final int VERSION_FOR_CANCEL_FREE_ROAD2 = 1117;
  
      /**
       * Minimum version (2.4.00) where {@link #getPlayerWithLongestRoad()} and {@link #getPlayerWithLargestArmy()}
       * are calculated only at the server, instead of also at the client.
       * Client should calculate them if {@link #serverVersion} &lt; this version.
       * @since 2.4.00
       */
      public static final int VERSION_FOR_LONGEST_LARGEST_FROM_SERVER = 2400;
  
      /**
       * an empty set of resources.
       * @see SOCSettlement#COST
       */
      public static final ResourceSet EMPTY_RESOURCES = new SOCResourceSet();
  
      /**
       * The {@link SOCBoard.BoardFactory} for creating new boards in the SOCGame constructors.
       * Differs at client and at server.
       * If null, SOCGame constructor sets to {@link SOCBoard.DefaultBoardFactory}.
       * @see soc.server.SOCBoardAtServer.BoardFactoryAtServer
       * @since 2.0.00
       */
      public static SOCBoard.BoardFactory boardFactory;
  
      /**
       * An empty int array for use in method calls.
       * @since 2.0.00
       */
      private static final int[] EMPTY_INT_ARRAY = { };
  
      /**
       * monitor for synchronization
       */
      boolean inUse;
  
      /**
       * the name of the game
       */
      private String name;
  
      /**
       * Is this the server's complete copy of the game, not the client's (with some details unknown)?
       * Is set during {@link #startGame()}. Treat as read-only.
       * @see #serverVersion
       * @see #hasDoneGameOverTasks
       * @since 1.1.17
       */
      public boolean isAtServer;
  
      /**
       * For use at client, version of the server hosting this game;
       * same format as {@link soc.util.Version#versionNumber()}.
       * If {@link #isPractice}, holds same version as client.
       *<P>
       * Unused if {@link #isAtServer}.
       *<P>
       * Needed because some versions have a different balance of which game-data tasks are done at the server,
       * and which are at the client or at both. See {@link #VERSION_FOR_LONGEST_LARGEST_FROM_SERVER} for an example.
       *<P>
       * Is set when client receives {@code SOCJoinGameAuth} message which tells client to create the {@link SOCGame}.
       * Treat as read-only.
       * @since 2.4.00
       */
      public transient int serverVersion;
  
      /**
       * For games at server, a convenient queue to hold any outbound SOCMessages during game actions.
       * Public access for use by SOCServer.  The server will handle a game action as usual
       * (for example, {@link #putPiece(SOCPlayingPiece)}), create pending PLAYERELEMENT messages from
       * {@link SOCGameEventListener#playerEvent(SOCGame, SOCPlayer, SOCPlayerEvent, boolean, Object) SOCGameEventListener.playerEvent(...)},
       * send the usual messages related to that action, then check this list and send out
       * the pending PLAYERELEMENT message so that the game's clients will update that player's
       * {@link SOCPlayer#setPlayerEvents(int)} or other related fields, before the GAMESTATE message.
       *<P>
       * For pending messages to send only to one player's client, see {@link SOCPlayer#pendingMessagesOut}.
       *<P>
       * <B>Contents:</B> When sending out to game members, the server handles queue elements by class:
       *<UL>
       * <LI> {@code SOCKeyedMessage}: Localize and send
       * <LI> Other {@code SOCMessage} types: Send
       * <LI> {@code UnlocalizedString}: Localize and send with
       *      {@code SOCServer.messageToGameKeyed(SOCGame, boolean, String, Object...)}
       *      or {@code SOCServer.messageToGameKeyedSpecial(..)}
       * <LI> Anything else: Ignore or print error message at server console
       *</UL>
       *<P>
       * <B>Note:</B> Only a few of the server message-handling methods check this queue, because
       * only those few can potentially lead to special victory points or other game/scenario events.
       * If you add code where other player actions can lead to {@code pendingMessagesOut} adds, be sure
       * the server's SOCGameHandler/SOCGameMessageHandler for those actions checks this list afterwards,
       * to send before GAMESTATE via {@code SOCGameHandler.sendGamePendingMessages(..)}.
       *<P>
       * Because this queue is server-only, it's null until {@link #startGame()}.
       * To send and clear contents, the server should call
       * {@code SOCGameHandler.sendGamePendingMessages(SOCGame, boolean)}.
       *<P>
       * <B>Locking:</B> Not thread-safe, because all of a game's message handling
       * is done within a single thread.
       *
       * @since 2.0.00
       */
      public transient List<Object> pendingMessagesOut;
  
      /**
       * For a game at server which was loaded from disk and hasn't yet been resumed,
       * its {@link soc.server.savegame.SavedGameModel}. Otherwise {@code null}.
       * After the game is resumed by {@link soc.server.savegame.SavedGameModel#resumePlay(boolean)},
       * there's no field or flag that remembers it was previously loaded.
       *<P>
       * Declared as Object here to avoid needing server class at client.
       * @since 2.3.00
       */
      public transient Object savedGameModel;
  
      /**
       * For games at the server, the owner (creator) of the game.
       * Will be the name of a player / server connection.
       * Currently, if the game is reset, {@link #resetAsCopy()} copies ownerName,
       * even if they aren't still connected to the game.
       * NOT CURRENTLY SET AT CLIENT.
       * @since 1.1.10
       */
      private String ownerName;
  
      /**
       * Locale of the client connection that created this game.
       * Used to determine whether to set {@link #hasMultiLocales} when adding a member to the game.
       * Currently, if the game is reset, {@link #resetAsCopy()} copies {@code ownerLocale},
       * even if they aren't still connected to the game.
       *<P>
       * Server only, this field is not set at client.
       * @since 2.0.00
       */
      private String ownerLocale;
  
      /**
       * True if this game at server has already done the tasks which happen once when game ends:
       * Update server's game stats, write results to optional database, etc.
       *<P>
       * Server only, this field is not used at client.
       * @since 2.3.00
       */
      public boolean hasDoneGameOverTasks;
  
      /**
       * true if this game is ACTIVE
       */
      private boolean active;
  
      /**
       * Number of victory points needed to win this game (default {@link #VP_WINNER_STANDARD} == 10).
       * After game events such as playing a piece or moving the robber, check if current player's
       * VP &gt;= {@link #vp_winner} and call {@link #checkForWinner()} if so.
       * @see #hasScenarioWinCondition
       * @since 1.1.14
       */
      public final int vp_winner;
  
      /**
       * Does this game's scenario have a special win condition besides {@link #vp_winner}?
       * For example, scenario {@link SOCGameOptionSet#K_SC_CLVI _SC_CLVI} will end the game if
       * less than half the {@link SOCVillage}s have cloth remaining.  See {@link #checkForWinner()}
       * for a full list and more details.
       *<P>
       * When set, methods that check current player's VP &gt;= {@link #vp_winner} will
       * also call {@link #checkForWinner()}.
       * @since 2.0.00
       */
      public final boolean hasScenarioWinCondition;
  
      /**
       * true if the game's network is local for practice.  Used by
       * client to route messages to appropriate connection.
       * NOT CURRENTLY SET AT SERVER.  Instead check if server's strSocketName != null,
       * or if connection instanceof {@link StringConnection}.
       *<P>
       * Since 1.1.09: This flag is set at the server, true only if the server is a local practice
       * server whose stringport name equals {@code SOCServer.PRACTICE_STRINGPORT}.
       *<P>
       * Before 1.1.13 this field was called {@code isLocal}, but that was misleading;
       * the full client can launch a locally hosted tcp LAN server.
       *
       * @see #serverVersion
       * @since 1.1.00
       */
      public boolean isPractice;
  
      /**
       * true if the game's only players are bots, no humans.  Useful for bot AI experiments.
       * Not sent over the network: Must be set at server, or set at bot client at start of game.
       *<P>
       * This flag should be set by the server when creating the game.  If a human observer exits
       * a game with this flag, the game should continue play unless its state is {@link #OVER}.
       * @since 2.0.00
       * @see soc.robot.SOCRobotBrain#BOTS_ONLY_FAST_PAUSE_FACTOR
       * @see soc.server.SOCGameHandler#DESTROY_BOT_ONLY_GAMES_WHEN_OVER
       */
      public boolean isBotsOnly;
  
      /**
       * True once any player has built a city.
       * Used with house-rule game option {@code "N7C"}.
       * @since 1.1.19
       */
      private boolean hasBuiltCity;
  
      /**
       * Listener for scenario events on the {@link #hasSeaBoard large sea board}, or null.
       * Package access for read-only use by {@link SOCPlayer}.
       * @since 2.0.00
       */
      SOCGameEventListener gameEventListener;
  
      /**
       * For use at server; are there clients connected which aren't at the latest version?
       * @since 1.1.00
       */
      public boolean hasOldClients;
  
      /**
       * For use at server; lowest and highest version of connected clients.
       * @since 1.1.00
       */
      public int clientVersionLowest, clientVersionHighest;
  
      /**
       * For use at server; lowest version of client which can connect to
       * this game (based on game options/features added in a given version),
       * or -1 if unknown.
       *<P>
       * Calculated by {@link SOCVersionedItem#itemsMinimumVersion(Map)}.
       * Format is the internal integer format, see {@link soc.util.Version#versionNumber()}.
       * Value may sometimes be too low at client, see {@link #getClientVersionMinRequired()} for details.
       * @see #clientFeaturesRequired
       * @since 1.1.06
       */
      private int clientVersionMinRequired;
  
      /**
       * For use at server; optional client features needed to connect to this game,
       * or {@code null} if none.
       * @see #clientVersionMinRequired
       * @since 2.0.00
       */
      private SOCFeatureSet clientFeaturesRequired;
  
      /**
       * For use at server for i18n; does this game have any members (players or observers)
       * with a locale different than {@link #getOwnerLocale()}?
       * Initially false, set true in {@code SOCGameListAtServer.addMember} if needed.
       * @since 2.0.00
       */
      public boolean hasMultiLocales;
  
      /**
       * Are we in the 'free placement' debug mode?
       * See {@link #isDebugFreePlacement()} for more details.
       * @since 1.1.12
       */
      private boolean debugFreePlacement;
  
      /**
       * Have we placed pieces in {@link #debugFreePlacement}
       * during initial placement?  Set in {@link #putPiece(SOCPlayingPiece)}.
       * @since 1.1.12
       */
      private boolean debugFreePlacementStartPlaced;
  
      /**
       * true if the game came from a board reset
       * @since 1.1.00
       */
      private boolean isFromBoardReset;
  
      /**
       * For the server's use, if a reset is in progress, this holds the reset data
       * until all robots have left (new game state is {@link #READY_RESET_WAIT_ROBOT_DISMISS}).
       * This field is null except within the newly-created game object during reset.
       * @since 1.1.07
       */
      public transient SOCGameBoardReset boardResetOngoingInfo;
  
      /**
       * If a board reset vote is active, player number who requested the vote.
       * All human players must vote unanimously, or board reset is rejected.
       * -1 if no vote is active.
       * Synchronize on {@link #boardResetVotes} before reading or writing.
       * @since 1.1.00
       */
      private int boardResetVoteRequester;
  
      /**
       * If a board reset vote is active, votes are recorded here.
       * Values: {@link #VOTE_NONE}, {@link #VOTE_YES}, {@link #VOTE_NO}.
       * Indexed 0 to SOCGame.MAXPLAYERS-1.
       * Synchronize on this object before reading or writing.
       * @since 1.1.00
       */
      private int boardResetVotes[];
  
      /**
       * If a board reset vote is active, we're waiting to receive this many more votes.
       * All human players vote, except the vote requester. Robots do not vote.
       * Synchronize on {@link #boardResetVotes} before reading or writing.
       * When the vote is complete, or before the first vote has begun, this is 0.
       * Set in resetVoteBegin, resetVoteRegister. Cleared in resetVoteClear.
       * @since 1.1.00
       */
      private int boardResetVotesWaiting;
  
      /**
       * The game board.
       *<P>
       * If {@link #hasSeaBoard}, can always be cast to {@link SOCBoardLarge}.
       */
      private SOCBoard board;
  
      /**
       * the game options ({@link SOCGameOption}), or null
       * @see #knownOpts
       * @since 1.1.07
       */
      private final SOCGameOptionSet opts;
  
      /**
       * All Known Options, for {@link #opts} validation and adding options from scenario if present.
       * Not null unless {@link #opts} is null.
       *<P>
       * At client, practice games use a different set of Known Options than games being played on a server
       * (whose options may vary by version and server config).
       *
       * @since 2.5.00
       */
      private final SOCGameOptionSet knownOpts;
  
      /**
       * the players; never contains a null element during play, use {@link #isSeatVacant(int)}
       * to see if a position is occupied.  Length is {@link #maxPlayers}.
       *<P>
       * If the game is reset or restarted by {@link #resetAsCopy()},
       * the new game gets new player objects, not the ones in this array.
       *<P>
       * Contains nulls after {@link #destroyGame()} is called.
       *
       * @see #currentPlayerNumber
       */
      private final SOCPlayer[] players;
  
      /**
       * State of each player number's seat: {@link #OCCUPIED}, {@link #VACANT}, etc.
       * Length is {@link #maxPlayers}.
       * @see #seatLocks
       */
      private int[] seats;
  
      /**
       * The lock state for each player number's seat. Length is {@link #maxPlayers}.
       * @see #seats
       */
      private SeatLockState[] seatLocks;
  
      /**
       * The seat number of the current player within {@link #players}[],
       * or -1 if the game isn't started yet.
       * @see #specialBuildPhase_afterPlayerNumber
       */
      private int currentPlayerNumber;
  
      /**
       * the first player to place a settlement
       */
      private int firstPlayerNumber;
  
      /**
       * the last player to place the first settlement
       */
      private int lastPlayerNumber;
  
      /**
       * The standard {@code maxPlayers} is 4 for the original classic game,
       * or 6 if this game is on the 6-player board, with corresponding rules.
       * The value of game option {@code "PL"} may be 2, 3, 4, 5, or 6, but
       * the {@code maxPlayers} field will be 4 or 6.
       *<P>
       * The 6-player extensions are orthogonal to other board type/expansion flags such as {@link #hasSeaBoard};
       * one doesn't imply or exclude the other.
       *
       * @see #getAvailableSeatCount()
       * @see #getPlayerCount()
       * @since 1.1.08
       */
      public final int maxPlayers;
  
      /**
       * Is this game played on the {@link SOCBoardLarge} large board / sea board?
       * If true, our board's {@link SOCBoard#getBoardEncodingFormat()}
       * must be {@link SOCBoard#BOARD_ENCODING_LARGE}.
       * When {@code hasSeaBoard}, {@link #getBoard()} can always be cast to {@link SOCBoardLarge}.
       *<P>
       * The 6-player extensions ({@link #maxPlayers} == 6) are orthogonal to {@code hasSeaBoard}
       * or other board types/expansions; one doesn't imply or exclude the other.
       *<P>
       * In most scenarios the sea board has a pirate ship that can be moved instead of
       * the robber.  See game states {@link #WAITING_FOR_ROBBER_OR_PIRATE} and {@link #PLACING_PIRATE}.
       * @since 2.0.00
       */
      public final boolean hasSeaBoard;
  
      /**
       * the current dice result. -1 at start of game, 0 during player's turn before roll (state {@link #ROLL_OR_CARD}).
       * @see #getCurrentDice()
       * @see #currentRoll
       * @see #hasRolledSeven
       */
      private int currentDice;
  
      /**
       * The current dice result, including any scenario items such as
       * {@link SOCVillage#distributeCloth(SOCGame, RollResult)} results.
       * This is the object returned from {@link #rollDice()} each turn.
       * @since 2.0.00
       * @see #currentDice
       */
      private RollResult currentRoll;
  
      /**
       * Has a 7 been rolled yet in this game?
       * See {@link #hasRolledSeven()} for details.
       * @see #currentDice
       * @since 2.5.00
       */
      private boolean hasRolledSeven;
  
      /**
       * The most recent {@link #moveRobber(int, int)} or {@link #movePirate(int, int)} result.
       * Used at server only.
       * @since 2.0.00
       */
      private SOCMoveRobberResult robberResult;
  
      /**
       * the current game state
       *<P>
       * Instead of directly setting <tt>gameState = {@link #OVER}</tt>,
       * code should call {@link #setGameStateOVER()}
       * to also set {@link #finalDurationSeconds}.
       */
      private int gameState;
  
      /**
       * The saved game state; used in only a few places, where a state can happen from different start states.
       * Not set every time the game state changes.
       *<P>
       * oldGameState is read in these states:
       *<UL>
       *<LI> {@link #STARTS_WAITING_FOR_PICK_GOLD_RESOURCE}:
       *        After the resource is picked, oldGameState will be the starting state for
       *        {@link #advanceTurnStateAfterPutPiece()}.
       *<LI> {@link #PLACING_ROAD}, {@link #PLACING_SETTLEMENT}, {@link #PLACING_CITY}, {@link #PLACING_SHIP}:
       *        Unless <tt>oldGameState</tt> is {@link #SPECIAL_BUILDING},
       *        {@link #advanceTurnStateAfterPutPiece()} will set the state to {@link #PLAY1}.
       *        So will {@link #cancelBuildRoad(int)}, {@link #cancelBuildSettlement(int)}, etc.
       *<LI> {@link #PLACING_INV_ITEM}:
       *        {@code oldGameState} == {@link #PLAY1} or {@link #SPECIAL_BUILDING},
       *        same advance/cancel logic as other {@code PLACING_} states mentioned above.
       *<LI> {@link #PLACING_ROBBER}, {@link #WAITING_FOR_ROBBER_OR_PIRATE}:
       *        <tt>oldGameState</tt> = {@link #PLAY1} or {@link #OVER}
       *<LI> {@link #WAITING_FOR_ROB_CHOOSE_PLAYER}, {@link #PLACING_PIRATE}, {@link #WAITING_FOR_ROB_CLOTH_OR_RESOURCE}
       *<LI> {@link #WAITING_FOR_DISCOVERY} in {@link #playDiscovery()}, {@link #doDiscoveryAction(SOCResourceSet)}
       *<LI> {@link #WAITING_FOR_MONOPOLY} in {@link #playMonopoly()}, {@link #doMonopolyAction(int)}
       *<LI> {@link #WAITING_FOR_PICK_GOLD_RESOURCE}:
       *        oldGameState holds the <B>next</B> state to go to when all players are done picking resources.
       *        After picking gold from a dice roll, this will usually be {@link #PLAY1}.
       *        Sometimes will be {@link #PLACING_FREE_ROAD2} or {@link #SPECIAL_BUILDING}.
       *        Can be {@link #ROLL_OR_CARD} in scenario {@link SOCGameOptionSet#K_SC_PIRI SC_PIRI}:
       *        See {@link #pickGoldHexResources(int, SOCResourceSet)} and {@link #rollDice_update7gameState()}.
       *<LI> {@link #LOADING}, {@link #LOADING_RESUMING}:
       *        Holds the actual game state, to be resumed once optional constraints are met and all bots have joined.
       *</UL>
       * Also used if the game board was reset: Holds the state before the reset.
       */
      private int oldGameState;
  
      /**
       * If true, and if state is {@link #PLACING_ROBBER}, {@link #PLACING_PIRATE},
       * or {@link #WAITING_FOR_ROBBER_OR_PIRATE},
       * the robber or pirate is being moved because a knight card
       * has just been played: {@link #playKnight()}.
       * If {@link #forceEndTurn()} is called, the knight card
       * should be returned to the player's inventory.
       *
       * @see #robberyWithPirateNotRobber
       * @see #playingRoadBuildingCardForLastRoad
       * @since 1.1.00
       */
      private boolean placingRobberForKnightCard;
  
      /**
       * If true, and state is {@link #PLACING_FREE_ROAD2}, the Road Building dev card was played when
       * player had only 1 road or ship remaining: {@link #playRoadBuilding()}.
       * Checked by {@link #doesCancelRoadBuildingReturnCard()}.
       * If {@link #cancelBuildRoad(int)}, {@link #cancelBuildShip(int)}, {@link #endTurn()} or
       * {@link #forceEndTurn()} is called, the road building card should be returned to the player's inventory.
       *
       * @see #placingRobberForKnightCard
       * @since 2.5.00
       */
      private boolean playingRoadBuildingCardForLastRoad;
  
      /**
       * If true, this turn is being ended. Controller of game (server) should call {@link #endTurn()}
       * whenever possible.  Usually set when we have called {@link #forceEndTurn()}, and
       * forced the current player to discard randomly, and are waiting for other players
       * to discard in gamestate {@link #WAITING_FOR_DISCARDS}.  Once all players have
       * discarded, the turn should be ended.
       * @see #forceEndTurn()
       * @since 1.1.00
       */
      private boolean forcingEndTurn;
  
      /**
       * If true, it's a 6-player board and at least one player has requested to build
       * during the Special Building Phase that occurs between turns.
       * @see #specialBuildPhase_afterPlayerNumber
       * @since 1.1.08
       */
      private boolean askedSpecialBuildPhase;
  
      /**
       * During the 6-player board's Special Building Phase, the player number whose
       * normal turn (roll, place, etc) has just ended.
       * Game state is {@link #SPECIAL_BUILDING}.
       * See {@link #getSpecialBuildingPlayerNumberAfter()} for gameplay details.
       * @see #askedSpecialBuildPhase
       * @since 1.1.08
       */
      private int specialBuildPhase_afterPlayerNumber;
  
      /**
       * the player with the largest army, or -1 if none
       */
      private int playerWithLargestArmy;
  
      /**
       * To remember last {@link #playerWithLargestArmy} during
       * {@link #saveLargestArmyState()} / {@link #restoreLargestArmyState()}.
       */
      private int oldPlayerWithLargestArmy;
  
      /**
       * the player with the longest road or trade route, or -1 if none
       */
      private int playerWithLongestRoad;
  
      /**
       * used to restore the LR player
       */
      Stack<SOCOldLRStats> oldPlayerWithLongestRoad;
  
      /**
       * the player declared winner, if gamestate == OVER; otherwise -1
       * @since 1.1.00
       */
      private int playerWithWin;
  
      /**
       * The number of development cards remaining in {@link #devCardDeck};
       * {@code numDevCards - 1} is index of the next card to buy for {@link #buyDevCard()}.
       * @see #getNumDevCards()
       */
      private int numDevCards;
  
      /**
       * The development card deck's remaining and already-drawn cards.
       * Each element is a dev card type from {@link SOCDevCardConstants}.
       * {@link #numDevCards} counts down to track the next card to buy for {@link #buyDevCard()}.
       */
      private int[] devCardDeck;
  
      /**
       * Game's {@link SOCSpecialItem}s, if any, by type.
       * This is not the union of each player's {@code spItems}, but a separately maintained Map of item lists.
       * See getter/setter javadocs for details on type keys and rationale for lack of synchronization.
       * ArrayList is used to guarantee we can store null items.
       *<P>
       * Initialized at server and client in {@link #updateAtBoardLayout()} using
       * {@link SOCSpecialItem#makeKnownItem(String, int)}.
       *
       * @since 2.0.00
       */
      private HashMap<String, ArrayList<SOCSpecialItem>> spItems;
  
      /**
       * used to generate random numbers
       */
      private Random rand = new Random();
  
      /**
       * used to track if there were any player subs
       */
      boolean allOriginalPlayers;
  
      /**
       * Time when this game was created, or null if not {@link #active} when created.
       * Used by {@link #getStartTime()} and {@link #getDurationSeconds()}.
       * @see #lastActionTime
       * @see #expiration
       * @see #finalDurationSeconds
       */
      Date startTime;
  
      /**
       * If game is {@link SOCGame#OVER}, its {@link #getDurationSeconds()} when state became {@code OVER}.
       * Otherwise 0.
       * @since 2.3.00
       */
      private int finalDurationSeconds;
  
      /**
       * Expiration time for this game in milliseconds
       * (system clock time, not a duration from {@link #startTime});
       * Same format as {@link System#currentTimeMillis()}.
       * @see #startTime
       * @see #hasWarnedExpir
       */
      long expiration;
  
      /**
       * Has the server warned game's member clients that this game will expire soon?
       * See {@link #hasWarnedExpiration()} for details.
       * @see #expiration
       * @since 1.2.01
       */
      private boolean hasWarnedExpir;
  
      /**
       * The last time a game action happened; can be used to check for game inactivity.
       * Updated in {@link #updateAtTurn()}, {@link #putPiece(SOCPlayingPiece)},
       * and a few other game action methods.
       *<P>
       * Same format as {@link System#currentTimeMillis()}.
       * The server can set this field to 0 to tell itself to end a turn soon, but
       * otherwise the value should be a recent time.
       *<P>
       * At the end of a game, the server may increase this value by
       * {@link soc.server.SOCGameListAtServer#GAME_TIME_EXPIRE_MINUTES}
       * in order to remove it from the {@link soc.server.SOCGameTimeoutChecker}
       * run loop.
       *
       * @see #getStartTime()
       * @see #getExpiration()
       * @since 1.1.11
       */
      public long lastActionTime;
  
      /**
       * Used at server; was the most recent player action a bank trade?
       * If true, allow the player to undo that trade.
       * Updated whenever {@link #lastActionTime} is updated.
       *<P>
       * TODO: Consider lastActionType instead, it's more general.
       * @since 1.1.13
       */
      private boolean lastActionWasBankTrade;
  
      /**
       * Is the current robbery using the pirate ship, not the robber?
       * If true, victims will be based on adjacent ships, not settlements/cities.
       * Set in {@link #chooseMovePirate(boolean)}, {@link #movePirate(int, int)}, {@link #moveRobber(int, int)},
       * and other places that set gameState to {@link #PLACING_ROBBER} or {@link #PLACING_PIRATE}.
       *
       * @see #getRobberyPirateFlag()
       * @see #placingRobberForKnightCard
       * @since 2.0.00
       */
      private boolean robberyWithPirateNotRobber;
          // TODO: Consider refactor to create lastActionType instead, it's more general
  
      /**
       * Has the current player moved a ship already this turn?
       * Valid only when {@link #hasSeaBoard}.
       * @since 2.0.00
       */
      private boolean movedShipThisTurn;
  
      /**
       * List of ship edge coordinates placed this turn.
       * A ship cannot be placed and moved on the same turn.
       * Null when not {@link #hasSeaBoard}.
       * @see #canMoveShip(int, int)
       * @since 2.0.00
       */
      private Vector<Integer> shipsPlacedThisTurn;
  
      /**
       * The special inventory item currently being placed in state {@link #PLACING_INV_ITEM}, or null.
       * Can be set with {@link #setPlacingItem(SOCInventoryItem)} before or during that state.
       * @since 2.0.00
       */
      private SOCInventoryItem placingItem;
  
      /**
       * The number of normal turns; see {@link #getTurnCount()} for details.
       * @since 1.1.07
       */
      private int turnCount;
  
      /**
       * The number of normal rounds (each player has 1 turn per round, after initial placements), including this round.
       *  for gameoption N7: Roll no 7s during first # rounds.
       *  This is 0 during initial piece placement, and 1 when the first player is about to
       *  roll dice for the first time.  It becomes 2 when that first player's turn begins again.
       *  updated in {@link #updateAtTurn()}.
       * @since 1.1.07
       */
      private int roundCount;
  
      /**
       * create a new, active game
       *
       * @param gameName  the name of the game.  For network message safety, must not contain
       *           control characters, {@link SOCMessage#sep_char}, or {@link SOCMessage#sep2_char}.
       *           This is enforced by calling {@link SOCMessage#isSingleLineAndSafe(String)}.
       * @see #SOCGame(String, SOCGameOptionSet, SOCGameOptionSet)
       */
      public SOCGame(final String gameName)
      {
          this(gameName, true, null, null);
      }
  
      /**
       * create a new, active game with options and optionally a scenario (game option {@code "SC"}).
       *
       * @param gameName  the name of the game.  For network message safety, must not contain
       *           control characters, {@link SOCMessage#sep_char}, or {@link SOCMessage#sep2_char}.
       *           This is enforced by calling {@link SOCMessage#isSingleLineAndSafe(String)}.
       * @param op game's {@link SOCGameOption}s if any; otherwise null.
       *           Will validate options and include optional scenario's {@link SOCScenario#scOpts} by calling
       *           {@link SOCGameOptionSet#adjustOptionsToKnown(SOCGameOptionSet, boolean, SOCFeatureSet)}
       *           with <tt>doServerPreadjust</tt> false,
       *           and set game's minimum version by calling
       *           {@link SOCVersionedItem#itemsMinimumVersion(Map)}.
       *           <P>
       *           When creating a game at the server, {@code op} must already be validated by calling
       *           {@link SOCGameOptionSet#adjustOptionsToKnown(SOCGameOptionSet, boolean, SOCFeatureSet)}
       *           with <tt>doServerPreadjust</tt> true.
       *           That call is also needed to add any game options from scenario ({@code "SC"}) if present.
       * @param knownOpts  All Known Options, for {@code op} validation and adding options from scenario if present;
       *           not null unless {@code op} is null
       * @throws IllegalArgumentException if op contains unknown options,
       *           or {@code knownOpts} is null but {@code op} is non-null
       * @since 1.1.07
       */
      public SOCGame(final String gameName, final SOCGameOptionSet op, final SOCGameOptionSet knownOpts)
          throws IllegalArgumentException
      {
          this(gameName, true, op, knownOpts);
      }
  
      /**
       * create a new game that can be ACTIVE or INACTIVE
       *
       * @param gameName  the name of the game.  For network message safety, must not contain
       *           control characters, {@link SOCMessage#sep_char}, or {@link SOCMessage#sep2_char}.
       *           This is enforced by calling {@link SOCMessage#isSingleLineAndSafe(String)}.
       * @param isActive  true if this is an active game, false for inactive
       * @throws IllegalArgumentException if game name fails
       *           {@link SOCMessage#isSingleLineAndSafe(String)}. This check was added in 1.1.07.
       */
      public SOCGame(final String gameName, final boolean isActive)
          throws IllegalArgumentException
      {
          this(gameName, isActive, null, null);
      }
  
      /**
       * create a new game that can be ACTIVE or INACTIVE, and have options
       * and optionally a scenario (game option {@code "SC"}).
       *
       * @param gameName  the name of the game.  For network message safety, must not contain
       *           control characters, {@link SOCMessage#sep_char}, or {@link SOCMessage#sep2_char}.
       *           This is enforced by calling {@link SOCMessage#isSingleLineAndSafe(String)}.
       * @param isActive  true if this is an active game, false for inactive
       * @param op if game has options, its set of {@link SOCGameOption}s; otherwise null.
       *           Will validate options and include optional scenario's {@link SOCScenario#scOpts} by calling
       *           {@link SOCGameOptionSet#adjustOptionsToKnown(SOCGameOptionSet, boolean, SOCFeatureSet)}
       *           with <tt>doServerPreadjust</tt> false,
       *           and set game's minimum version by calling
       *           {@link SOCVersionedItem#itemsMinimumVersion(Map)}.
       *           <P>
       *           New game will use {@code op}, not make a copy of {@code op}.
       *           <P>
       *           When creating a game at the server, {@code op} must already be validated by calling
       *           {@link SOCGameOptionSet#adjustOptionsToKnown(SOCGameOptionSet, boolean, SOCFeatureSet)}
       *           with <tt>doServerPreadjust</tt> true.
       *           That call is also needed to add any game options from scenario ({@code "SC"}) if present.
       * @param knownOpts  All Known Options, for {@code op} validation and adding options from scenario if present;
       *           not null unless {@code op} is null
       * @throws IllegalArgumentException if op contains unknown options, or if game name
       *             fails {@link SOCMessage#isSingleLineAndSafe(String)},
       *             or {@code knownOpts} is null but {@code op} is non-null
       * @since 1.1.07
       */
      public SOCGame
          (final String gameName, final boolean isActive, final SOCGameOptionSet op, final SOCGameOptionSet knownOpts)
          throws IllegalArgumentException
      {
          // For places to initialize fields, see also resetAsCopy().
  
          if (! SOCMessage.isSingleLineAndSafe(gameName))
              throw new IllegalArgumentException("gameName");
  
          active = isActive;
          inUse = false;
          name = gameName;
          if (op != null)
          {
              if (knownOpts == null)
                  throw new IllegalArgumentException("knownOpts");
  
              // apply options from scenario, if any:
              {
                  final Map<String, String> optProblems = op.adjustOptionsToKnown(knownOpts, false, null);
                  if (optProblems != null)
                  {
                      StringBuilder sb = new StringBuilder("op: unknown option(s): ");
                      DataUtils.mapIntoStringBuilder(optProblems, sb, null, "; ");
                      throw new IllegalArgumentException(sb.toString());
                  }
              }
  
              hasSeaBoard = op.isOptionSet("SBL");
              final boolean wants6board = op.isOptionSet("PLB");
              final int maxpl = op.getOptionIntValue("PL", 4, false);
              if (wants6board || (maxpl > 4))
                  maxPlayers = MAXPLAYERS;  // == 6
              else
                  maxPlayers = 4;
              vp_winner = op.getOptionIntValue("VP", VP_WINNER_STANDARD, true);
              hasScenarioWinCondition = op.isOptionSet(SOCGameOptionSet.K_SC_CLVI)
                  || op.isOptionSet(SOCGameOptionSet.K_SC_PIRI)
                  || op.isOptionSet(SOCGameOptionSet.K_SC_WOND);
              clientVersionMinRequired = SOCVersionedItem.itemsMinimumVersion(op.getAll());
          } else {
              maxPlayers = 4;
              hasSeaBoard = false;
              vp_winner = VP_WINNER_STANDARD;
              hasScenarioWinCondition = false;
              clientVersionMinRequired = -1;
          }
          this.knownOpts = knownOpts;
  
          if (boardFactory == null)
              boardFactory = new SOCBoard.DefaultBoardFactory();
          board = boardFactory.createBoard(op, hasSeaBoard, maxPlayers);
          opts = op;
  
          players = new SOCPlayer[maxPlayers];
          seats = new int[maxPlayers];
          seatLocks = new SeatLockState[maxPlayers];
          boardResetVotes = new int[maxPlayers];
          spItems = new HashMap<String, ArrayList<SOCSpecialItem>>();
  
          for (int i = 0; i < maxPlayers; i++)
          {
              players[i] = new SOCPlayer(i, this);
              seats[i] = VACANT;
              seatLocks[i] = SeatLockState.UNLOCKED;
          }
  
          currentPlayerNumber = -1;
          firstPlayerNumber = -1;
          currentDice = -1;
          currentRoll = new RollResult();
          playerWithLargestArmy = -1;
          playerWithLongestRoad = -1;
          boardResetVoteRequester = -1;
          playerWithWin = -1;
          gameState = NEW;
          turnCount = 0;
          roundCount = 0;
          forcingEndTurn = false;
          askedSpecialBuildPhase = false;
          placingRobberForKnightCard = false;
          oldPlayerWithLongestRoad = new Stack<SOCOldLRStats>();
          lastActionWasBankTrade = false;
          movedShipThisTurn = false;
          if (hasSeaBoard)
              shipsPlacedThisTurn = new Vector<Integer>();
  
          if (maxPlayers > 4)
              numDevCards = NUM_DEVCARDS_6PLAYER;
          else
              numDevCards = NUM_DEVCARDS_STANDARD;
  
          if (active)
              startTime = new Date();
          lastActionTime = System.currentTimeMillis();
      }
  
      /**
       * Take the synchronization monitor for this game.
       * When done, release it with {@link #releaseMonitor()}.
       */
      public synchronized void takeMonitor()
      {
          //D.ebugPrintln("TAKE MONITOR");
          while (inUse)
          {
              try
              {
                  wait(1000);  // timeout to help avoid deadlock
              }
              catch (InterruptedException e)
              {
                  System.err.println("EXCEPTION IN takeMonitor() -- " + e);
              }
          }
  
          inUse = true;
      }
  
      /**
       * release the monitor for this game
       * @see #takeMonitor()
       */
      public synchronized void releaseMonitor()
      {
          //D.ebugPrintln("RELEASE MONITOR");
          inUse = false;
          this.notify();
      }
  
      /**
       * For saving a game snapshot, get the values of several private boolean fields:
       *<UL>
       * <LI> placingRobberForKnightCard
       * <LI> robberyWithPirateNotRobber (also available from {@link #getRobberyPirateFlag()})
       * <LI> askedSpecialBuildPhase
       * <LI> movedShipThisTurn
       * <LI> playingRoadBuildingCardForLastRoad (added in v2.5.00;
       *      related to {@link #doesCancelRoadBuildingReturnCard()})
       *</UL>
       * For some other fields to save, see
       * {@link #setFieldsForLoad(List, int, List, boolean, boolean, boolean, boolean, boolean)}.
       *
       * @return an array with the current values of those fields, in the order listed here
       * @since 2.3.00
       */
      public final boolean[] getFlagFieldsForSave()
      {
          return new boolean[]
          {
              placingRobberForKnightCard, robberyWithPirateNotRobber,
              askedSpecialBuildPhase, movedShipThisTurn,
              playingRoadBuildingCardForLastRoad
          };
      }
  
      /**
       * To help load a saved game snapshot, set the values of the private fields
       * previously returned from {@link #getFlagFieldsForSave()}, along with
       * values saved from some other getters where mentioned in parameter javadocs.
       *
       * @param cards  Deck, same format as {@link #getDevCardDeck()} but as {@link List} instead of {@code int[]}.
       *     Contents will be copied. Can be empty, but not null.
       * @param oldGameState  State from {@link #getOldGameState()}
       * @param shipsPlacedThisTurn  Ships from {@link #getShipsPlacedThisTurn()}. May be null or empty.
       * @param placingRobberForKnightCard
       * @param robberyWithPirateNotRobber
       * @param askedSpecialBuildPhase
       * @param movedShipThisTurn
       * @throws IllegalArgumentException if {@code cards} is null
       * @since 2.3.00
       */
      public void setFieldsForLoad
          (final List<Integer> cards, final int oldGameState, final List<Integer> shipsPlacedThisTurn,
           final boolean placingRobberForKnightCard, final boolean robberyWithPirateNotRobber,
           final boolean askedSpecialBuildPhase, final boolean movedShipThisTurn,
           final boolean playingRoadBuildingCardForLastRoad)
          throws IllegalArgumentException
      {
          if (cards == null)
              throw new IllegalArgumentException("cards");
  
          final int L = cards.size();
          if ((devCardDeck == null) || (L > devCardDeck.length))
              devCardDeck = new int[L];
          numDevCards = L;
          for (int i = 0; i < L; ++i)
              devCardDeck[i] = cards.get(i);
  
          this.oldGameState = oldGameState;
  
          if (shipsPlacedThisTurn == null)
          {
              if (this.shipsPlacedThisTurn != null)
                  this.shipsPlacedThisTurn.clear();
          } else {
              if (this.shipsPlacedThisTurn == null)
                  this.shipsPlacedThisTurn = new Vector<>(shipsPlacedThisTurn);
              else
                  synchronized (this.shipsPlacedThisTurn)
                  {
                      this.shipsPlacedThisTurn.clear();
                      this.shipsPlacedThisTurn.addAll(shipsPlacedThisTurn);
                  }
          }
  
          this.placingRobberForKnightCard = placingRobberForKnightCard;
          this.robberyWithPirateNotRobber = robberyWithPirateNotRobber;
          this.askedSpecialBuildPhase = askedSpecialBuildPhase;
          this.movedShipThisTurn = movedShipThisTurn;
          this.playingRoadBuildingCardForLastRoad = playingRoadBuildingCardForLastRoad;
      }
  
      /**
       * @return allOriginalPlayers
       * @see #hasHumanPlayers()
       */
      public boolean allOriginalPlayers()
      {
          return allOriginalPlayers;
      }
  
      /**
       * Does this game contain any human players?
       * @return  True if at least one non-vacant seat has
       *          a human player (! {@link SOCPlayer#isRobot()}).
       * @see #allOriginalPlayers()
       * @since 1.1.18
       */
      public boolean hasHumanPlayers()
      {
          for (int i = 0; i < maxPlayers; ++i)
          {
              if (isSeatVacant(i))
                  continue;
              if (! players[i].isRobot())
                  return true;
          }
  
          return false;
      }
  
      /**
       * Get the time at which this game was created.
       * Used only at server.
       *<P>
       * To overwrite this time, call {@link #setTimeSinceCreated(int)}.
       *
       * @return the start time for this game, or null if not active when created
       * @see #getExpiration()
       * @see #getDurationSeconds()
       */
      public Date getStartTime()
      {
          return startTime;
      }
  
      /**
       * Get the time since this game was created, its age in seconds:
       * If game isn't over, the difference between current time and {@link #getStartTime()}.
       * Otherwise, its duration at game-over time.
       * @return  Game duration, rounded to the nearest second, or 0 if {@link #getStartTime()} is {@code null}
       * @see #setTimeSinceCreated(int)
       * @since 2.3.00
       */
      public int getDurationSeconds()
      {
          if ((gameState >= SOCGame.OVER) && (finalDurationSeconds != 0))
              return finalDurationSeconds;
  
          return (startTime != null)
              ? (int) (((System.currentTimeMillis() - startTime.getTime()) + 500) / 1000L)
              : 0;
      }
  
      /**
       * Set how long this game has existed.
       * Overwrites and replaces the times returned by {@link #getStartTime()}
       * and {@link #getDurationSeconds()}.
       * @param ageSeconds Game's new age in seconds; can be 0 but not negative
       * @throws IllegalArgumentException if {@code ageSeconds} &lt; 0
       * @since 2.3.00
       */
      public void setTimeSinceCreated(final int ageSeconds)
          throws IllegalArgumentException
      {
          if (ageSeconds < 0)
              throw new IllegalArgumentException("ageSeconds");
  
          final long t = System.currentTimeMillis() - (1000L * ageSeconds);
          if (startTime == null)
              startTime = new Date(t);
          else
              startTime.setTime(t);
  
          finalDurationSeconds = (gameState >= SOCGame.OVER) ? ageSeconds : 0;
      }
  
      /**
       * Get the expiration time at which this game will be destroyed.
       * Used only at server.
       * @return the expiration time in milliseconds
       *         (system clock time, not a duration from {@link #startTime});
       *         same epoch as {@link java.util.Date#getTime()}.
       *         Not used at client, returns 0.
       * @see #getStartTime()
       * @see #setExpiration(long)
       * @see #hasWarnedExpiration()
       */
      public long getExpiration()
      {
          return expiration;
      }
  
      /**
       * Has the server warned game's member clients that this game will expire soon?
       * All games get at least 1 warning before expiring. Otherwise a local-server game
       * might immediately expire when a sleeping laptop wakes.
       *<P>
       * Used only at server, which calls {@link #setWarnedExpiration()}.
       * @return true if this warning flag is set.
       *     Not used at client, returns false.
       * @see #getExpiration()
       * @since 1.2.01
       */
      public boolean hasWarnedExpiration()
      {
          return hasWarnedExpir;
      }
  
      /**
       * Set the {@link #hasWarnedExpiration()} flag.
       * To clear this flag, call {@link #setExpiration(long)} to change the expiration time.
       * @since 1.2.01
       */
      public void setWarnedExpiration()
      {
          hasWarnedExpir = true;
      }
  
      /**
       * Set or clear the scenario event listener.
       * Used with {@link #hasSeaBoard large sea board} scenario events.
       *<P>
       * Only one listener is allowed.  If you are setting the listener, it must currently be null.
       * @param sel  Listener, or null for none
       * @throws IllegalStateException  If listener already not null, <tt>sel</tt> is not null, and listener is not <tt>sel</tt>
       * @since 2.0.00
       */
      public void setGameEventListener(SOCGameEventListener sel)
          throws IllegalStateException
      {
          if ((sel != null) && (gameEventListener != null) && (gameEventListener != sel))
              throw new IllegalStateException("Listener already " + gameEventListener + ", wants " + sel);
  
          gameEventListener = sel;
      }
  
      /**
       * Set the expiration time at which this game will be destroyed.
       * Also clears the {@link #hasWarnedExpiration()} flag, for use when extending the game,
       * so server can warn again near the new expiration time.
       *<P>
       * Called at server, not client.
       *
       * @param ex  the absolute expiration time in milliseconds
       *            (system clock time, not a duration from {@link #startTime});
       *            same epoch as {@link java.util.Date#getTime()}
       * @see #getExpiration()
       */
      public void setExpiration(final long ex)
      {
          expiration = ex;
          hasWarnedExpir = false;
      }
  
      /**
       * For games at the server, the owner (creator) of the game.
       * Will be the name of a player / server connection.
       * Even if the owner leaves the game, their name may be retained here.
       * @return the owner's player name, or null if {@link #setOwner(String, String)} was never called
       * @since 1.1.10
       * @see #getOwnerLocale()
       */
      public String getOwner()
      {
          return ownerName;
      }
  
      /**
       * For games at the server, set the game owner (creator).
       * Will be the name of a player / server connection.
       * @param gameOwnerName The game owner's player name, or null to clear
       * @param gameOwnerLocale  The game owner's locale, or {@code null} to clear
       * @since 1.1.10
       * @throws IllegalStateException if <tt>gameOwnerName</tt> not null, but the game's owner is already set
       */
      public void setOwner(final String gameOwnerName, final String gameOwnerLocale)
          throws IllegalStateException
      {
          if ((ownerName != null) && (gameOwnerName != null))
              throw new IllegalStateException("owner already set");
          ownerName = gameOwnerName;
          ownerLocale = gameOwnerLocale;
      }
  
      /**
       * For games at the server, get the game owner (creator)'s locale, or {@code null}.
       * Used by {@code SOCGameListAtServer.addMember} to determine whether to set {@link #hasMultiLocales}.
       * @since 2.0.00
       */
      public final String getOwnerLocale()
      {
          return ownerLocale;
      }
  
      /**
       * Add a new player sitting at a certain seat, or sit them again in their same seat (rejoining after a disconnect).
       * Called at server and at client.
       *<P>
       * If the game just started, players are placing their first settlement and road
       * (gamestate &lt; {@link #START2A}), and the new player sits at a vacant seat,
       * check and update the first player number or last player number if necessary.
       * If the new player's {@code pn} is less than {@link #getCurrentPlayerNumber()},
       * they already missed their first settlement and road placement but will get their second one.
       *<P>
       * <B>Note:</B> Once the game has started and everyone already has placed their
       * first settlement and road (gamestate &gt;= {@link #START2A}), no one new
       * should sit down at a vacant seat, they won't have initial placements to receive
       * resources.  This method doesn't know if the seat has always been vacant, or if
       * a robot has just left the game to vacate the seat. So this restriction must be
       * enforced earlier, when the player requests sitting down at a vacant seat or at
       * a robot's position.
       *
       * @param plName  the player's name; must pass {@link SOCMessage#isSingleLineAndSafe(String)}.
       * @param pn    the player's requested player number; the seat number at which they would sit
       * @throws IllegalStateException if player is already sitting in
       *              another seat in this game, or if there are no open seats
       *              (based on seats[pn] == OCCUPIED, and game option "PL" or {@link #maxPlayers})
       *               via {@link #getAvailableSeatCount()}
       * @throws IllegalArgumentException if name fails {@link SOCMessage#isSingleLineAndSafe(String)}.
       *           This exception was added in 1.1.07.
       * @see #isSeatVacant(int)
       * @see #removePlayer(String, boolean)
       */
      public void addPlayer(final String plName, final int pn)
          throws IllegalStateException, IllegalArgumentException
      {
          if (! SOCMessage.isSingleLineAndSafe(plName))
              throw new IllegalArgumentException("plName");
  
          final boolean wasVacant = (seats[pn] == VACANT);
              // but not VACANT_PENDING_REPLACE, so firstPlayer/lastPlayer won't be recalc'd while temporarily vacant
          if (wasVacant)
          {
              if (0 == getAvailableSeatCount())
                  throw new IllegalStateException("Game is full");
          }
          SOCPlayer already = getPlayer(plName);
          if ((already != null) && (pn != already.getPlayerNumber()))
          {
              throw new IllegalStateException("Already sitting in this game");
          }
  
          players[pn].setName(plName);
          seats[pn] = OCCUPIED;
  
          if ((gameState > NEW) && (gameState < OVER))
          {
              if (wasVacant && (gameState < START2A))
              {
                  // Still placing first initial settlement + road; check first/last player number
                  if (pn > lastPlayerNumber)
                      setFirstPlayer(firstPlayerNumber);  // recalc lastPlayerNumber
                  else if (pn < firstPlayerNumber)
                      setFirstPlayer(pn);  // too late for first settlement, but can place their 2nd
              }
  
              allOriginalPlayers = false;
          }
      }
  
      /**
       * remove a player from their seat.
       * Player's name becomes {@code null}.  {@link #isSeatVacant(int) isSeatVacant(playerNum)} becomes true.
       *<P>
       * <b>If they are the current player,</b>
       * call this and then call {@link #canEndTurn(int)}.
       * You'll need to then call {@link #endTurn()} or {@link #forceEndTurn()}.
       *
       * @param plName  the player's name
       * @param hasReplacement  if true (server only), the leaving connection is a bot, and there's a
       *     waiting client who will be told to sit down in this bot's seat, so that isn't really becoming vacant.
       *     To prevent a race condition where a different client could take this seat as bot leaves,
       *     assumes single-threaded processing of incoming messages to this game.
       * @throws IllegalArgumentException if name isn't in this game.
       *           This exception was added in 1.1.07.
       * @see #isSeatVacant(int)
       * @see #addPlayer(String, int)
       */
      public void removePlayer(final String plName, final boolean hasReplacement)
          throws IllegalArgumentException
      {
          SOCPlayer pl = getPlayer(plName);
          if (pl == null)
              throw new IllegalArgumentException("plName");
          pl.setName(null);
          seats[pl.getPlayerNumber()] = (hasReplacement) ? VACANT_PENDING_REPLACE : VACANT;
  
          //D.ebugPrintln("seats["+pl.getPlayerNumber()+"] = VACANT");
      }
  
      /**
       * @return true if the seat is VACANT
       *
       * @param pn the number of the seat
       * @see #getAvailableSeatCount()
       * @see #addPlayer(String, int)
       * @see #removePlayer(String, boolean)
       */
      public boolean isSeatVacant(final int pn)
      {
          return (seats[pn] != OCCUPIED);
      }
  
      /**
       * How many seats are vacant and available for players?
       * Based on {@link #isSeatVacant(int)} and game
       * option "PL" (maximum players) or {@link #maxPlayers}.
       *<P>
       * <B>Note:</B> Once the game has started and everyone already has placed their
       * first settlement and road (gamestate &gt;= {@link #START2A}), no one new
       * should sit down at a vacant seat; see {@link #addPlayer(String, int)}.
       *
       * @return number of available vacant seats
       * @see #isSeatVacant(int)
       * @see #getPlayerCount()
       * @since 1.1.07
       */
      public int getAvailableSeatCount()
      {
          int availSeats;
          if (isGameOptionDefined("PL"))
              availSeats = getGameOptionIntValue("PL");
          else
              availSeats = maxPlayers;
  
          for (int i = 0; i < maxPlayers; ++i)
              if (seats[i] == OCCUPIED)
                  --availSeats;
  
          return availSeats;
      }
  
      /**
       * Get a seat's lock state.
       * @param pn the number of the seat
       * @see #getSeatLocks()
       * @since 2.0.00
       */
      public SeatLockState getSeatLock(final int pn)
      {
          return seatLocks[pn];
      }
  
      /**
       * Get all seats' lock states.
       * @return all seats' lock states; an array indexed by player number, containing all seats in this game
       * @see #getSeatLock(int)
       * @since 2.0.00
       */
      public SeatLockState[] getSeatLocks()
      {
          return seatLocks;
      }
  
      /**
       * Lock or unlock a seat, or mark a bot's seat to be cleared on reset.
       * The meaning of "locked" is different when the game is still forming
       * (gameState {@link #NEW}) versus when the game is active.
       * For details, see the javadocs for {@link SeatLockState#LOCKED},
       * {@link SeatLockState#UNLOCKED UNLOCKED} and {@link SeatLockState#CLEAR_ON_RESET CLEAR_ON_RESET}.
       *<P>
       * For player consistency, seat locks can't be changed while {@link #getResetVoteActive()}
       * in server version 1.1.19 and higher.
       *<P>
       * Before v2.0.00, this was {@code lockSeat(pn)} and {@code unlockSeat(pn)}.
       *
       * @param pn the number of the seat
       * @param sl  the new lock state for this seat
       * @throws IllegalStateException if the game is still forming
       *     but {@code sl} is {@link SeatLockState#CLEAR_ON_RESET},
       *     or if {@link #getResetVoteActive()}
       * @throws IllegalArgumentException if {@code sl} is null
       * @see #setSeatLocks(SeatLockState[])
       * @since 2.0.00
       */
      public void setSeatLock(final int pn, final SeatLockState sl)
          throws IllegalStateException, IllegalArgumentException
      {
          if (sl == null)
              throw new IllegalArgumentException("sl");
          if (isAtServer
              && (getResetVoteActive()
                  || ((sl == SeatLockState.CLEAR_ON_RESET) && (gameState == NEW))))
              throw new IllegalStateException();
  
          seatLocks[pn] = sl;
      }
  
      /**
       * At client, set all seats' locked or unlocked status. Sent from server while joining a game.
       *<P>
       * Since this is client-only, does not perform the server-side state check that
       * {@link #setSeatLock(int, SeatLockState)} does.
       *
       * @param sls  All seats' lock states; an array indexed by player number, containing all seats in this game
       * @throws IllegalArgumentException if {@code sls.length} is not {@link #maxPlayers}
       * @see {@link #setSeatLock(int, SeatLockState)}
       * @since 2.0.00
       */
      public void setSeatLocks(final SeatLockState[] sls)
          throws IllegalArgumentException
      {
          if (sls.length != maxPlayers)
              throw new IllegalArgumentException("length");
  
          for (int pn = 0; pn < sls.length; ++pn)
              seatLocks[pn] = sls[pn];
      }
  
      /**
       * How many players are currently sitting/playing in the game?
       * That is, how many seats are currently occupied by players?
       * Useful for house rule game option {@code "PLP"} on 6-player board.
       *
       * @return  Number of seats where {@link #isSeatVacant(int)} is false;
       *     minimum 0, maximum {@link #maxPlayers}.
       * @see #getAvailableSeatCount()
       * @since 2.3.00
       */
      public int getPlayerCount()
      {
          int n = 0;
          for (int pn = 0; pn < seats.length; ++pn)
              if (seats[pn] == OCCUPIED)
                  ++n;
  
          return n;
      }
  
      /**
       * Get the player object sitting at this position.
       * <B>Does not check for vacant seats:</B>
       * Even if a seat is vacant, its unused {@link SOCPlayer} will be returned.
       * Call {@link #isSeatVacant(int)} to check for vacant seats.
       *
       * @param pn  the player number, in range 0 to {@link #maxPlayers}-1
       * @return the player object for a player id; never null if {@code pn} is in range.
       *     If a player leaves the game and another human or bot sits down at the
       *     now-vacant {@code pn}, the same {@code SOCPlayer} object is reused for the new player.
       * @throws ArrayIndexOutOfBoundsException if {@code pn} is out of range
       * @see #getPlayer(String)
       * @see #getPlayers()
       */
      public SOCPlayer getPlayer(final int pn)
          throws ArrayIndexOutOfBoundsException
      {
          return players[pn];
      }
  
      /**
       * Get the player sitting in this game with this name.
       * @return the player object for a player nickname.
       *   if there is no match, return null
       *
       * @param nn  the nickname
       * @see #getPlayer(int)
       */
      public SOCPlayer getPlayer(final String nn)
      {
          if (nn != null)
          {
              for (int i = 0; i < maxPlayers; i++)
              {
                  if (! isSeatVacant(i))
                  {
                      final SOCPlayer pl = players[i];  // may be null during end-of-game cleanup or reset
                      if ((pl != null) && nn.equals(pl.getName()))
                          return pl;
                  }
              }
          }
  
          return null;
      }
  
      /**
       * Is the current player a robot which has been slow or buggy enough ("stubborn")
       * that their turn has been forced to end several times?
       * @return true if there's a current player and their {@link SOCPlayer#isStubbornRobot()} is true
       * @since 2.0.00
       */
      public boolean isCurrentPlayerStubbornRobot()
      {
          return (currentPlayerNumber >= 0) && players[currentPlayerNumber].isStubbornRobot();
      }
  
      /**
       * @return the name of the game
       * @see #setName(String)
       */
      public String getName()
      {
          return name;
      }
  
      /**
       * Rename this game. Useful for reloading a saved game snapshot.
       * Should do so only before adding to server's game list.
       * @param newName  New name for this game
       * @throws IllegalStateException if {@link #getGameState()} != {@link #LOADING}
       * @throws IllegalArgumentException if {@code newName} fails {@link SOCMessage#isSingleLineAndSafe(String)}
       * @since 2.3.00
       */
      public void setName(final String newName)
          throws IllegalStateException, IllegalArgumentException
      {
          if ((gameState != SOCGame.NEW) && (gameState != SOCGame.LOADING))
              throw new IllegalStateException("gameState");
          if (! SOCMessage.isSingleLineAndSafe(newName))
              throw new IllegalArgumentException("newName");
  
          name = newName;
      }
  
      /**
       * Get this game's {@link SOCGameOption}s, if any.
       *<P>
       * Before v2.5.00 this method returned a <tt>Map&lt;String, SOCGameOption&gt;</tt>.
       *
       * @return this game's options, or null
       * @since 1.1.07
       * @see #isGameOptionDefined(String)
       * @see #isGameOptionSet(String)
       * @see #getGameOptionIntValue(String)
       * @see SOCGameOption#packOptionsToString(Map, boolean, boolean)
       */
      public SOCGameOptionSet getGameOptions()
      {
          return opts;
      }
  
      /**
       * Is this game option contained in the current game's options?
       * @param optKey Name of a {@link SOCGameOption}
       * @return True if option is defined in ths game's options, false otherwise
       * @since 1.1.07
       * @see #isGameOptionSet(String)
       * @see #getGameOptionIntValue(String)
       */
      public boolean isGameOptionDefined(final String optKey)
      {
          return (opts != null) ? opts.containsKey(optKey) : false;
      }
  
      /**
       * Is this boolean-valued game option currently set to true?
       * @param optKey Name of a {@link SOCGameOption} of type {@link SOCGameOption#OTYPE_BOOL OTYPE_BOOL},
       *               {@link SOCGameOption#OTYPE_INTBOOL OTYPE_INTBOOL}
       *               or {@link SOCGameOption#OTYPE_ENUMBOOL OTYPE_ENUMBOOL}
       * @return True if option's boolean value is set, false if not set or not defined in this game's options
       * @since 1.1.07
       * @see #isGameOptionDefined(String)
       * @see #getGameOptionIntValue(String)
       * @see #getGameOptionStringValue(String)
       */
      public boolean isGameOptionSet(final String optKey)
      {
          // OTYPE_* - if a new type is added and it uses a boolean field, update this method's javadoc.
  
          return (opts != null) ? opts.isOptionSet(optKey) : false;
      }
  
      /**
       * What is this integer game option's current value?
       *<P>
       * Does not reference {@link SOCGameOption#getBoolValue()}, only the int value,
       * so this will return a value even if the bool value is false.
       * @param optKey A {@link SOCGameOption} of type {@link SOCGameOption#OTYPE_INT OTYPE_INT},
       *               {@link SOCGameOption#OTYPE_INTBOOL OTYPE_INTBOOL},
       *               {@link SOCGameOption#OTYPE_ENUM OTYPE_ENUM}
       *               or {@link SOCGameOption#OTYPE_ENUMBOOL OTYPE_ENUMBOOL}
       * @return Option's current {@link SOCGameOption#getIntValue() intValue},
       *         or 0 if not defined in this game's options;
       *         OTYPE_ENUM's choices give an intVal in range 1 to n.
       * @since 1.1.07
       * @see #getGameOptionIntValue(String, int, boolean)
       * @see #isGameOptionDefined(String)
       * @see #isGameOptionSet(String)
       * @see #getGameOptionStringValue(String)
       */
      public int getGameOptionIntValue(final String optKey)
      {
          // OTYPE_* - if a new type is added, update this method's javadoc.
  
          return (opts != null) ? opts.getOptionIntValue(optKey) : 0;
      }
  
      /**
       * What is this integer game option's current value?
       *<P>
       * Can optionally reference {@link SOCGameOption#getBoolValue()}, not only the int value.
       * @param optKey A {@link SOCGameOption} of type {@link SOCGameOption#OTYPE_INT OTYPE_INT},
       *               {@link SOCGameOption#OTYPE_INTBOOL OTYPE_INTBOOL},
       *               {@link SOCGameOption#OTYPE_ENUM OTYPE_ENUM}
       *               or {@link SOCGameOption#OTYPE_ENUMBOOL OTYPE_ENUMBOOL}
       * @param defValue  Default value to use if <tt>optKey</tt> not defined
       * @param onlyIfBoolSet  Check the option's {@link SOCGameOption#getBoolValue()} too;
       *               if false, return <tt>defValue</tt>.
       *               Do not set this parameter if the type doesn't use a boolean component.
       * @return Option's current {@link SOCGameOption#getIntValue() intValue},
       *         or <tt>defValue</tt> if not defined in the set of options;
       *         OTYPE_ENUM's and _ENUMBOOL's choices give an intVal in range 1 to n.
       * @since 2.5.00
       * @see #isOptionSet(String)
       * @see #getOptionIntValue(String)
       * @see #getOptionStringValue(String)
       */
      public int getGameOptionIntValue
          (final String optKey, final int defValue, final boolean onlyIfBoolSet)
      {
          // OTYPE_* - if a new type is added, update this method's javadoc.
  
          return (opts != null) ? opts.getOptionIntValue(optKey, defValue, onlyIfBoolSet) : defValue;
      }
  
      /**
       * What is this string game option's current value?
       * @param optKey A {@link SOCGameOption} of type
       *               {@link SOCGameOption#OTYPE_STR OTYPE_STR}
       *               or {@link SOCGameOption#OTYPE_STRHIDE OTYPE_STRHIDE}
       * @return Option's current {@link SOCGameOption#getStringValue() getStringValue}
       *         or null if not defined in this game's options
       * @since 1.1.07
       * @see #isGameOptionDefined(String)
       * @see #isGameOptionSet(String)
       * @see #getGameOptionIntValue(String)
       */
      public String getGameOptionStringValue(final String optKey)
      {
          // OTYPE_* - if a new type is added, update this method's javadoc.
  
          return (opts != null) ? opts.getOptionStringValue(optKey) : null;
      }
  
      /**
       * For use at server; lowest version of client which can connect to
       * this game (based on game options/features added in a given version),
       * or -1 if unknown or if this game has no options.
       * Calculated by {@link SOCVersionedItem#itemsMinimumVersion(Map)}.
       *<P>
       * For options where the minimum version changes with its current value, some
       * option version data is hardcoded in {@link SOCGameOption#getMinVersion(Map)},
       * executed on the server with a newer version than an older client.  So, the
       * version returned may be too low when called at that client.  The server
       * will let the client know if it's too old to join or create a game due
       * to options.
       *
       * @return game version, in same format as {@link soc.util.Version#versionNumber()}.
       * @see #getClientFeaturesRequired()
       * @since 1.1.06
       */
      public int getClientVersionMinRequired()
      {
          return clientVersionMinRequired;
      }
  
      /**
       * At server, get the optional client features needed to connect to this game, if any.
       * Those features are optional for clients to implement, but required for them to join this game.
       * @return this game's required client features, or {@code null} if none
       * @see #getClientVersionMinRequired()
       * @see #setClientFeaturesRequired(SOCFeatureSet)
       * @since 2.0.00
       */
      public SOCFeatureSet getClientFeaturesRequired()
      {
          return clientFeaturesRequired;
      }
  
      /**
       * At server, set the required client features returned by {@link #getClientFeaturesRequired()}, if any.
       * @param feats  New feature set, or {@code null} if no features are required;
       *     do not pass in an empty {@link SOCFeatureSet}
       * @since 2.0.00
       */
      public void setClientFeaturesRequired(SOCFeatureSet feats)
      {
          clientFeaturesRequired = feats;
      }
  
      /**
       * Given its features, can a client join this game? Assumes client's
       * {@link soc.server.SOCClientData#hasLimitedFeats} flag is true, otherwise they could join any game.
       * Calls {@link #checkClientFeatures(SOCFeatureSet, boolean) checkClientFeatures(cliFeats, true)}.
       * @param cliFeats  Client's limited subset of optional features,
       *     from {@link soc.server.SOCClientData#feats}, or {@code null} or empty set if no features
       * @return  True if client can join, false otherwise.
       *     Always true if {@link #getClientFeaturesRequired()} is {@code null}.
       * @since 2.0.00
       */
      public boolean canClientJoin(final SOCFeatureSet cliFeats)
      {
          return (null == checkClientFeatures(cliFeats, true));
      }
  
      /**
       * Check whether a client can join this game, given its feature set.
       * Assumes client's {@link soc.server.SOCClientData#hasLimitedFeats} flag is true,
       * otherwise they could join any game. If this game requires any client features, calls
       * {@link SOCFeatureSet#findMissingAgainst(SOCFeatureSet, boolean) cliFeats.findMissingAgainst(gameFeats, stopAtFirstFound)}.
       * @param cliFeats  Client's limited subset of optional features,
       *     from {@link soc.server.SOCClientData#feats}, or {@code null} or empty set if no features
       * @param stopAtFirstFound  True if caller needs to know only if any features are missing,
       *     but doesn't need the full list. If game's features and {@code cliFeats} are both not null,
       *     will stop checking after finding any missing feature.
       * @return  Null if client can join, otherwise the list of features which
       *     this game requires but are not included in {@code cliFeats}.
       *     Always null if {@link #getClientFeaturesRequired()} is {@code null}.
       *     If {@code stopAtFirstFound}, might return one missing feature instead of the full list.
       * @see #canClientJoin(SOCFeatureSet)
       * @since 2.0.00
       */
      public String checkClientFeatures(final SOCFeatureSet cliFeats, final boolean stopAtFirstFound)
      {
          if (clientFeaturesRequired == null)
              return null;  // anyone can join
  
          if (cliFeats == null)
              return clientFeaturesRequired.getEncodedList();  // cli has no features, this game requires some
  
          return cliFeats.findMissingAgainst(clientFeaturesRequired, stopAtFirstFound);
      }
  
      /**
       * @return whether this game was created by board reset of an earlier game
       * @see #resetAsCopy()
       * @since 1.1.00
       */
      public boolean isBoardReset()
      {
          return isFromBoardReset;
      }
  
      /**
       * Get the game board.
       * When {@link #hasSeaBoard}, {@code getBoard()} can always be cast to {@link SOCBoardLarge}.
       * @return the game board
       */
      public SOCBoard getBoard()
      {
          return board;
      }
  
      /**
       * @return the list of players
       * @see #getPlayer(int)
       */
      public SOCPlayer[] getPlayers()
      {
          return players;
      }
  
      /**
       * set the data for a player
       *
       * @param pn  the number of the player
       * @param pl  the player data
       * @throws IllegalArgumentException if pl is null
       */
      protected void setPlayer(final int pn, SOCPlayer pl)
      {
          if (pl != null)
              players[pn] = pl;
          else
              throw new IllegalArgumentException("null pl");
      }
  
      /**
       * @return the number of the current player, or -1 if the game isn't started yet
       */
      public int getCurrentPlayerNumber()
      {
          return currentPlayerNumber;
      }
  
      /**
       * Set the number of the current player, and check for winner.
       * If you want to update other game status, call {@link #updateAtTurn()} afterwards.
       * Called only at client: Server instead calls {@link #endTurn()}
       * or {@link #advanceTurn()}.
       * Check for gamestate {@link #OVER} after calling setCurrentPlayerNumber.
       * This is needed because a player can win only during their own turn;
       * if they reach winning points ({@link #vp_winner} or more) during another
       * player's turn, they don't win immediately.  When it later becomes their turn,
       * and setCurrentPlayerNumber is called, gamestate may become {@link #OVER}.
       *
       * @param pn  the player number; -1 is permitted at client in state {@link #OVER}
       *     or if game deleted or connection to server lost
       * @see #endTurn()
       * @see #checkForWinner()
       */
      public void setCurrentPlayerNumber(final int pn)
      {
          //D.ebugPrintln("SETTING CURRENT PLAYER NUMBER TO "+pn);
          if ((pn >= -1) && (pn < players.length))
          {
              currentPlayerNumber = pn;
              if ((pn >= 0) && ((players[pn].getTotalVP() >= vp_winner) || hasScenarioWinCondition))
                  checkForWinner();
          }
      }
  
      /**
       * During the 6-player board's Special Building Phase, the player number whose
       * normal turn (roll, place, etc) has just ended.
       *<P>
       * The Special Building Phase changes {@link #getCurrentPlayerNumber()}.
       * So, it begins by calling {@link #advanceTurn()} to
       * the next player, and continues clockwise until
       * {@code getCurrentPlayerNumber()} == {@code getSpecialBuildingPlayerNumberAfter()}.
       * At that point, the Special Building Phase is over
       * and it's the next player's turn as usual.
       *
       * @return that player number if game state is {@link #SPECIAL_BUILDING}, -1 otherwise
       * @since 2.3.00
       */
      public int getSpecialBuildingPlayerNumberAfter()
      {
          return (gameState == SPECIAL_BUILDING) ? specialBuildPhase_afterPlayerNumber : -1;
      }
  
      /**
       * For use while reloading a saved game, sets {@link #getSpecialBuildingPlayerNumberAfter()}.
       * This field is ignored except during gameState {@link #SPECIAL_BUILDING}.
       * @param pn  Player number, or -1 to do nothing
       * @throws IllegalArgumentException if {@code pn} &lt; -1 or >= {@link #maxPlayers}
       * @since 2.3.00
       */
      public void setSpecialBuildingPlayerNumberAfter(final int pn)
      {
          if (pn == -1)
              return;
          if ((pn < -1) || (pn >= maxPlayers))
              throw new IllegalArgumentException("pn");
  
          specialBuildPhase_afterPlayerNumber = pn;
      }
  
      /**
       * The number of normal rounds (each player has 1 turn per round, after initial placements), including this round.
       *  This is 0 during initial piece placement, and 1 when the first player is about to
       *  roll dice for the first time.  It becomes 2 when that first player's turn begins again.
       *  @since 1.1.07
       *  @see #setRoundCount(int)
       *  @see #getTurnCount()
       */
      public int getRoundCount()
      {
          return roundCount;
      }
  
      /**
       * Set the number of rounds played, including this one.
       * See {@link #getRoundCount()} for details.
       * Used at client when joining a game in progress.
       * @param count  Round count
       * @since 2.0.00
       */
      public void setRoundCount(final int count)
      {
          roundCount = count;
      }
  
      /**
       * Has any player built a city?
       * Used with house-rule {@link SOCGameOption game option} {@code "N7C"}.
       *<P>
       * No setter is needed: During normal game play, {@link #putPiece(SOCPlayingPiece) putPiece(SOCCity)} will
       * update this flag. If a client joins a game after it's started, the server will send PUTPIECE messages
       * for any cities already on the board.
       * @return  True if {@link #putPiece}({@link SOCCity}) has been called for a non-temporary piece
       * @since 1.1.19
       */
      public boolean hasBuiltCity()
      {
          return hasBuiltCity;
      }
  
      /**
       * Get the current dice total from the most recent roll.
       *  -1 at start of game, 0 during player's turn before roll (state {@link #ROLL_OR_CARD}).
       * @return the current dice result
       * @see #rollDice()
       * @see SOCPlayer#getRolledResources()
       * @see #hasRolledSeven()
       */
      public int getCurrentDice()
      {
          return currentDice;
      }
  
      /**
       * set the current dice result
       *
       * @param dr  the dice result
       */
      public void setCurrentDice(final int dr)
      {
          currentDice = dr;
  
          if (dr == 7)
              hasRolledSeven = true;
      }
  
      /**
       * Has a 7 been rolled yet in this game?
       *<P>
       * Not sent over the network: Accurate at server, or client joining at start of game,
       * but not for a client who joined after game play started.
       *
       * @return true if {@link #rollDice()} at server, or {@link #setCurrentDice(int)} at client,
       *     has encountered a 7
       * @see #getCurrentDice()
       * @since 2.5.00
       */
      public boolean hasRolledSeven()
      {
          return hasRolledSeven;
      }
  
      /**
       * Current game state.  For general information about
       * what states are expected when, please see the javadoc for {@link #NEW}.
       *<P>
       * At the client, a newly joined game has state 0 until the server sends a GAMESTATE message.
       * Keep this in mind when initializing the game's user interface.
       *
       * @return the current game state
       * @see #isInitialPlacement()
       * @see #isSpecialBuilding()
       * @see #getOldGameState()
       */
      public int getGameState()
      {
          return gameState;
      }
  
      /**
       * set the current game state.
       * For general information about what states are expected when,
       * please see the javadoc for {@link #NEW}.
       *<P>
       * If the new state is {@link #OVER} and no playerWithWin yet determined, calls {@link #checkForWinner()};
       * caller should check {@link #getPlayerWithWin()} afterwards.
       *<P>
       * This method is generally called at the client, due to messages from the server
       * based on the server's complete game data.
       *<P>
       * At the client if this is the first time {@code gs} == {@link #ROLL_OR_CARD} during this game,
       * and state is currently in initial placement ({@link #START1A} to {@link #START3B}),
       * calls {@link #updateAtGameFirstTurn()}.
       *
       * @param gs  the game state
       * @see #updateAtTurn()
       */
      public void setGameState(final int gs)
      {
          final boolean cliFirstRegularTurn =
              (gs == ROLL_OR_CARD) && (! isAtServer) && (gameState >= START1A) && (gameState <= START3B);
                  // not if gameState == 0, to prevent updateAtGameFirstTurn() when joining an already-started game
  
          if ((gs >= SOCGame.OVER) && (finalDurationSeconds == 0))
              finalDurationSeconds = getDurationSeconds();
  
          if ((gs == ROLL_OR_CARD) && (gameState == SPECIAL_BUILDING))
              oldGameState = PLAY1;  // Needed for isSpecialBuilding() to work at client
          else
              oldGameState = gameState;
  
          gameState = gs;
  
          if ((gameState == OVER) && (playerWithWin == -1))
              checkForWinner();
  
          if (cliFirstRegularTurn)
              updateAtGameFirstTurn();
      }
  
      /**
       * Set game state to {@link #OVER} and set {@link #finalDurationSeconds} if 0,
       * but don't call {@link #checkForWinner()} like {@link #setGameState(int)} does.
       * This should be called instead of setting <tt>{@link #gameState} = OVER</tt>.
       * @since 2.3.00
       */
      private void setGameStateOVER()
      {
          gameState = OVER;
  
          if (finalDurationSeconds == 0)
              finalDurationSeconds = getDurationSeconds();
      }
  
      /**
       * If the game board was reset, get the old game state.
       *
       * @deprecated Use {@link #getOldGameState()} to get the old game state without usage restrictions
       * @return the old game state
       * @throws IllegalStateException Game state must be RESET_OLD
       *    when called; during normal game play, oldGameState is private.
       * @since 1.1.00
       */
      public int getResetOldGameState() throws IllegalStateException
      {
          if (gameState != RESET_OLD)
              throw new IllegalStateException
                  ("Current state is not RESET_OLD: " + gameState);
  
          return oldGameState;
      }
  
      /**
       * Get the old game state, for saving the game while state is temporarily changed
       * or reference at old copy of game (having current state {@link #RESET_OLD}) after a game reset.
       * @return the old {@link #getGameState()}
       * @since 2.3.00
       */
      public int getOldGameState()
      {
          return oldGameState;
      }
  
      /**
       * Are we in the Initial Placement part of the game?
       * Includes game states {@link #START1A} - {@link #START3B}
       * and {@link #STARTS_WAITING_FOR_PICK_GOLD_RESOURCE}.
       *<P>
       * Returns false if game hasn't started yet (state &lt; {@link #START1A}),
       * or is in normal gameplay or over (state &gt;= {@link #ROLL_OR_CARD}).
       *
       * @return true if in Initial Placement
       * @since 1.1.12
       * @see #isInitialPlacementRoundDone(int)
       */
      public final boolean isInitialPlacement()
      {
          return (gameState >= START1A) && (gameState < ROLL_OR_CARD);
      }
  
      /**
       * During {@link #isInitialPlacement()}, has a round of initial placement finished?
       * First round goes clockwise to place each player's first settlement and road/ship.
       * Second round reverses the direction of play to go counterclockwise for the second
       * settlements and roads/ships. (In some scenarios there's a third round going clockwise again.)
       * Then, the first turn of normal play starts with state {@link #ROLL_OR_CARD}.
       *<P>
       * Useful for prompting a robot player to place its next settlement
       * or roll its first normal turn.
       *<P>
       * True at state change from {@link #START1B} to {@link #START2A},
       * {@link #START2B} to {@link #START3A} or to {@link #ROLL_OR_CARD},
       * {@link #START3B} to {@link #ROLL_OR_CARD}.
       * @param prevState Previous game state, such as {@link #START1B}
       * @return  True if a round has finished
       * @since 2.0.00
       */
      public final boolean isInitialPlacementRoundDone(final int prevState)
      {
          return ((prevState == START1B) && (gameState == START2A))
              || ((prevState == START2B) && (gameState == START3A))
              || (((prevState == START2B) || (prevState == START3B))
                  && (gameState == ROLL_OR_CARD));
      }
  
      /**
       * If true, this turn is being ended. Controller of game should call {@link #endTurn()}
       * whenever possible.  Usually set if we have called {@link #forceEndTurn()}, and
       * forced the current player to discard randomly, and are waiting for other players
       * to discard in gamestate {@link #WAITING_FOR_DISCARDS}.  Once all players have
       * discarded, the turn should be ended.
       * @see #forceEndTurn()
       * @since 1.1.00
       */
      public boolean isForcingEndTurn()
      {
          return forcingEndTurn;
      }
  
      /**
       * For scenario option {@link SOCGameOptionSet#K_SC_PIRI _SC_PIRI}, if true and
       * {@link #canPickGoldHexResources(int, ResourceSet)} in state {@link #WAITING_FOR_PICK_GOLD_RESOURCE},
       * this player's "gold hex" free resources include victory over a pirate fleet attack at a dice roll.
       * @param pn  Player number
       * @since 2.0.00
       */
      public final boolean isPickResourceIncludingPirateFleet(final int pn)
      {
          return (gameState == WAITING_FOR_PICK_GOLD_RESOURCE)
              && (players[pn] == currentRoll.sc_piri_fleetAttackVictim)
              && (currentRoll.sc_piri_fleetAttackRsrcs != null)
              && (currentRoll.sc_piri_fleetAttackRsrcs.contains(SOCResourceConstants.GOLD_LOCAL));
      }
  
      /**
       * Get the number of development cards remaining to be bought.
       * @return the number of dev cards in the deck
       * @see #setNumDevCards(int)
       * @see #buyDevCard()
       */
      public int getNumDevCards()
      {
          return numDevCards;
      }
  
      /**
       * At client, set the number of dev cards remaining in the deck.
       *
       * @param  nd  the number of dev cards in the deck
       * @see #getNumDevCards()
       */
      public void setNumDevCards(final int nd)
      {
          numDevCards = nd;
      }
  
      /**
       * At server, get the dev cards remaining in the unplayed deck.
       * Useful for saving and loading game snapshots.
       * @return Unplayed dev card deck: a copied array of dev card types from {@link SOCDevCardConstants}.
       *     May be empty, but never null.
       * @see #buyDevCard()
       * @see #getNumDevCards()
       * @since 2.3.00
       */
      public int[] getDevCardDeck()
      {
          int[] cards = new int[numDevCards];
          System.arraycopy(devCardDeck, 0, cards, 0, cards.length);
          return cards;
      }
  
      /**
       * Get all types of this game's {@link SOCSpecialItem}s, if any.
       * Only some scenarios and expansions use Special Items.
       * See {@link #getSpecialItems(String)} for Special Item details and locking.
       * @return  Special item type keys, or {@code null} if none
       * @since 2.0.00
       */
      public Set<String> getSpecialItemTypes()
      {
          return (spItems.isEmpty()) ? null : spItems.keySet();
      }
  
      /**
       * Get a list of all special items of a given type in this game.
       * This is not the union of each player's {@code spItems}, but a separately maintained list of items.
       * Only some scenarios and expansions use Special Items.
       *<P>
       * Item list is empty if game not started or if {@link #updateAtBoardLayout()} hasn't yet been called.
       *<P>
       * <B>Locks:</B> This getter is not synchronized: It's assumed that the structure of Special Item lists
       * is set up at game creation time, and not often changed.  If a specific item type or access pattern
       * requires synchronization, do so outside this class and document the details.
       *
       * @param typeKey  Special item type.  Typically a {@link SOCGameOption} keyname; see the {@link SOCSpecialItem}
       *     class javadoc for details.
       * @return  List of all special items of that type, or {@code null} if none; will never return an empty list.
       *     Some list items may be {@code null} depending on the list structure created by the scenario or expansion.
       * @since 2.0.00
       * @see SOCPlayer#getSpecialItems(String)
       * @see #getSpecialItemTypes()
       */
      public ArrayList<SOCSpecialItem> getSpecialItems(final String typeKey)
      {
          final ArrayList<SOCSpecialItem> ret = spItems.get(typeKey);
          if ((ret == null) || ret.isEmpty())
              return null;
  
          return ret;
      }
  
      /**
       * Get a special item of a given type, by index within the list of all items of that type in this game.
       * This is not the union of each player's {@code spItems}, but a separately maintained list of items.
       * Only some scenarios and expansions use Special Items.
       *<P>
       * Item list is empty if game not started or if {@link #updateAtBoardLayout()} hasn't yet been called.
       *<P>
       * <B>Locks:</B> This getter is not synchronized: It's assumed that the structure of Special Item lists
       * is set up at game creation time, and not often changed.  If a specific item type or access pattern
       * requires synchronization, do so outside this class and document the details.
       *
       * @param typeKey  Special item type.  Typically a {@link SOCGameOption} keyname; see the {@link SOCSpecialItem}
       *     class javadoc for details.
       * @param idx  Index within the list of special items of that type; must be within the list's current size
       * @return  The special item, or {@code null} if none of that type, or if that index is {@code null} within the list
       *     or is beyond the size of the list
       * @since 2.0.00
       * @see #getSpecialItem(String, int, int, int)
       * @see SOCPlayer#getSpecialItem(String, int)
       * @see SOCSpecialItem#playerPickItem(String, SOCGame, SOCPlayer, int, int)
       * @see SOCSpecialItem#playerSetItem(String, SOCGame, SOCPlayer, int, int, boolean)
       */
      public SOCSpecialItem getSpecialItem(final String typeKey, final int idx)
      {
          final ArrayList<SOCSpecialItem> li = spItems.get(typeKey);
          if (li == null)
              return null;
  
          try
          {
              return li.get(idx);
          } catch (IndexOutOfBoundsException e) {
              return null;
          }
      }
  
      /**
       * Get a special item of a given type, by index within the game's or player's list of all items of that type.
       * When both {@code gi} and {@code pi} are used, checks game first for an existing object, and if none (null),
       * checks the player. Only some scenarios and expansions use Special Items.
       *<P>
       * Item list is empty if game not started or if {@link #updateAtBoardLayout()} hasn't yet been called.
       *<P>
       * <B>Locks:</B> Call {@link #takeMonitor()} before calling this method.
       *
       * @param typeKey  Special item type.  Typically a {@link SOCGameOption} keyname; see the {@link SOCSpecialItem}
       *     class javadoc for details.
       * @param gi  Index within the game's list of special items of that type, or -1; must be within the list's current size
       * @param pi  Player item index (requires {@code pn} != -1), or -1
       * @param pn  Owning player number, or -1
       * @return  The special item, or {@code null} if none of that type or if that index is {@code null} within the list
       *     or is beyond the size of the list
       * @since 2.0.00
       * @see #getSpecialItem(String, int)
       * @see SOCPlayer#getSpecialItem(String, int)
       * @see SOCSpecialItem#playerPickItem(String, SOCGame, SOCPlayer, int, int)
       * @see SOCSpecialItem#playerSetItem(String, SOCGame, SOCPlayer, int, int, boolean)
       */
      public SOCSpecialItem getSpecialItem(final String typeKey, final int gi, final int pi, final int pn)
      {
          SOCSpecialItem item = null;
  
          if (gi != -1)
              item = getSpecialItem(typeKey, gi);
  
          if ((item == null) && (pn != -1) && (pi != -1))
              item = players[pn].getSpecialItem(typeKey, pi);
  
          return item;
      }
  
      /**
       * Add or replace a special item in the game's list of items of that type.
       * This is not the union of each player's {@code spItems}, but a separately maintained list of items.
       * Only some scenarios and expansions use Special Items.
       * @param typeKey  Special item type.  Typically a {@link SOCGameOption} keyname; see the {@link SOCSpecialItem}
       *     class javadoc for details.  If no list with this key exists, it will be created here.
       * @param idx  Index within the list of special items of that type; if this is past the list's current size,
       *     {@code null} elements will be inserted as needed until {@code idx} is a valid index
       *     If {@code idx} is within the list, the current element at that index will be replaced.
       * @param itm  Item object to set within the list
       * @return  The item previously at this index, or {@code null} if none
       * @throws IndexOutOfBoundsException  if {@code idx} &lt; 0
       * @since 2.0.00
       * @see SOCPlayer#setSpecialItem(String, int, SOCSpecialItem)
       */
      public SOCSpecialItem setSpecialItem(final String typeKey, final int idx, SOCSpecialItem itm)
          throws IndexOutOfBoundsException
      {
          ArrayList<SOCSpecialItem> li = spItems.get(typeKey);
          if (li == null)
          {
              li = new ArrayList<SOCSpecialItem>();
              spItems.put(typeKey, li);
          }
  
          final int L = li.size();
          if (idx < L)
          {
              return li.set(idx, itm);
          } else {
              for (int n = idx - L; n > 0; --n)  // if idx == L, n is 0, no nulls are needed
                  li.add(null);
  
              li.add(itm);
              return null;
          }
      }
  
      /**
       * Get the special Inventory Item to be placed by the current player in state {@link #PLACING_INV_ITEM},
       * if any, from the most recent call to {@link #setPlacingItem(SOCInventoryItem)}.
       * See that method for lifecycle details.
       * @return  The item being placed, or {@code null}
       * @since 2.0.00
       */
      public SOCInventoryItem getPlacingItem()
      {
          return placingItem;
      }
  
      /**
       * Set or clear the special Inventory Item to be placed by the current player in state {@link #PLACING_INV_ITEM}.
       * Can be set before or during that state, either for convenience or because the server must send multiple messages.
       *<P>
       * The item can't be held between turns, and is lost if not placed on the current player's turn.
       * Inventory Item placement methods such as {@link #placePort(SOCPlayer, int, int)} will clear the item
       * to {@code null} when called in {@link #PLACING_INV_ITEM}.
       *
       * @param item  The item being placed, or {@code null} to clear
       * @since 2.0.00
       * @see #getPlacingItem()
       */
      public void setPlacingItem(SOCInventoryItem item)
      {
          placingItem = item;
      }
  
      /**
       * @return the player with the largest army
       */
      public SOCPlayer getPlayerWithLargestArmy()
      {
          if (playerWithLargestArmy != -1)
              return players[playerWithLargestArmy];
          else
              return null;
      }
  
      /**
       * set the player with the largest army
       *
       * @param pl  the player
       */
      public void setPlayerWithLargestArmy(SOCPlayer pl)
      {
          if (pl == null)
          {
              playerWithLargestArmy = -1;
          }
          else
          {
              playerWithLargestArmy = pl.getPlayerNumber();
          }
      }
  
      /**
       * @return the player with the longest road or trade route, or null if none
       */
      public SOCPlayer getPlayerWithLongestRoad()
      {
          if (playerWithLongestRoad != -1)
              return players[playerWithLongestRoad];
          else
              return null;
      }
  
      /**
       * set the player with the longest road or trade route
       *
       * @param pl  the player, or null to clear
       */
      public void setPlayerWithLongestRoad(SOCPlayer pl)
      {
          if (pl == null)
          {
              playerWithLongestRoad = -1;
          }
          else
          {
              playerWithLongestRoad = pl.getPlayerNumber();
          }
      }
  
      /**
       * Find the player who was declared winner at end of game.
       * This is determined in {@link #checkForWinner()}; there is no corresponding setter.
       *
       * @return the winning player, or null if none, or if game is not yet over.
       * @since 1.1.00
       */
      public SOCPlayer getPlayerWithWin()
      {
          if (playerWithWin != -1)
              return players[playerWithWin];
          else
              return null;
      }
  
      /**
       * For each player, call
       * {@link SOCPlayerNumbers#setLandHexCoordinates(int[]) pl.setLandHexCoordinates}
       * ({@link SOCBoardLarge#getLandHexCoords()}).
       * If the landhex coords are <tt>null</tt>, do nothing.
       *<P>
       * To be used with {@link #hasSeaBoard} (v3 board encoding) after creating (at server)
       * or receiving (at client) a new board layout.  So, call from
       * {@link #startGame()} or after {@link SOCBoardLarge#setLandHexLayout(int[])}.
       *<P>
       * For the v1 and v2 board encodings, the land hex coordinates never change, so
       * {@link SOCPlayerNumbers} knows them already.
       *
       * @since 2.0.00
       * @throws IllegalStateException if the board doesn't have the v3 encoding
       */
      public void setPlayersLandHexCoordinates()
          throws IllegalStateException
      {
          final int bef = board.getBoardEncodingFormat();
          if (bef != SOCBoard.BOARD_ENCODING_LARGE)
              throw new IllegalStateException("board encoding: " + bef);
  
          final int[] landHex = board.getLandHexCoords();
          if (landHex == null)
              return;
          for (int i = 0; i < maxPlayers; ++i)
              players[i].getNumbers().setLandHexCoordinates(landHex);
      }
  
      /**
       * If game is over, formulate a message to tell a player.
       * @param pl Player to tell (may be the winner)
       * @return A message of one of these forms:
       *       "The game is over; you are the winner!"
       *       "The game is over; <someone> won."
       *       "The game is over; no one won."
       * @throws IllegalStateException If the game state is not OVER
       * @since 1.1.00
       */
      public String gameOverMessageToPlayer(SOCPlayer pl)
          throws IllegalStateException
      {
          if (gameState != OVER)
              throw new IllegalStateException("This game is not over yet");
  
          String msg;
          SOCPlayer wn = getPlayerWithWin();
  
          if ((pl != null) && (pl == wn))
          {
              msg = "The game is over; you are the winner!";
          }
          else if (wn != null)
          {
              msg = "The game is over; " + wn.getName() + " won.";
          }
          else
          {
              // Just in case; don't think this can happen
              msg = "The game is over; no one won.";
          }
  
          return msg;
      }
  
      /**
       * advance the turn to the previous player,
       * used during initial placement. Does not change any other game state,
       * unless all players have left the game.
       * Clears the {@link #forcingEndTurn} flag.
       * @return true if the turn advances, false if all players have left and
       *          the gamestate has been changed here to {@link #OVER}.
       * @see #advanceTurn()
       */
      protected boolean advanceTurnBackwards()
      {
          final int prevCPN = currentPlayerNumber;
  
          //D.ebugPrintln("ADVANCE TURN BACKWARDS");
          forcingEndTurn = false;
          currentPlayerNumber--;
  
          if (currentPlayerNumber < 0)
              currentPlayerNumber = maxPlayers - 1;
  
          while (isSeatVacant (currentPlayerNumber))
          {
              --currentPlayerNumber;
  
              if (currentPlayerNumber < 0)
                  currentPlayerNumber = maxPlayers - 1;
  
              if (currentPlayerNumber == prevCPN)
              {
                  setGameStateOVER();  // Looped around, no one is here
  
                  return false;
              }
          }
  
          return true;
      }
  
      /**
       * advance the turn to the next player. Does not change any other game state,
       * unless all players have left the game.
       * Clears the {@link #forcingEndTurn} flag.
       * @return true if the turn advances, false if all players have left and
       *          the gamestate has been changed here to {@link #OVER}.
       * @see #advanceTurnBackwards()
       */
      protected boolean advanceTurn()
      {
          final int prevCPN = currentPlayerNumber;
  
          currentPlayerNumber++;
          forcingEndTurn = false;
  
          if (currentPlayerNumber == maxPlayers)
              currentPlayerNumber = 0;
  
          while (isSeatVacant (currentPlayerNumber))
          {
              ++currentPlayerNumber;
  
              if (currentPlayerNumber == maxPlayers)
                  currentPlayerNumber = 0;
  
              if (currentPlayerNumber == prevCPN)
              {
                  setGameStateOVER();  // Looped around, no one is here
  
                  return false;
              }
          }
  
          return true;
      }
  
      /**
       * For the 6-player board, check whether we should either start
       * or continue the {@link #SPECIAL_BUILDING Special Building Phase}.
       * This method does 1 of 4 possible things:
       *<UL>
       *<LI> A: If this isn't a 6-player board, or no player has asked
       *     to Special Build this turn, do nothing.
       *<LI> B: If we haven't started Special Building yet (gameState not {@link #SPECIAL_BUILDING})
       *     but it's asked for: Set gameState to {@link #SPECIAL_BUILDING}.
       *     Set {@link #specialBuildPhase_afterPlayerNumber} to current player number,
       *     because their turn is ending.
       *     (Special case: if current player wants to special build at start of
       *     their own turn, set _afterPlayerNumber to PREVIOUS player.)
       *     Then, set current player to the first player who wants to Special Build
       *     (it may be unchanged).
       *<LI> C: If we already did some players' Special Build this turn,
       *     and some remain, set current player to the next player
       *     who wants to Special Build.  gameState remains {@link #SPECIAL_BUILDING}.
       *<LI> D: If we already did some players' Special Build this turn,
       *     and no more players are left to special build, prepare to
       *     end the turn normally: Set gameState to {@link #PLAY1}.
       *     Set current player to the player whose turn is ending
       *     ({@link #specialBuildPhase_afterPlayerNumber}).
       *</UL>
       *<P>
       *<b>In 1.1.09 and later:</b>
       *<UL>
       *<LI> Player is allowed to Special Build at start of their own
       *     turn, only if they haven't yet rolled or played a dev card.
       *
       *<LI> During Special Building Phase, a player can ask to Special Build after
       *     the phase has begun, even if this means we temporarily go backwards
       *     in turn order.  (Normal turn order resumes at the end of the SBP.)
       *     The board game does not allow this out-of-order building.
       *</UL>
       *
       * @return true if gamestate is now {@link #SPECIAL_BUILDING}
       * @since 1.1.08
       */
      private boolean advanceTurnToSpecialBuilding()
      {
          if (! askedSpecialBuildPhase)
              return false;  // case "A" part 1: not 6-player or not asked
  
          final boolean alreadyInPhase = (gameState == SPECIAL_BUILDING);
  
          // See if anyone can place.
          // Set currentPlayerNumber if it's possible.
          // Unlike the board game, check every player, even if we'd go backwards
          // in turn order temporarily during the SBP.
  
          final int prevPlayer = currentPlayerNumber;
          boolean anyPlayerWantsSB = false;
          do
          {
              if (! advanceTurn())
                  return false;  // All players have left
  
              anyPlayerWantsSB = players[currentPlayerNumber].hasAskedSpecialBuild();
          } while ((! anyPlayerWantsSB)
                    && (currentPlayerNumber != prevPlayer));
  
          // Postcondition: If anyPlayerWantsSB false,
          // then currentPlayerNumber is unchanged.
  
          if (! anyPlayerWantsSB)
          {
              // No one is left to special build.
              // Case "A" or "D".
              if (alreadyInPhase)
              {
                  currentPlayerNumber = specialBuildPhase_afterPlayerNumber;
                  gameState = PLAY1;  // case "D"
              }
              return false;
          }
  
          // Case "B" or "C".
          if (! alreadyInPhase)
          {
              // case "B":
  
              // usually the current player can't call for SBP.
              // unless, that is, it's the very start of their turn
              // and they haven't rolled or played a card yet.
  
              if (players[prevPlayer].hasAskedSpecialBuild()
                  && (gameState == ROLL_OR_CARD)
                  && ! players[prevPlayer].hasPlayedDevCard())
              {
                  // remember previous player, re-set current player:
  
                  gameState = SPECIAL_BUILDING;
                  currentPlayerNumber = prevPlayer;
                  if (! advanceTurnBackwards())
                      return false;  // all players have left
                  specialBuildPhase_afterPlayerNumber = currentPlayerNumber;
                  currentPlayerNumber = prevPlayer;
              } else {
                  // usual case: ending current player's turn.
  
                  gameState = SPECIAL_BUILDING;
                  specialBuildPhase_afterPlayerNumber = prevPlayer;
              }
          }
          // currentPlayerNumber is set already by advanceTurn.
  
          return true;
      }
  
      /**
       * Reveal one land or water hex hidden by {@link SOCBoardLarge#FOG_HEX fog}.
       * Called at server and clients. Updates board.
       * If a {@link SOCBoard#WATER_HEX} is revealed, updates players' legal road and ship edges.
       * (Unrevealed fog hexes are treated as land when setting up legal and potential sets.)
       *
       * @param hexCoord  Coordinate of the hex to reveal
       * @param hexType   Revealed hex type, same value as {@link SOCBoard#getHexTypeFromCoord(int)},
       *                    from {@link SOCBoardLarge#revealFogHiddenHexPrep(int)}
       * @param diceNum   Revealed hex dice number, same value as {@link SOCBoard#getNumberOnHexFromCoord(int)}, or 0
       * @throws IllegalArgumentException if <tt>hexCoord</tt> isn't currently a {@link SOCBoardLarge#FOG_HEX FOG_HEX}
       * @throws IllegalStateException if <tt>! game.{@link #hasSeaBoard}</tt>
       * @since 2.0.00
       */
      public void revealFogHiddenHex(final int hexCoord, final int hexType, int diceNum)
          throws IllegalArgumentException, IllegalStateException
      {
          if (! hasSeaBoard)
              throw new IllegalStateException();
  
          final boolean wasWaterRemovedLegals = ((SOCBoardLarge) board).revealFogHiddenHex(hexCoord, hexType, diceNum);
              // throws IllegalArgumentException if any problem noted above
  
          if ((hexType == SOCBoard.WATER_HEX) || ((SOCBoardLarge) board).isHexAtBoardMargin(hexCoord))
          {
              // Previously not a legal ship edge, because
              // we didn't know if the fog hid land or water
              for (SOCPlayer pl : players)
              {
                  pl.updateLegalShipsAddHex(hexCoord);
                  if (wasWaterRemovedLegals)
                      pl.updatePotentialsAndLegalsAroundRevealedHex(hexCoord);
              }
          }
      }
  
      /**
       * For scenario option {@link SOCGameOptionSet#K_SC_FTRI _SC_FTRI},
       * can a "gift" port be removed from this edge? <BR>
       * All these conditions must be met:
       *<UL>
       * <LI> {@link #hasSeaBoard} is true
       * <LI> Player is current player
       * <LI> Game state is {@link #PLACING_SHIP}, {@link #PLACING_FREE_ROAD1} or {@link #PLACING_FREE_ROAD2}
       * <LI> {@code edge} has a port
       * <LI> Port must be in no land area (LA == 0), or in {@link SOCBoardLarge#getPlayerExcludedLandAreas()}
       *</UL>
       * Does not check whether {@link SOCGameOptionSet#K_SC_FTRI} is set.
       *
       * @param pl  Player who would remove the port
       * @param edge  Port's edge coordinate
       * @return  True if that edge has a port which can be removed now by {@code pl}
       * @throws NullPointerException if {@code pl} is null
       * @since 2.0.00
       * @see SOCBoardLarge#canRemovePort(int)
       * @see #removePort(SOCPlayer, int)
       * @see #canPlacePort(SOCPlayer, int)
       */
      public boolean canRemovePort(final SOCPlayer pl, final int edge)
          throws NullPointerException
      {
          if (! hasSeaBoard)
              return false;
          if (pl.getPlayerNumber() != currentPlayerNumber)
              return false;
          if ((gameState != PLACING_SHIP) && (gameState != PLACING_FREE_ROAD1) && (gameState != PLACING_FREE_ROAD2))
              return false;
  
          return ((SOCBoardLarge) board).canRemovePort(edge);
      }
  
      /**
       * For scenario option {@link SOCGameOptionSet#K_SC_FTRI _SC_FTRI}, remove a "gift" port
       * at this edge to be placed elsewhere. Assumes {@link #canRemovePort(SOCPlayer, int)} has already
       * been called to validate player, edge, and game state.
       *<P>
       * This method will remove the port from the board.  At server it will also either add it to the
       * player's inventory or set the game's placingItem field; change the game state if placing
       * it immediately; and then fire {@link SOCPlayerEvent#REMOVED_TRADE_PORT}.
       *<P>
       * <b>At the server:</b> Not called directly; called only from other game/player methods.
       * Ports are currently removed only by a player's adjacent ship placement, so
       * {@link SOCPlayer#putPiece(SOCPlayingPiece, boolean)} would eventually call this
       * method if {@code canRemovePort(..)}.  The server calls
       * {@link SOCPlayer#getPortMovePotentialLocations(boolean) pl.getPortMovePotentialLocations(false)}
       * to determine if immediate placement is possible and thus required.
       *<P>
       * In the PlayerEvent handler or after this method returns, check
       * {@link #getGameState()} == {@link #PLACING_INV_ITEM} to see whether the port must immediately
       * be placed, or was instead added to the player's inventory.
       *<P>
       * <b>At the client:</b> Call this method to update the board data and player port flags.
       * The server will send other messages about game state and player inventory.  If those set
       * {@link #getGameState()} to {@link #PLACING_INV_ITEM}, the current player must choose a location for
       * port placement: Call
       * {@link SOCPlayer#getPortMovePotentialLocations(boolean) SOCPlayer.getPortMovePotentialLocations(true)}
       * to present options to the user.  The user will choose an edge and send a request to the server. The server
       * will validate and if valid call {@link #placePort(int)} and send updated status to the game's clients.
       *
       * @param pl  Player who is removing the port: Must be current player. Ignored at client, {@code null} is okay there.
       * @param edge  Port's edge coordinate
       * @throws UnsupportedOperationException if ! {@link #hasSeaBoard}
       * @throws NullPointerException if {@code pl} is null at server
       * @return  The port removed, with its {@link SOCInventoryItem#itype} in range
       *      -{@link SOCBoard#WOOD_PORT WOOD_PORT} to -{@link SOCBoard#MISC_PORT MISC_PORT}
       */
      public SOCInventoryItem removePort(SOCPlayer pl, final int edge)
          throws UnsupportedOperationException, NullPointerException
      {
          if (! hasSeaBoard)
              throw new UnsupportedOperationException();
  
          final int ptype = ((SOCBoardLarge) board).removePort(edge);
          for (int pn = 0; pn < maxPlayers; ++pn)
              players[pn].updatePortFlagsAfterRemove(ptype, true);
  
          if ((pl == null) || ! isAtServer)
              pl = players[currentPlayerNumber];
  
          // note: if this logic changes, also update SOCGameHandler.processDebugCommand_scenario
  
          final boolean placeNow = (pl.getPortMovePotentialLocations(false) != null);
          final SOCInventoryItem port = SOCInventoryItem.createForScenario(this, -ptype, true, false, false, ! placeNow);
  
          if (isAtServer)
          {
              if (! placeNow)
              {
                  pl.getInventory().addItem(port);
              } else {
                  placingItem = port;
                  if (oldGameState != SPECIAL_BUILDING)
                      oldGameState = (gameState == SPECIAL_BUILDING) ? SPECIAL_BUILDING : PLAY1;
                  gameState = PLACING_INV_ITEM;
              }
  
              // Fire the scenario player event, with the removed port's edge coord and type
              if (gameEventListener != null)
                  gameEventListener.playerEvent
                      (this, pl, SOCPlayerEvent.REMOVED_TRADE_PORT, false, new IntPair(edge, ptype));
          }
  
          return port;
      }
  
      /**
       * For scenario option {@link SOCGameOptionSet#K_SC_FTRI _SC_FTRI},
       * can a "gift" port be placed at this edge? <BR>
       * All these conditions must be met:
       *<UL>
       * <LI> {@link #hasSeaBoard} is true
       * <LI> Must be a coastal edge: {@link SOCBoardLarge#isEdgeCoastline(int)}
       * <LI> No port already at this edge or an adjacent edge
       * <LI> Player must have a settlement or city at one node (one end) of the edge
       * <LI> Player is current player
       *</UL>
       * Does not check whether {@link SOCGameOptionSet#K_SC_FTRI} is set.  Does not check {@link #getGameState()}.
       *
       * @param pl  Player who would place
       * @param edge  Edge where a port is wanted; coordinate not checked for validity.
       *             {@link SOCPlayer#getPortMovePotentialLocations(boolean)} can calculate edges that
       *             meet all conditions for {@code canPlacePort}.
       * @return  True if a port can be placed at this edge
       * @throws NullPointerException if {@code pl} is null
       * @see #canRemovePort(SOCPlayer, int)
       * @see #placePort(SOCPlayer, int, int)
       * @since 2.0.00
       */
      public boolean canPlacePort(final SOCPlayer pl, final int edge)
          throws NullPointerException
      {
          if (! hasSeaBoard)
              return false;
          if (pl.getPlayerNumber() != currentPlayerNumber)
              return false;
          if (! ((SOCBoardLarge) board).isEdgeCoastline(edge))
              return false;
  
          boolean plHasSettleOrCity = false;
          final int[] portNodes = board.getAdjacentNodesToEdge_arr(edge);
          for (int i = 0; i <= 1; ++i)
          {
              if (board.getPortTypeFromNodeCoord(portNodes[i]) != -1)
                  return false;  // Already a port at edge or adjacent
  
              final SOCPlayingPiece ppiece = board.settlementAtNode(portNodes[i]);
              if ((ppiece != null) && (ppiece.getPlayerNumber() == currentPlayerNumber))
                  plHasSettleOrCity = true;  // don't return yet, need to check both nodes for ports
          }
  
          return plHasSettleOrCity;
      }
  
      /**
       * For scenario option {@link SOCGameOptionSet#K_SC_FTRI _SC_FTRI} in game state {@link #PLACING_INV_ITEM}, place
       * the "gift" port at this edge.  The port in {@link #getPlacingItem()} will be placed.  State becomes previous state
       * ({@link #PLAY1} or {@link #SPECIAL_BUILDING}).  Calls {@link #setPlacingItem(SOCInventoryItem) setPlacingItem(null)}.
       *<P>
       * Assumes {@link #canPlacePort(SOCPlayer, int)} has already been called to validate.
       *
       * @param edge  An available coastal edge adjacent to {@code pl}'s settlement or city,
       *          which should be checked with {@link #canPlacePort(SOCPlayer, int)}
       * @return ptype  The type of port placed (in range {@link SOCBoard#MISC_PORT MISC_PORT}
       *          to {@link SOCBoard#WOOD_PORT WOOD_PORT})
       * @throws IllegalStateException if not state {@link #PLACING_INV_ITEM}, or (internal error) {@link #placingItem} is null
       * @throws IllegalArgumentException if {@code ptype} is out of range, or
       *           if {@code edge} is not coastal (is between 2 land hexes or 2 water hexes)
       * @throws UnsupportedOperationException if ! {@link #hasSeaBoard}
       * @since 2.0.00
       * @see #placePort(SOCPlayer, int, int)
       * @see #removePort(SOCPlayer, int)
       */
      public int placePort(final int edge)
          throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException
      {
          if ((gameState != PLACING_INV_ITEM) || (placingItem == null))
              throw new IllegalStateException("state " + gameState + ", placingItem " + placingItem);
  
          final int ptype = -placingItem.itype;
          placePort(players[currentPlayerNumber], edge, ptype);  // clears placingItem
          gameState = oldGameState;
  
          return ptype;
      }
  
      /**
       * For scenario option {@link SOCGameOptionSet#K_SC_FTRI _SC_FTRI}, place a "gift" port at this edge.
       * Assumes {@link #canPlacePort(SOCPlayer, int)} has already been called to validate.
       * Through that validation, assumes {@code edge} is a coastal edge and {@code pl} has
       * an adjacent settlement or city.
       *<P>
       * Any port placement in state {@link #PLACING_INV_ITEM} calls
       * {@link #setPlacingItem(SOCInventoryItem) setPlacingItem(null)}.
       *<P>
       * Solely for debugging, a port can be placed at coordinates "off the side of the board"
       * with the intention of removing it soon to give to a player with the proper internal
       * state changes. To do so, call with {@code pl} {@code null}.
       *
       * @param pl  Player who is placing. Will call
       *          {@link SOCPlayer#setPortFlag(int, boolean) pl.setPortFlag}({@code ptype, true})
       *          because {@code pl}'s adjacent coastal settlement or city will become a port.
       *          Can be {@code null} if port is being placed for debugging.
       * @param edge  An available coastal edge adjacent to {@code pl}'s settlement or city,
       *          which should have been checked with {@link #canPlacePort(SOCPlayer, int)} already
       * @param ptype  The type of port (in range {@link SOCBoard#MISC_PORT MISC_PORT}
       *          to {@link SOCBoard#WOOD_PORT WOOD_PORT})
       * @throws IllegalArgumentException if {@code ptype} is out of range, or
       *           if {@code edge} is not coastal (is between 2 land hexes or 2 water hexes)
       *           when {@code pl != null}
       * @throws UnsupportedOperationException if ! {@link #hasSeaBoard}
       * @since 2.0.00
       * @see #placePort(int)
       * @see #removePort(SOCPlayer, int)
       */
      public void placePort(final SOCPlayer pl, final int edge, final int ptype)
          throws IllegalArgumentException, UnsupportedOperationException
      {
          if ((ptype < SOCBoard.MISC_PORT) || (ptype > SOCBoard.WOOD_PORT))
              throw new IllegalArgumentException("ptype: " + ptype);
          if (! hasSeaBoard)
              throw new UnsupportedOperationException();
  
          if (gameState == PLACING_INV_ITEM)
              placingItem = null;
  
          if (pl != null)
          {
              ((SOCBoardLarge) board).placePort(edge, ptype);  // validates coastal edge to calculate facing
              pl.setPortFlag(ptype, true);  // might already be set, that's fine
          } else {
              // assume off-board temp placement for debug: blindly calc facing from edge
              ((SOCBoardLarge) board).placePort
                  (edge, ((SOCBoardLarge) board).getPortFacingFromEdge(edge, true), ptype);
          }
      }
  
      /**
       * Can this player place a ship on this edge?
       * The edge must be {@link SOCPlayer#isPotentialShip(int)}
       * and must not be adjacent to {@link SOCBoardLarge#getPirateHex()}.
       * Does not check game state, resources, or pieces remaining.
       *<P>
       * If {@link #isDebugFreePlacement()}, caller might also want to call
       * {@link SOCPlayer#canPlaceShip_debugFreePlace(int)}.
       *
       * @param pl  Player
       * @param shipEdge  Edge to place a ship
       * @return true if this player's ship could be placed there
       * @since 2.0.00
       * @see #canMoveShip(int, int, int)
       * @see SOCPlayer#getNumPieces(int)
       */
      public boolean canPlaceShip(final SOCPlayer pl, final int shipEdge)
      {
          if (! pl.isPotentialShip(shipEdge))
              return false;
  
          // check shipEdge vs. pirate hex
          {
              final SOCBoardLarge bL = (SOCBoardLarge) board;
              final int ph = bL.getPirateHex();
              if ((ph != 0) && bL.isEdgeAdjacentToHex(shipEdge, ph))
                  return false;
          }
  
          return true;
      }
  
      /**
       * Get the list of ships placed this turn by the current player, if {@link #hasSeaBoard}.
       * @return copy of the list of ships placed this turn, if any.
       *     May be empty; won't be {@code null} unless ! {@link #hasSeaBoard}
       * @since 2.3.00
       */
      public List<Integer> getShipsPlacedThisTurn()
      {
          return (shipsPlacedThisTurn != null)
              ? new ArrayList<>(shipsPlacedThisTurn)
              : null;
      }
  
      /**
       * Put this piece on the board and update all related game state.
       * May change current player (at server) and gamestate.
       * Calls {@link #checkForWinner()}; gamestate may become {@link #OVER}.
       *<P>
       * For example, if game state when called is {@link #START2A} (or {@link #START3A} in
       * some scenarios), this is their final initial settlement, so it gives the player
       * some resources.
       *<P>
       * If {@link #hasSeaBoard} and {@link SOCGameOptionSet#K_SC_FOG _SC_FOG},
       * you should check for gamestate {@link #WAITING_FOR_PICK_GOLD_RESOURCE}
       * after calling, to see if they placed next to a gold hex revealed from fog
       * (see paragraph below).
       *
       *<H3>Actions taken:</H3>
       * Calls {@link SOCBoard#putPiece(SOCPlayingPiece)} and each player's
       * {@link SOCPlayer#putPiece(SOCPlayingPiece, boolean) SOCPlayer.putPiece(pp, false)}.
       * Updates longest road if necessary.
       * Calls {@link #advanceTurnStateAfterPutPiece()}.
       * (player.putPiece may also score Special Victory Point(s), see below.)
       *<P>
       * If the piece is a city, putPiece removes the settlement there.
       *<P>
       * If the piece is a settlement, and its owning player has their {@link SOCFortress} there
       * (scenario {@link SOCGameOptionSet#K_SC_PIRI _SC_PIRI}), putPiece removes the defeated fortress.
       *<P>
       * If placing this piece reveals any {@link SOCBoardLarge#FOG_HEX fog hex}, that happens first of all.
       * Hex is revealed (at server only) via {@link #putPieceCommon_checkFogHexes(int[], boolean)}.
       * Current player gets a resource from each revealed hex, and a scenario player event is fired.
       * See that method's javadoc for details.
       * putPiece's caller should check {@link SOCPlayer#getNeedToPickGoldHexResources()} != 0.
       * Revealing a gold hex from fog will set that player field and also
       * sets gamestate to {@link #WAITING_FOR_PICK_GOLD_RESOURCE}.
       *<P>
       * Calls {@link #checkForWinner()} and otherwise advances turn or state,
       * unless gamestate is {@link #LOADING}.
       * At the client, {@link #advanceTurnStateAfterPutPiece()} won't change the current player;
       * the server will send a message when the current player changes.
       *<P>
       * After the final initial road or ship placement, clears all players' potential settlements by
       * calling {@link #advanceTurnStateAfterPutPiece()} which at the server calls {@link #updateAtGameFirstTurn()}.
       *
       *<H3>Valid placements:</H3>
       * Because <tt>pp</tt> is not checked for validity, please call methods such as
       * {@link SOCPlayer#isPotentialRoad(int)} and {@link SOCPlayer#getNumPieces(int)}
       * to verify <tt>pp</tt> before calling this method, and also check
       * {@link #getGameState()} to ensure that piece type can be placed now.<BR>
       * For settlements, call {@link SOCPlayer#canPlaceSettlement(int)} to check potentials and other game conditions.<BR>
       * For ships, call {@link #canPlaceShip(SOCPlayer, int)} to check
       * the potentials and pirate ship location.
       *
       *<H3>Other things to note:</H3>
       * For some scenarios on the {@link SOCGame#hasSeaBoard large sea board}, placing
       * a settlement in a new Land Area may award the player a Special Victory Point (SVP).
       * This method will increment {@link SOCPlayer#getSpecialVP()}
       * and set the player's {@link SOCPlayerEvent#SVP_SETTLED_ANY_NEW_LANDAREA} flag.
       *<P>
       * Some scenarios use extra initial pieces in fixed locations, placed in
       * {@link SOCBoardAtServer#startGame_putInitPieces(SOCGame)}.  To prevent the state or current player from
       * advancing, temporarily set game state {@link #READY} before calling putPiece for these.
       *<P>
       * During {@link #isDebugFreePlacement()}, the gamestate is not changed,
       * unless the current player gains enough points to win.
       *
       * @param pp the piece to put on the board; coordinates are not checked for validity, see "valid placements" note
       */
      public void putPiece(SOCPlayingPiece pp)
      {
          putPieceCommon(pp, false);
      }
  
      /**
       * Put a piece or temporary piece on the board, and update all related game state.
       * Update player potentials, longest road, reveal fog hexes at server, etc.
       * Common to {@link #putPiece(SOCPlayingPiece)} and {@link #putTempPiece(SOCPlayingPiece)}.
       * See {@link #putPiece(SOCPlayingPiece)} javadoc for more information on what putPieceCommon does.
       *
       * @param pp  The piece to put on the board; coordinates are not checked for validity
       * @param isTempPiece  Is this a temporary piece?  If so, do not change current
       *            player or gamestate, or call our {@link SOCGameEventListener}.
       * @since 1.1.14
       */
      private void putPieceCommon(SOCPlayingPiece pp, final boolean isTempPiece)
      {
          final int coord = pp.getCoordinates();
  
          /**
           * FOG_HEX: On large board, look for fog and reveal its hex if we're
           * placing a road or ship touching the fog hex's corner.
           * During initial placement, a settlement could reveal up to 3 hexes.
           * Current player gets a resource from each revealed hex.
           */
          if (hasSeaBoard && isAtServer && ! (pp instanceof SOCVillage))
          {
              if (pp instanceof SOCRoutePiece)
              {
                  // roads, ships
                  final int[] endHexes = ((SOCBoardLarge) board).getAdjacentHexesToEdgeEnds(coord);
                  putPieceCommon_checkFogHexes(endHexes, false);
              }
              else if ((pp instanceof SOCSettlement) && isInitialPlacement())
              {
                  // settlements
                  final List<Integer> adjacHexes = board.getAdjacentHexesToNode(coord);
                  final int L = adjacHexes.size();
                  int[] seHexes = new int[L];
                  for (int i = 0; i < L; ++i)
                      seHexes[i] = adjacHexes.get(i);
                  putPieceCommon_checkFogHexes(seHexes, true);
  
                  // Any settlement might reveal 1-3 fog hexes.
                  // So, the player's revealed getNeedToPickGoldHexResources might be 0 to 3.
                  // For the final initial settlement, this is recalculated below
                  // to also include adjacent gold hexes that weren't hidden by fog.
              }
          }
  
          /**
           * call putPiece() on every player so that each
           * player's updatePotentials() function gets called
           */
          if (! (pp instanceof SOCVillage))
          {
              for (int i = 0; i < maxPlayers; i++)
                  players[i].putPiece(pp, isTempPiece);
          }
  
          board.putPiece(pp);
  
          if ((pp instanceof SOCFortress) || (pp instanceof SOCVillage))
          {
              return;  // <--- Early return: Piece is part of initial layout ---
          }
  
          if ((! isTempPiece) && debugFreePlacement && (gameState <= START3B))
              debugFreePlacementStartPlaced = true;
  
          /**
           * if the piece is a city, remove the settlement there
           */
          final int pieceType = pp.getType();
          final SOCPlayer ppPlayer = pp.getPlayer();
          if (pieceType == SOCPlayingPiece.CITY)
          {
              if (! (isTempPiece || hasBuiltCity))
              {
                  hasBuiltCity = true;  // for house-rule game option "N7C"
              }
  
              SOCSettlement se = new SOCSettlement(ppPlayer, coord, board);
  
              for (int i = 0; i < maxPlayers; i++)
                  players[i].removePiece(se, pp, true);
  
              board.removePiece(se);
          }
  
          if (gameState == LOADING)
          {
              return;  // <---- Early return: Loading game, so skip any side effects ----
          }
  
          /**
           * the rare situation "if the piece is a settlement, remove the fortress there" is
           * handled in player.putPiece instead of here, because the SOCPlayer knows about the
           * fortress and settlement, and that single call can correlate the removal and placement.
           */
  
          /**
           * if this their final initial settlement, give the player some resources.
           * (skip for temporary pieces)
           */
          if ((! isTempPiece)
              && (pieceType == SOCPlayingPiece.SETTLEMENT)
              && ((gameState == START2A) || (gameState == START3A)))
          {
              final boolean init3 = isGameOptionDefined(SOCGameOptionSet.K_SC_3IP);
              final int lastInitSettle = init3 ? START3A : START2A;
              if ( (gameState == lastInitSettle)
                   || (debugFreePlacementStartPlaced
                       && (ppPlayer.getPieces().size() == (init3 ? 5 : 3))) )
              {
                  SOCResourceSet resources = new SOCResourceSet();
                  int goldHexAdjacent = 0;
  
                  for (final int hexCoord : board.getAdjacentHexesToNode(coord))
                  {
                      switch (board.getHexTypeFromCoord(hexCoord))
                      {
                      case SOCBoard.CLAY_HEX:
                          resources.add(1, SOCResourceConstants.CLAY);
                          break;
  
                      case SOCBoard.ORE_HEX:
                          resources.add(1, SOCResourceConstants.ORE);
                          break;
  
                      case SOCBoard.SHEEP_HEX:
                          resources.add(1, SOCResourceConstants.SHEEP);
                          break;
  
                      case SOCBoard.WHEAT_HEX:
                          resources.add(1, SOCResourceConstants.WHEAT);
                          break;
  
                      case SOCBoard.WOOD_HEX:
                          resources.add(1, SOCResourceConstants.WOOD);
                          break;
  
                      case SOCBoardLarge.GOLD_HEX:
                          if (hasSeaBoard)
                              ++goldHexAdjacent;
                      }
                  }
  
                  ppPlayer.getResources().add(resources);
                  if (goldHexAdjacent > 0)
                      ppPlayer.setNeedToPickGoldHexResources(goldHexAdjacent);
              }
          }
  
          /**
           * update which player has longest road or trade route
           */
          if (pieceType != SOCPlayingPiece.CITY)
          {
              final int placingPN = ppPlayer.getPlayerNumber();
  
              if (pp instanceof SOCRoutePiece)
              {
                  /**
                   * the affected player is the one who build the road or ship
                   */
                  updateLongestRoad(placingPN);
              }
              else if (pieceType == SOCPlayingPiece.SETTLEMENT)
              {
                  /**
                   * this is a settlement, check if it cut anyone else's road or trade route
                   * or (on sea boards) if this connects a player's own roads to their ships
                   */
                  int[] roads = new int[maxPlayers];
                  boolean ownRoad = false, ownShip = false;
  
                  for (final int adjEdge : board.getAdjacentEdgesToNode(coord))
                  {
                      /**
                       * check all roads and ships adjacent to this node
                       */
                      for (SOCRoutePiece road : board.getRoadsAndShips())
                      {
                          if (adjEdge != road.getCoordinates())
                              continue;
  
                          final int roadPN = road.getPlayerNumber();
                          roads[roadPN]++;
  
                          if (roadPN == placingPN)
                          {
                              if (road.isRoadNotShip())
                                  ownRoad = true;
                              else
                                  ownShip = true;
                          }
                      }
                  }
  
                  /**
                   * if a player other than the one who put the settlement
                   * down has 2 roads or ships adjacent to it, then we need to recalculate
                   * their longest road / trade route
                   */
                  for (int i = 0; i < maxPlayers; i++)
                  {
                      if ((i != placingPN) && (roads[i] == 2))
                      {
                          updateLongestRoad(i);
  
                          /**
                           * check to see if this created a tie
                           */
                          break;
                      }
                  }
  
                  /**
                   * if placing player has connected their own roads and ships
                   * at this new coastal settlement, recalculate their longest route
                   */
                  if (ownRoad && ownShip)
                      updateLongestRoad(placingPN);
              }
          }
  
          /**
           * If temporary piece, don't update gamestate-related info.
           */
          if (isTempPiece)
          {
              return;   // <--- Early return: Temporary piece ---
          }
  
          lastActionTime = System.currentTimeMillis();
          lastActionWasBankTrade = false;
  
          /**
           * Remember ships placed this turn
           */
          if ((pp.getType() == SOCPlayingPiece.SHIP) && (gameState != LOADING))
              shipsPlacedThisTurn.add(Integer.valueOf(coord));
  
          /**
           * check if the game is over
           */
          if ((gameState > READY) && (oldGameState != SPECIAL_BUILDING))
              checkForWinner();
  
          /**
           * update the state of the game, and possibly current player
           */
          if (active)
              advanceTurnStateAfterPutPiece();
      }
  
      /**
       * On the large sea board, look for and reveal any adjacent fog hex,
       * if we're placing a road or ship touching the fog hex's corner node.
       * Reveal it before placing the new piece, so it's easier for
       * players and bots to updatePotentials (their data about the
       * board reachable through their roads/ships).
       * Each revealed fog hex triggers {@link SOCGameEvent#SGE_FOG_HEX_REVEALED}
       * and gives the current player that resource (if not desert or water or gold).
       * The server should send the clients messages to reveal the hex
       * and give the resource to that player.
       * If gold is revealed, calls
       * {@link SOCPlayer#setNeedToPickGoldHexResources(int) currentPlayer.setNeedToPickGoldHexResources(numGoldHexes)}.
       *<P>
       * Called only at server, only when {@link #hasSeaBoard}.
       * During initial placement, placing a settlement could reveal up to 3 hexes.
       *
       * @param hexCoords  Hex coordinates to check type for {@link SOCBoardLarge#FOG_HEX}
       * @param initialSettlement  Are we checking for initial settlement placement?
       *     If so, keep checking after finding a fog hex.
       * @since 2.0.00
       */
      private final void putPieceCommon_checkFogHexes(final int[] hexCoords, final boolean initialSettlement)
      {
          int goldHexes = 0;
  
          for (int i = 0; i < hexCoords.length; ++i)
          {
              final int hexCoord = hexCoords[i];
              if ((hexCoord != 0) && (board.getHexTypeFromCoord(hexCoord) == SOCBoardLarge.FOG_HEX))
              {
                  final int encodedHexInfo =
                      ((SOCBoardLarge) board).revealFogHiddenHexPrep(hexCoord);
                  final int hexType = encodedHexInfo >> 8;
                  int diceNum = encodedHexInfo & 0xFF;
                  if (diceNum == 0xFF)
                      diceNum = 0;
  
                  revealFogHiddenHex(hexCoord, hexType, diceNum);
                  if (currentPlayerNumber != -1)
                  {
                      if ((hexType >= SOCResourceConstants.CLAY) && (hexType <= SOCResourceConstants.WOOD))
                          players[currentPlayerNumber].getResources().add(1, hexType);
                      else if (hexType == SOCBoardLarge.GOLD_HEX)
                          ++goldHexes;
                  }
  
                  if (gameEventListener != null)
                      gameEventListener.gameEvent
                          (this, SOCGameEvent.SGE_FOG_HEX_REVEALED, Integer.valueOf(hexCoord));
  
                  if (! initialSettlement)
                      break;
                      // No need to keep looking, because only one end of the road or ship's
                      // edge is new; player was already at the other end, so it can't be fog.
              }
          }
  
          if ((goldHexes > 0) && (currentPlayerNumber != -1))
          {
              // ask player to pick a resource from the revealed gold hex
              // in advanceTurnStateAfterPutPiece()
              players[currentPlayerNumber].setNeedToPickGoldHexResources(goldHexes);
          }
      }
  
      /**
       * After placing a piece on the board, update the state of
       * the game, and possibly current player at server, for play to continue.
       *<P>
       * Called at server and at each client by {@link #putPieceCommon(SOCPlayingPiece, boolean)}.
       * At clients, the PUTPIECE message that triggers that call will soon be
       * followed by a GAMESTATE message to confirm the new state.
       *<P>
       * During {@link #isInitialPlacement()}, only the server advances game state; when client's game calls
       * this method it returns immediately. In {@link #START2B} or {@link #START3B} after the last initial
       * road/ship placement, at server this method calls {@link #updateAtGameFirstTurn()}
       * which includes {@link #updateAtTurn()}.
       *<P>
       * If {@link #debugFreePlacement}, does nothing unless current player's
       * {@link SOCPlayer#getNeedToPickGoldHexResources()} &gt; 0 and so the state should
       * change to one where they're prompted to pick their gains.
       *<P>
       * Also used in {@link #forceEndTurn()} to continue the game
       * after a cancelled piece placement in {@link #START1A}..{@link #START3B} .
       * If the current player number changes here, {@link #isForcingEndTurn()} is cleared.
       *<P>
       * This method is not called after placing a {@link SOCInventoryItem} on the board, which
       * happens only with some scenario options such as {@link SOCGameOptionSet#K_SC_FTRI _SC_FTRI}.
       *
       * @return true if game is still active, false if all players have left and
       *          the gamestate has been changed here to {@link #OVER}.
       * @since 1.1.00
       */
      private boolean advanceTurnStateAfterPutPiece()
      {
          if (currentPlayerNumber < 0)
              return true;  // Game hasn't started yet
  
          if ((gameState < ROLL_OR_CARD) && ! isAtServer)
              return true;  // Only server advances state during initial placement
  
          final boolean needToPickFromGold
              = hasSeaBoard && (players[currentPlayerNumber].getNeedToPickGoldHexResources() != 0);
  
          if (debugFreePlacement && ! needToPickFromGold)
              return true;  // Free placement doesn't change state or player
  
          //D.ebugPrintln("CHANGING GAME STATE FROM "+gameState);
  
          switch (gameState)
          {
          case START1A:
              if (needToPickFromGold)
              {
                  oldGameState = START1A;
                  gameState = STARTS_WAITING_FOR_PICK_GOLD_RESOURCE;
              } else {
                  gameState = START1B;
              }
              break;
  
          case START1B:
              if (needToPickFromGold)
              {
                  oldGameState = START1B;
                  gameState = STARTS_WAITING_FOR_PICK_GOLD_RESOURCE;
              } else {
                  int tmpCPN = currentPlayerNumber + 1;
                  if (tmpCPN >= maxPlayers)
                      tmpCPN = 0;
  
                  while (isSeatVacant (tmpCPN))
                  {
                      ++tmpCPN;
  
                      if (tmpCPN >= maxPlayers)
                          tmpCPN = 0;
  
                      if (tmpCPN == currentPlayerNumber)
                      {
                          setGameStateOVER();  // Looped around, no one is here
  
                          return false;
                      }
                  }
  
                  if (tmpCPN == firstPlayerNumber)
                  {
                      // All have placed their first settlement/road.
                      // Begin second placement.
                      gameState = START2A;
                  }
                  else
                  {
                      if (advanceTurn())
                          gameState = START1A;
                  }
              }
              break;
  
          case START2A:
              if (needToPickFromGold)
              {
                  oldGameState = START2A;
                  gameState = STARTS_WAITING_FOR_PICK_GOLD_RESOURCE;
              } else {
                  gameState = START2B;
              }
              break;
  
          case START2B:
              if (needToPickFromGold)
              {
                  oldGameState = START2B;
                  gameState = STARTS_WAITING_FOR_PICK_GOLD_RESOURCE;
              } else {
                  int tmpCPN = currentPlayerNumber - 1;
  
                  // who places next? same algorithm as advanceTurnBackwards.
                  if (tmpCPN < 0)
                      tmpCPN = maxPlayers - 1;
  
                  while (isSeatVacant (tmpCPN))
                  {
                      --tmpCPN;
  
                      if (tmpCPN < 0)
                          tmpCPN = maxPlayers - 1;
  
                      if (tmpCPN == currentPlayerNumber)
                      {
                          setGameStateOVER();  // Looped around, no one is here
  
                          return false;
                      }
                  }
  
                  if (tmpCPN == lastPlayerNumber)
                  {
                      // All have placed their second settlement/road.
                      if (! isGameOptionSet(SOCGameOptionSet.K_SC_3IP))
                      {
                          // Begin play.
                          // Player number is unchanged; "virtual" endTurn here.
                          // Don't clear forcingEndTurn flag, if it's set.
                          //
                          // Not done if at client; instead, server will send a TURN message
                          // from which client will call updateAtTurn and setGameState
                          // in order to call updateAtGameFirstTurn.
                          if (isAtServer)
                          {
                              gameState = ROLL_OR_CARD;
                              updateAtGameFirstTurn();
                          }
                      } else {
                          // Begin third placement.
                          gameState = START3A;
                      }
                  }
                  else
                  {
                      if (advanceTurnBackwards())
                          gameState = START2A;
                  }
              }
              break;
  
          case START3A:
              if (needToPickFromGold)
              {
                  oldGameState = START3A;
                  gameState = STARTS_WAITING_FOR_PICK_GOLD_RESOURCE;
              } else {
                  gameState = START3B;
              }
              break;
  
          case START3B:
              if (needToPickFromGold)
              {
                  oldGameState = START3B;
                  gameState = STARTS_WAITING_FOR_PICK_GOLD_RESOURCE;
              } else {
                  // who places next? same algorithm as advanceTurn.
                  int tmpCPN = currentPlayerNumber + 1;
                  if (tmpCPN >= maxPlayers)
                      tmpCPN = 0;
  
                  while (isSeatVacant (tmpCPN))
                  {
                      ++tmpCPN;
  
                      if (tmpCPN >= maxPlayers)
                          tmpCPN = 0;
  
                      if (tmpCPN == currentPlayerNumber)
                      {
                          setGameStateOVER();  // Looped around, no one is here
  
                          return false;
                      }
                  }
  
                  if (tmpCPN == firstPlayerNumber)
                  {
                      // All have placed their third settlement/road.
                      // Begin play.  The first player to roll is firstPlayerNumber.
                      // "virtual" endTurn here.
                      // Don't clear forcingEndTurn flag, if it's set.
                      //
                      // Not done if at client; instead, server will send a TURN message
                      // from which client will call updateAtTurn and setGameState
                      // in order to call updateAtGameFirstTurn.
                      if (isAtServer)
                      {
                          currentPlayerNumber = firstPlayerNumber;
                          gameState = ROLL_OR_CARD;
                          updateAtGameFirstTurn();
                      }
                  }
                  else
                  {
                      if (advanceTurn())
                          gameState = START3A;
                  }
              }
              break;
  
          case PLAY1:
              // PLAY1 is the gamestate when moveShip calls putPiece.
              // In scenario _SC_FOG, moving a ship might reveal a gold hex.
              // In scenario _SC_PIRI, PLAY1 is the gamestate when a SOCFortress is conquered
              //   and the replacement SOCSettlement is placed by attackPirateFortress.
              //   No special action is needed here.
              if (needToPickFromGold)
              {
                  oldGameState = PLAY1;
                  gameState = WAITING_FOR_PICK_GOLD_RESOURCE;
              }
              break;
  
          case PLACING_ROAD:
              // fall through
          case PLACING_SETTLEMENT:
              // fall through
          case PLACING_CITY:
              // fall through
          case PLACING_SHIP:
              if (needToPickFromGold)
              {
                  if (oldGameState != SPECIAL_BUILDING)
                      oldGameState = PLAY1;
                  gameState = WAITING_FOR_PICK_GOLD_RESOURCE;
              }
              else if (oldGameState != SPECIAL_BUILDING)
              {
                  gameState = PLAY1;
              }
              else
              {
                  gameState = SPECIAL_BUILDING;
              }
              break;
  
          case PLACING_FREE_ROAD1:
              if (needToPickFromGold)
              {
                  oldGameState = PLACING_FREE_ROAD2;
                  gameState = WAITING_FOR_PICK_GOLD_RESOURCE;
              } else {
                  gameState = PLACING_FREE_ROAD2;
              }
              break;
  
          case PLACING_FREE_ROAD2:
              {
                  final int nextState;
                  if (currentDice != 0)
                      nextState = PLAY1;
                  else
                      nextState = ROLL_OR_CARD;  // played dev card before roll
  
                  if (needToPickFromGold)
                  {
                      oldGameState = nextState;
                      gameState = WAITING_FOR_PICK_GOLD_RESOURCE;
                  } else {
                      gameState = nextState;
                  }
  
                  playingRoadBuildingCardForLastRoad = false;
              }
              break;
  
          // case PLACING_INV_ITEM:
              //    No advance needed if that's the current state; in _SC_FTRI
              //    we're here because the player placed a ship on a special edge
              //    with a port, and that changed the state, we're still waiting
              //    for the player to place their port.  State mentioned here
              //    only for completeness.
  
          }
  
          //D.ebugPrintln("  TO "+gameState);
          return true;
      }
  
      /**
       * A temporary piece has been put on the board; update all related game state.
       * Update player potentials, longest road, etc.
       * Does not advance turn or update gamestate-related fields.
       *
       * @param pp the piece to put on the board
       *
       * @see #undoPutTempPiece(SOCPlayingPiece)
       * @see #saveLargestArmyState()
       */
      public void putTempPiece(SOCPlayingPiece pp)
      {
          //D.ebugPrintln("@@@ putTempPiece "+pp);
  
          /**
           * save who the last lr player was
           */
          oldPlayerWithLongestRoad.push(new SOCOldLRStats(this));
  
          putPieceCommon(pp, true);
      }
  
      /**
       * Can this player currently move this ship, based on game state and
       * their trade routes and settlements/cities?
       * Must be current player.  Game state must be {@link #PLAY1}.
       *<P>
       * Use this method to check a specific move-from location.
       * Use {@link #canMoveShip(int, int, int)} to also check a specific move-to location.
       *<P>
       * Only the ship at the newer end of an open trade route can be moved.
       * So, to move a ship, one of its end nodes must be clear: No
       * settlement or city, and no other adjacent ship on the other
       * side of the node.
       *<P>
       * You cannot place a ship, and then move the same ship, during the same turn.
       * You cannot move a ship from an edge of the pirate ship's hex.
       *<P>
       * Trade routes can branch, so it may be that more than one ship
       * could be moved.  The game limits players to one move per turn.
       *
       * @param pn   Player number
       * @param fromEdge  Edge coordinate to move the ship from; must contain this player's ship
       * @return  The ship at {@code fromEdge} if player can move that ship now; {@code null} otherwise
       * @see #canMoveShip(int, int, int)
       * @since 2.0.00
       */
      public SOCShip canMoveShip(final int pn, final int fromEdge)
      {
          if (movedShipThisTurn || ! (hasSeaBoard && (currentPlayerNumber == pn) && (gameState == PLAY1)))
              return null;
          if (shipsPlacedThisTurn.contains(Integer.valueOf(fromEdge)))
              return null;
  
          // check fromEdge vs. pirate hex
          {
              SOCBoardLarge bL = (SOCBoardLarge) board;
              final int ph = bL.getPirateHex();
              if ((ph != 0) && bL.isEdgeAdjacentToHex(fromEdge, ph))
                  return null;
          }
  
          final SOCPlayer pl = players[pn];
          final SOCRoutePiece pieceAtFrom = pl.getRoadOrShip(fromEdge);
          if ((pieceAtFrom == null) || pieceAtFrom.isRoadNotShip())
              return null;
          SOCShip canShip = (SOCShip) pieceAtFrom;
          if (! pl.canMoveShip(canShip))
              return null;
  
          return canShip;
      }
  
      /**
       * Can this player currently move this ship to this new coordinate,
       * based on game state and their trade routes and settlements/cities?
       * Must be current player.  Game state must be {@link #PLAY1}.
       *<P>
       * Use this method to check a specific move-from and move-to location pair.
       * Use {@link #canMoveShip(int, int)} to check only the move-from.
       *<P>
       * Only the ship at the newer end of an open trade route can be moved.
       * So, to move a ship, one of <tt>fromEdge</tt>'s end nodes must be
       * clear: No settlement or city, and no other adjacent ship on the other
       * side of the node.
       *<P>
       * The new location <tt>toEdge</tt> must also be a potential ship location,
       * even if <tt>fromEdge</tt> was unoccupied; calls
       * {@link SOCPlayer#isPotentialShipMoveTo(int, int) pn.isPotentialShipMoveTo(toEdge, fromEdge)}
       * to check that.
       *<P>
       * You cannot move a ship to or from an edge of the pirate ship's hex.
       *<P>
       * Trade routes can branch, so it may be that more than one ship
       * could be moved.  The game limits players to one move per turn.
       *<P>
       * <B>Scenario option {@link SOCGameOptionSet#K_SC_PIRI _SC_PIRI}:</B><br>
       * Ship movement options are limited, because the route can't branch
       * and only a few sea edges are legal for placement.
       *
       * @param pn   Player number
       * @param fromEdge  Edge coordinate to move the ship from; must contain this player's ship.
       * @param toEdge    Edge coordinate to move to; must be different than <tt>fromEdge</tt>.
       *            Checks {@link SOCPlayer#isPotentialShip(int) players[pn].isPotentialShip(toEdge)}.
       * @return  The ship at {@code fromEdge} if player can move that ship now; {@code null} otherwise
       * @see #canMoveShip(int, int)
       * @see #moveShip(SOCShip, int)
       * @since 2.0.00
       */
      public SOCShip canMoveShip(final int pn, final int fromEdge, final int toEdge)
      {
          if (fromEdge == toEdge)
              return null;
          final SOCPlayer pl = players[pn];
          if (! pl.isPotentialShipMoveTo(toEdge, fromEdge))
              return null;
  
          // check toEdge vs. pirate hex
          {
              SOCBoardLarge bL = (SOCBoardLarge) board;
              final int ph = bL.getPirateHex();
              if ((ph != 0) && bL.isEdgeAdjacentToHex(toEdge, ph))
                  return null;
          }
  
          return canMoveShip(pn, fromEdge);  // <-- checks most other conditions
      }
  
      /**
       * Move this ship on the board and update all related game state.
       * Calls {@link #checkForWinner()}; gamestate may become {@link #OVER}
       * if a player gets the longest trade route.
       *<P>
       * Calls {@link #undoPutPieceCommon(SOCPlayingPiece, boolean, boolean) undoPutPieceCommon(sh, false, true)}
       * and {@link #putPiece(SOCPlayingPiece)}.
       * Updates longest trade route.
       * Not for use with temporary pieces.
       *<P>
       *<b>Note:</b> Because <tt>sh</tt> and <tt>toEdge</tt>
       * are not checked for validity, please call
       * {@link #canMoveShip(int, int, int)} before calling this method.
       *<P>
       * The call to putPiece incorrectly adds the moved ship's
       * new location to {@link #getShipsPlacedThisTurn()}, but since
       * we can only move 1 ship per turn, the add is harmless.
       *<P>
       * During {@link #isDebugFreePlacement()}, the gamestate is not changed,
       * unless the current player gains enough points to win.
       *<P>
       * <B>Scenario option {@link SOCGameOptionSet#K_SC_FOG _SC_FOG}:</B><br>
       * moveShip's caller should check {@link SOCPlayer#getNeedToPickGoldHexResources()} != 0.
       * Revealing a gold hex from fog will set that player field and also
       * sets gamestate to {@link #WAITING_FOR_PICK_GOLD_RESOURCE}.
       *
       * @param sh the ship to move on the board; its coordinate must be
       *           the edge to move from. Must not be a temporary ship.
       * @param toEdge    Edge coordinate to move to
       * @since 2.0.00
       */
      public void moveShip(SOCShip sh, final int toEdge)
      {
          undoPutPieceCommon(sh, false, true);
          putPiece(new SOCShip(sh.getPlayer(), toEdge, board));  // calls checkForWinner, etc
          movedShipThisTurn = true;
      }
  
      /**
       * Remove this ship from the board and update all related game state.
       * Used in scenario option {@link SOCGameOptionSet#K_SC_PIRI _SC_PIRI}
       * by {@link #attackPirateFortress(SOCShip)} and at the client.
       *<P>
       * Calls {@link #undoPutPieceCommon(SOCPlayingPiece, boolean, boolean) undoPutPieceCommon(sh, false, false)}.
       * Not for use with temporary pieces or ship moves.
       *
       * @param sh  the ship to remove
       * @since 2.0.00
       */
      public void removeShip(SOCShip sh)
      {
          undoPutPieceCommon(sh, false, false);
      }
  
      /**
       * undo the putting of a temporary or initial piece
       * or a ship being moved.
       * If state is START2B or START3B and resources were given, they will be returned.
       *<P>
       * If a ship is removed in scenario {@code _SC_PIRI}, makes sure its player's
       * {@link SOCPlayer#getNumWarships()} is never more than the number of their ships on the board.
       *
       * @param pp  the piece to remove from the board
       * @param isTempPiece  Is this a temporary piece?  If so, do not call the
       *            game's {@link SOCGameEventListener}.
       * @param isMoveOrReplacement  Is the piece really being moved to a new location, or replaced with another?
       *            If so, don't remove its {@link SOCPlayingPiece#specialVP} from player.
       * @since 1.1.00
       */
      protected void undoPutPieceCommon
          (SOCPlayingPiece pp, final boolean isTempPiece, final boolean isMoveOrReplacement)
      {
          //D.ebugPrintln("@@@ undoPutTempPiece "+pp);
          board.removePiece(pp);
  
          //
          // call undoPutPiece() on every player so that
          // they can update their potentials
          //
          for (int i = 0; i < maxPlayers; i++)
          {
              players[i].undoPutPiece(pp, isMoveOrReplacement);   // If state START2B or START3B, will also zero resources
          }
  
          //
          // if the piece is a city, put the settlement back
          //
          if (pp.getType() == SOCPlayingPiece.CITY)
          {
              SOCSettlement se = new SOCSettlement(pp.getPlayer(), pp.getCoordinates(), board);
  
              for (int i = 0; i < maxPlayers; i++)
              {
                  players[i].putPiece(se, isTempPiece);
              }
  
              board.putPiece(se);
          }
      }
  
      /**
       * undo the putting of a temporary piece
       *
       * @param pp  the piece to remove from the board
       *
       * @see #undoPutInitSettlement(SOCPlayingPiece)
       * @see #restoreLargestArmyState()
       */
      public void undoPutTempPiece(SOCPlayingPiece pp)
      {
          undoPutPieceCommon(pp, true, false);
  
          //
          // update which player has longest road
          //
          SOCOldLRStats oldLRStats = oldPlayerWithLongestRoad.pop();
          oldLRStats.restoreOldStats(this);
      }
  
      /**
       * undo the putting of an initial settlement.
       * If state is START2B or START3B and resources were given, they will be returned.
       * Player is unchanged; state will become START1A or START2A or START3A.
       * Not for use with temporary pieces (use {@link #undoPutTempPiece(SOCPlayingPiece)} instead).
       *
       * @param pp the piece to remove from the board
       * @see #canCancelBuildPiece(int)
       * @since 1.1.00
       */
      public void undoPutInitSettlement(SOCPlayingPiece pp)
      {
          if ((gameState != START1B) && (gameState != START2B) && (gameState != START3B))
              throw new IllegalStateException("Cannot remove at this game state: " + gameState);
          if (pp.getType() != SOCPlayingPiece.SETTLEMENT)
              throw new IllegalArgumentException("Not a settlement: type " + pp.getType());
          if (pp.getCoordinates() != pp.getPlayer().getLastSettlementCoord())
              throw new IllegalArgumentException("Not coordinate of last settlement");
  
          undoPutPieceCommon(pp, false, false);  // Will also zero resources via player.undoPutPiece
  
          if (gameState == START1B)
              gameState = START1A;
          else if (gameState == START2B)
              gameState = START2A;
          else // gameState == START3B
              gameState = START3A;
      }
  
      /**
       * Initialize server-only game fields.
       * Called from {@link #startGame()} and saved-game loader.
       * Sets {@link #isAtServer} and {@link #allOriginalPlayers()} flags.
       * Updates {@link #lastActionTime}.
       * Initializes misc fields like each player's {@link SOCPlayer#pendingMessagesOut}.
       * @since 2.3.00
       */
      public void initAtServer()
      {
          isAtServer = true;
  
          pendingMessagesOut = new ArrayList<Object>();
          for (int i = 0; i < maxPlayers; ++i)
              players[i].pendingMessagesOut = new ArrayList<Object>();
  
          // make sure game doesn't look idle, in case first player is a robot
          lastActionTime = System.currentTimeMillis();
  
          allOriginalPlayers = true;
      }
  
      /**
       * Do the things involved in starting a game at server:
       * Call {@link #initAtServer()},
       * shuffle the tiles and cards,
       * make a board by calling {@link SOCBoard#makeNewBoard(SOCGameOptionSet)},
       * set players' legal and potential piece locations,
       * choose first player.
       * gameState becomes {@link #START1A}.
       * Updates {@link #lastActionTime}.
       *<P>
       * <B>Note:</B> This method requires at least 1 seated player, or it will loop forever.
       *<P>
       * Called only at server, not client.  For a method called during game start
       * at server and clients, see {@link #updateAtBoardLayout()}.
       *<P>
       * Some scenarios require other methods to finish setting up the game;
       * call them in this order before any other board or game methods:
       *<UL>
       * <LI> This method {@code startGame()}
       * <LI> If appropriate, {@link soc.server.SOCBoardAtServer#startGame_scenarioSetup(SOCGame)}
       *</UL>
       */
      public void startGame()
      {
          initAtServer();
  
          startGame_setupDevCards();
  
          board.makeNewBoard(opts);
          if (hasSeaBoard)
          {
              /**
               * Set each player's legal and potential settlements and roads
               * to reflect the new board layout.
               *
               * Only necessary when hasSeaBoard (v3 board encoding):
               * In the v1 and v2 board encodings, the legal coordinates never change, so
               * SOCPlayer knows them already.
               */
              setPlayersLandHexCoordinates();
              HashSet<Integer> psList = ((SOCBoardLarge) board).getLegalSettlements();
              final HashSet<Integer>[] las = ((SOCBoardLarge) board).getLandAreasLegalNodes();
              for (int i = 0; i < maxPlayers; ++i)
                  players[i].setPotentialAndLegalSettlements(psList, true, las);
          }
          updateAtBoardLayout();
  
          gameState = START1A;
  
          /**
           * choose who goes first
           */
          do
          {
  