From 21c599cd0a5cf765eca7bf24b8139bc2853acdcf Mon Sep 17 00:00:00 2001 From: Walter Korman Date: Tue, 15 Oct 2002 23:07:23 +0000 Subject: [PATCH] Revamped the player array in the game object to allow sparse player arrays that may have one or more null player entries so that party games can create their game with the maximum number of players they support and can use the player index as the player's "seat". Simplified some of the jockeying we had to do when adding and removing players. Complexified some of the turn game management in order to deal properly with the sparse player arrays. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1802 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../threerings/parlor/game/GameManager.java | 178 +++++++++++------- .../threerings/parlor/game/GameObject.dobj | 39 +++- .../threerings/parlor/game/GameObject.java | 56 +++++- .../turn/TurnGameControllerDelegate.java | 10 +- .../parlor/turn/TurnGameManager.java | 8 +- .../parlor/turn/TurnGameManagerDelegate.java | 63 ++++--- .../parlor/turn/TurnGameObject.java | 11 +- 7 files changed, 257 insertions(+), 108 deletions(-) diff --git a/src/java/com/threerings/parlor/game/GameManager.java b/src/java/com/threerings/parlor/game/GameManager.java index cb14dea22..93152f3df 100644 --- a/src/java/com/threerings/parlor/game/GameManager.java +++ b/src/java/com/threerings/parlor/game/GameManager.java @@ -1,5 +1,5 @@ // -// $Id: GameManager.java,v 1.47 2002/10/07 18:42:21 mdb Exp $ +// $Id: GameManager.java,v 1.48 2002/10/15 23:07:23 shaper Exp $ package com.threerings.parlor.game; @@ -7,8 +7,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; -import com.samskivert.util.ArrayUtil; -import com.samskivert.util.ListUtil; import com.samskivert.util.StringUtil; import com.threerings.presents.data.ClientObject; @@ -49,52 +47,70 @@ public class GameManager extends PlaceManager // save off a casted reference to our config _gameconfig = (GameConfig)_config; - - // keep this around for now, we'll need it later - _players = _gameconfig.players; - - // instantiate a player oid array which we'll fill in later - _playerOids = new int[_players.length]; } /** - * Adds the given player to the game. This should only be called - * before the game is started, and is most likely to be used to add - * players to party games. + * Adds the given player to the game at the first available player + * index. This should only be called before the game is started, and + * is most likely to be used to add players to party games. * * @param player the username of the player to add to this game. + * @return the player index at which the player was added, or + * -1 if the player could not be added to the game. */ - public void addPlayer (String player) + public int addPlayer (String player) { - // append the player to the player list - int pcount = _players.length; - int npcount = pcount + 1; - String[] nplayers = new String[npcount]; - System.arraycopy(_players, 0, nplayers, 0, pcount); - nplayers[pcount] = player; - _players = nplayers; - - // expand the player oids list - int[] nplayerOids = new int[npcount]; - System.arraycopy(_playerOids, 0, nplayerOids, 0, pcount); - _playerOids = nplayerOids; - - if (_AIs != null) { - // expand the AI list - byte[] nAIs = new byte[npcount]; - System.arraycopy(_AIs, 0, nAIs, 0, pcount); - _AIs = nAIs; + // make sure we've space to add another player + int size = _gameobj.players.length; + if (_playerCount == size) { + Log.warning("Attempt to add player to full game " + + "[game=" + _gameobj.which() + ", player=" + player + + ", players=" + StringUtil.toString(_gameobj.players) + + "]."); + return -1; } - // set the new players list in the game object - _gameobj.setPlayers(_players); + // determine the first available player index + int pidx = -1; + for (int ii = 0; ii < size; ii++) { + if (!_gameobj.isOccupiedPlayer(ii)) { + pidx = ii; + break; + } + } + + // sanity-check the player index + if (pidx == -1) { + Log.warning("Couldn't find free player index for player who " + + "ought to be addable?! [game=" + _gameobj.which() + + ", player=" + player + + ", players=" + StringUtil.toString(_gameobj.players) + + "]."); + return -1; + } + + // fill in the player's information + _gameobj.setPlayersAt(player, pidx); + + // increment the number of players in the game + _playerCount++; // deliver a game ready notification to the player BodyObject bobj = CrowdServer.lookupBody(player); - ParlorSender.gameIsReady(bobj, _gameobj.getOid()); + if (bobj == null) { + Log.warning("Newly added player's body object went away before " + + "game ready notification could be sent " + + "[game=" + _gameobj.which() + ", player=" + player + + ", players=" + StringUtil.toString(_gameobj.players) + + "]."); + } else { + ParlorSender.gameIsReady(bobj, _gameobj.getOid()); + } // let derived classes do what they like - playerWasAdded(player, pcount); + playerWasAdded(player, pidx); + + return pidx; } /** @@ -116,34 +132,41 @@ public class GameManager extends PlaceManager * early-on if they realize they'd rather not play for some reason. * * @param player the username of the player to remove from this game. + * @return true if the player was successfully removed, false if not. */ - public void removePlayer (String player) + public boolean removePlayer (String player) { // get the player's index in the player list - int pidx = ListUtil.indexOfEqual(_players, player); + int pidx = _gameobj.getPlayerIndex(player); + + // sanity-check the player index if (pidx == -1) { Log.warning("Attempt to remove non-player from players list " + "[game=" + _gameobj.which() + - ", player=" + player + "]."); - return; + ", player=" + player + + ", players=" + StringUtil.toString(_gameobj.players) + + "]."); + return false; } // remove the player from the players list - _players = ArrayUtil.splice(_players, pidx, 1); + _gameobj.setPlayersAt(null, pidx); - // remove the player slot from the player oid list - _playerOids = ArrayUtil.splice(_playerOids, pidx, 1); + // clear out the player's entry in the player oid list + _playerOids[pidx] = 0; if (_AIs != null) { - // remove the player slot from the AI list - _AIs = ArrayUtil.splice(_AIs, pidx, 1); + // clear out the player's entry in the AI list + _AIs[pidx] = -1; } - // set the new players list in the game object - _gameobj.setPlayers(_players); + // decrement the number of players in the game + _playerCount--; // let derived classes do what they like playerWasRemoved(player, pidx); + + return true; } /** @@ -173,7 +196,7 @@ public class GameManager extends PlaceManager { if (_AIs == null) { // create and initialize the AI skill level array - _AIs = new byte[_players.length]; + _AIs = new byte[_gameobj.players.length]; Arrays.fill(_AIs, (byte)-1); // set up a delegate op for AI ticking _tickAIOp = new TickAIDelegateOp(); @@ -195,7 +218,16 @@ public class GameManager extends PlaceManager */ public String getPlayerName (int index) { - return _players[index]; + return _gameobj.players[index]; + } + + /** + * Returns the player index of the given user in the game, or + * -1 if the player is not involved in the game. + */ + public int getPlayerIndex (String username) + { + return _gameobj.getPlayerIndex(username); } /** @@ -211,7 +243,7 @@ public class GameManager extends PlaceManager */ public int getPlayerCount () { - return _players.length; + return _playerCount; } /** @@ -272,28 +304,41 @@ public class GameManager extends PlaceManager // documentation inherited protected void didStartup () { - super.didStartup(); - // obtain a casted reference to our game object _gameobj = (GameObject)_plobj; // stick the players into the game object - _gameobj.setPlayers(_players); + _gameobj.setPlayers(_gameconfig.players); + + // save off the number of players so that we needn't repeatedly + // iterate through the player name array server-side unnecessarily + _playerCount = _gameobj.getPlayerCount(); + + // instantiate a player oid array which we'll fill in later + int size = _gameobj.players.length; + _playerOids = new int[size]; // create and fill in our game service object GameMarshaller service = (GameMarshaller) _invmgr.registerDispatcher(new GameDispatcher(this), false); _gameobj.setGameService(service); + // give delegates a chance to do their thing + super.didStartup(); + // let the players of this game know that we're ready to roll (if // we have a specific set of players) - int pcount = (_players != null) ? _players.length : 0; - for (int ii = 0; ii < pcount; ii++) { - BodyObject bobj = CrowdServer.lookupBody(_players[ii]); + for (int ii = 0; ii < size; ii++) { + // skip non-existent players + if (!_gameobj.isOccupiedPlayer(ii)) { + continue; + } + + BodyObject bobj = CrowdServer.lookupBody(_gameobj.players[ii]); if (bobj == null) { Log.warning("Unable to deliver game ready to non-existent " + "player [game=" + _gameobj.which() + - ", player=" + _players[ii] + "]."); + ", player=" + _gameobj.players[ii] + "]."); continue; } @@ -316,6 +361,9 @@ public class GameManager extends PlaceManager { super.bodyLeft(bodyOid); + // TODO: if this is a party game not yet in play and the creator + // left, choose a new creator if at least one player still remains + // deal with disappearing players } @@ -378,7 +426,7 @@ public class GameManager extends PlaceManager if (!allPlayersReady()) { Log.warning("Requested to start a game that is still " + "awaiting players [game=" + _gameobj.which() + - ", pnames=" + StringUtil.toString(_players) + + ", pnames=" + StringUtil.toString(_gameobj.players) + ", poids=" + StringUtil.toString(_playerOids) + "]."); return false; } @@ -594,9 +642,11 @@ public class GameManager extends PlaceManager */ protected boolean allPlayersReady () { - for (int ii = 0; ii < _players.length; ii++) { - if ((_playerOids[ii] == 0) && - ((_AIs == null) || (_AIs[ii] == -1))) { + int pcount = _gameobj.players.length; + for (int ii = 0; ii < pcount; ii++) { + if (_gameobj.isOccupiedPlayer(ii) && + (_playerOids[ii] == 0) && + (_AIs == null || _AIs[ii] == -1)) { return false; } } @@ -616,9 +666,11 @@ public class GameManager extends PlaceManager // make sure the caller is the creating player BodyObject plobj = (BodyObject)caller; - if (!plobj.username.equals(_players[0])) { + int pidx = _gameobj.getPlayerIndex(plobj.username); + if (pidx != _gameobj.creator) { Log.warning("Attempt to start party game by non-creating player " + "[game=" + _gameobj.which() + + ", creator=" + getPlayerName(_gameobj.creator) + ", caller=" + caller + "]."); return; } @@ -686,8 +738,8 @@ public class GameManager extends PlaceManager /** A reference to our game object. */ protected GameObject _gameobj; - /** The usernames of the players of this game. */ - protected String[] _players; + /** The number of players in the game. */ + protected int _playerCount; /** The oids of our player and AI body objects. */ protected int[] _playerOids; diff --git a/src/java/com/threerings/parlor/game/GameObject.dobj b/src/java/com/threerings/parlor/game/GameObject.dobj index ed4e3dd29..b354fed79 100644 --- a/src/java/com/threerings/parlor/game/GameObject.dobj +++ b/src/java/com/threerings/parlor/game/GameObject.dobj @@ -1,5 +1,5 @@ // -// $Id: GameObject.dobj,v 1.14 2002/09/20 04:53:42 mdb Exp $ +// $Id: GameObject.dobj,v 1.15 2002/10/15 23:07:23 shaper Exp $ package com.threerings.parlor.game; @@ -43,20 +43,43 @@ public class GameObject extends PlaceObject /** Indicates whether or not this game is rated. */ public boolean isRated; - /** The username of the players involved in this game. */ + /** The usernames of the players involved in this game. */ public String[] players; /** The unique round identifier for the current round. */ public int roundId; + /** The player index of the creating player if this is a party game. */ + public int creator; + + /** + * Returns the number of players in the game. + */ + protected int getPlayerCount () + { + int count = 0; + int size = players.length; + for (int ii = 0; ii < size; ii++) { + if (players[ii] != null) { + count++; + } + } + return count; + } + /** * Returns the player index of the given user in the game, or * -1 if the player is not involved in the game. */ public int getPlayerIndex (String username) { - return (players == null) ? -1 : - ListUtil.indexOfEqual(players, username); + int size = (players == null) ? 0 : players.length; + for (int ii = 0; ii < size; ii++) { + if (players[ii] != null && players[ii].equals(username)) { + return ii; + } + } + return -1; } /** @@ -67,4 +90,12 @@ public class GameObject extends PlaceObject { return (state == IN_PLAY); } + + /** + * Returns whether the given player index in the game is occupied. + */ + public boolean isOccupiedPlayer (int index) + { + return (players[index] != null); + } } diff --git a/src/java/com/threerings/parlor/game/GameObject.java b/src/java/com/threerings/parlor/game/GameObject.java index 07b336c10..015411d7e 100644 --- a/src/java/com/threerings/parlor/game/GameObject.java +++ b/src/java/com/threerings/parlor/game/GameObject.java @@ -1,5 +1,5 @@ // -// $Id: GameObject.java,v 1.9 2002/09/20 04:53:42 mdb Exp $ +// $Id: GameObject.java,v 1.10 2002/10/15 23:07:23 shaper Exp $ package com.threerings.parlor.game; @@ -35,6 +35,9 @@ public class GameObject extends PlaceObject /** The field name of the roundId field. */ public static final String ROUND_ID = "roundId"; + /** The field name of the creator field. */ + public static final String CREATOR = "creator"; + /** A game state constant indicating that the game has not yet started * and is still awaiting the arrival of all of the players. */ public static final int AWAITING_PLAYERS = 0; @@ -58,20 +61,43 @@ public class GameObject extends PlaceObject /** Indicates whether or not this game is rated. */ public boolean isRated; - /** The username of the players involved in this game. */ + /** The usernames of the players involved in this game. */ public String[] players; /** The unique round identifier for the current round. */ public int roundId; + /** The player index of the creating player if this is a party game. */ + public int creator; + + /** + * Returns the number of players in the game. + */ + protected int getPlayerCount () + { + int count = 0; + int size = players.length; + for (int ii = 0; ii < size; ii++) { + if (players[ii] != null) { + count++; + } + } + return count; + } + /** * Returns the player index of the given user in the game, or * -1 if the player is not involved in the game. */ public int getPlayerIndex (String username) { - return (players == null) ? -1 : - ListUtil.indexOfEqual(players, username); + int size = (players == null) ? 0 : players.length; + for (int ii = 0; ii < size; ii++) { + if (players[ii] != null && players[ii].equals(username)) { + return ii; + } + } + return -1; } /** @@ -83,6 +109,14 @@ public class GameObject extends PlaceObject return (state == IN_PLAY); } + /** + * Returns whether the given player index in the game is occupied. + */ + public boolean isOccupiedPlayer (int index) + { + return (players[index] != null); + } + /** * Requests that the gameService field be set to the specified * value. The local value will be updated immediately and an event @@ -166,4 +200,18 @@ public class GameObject extends PlaceObject this.roundId = roundId; requestAttributeChange(ROUND_ID, new Integer(roundId)); } + + /** + * Requests that the creator field be set to the specified + * value. The local value will be updated immediately and an event + * will be propagated through the system to notify all listeners that + * the attribute did change. Proxied copies of this object (on + * clients) will apply the value change when they received the + * attribute changed notification. + */ + public void setCreator (int creator) + { + this.creator = creator; + requestAttributeChange(CREATOR, new Integer(creator)); + } } diff --git a/src/java/com/threerings/parlor/turn/TurnGameControllerDelegate.java b/src/java/com/threerings/parlor/turn/TurnGameControllerDelegate.java index 313107cb7..4527c2ede 100644 --- a/src/java/com/threerings/parlor/turn/TurnGameControllerDelegate.java +++ b/src/java/com/threerings/parlor/turn/TurnGameControllerDelegate.java @@ -1,5 +1,5 @@ // -// $Id: TurnGameControllerDelegate.java,v 1.3 2002/08/07 21:16:08 shaper Exp $ +// $Id: TurnGameControllerDelegate.java,v 1.4 2002/10/15 23:07:23 shaper Exp $ package com.threerings.parlor.turn; @@ -59,13 +59,7 @@ public class TurnGameControllerDelegate extends GameControllerDelegate */ public int getTurnHolderIndex () { - String holder = _turnGame.getTurnHolder(); - for (int i = 0; i < _gameObj.players.length; i++) { - if (_gameObj.players[i].equals(holder)) { - return i; - } - } - return -1; + return _gameObj.getPlayerIndex(_turnGame.getTurnHolder()); } // documentation inherited diff --git a/src/java/com/threerings/parlor/turn/TurnGameManager.java b/src/java/com/threerings/parlor/turn/TurnGameManager.java index c742e9118..f41fe2c55 100644 --- a/src/java/com/threerings/parlor/turn/TurnGameManager.java +++ b/src/java/com/threerings/parlor/turn/TurnGameManager.java @@ -1,5 +1,5 @@ // -// $Id: TurnGameManager.java,v 1.7 2002/08/06 23:23:39 shaper Exp $ +// $Id: TurnGameManager.java,v 1.8 2002/10/15 23:07:23 shaper Exp $ package com.threerings.parlor.turn; @@ -34,6 +34,12 @@ public interface TurnGameManager */ public String getPlayerName (int index); + /** + * Extending {@link GameManager} should automatically handle + * implementing this method. + */ + public int getPlayerIndex (String username); + /** * Extending {@link GameManager} should automatically handle * implementing this method. diff --git a/src/java/com/threerings/parlor/turn/TurnGameManagerDelegate.java b/src/java/com/threerings/parlor/turn/TurnGameManagerDelegate.java index 3fad8aae8..222fc2d2f 100644 --- a/src/java/com/threerings/parlor/turn/TurnGameManagerDelegate.java +++ b/src/java/com/threerings/parlor/turn/TurnGameManagerDelegate.java @@ -1,5 +1,5 @@ // -// $Id: TurnGameManagerDelegate.java,v 1.7 2002/08/07 21:17:50 shaper Exp $ +// $Id: TurnGameManagerDelegate.java,v 1.8 2002/10/15 23:07:23 shaper Exp $ package com.threerings.parlor.turn; @@ -8,7 +8,7 @@ import com.threerings.crowd.data.PlaceObject; import com.threerings.parlor.Log; import com.threerings.parlor.game.GameManager; import com.threerings.parlor.game.GameManagerDelegate; -import com.threerings.parlor.util.MathUtil; +import com.threerings.util.RandomUtil; /** * Performs the server-side turn-based game processing for a turn based @@ -36,17 +36,12 @@ public class TurnGameManagerDelegate extends GameManagerDelegate * game object. * * @return the index into the players array of the current turn holder - * or -1 if there is no current turn holder. + * or -1 if there is no current turn holder. */ public int getTurnHolderIndex () { String holder = _turnGame.getTurnHolder(); - for (int i = 0; i < _tgmgr.getPlayerCount(); i++) { - if (_tgmgr.getPlayerName(i).equals(holder)) { - return i; - } - } - return -1; + return _tgmgr.getPlayerIndex(_turnGame.getTurnHolder()); } /** @@ -64,18 +59,26 @@ public class TurnGameManagerDelegate extends GameManagerDelegate public void startTurn () { // sanity check - if (_turnIdx < 0 || _turnIdx >= _tgmgr.getPlayerCount()) { + if (_turnIdx < 0 || _turnIdx >= _turnGame.getPlayers().length) { Log.warning("startTurn() called with invalid turn index " + "[turnIdx=" + _turnIdx + "]."); // abort, abort return; } + // get the player name and sanity-check again + String name = _tgmgr.getPlayerName(_turnIdx); + if (name == null) { + Log.warning("startTurn() called with invalid player " + + "[turnIdx=" + _turnIdx + "]."); + return; + } + // do pre-start processing _tgmgr.turnWillStart(); // and set the turn indicator accordingly - _turnGame.setTurnHolder(_tgmgr.getPlayerName(_turnIdx)); + _turnGame.setTurnHolder(name); // do post-start processing _tgmgr.turnDidStart(); @@ -89,19 +92,19 @@ public class TurnGameManagerDelegate extends GameManagerDelegate * *

