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
This commit is contained in:
Walter Korman
2002-10-15 23:07:23 +00:00
parent 1d7f033586
commit 21c599cd0a
7 changed files with 257 additions and 108 deletions
@@ -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
* <code>-1</code> 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
* <code>-1</code> 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;
@@ -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
* <code>-1</code> 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);
}
}
@@ -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 <code>roundId</code> field. */
public static final String ROUND_ID = "roundId";
/** The field name of the <code>creator</code> 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
* <code>-1</code> 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 <code>gameService</code> 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 <code>creator</code> 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));
}
}
@@ -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
@@ -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.
@@ -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 <code>-1</code> 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
*
* <p> 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
* <code>-1</code> 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 <code>-1</code> if
* it's no one's turn. */
protected int _turnIdx = -1;
}
@@ -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 <code>null</code> 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 ();
}