/**
   * remove member from the game. Also updates game's client version range, with remaining connected
   * members. Please call {@link #takeMonitorForGame(String)} before calling this.
   *
   * @param gaName the name of the game
   * @param conn the member's connection
   */
  public synchronized void removeMember(StringConnection conn, String gaName) {
    System.err.println("L139: game " + gaName + " remove " + conn); // JM TEMP
    Vector members = getMembers(gaName);

    if ((members != null)) {
      members.removeElement(conn);

      // Check version of remaining members
      if (!members.isEmpty()) {
        StringConnection c = (StringConnection) members.firstElement();
        int lowVers = c.getVersion();
        int highVers = lowVers;
        for (int i = members.size() - 1; i > 1; --i) {
          c = (StringConnection) members.elementAt(i);
          int v = c.getVersion();
          if (v < lowVers) lowVers = v;
          if (v > highVers) highVers = v;
        }
        SOCGame ga = getGameData(gaName);
        ga.clientVersionLowest = lowVers;
        ga.clientVersionHighest = highVers;
        ga.hasOldClients = (lowVers < Version.versionNumber());
      }
    }
  }
  /**
   * add a member to the game. Also checks client's version against game's current range of client
   * versions. Please call {@link #takeMonitorForGame(String)} before calling this.
   *
   * @param gaName the name of the game
   * @param conn the member's connection; version should already be set
   */
  public synchronized void addMember(StringConnection conn, String gaName) {
    Vector members = getMembers(gaName);

    if ((members != null) && (!members.contains(conn))) {
      System.err.println("L139: game " + gaName + " add " + conn); // JM TEMP
      final boolean firstMember = members.isEmpty();
      members.addElement(conn);

      // Check version range
      SOCGame ga = getGameData(gaName);
      final int cliVers = conn.getVersion();
      if (firstMember) {
        ga.clientVersionLowest = cliVers;
        ga.clientVersionHighest = cliVers;
        ga.hasOldClients = (cliVers < Version.versionNumber());
      } else {
        final int cliLowestAlready = ga.clientVersionLowest;
        final int cliHighestAlready = ga.clientVersionHighest;
        if (cliVers < cliLowestAlready) {
          ga.clientVersionLowest = cliVers;
          if (cliVers < Version.versionNumber()) ga.hasOldClients = true;
        }
        if (cliVers > cliHighestAlready) {
          ga.clientVersionHighest = cliVers;
        }
      }
    }
  }
  /**
   * Replace member from all games, with a new connection with same name (after a network problem).
   *
   * @param oldConn the member's old connection
   * @param oldConn the member's new connection
   * @throws IllegalArgumentException if oldConn's keyname (via {@link StringConnection#getData()
   *     getData()}) differs from newConn's keyname
   * @see #memberGames(StringConnection, String)
   * @since 1.1.08
   */
  public synchronized void replaceMemberAllGames(StringConnection oldConn, StringConnection newConn)
      throws IllegalArgumentException {
    if (!oldConn.getData().equals(newConn.getData()))
      throw new IllegalArgumentException("keyname data");

    System.err.println("L212: replaceMemberAllGames(" + oldConn + ", " + newConn + ")"); // JM TEMP
    final boolean sameVersion = (oldConn.getVersion() == newConn.getVersion());
    for (String gaName : getGameNames()) {
      Vector<StringConnection> members = gameMembers.get(gaName);
      if ((members != null) && members.contains(oldConn)) {
        System.err.println("L221: for game " + gaName + ":"); // JM TEMP
        if (sameVersion) {
          if (members.remove(oldConn)) System.err.println("   OK");
          else System.err.println("   ** not found");
          members.addElement(newConn);
        } else {
          removeMember(oldConn, gaName);
          addMember(newConn, gaName);
        }
      }
    }
  }