If the next turn should not be started immediately after this * turn, the game manager should arrange for {@link - * #setNextTurnHolder} to set the {@link #_turnIdx} field to -1 which - * will cause us not to start the next turn. It can then call {@link - * GameManager#endGame} if the game is over or do whatever else it - * needs to do outside the context of the turn flow. To start things - * back up again it would set {@link #_turnIdx} to the next turn - * holder and call {@link #startTurn} itself. + * #setNextTurnHolder} to set the {@link #_turnIdx} field to + * -1 which will cause us not to start the next turn. It + * can then call {@link GameManager#endGame} if the game is over or do + * whatever else it needs to do outside the context of the turn flow. + * To start things back up again it would set {@link #_turnIdx} to the + * next turn holder and call {@link #startTurn} itself. */ public void endTurn () { // let the manager know that the turn is over _tgmgr.turnDidEnd(); - // figure out whose up next + // figure out who's up next setNextTurnHolder(); // and start the next turn if desired @@ -137,9 +140,10 @@ public class TurnGameManagerDelegate extends GameManagerDelegate */ protected void setFirstTurnHolder () { - // TODO: sort out a better random number generator and make it - // available via the parlor services - _turnIdx = MathUtil.random(_tgmgr.getPlayerCount()); + int size = _turnGame.getPlayers().length; + do { + _turnIdx = RandomUtil.getInt(size); + } while (_tgmgr.getPlayerName(_turnIdx) == null); } /** @@ -151,8 +155,17 @@ public class TurnGameManagerDelegate extends GameManagerDelegate */ protected void setNextTurnHolder () { - // next! - _turnIdx = (_turnIdx + 1) % _tgmgr.getPlayerCount(); + // stick with the current player if they're the only participant + if (_tgmgr.getPlayerCount() == 1) { + return; + } + + // find the next occupied player slot + int size = _turnGame.getPlayers().length; + int oturnIdx = _turnIdx; + do { + _turnIdx = (_turnIdx + 1) % size; + } while (_tgmgr.getPlayerName(_turnIdx) == null); } /** The game manager for which we are delegating. */ @@ -161,7 +174,7 @@ public class TurnGameManagerDelegate extends GameManagerDelegate /** A reference to our game object. */ protected TurnGameObject _turnGame; - /** The offset into the _players array of the current turn holder or - * -1 if it's no one's turn. */ + /** The player index of the current turn holder or -1 if + * it's no one's turn. */ protected int _turnIdx = -1; } diff --git a/src/java/com/threerings/parlor/turn/TurnGameObject.java b/src/java/com/threerings/parlor/turn/TurnGameObject.java index ed4662746..9a4652996 100644 --- a/src/java/com/threerings/parlor/turn/TurnGameObject.java +++ b/src/java/com/threerings/parlor/turn/TurnGameObject.java @@ -1,5 +1,5 @@ // -// $Id: TurnGameObject.java,v 1.3 2002/02/12 07:01:54 mdb Exp $ +// $Id: TurnGameObject.java,v 1.4 2002/10/15 23:07:23 shaper Exp $ package com.threerings.parlor.turn; @@ -20,8 +20,8 @@ public interface TurnGameObject /** * Returns the username of the player who is currently taking their - * turn in this turn-based game or null if no user currently holds the - * turn. + * turn in this turn-based game or null if no user + * currently holds the turn. */ public String getTurnHolder (); @@ -30,4 +30,9 @@ public interface TurnGameObject * value. */ public void setTurnHolder (String turnHolder); + + /** + * Returns the array of player names involved in the game. + */ + public String[] getPlayers (); }