From 9b89a3f994e073947a23fcd7d0f5465d0b427f68 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Fri, 19 Oct 2001 22:02:36 +0000 Subject: [PATCH] Back-end facilities for table matchmaking. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@508 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../threerings/parlor/client/ParlorCodes.java | 16 +- .../com/threerings/parlor/data/Table.java | 129 ++++++++++++++- .../parlor/server/ParlorManager.java | 151 +++++++++++++++++- 3 files changed, 289 insertions(+), 7 deletions(-) diff --git a/src/java/com/threerings/parlor/client/ParlorCodes.java b/src/java/com/threerings/parlor/client/ParlorCodes.java index 10b6dd416..92ea6f30e 100644 --- a/src/java/com/threerings/parlor/client/ParlorCodes.java +++ b/src/java/com/threerings/parlor/client/ParlorCodes.java @@ -1,5 +1,5 @@ // -// $Id: ParlorCodes.java,v 1.6 2001/10/19 02:04:29 mdb Exp $ +// $Id: ParlorCodes.java,v 1.7 2001/10/19 22:02:36 mdb Exp $ package com.threerings.parlor.client; @@ -84,4 +84,18 @@ public interface ParlorCodes extends InvocationCodes * by the invocation services to a call to {@link * ParlorDirector#handleJoinFailed}. */ public static final String JOIN_FAILED_RESPONSE = "JoinFailed"; + + /** An error code returned when a user requests to join a table that + * doesn't exist. */ + public static final String NO_SUCH_TABLE = "m.no_such_table"; + + /** An error code returned when a user requests to join a table at a + * position that is not valid. */ + public static final String INVALID_TABLE_POSITION = + "m.invalid_table_position"; + + /** An error code returned when a user requests to join a table in a + * position that is already occupied. */ + public static final String TABLE_POSITION_OCCUPIED = + "m.table_position_occupied"; } diff --git a/src/java/com/threerings/parlor/data/Table.java b/src/java/com/threerings/parlor/data/Table.java index 73f5e2c26..50296f02f 100644 --- a/src/java/com/threerings/parlor/data/Table.java +++ b/src/java/com/threerings/parlor/data/Table.java @@ -1,5 +1,5 @@ // -// $Id: Table.java,v 1.1 2001/10/19 02:04:29 mdb Exp $ +// $Id: Table.java,v 1.2 2001/10/19 22:02:36 mdb Exp $ package com.threerings.parlor.data; @@ -12,17 +12,27 @@ import com.samskivert.util.StringUtil; import com.threerings.presents.dobj.DSet; import com.threerings.presents.dobj.io.ValueMarshaller; +import com.threerings.parlor.client.ParlorCodes; import com.threerings.parlor.game.GameConfig; /** * This class represents a table that is being used to matchmake a game by * the Parlor services. */ -public class Table implements DSet.Element +public class Table + implements DSet.Element, ParlorCodes { /** The unique identifier for this table. */ public Integer tableId; + /** The object id of the lobby object with which this table is + * associated. */ + public int lobbyOid; + + /** The oid of the game that was created from this table or -1 if the + * table is still in matchmaking mode. */ + public int gameOid; + /** An array of the usernames of the occupants of this table (some * slots may not be filled. */ public String[] occupants; @@ -31,6 +41,39 @@ public class Table implements DSet.Element * instance will also implement {@link TableConfig}. */ public GameConfig config; + /** + * Creates a new table instance, and assigns it the next monotonically + * increasing table id. The supplied config instance must implement + * {@link TableConfig} or a {@link ClassCastException} will be thrown. + * + * @param lobbyOid the object id of the lobby in which this table is + * to live. + * @param config the configuration of the game being matchmade by this + * table. + */ + public Table (int lobbyOid, GameConfig config) + { + // assign a unique table id + tableId = new Integer(++_tableIdCounter); + + // keep track of our lobby oid + this.lobbyOid = lobbyOid; + + // keep a casted reference around + _tconfig = (TableConfig)config; + this.config = config; + + // make room for the maximum number of players + occupants = new String[_tconfig.getMaximumPlayers()]; + } + + /** + * Constructs a blank table instance, suitable for unserialization. + */ + public Table () + { + } + /** * A convenience function for accessing the table id as an int. */ @@ -39,6 +82,75 @@ public class Table implements DSet.Element return tableId.intValue(); } + /** + * Once a table is ready to play (see {@link #readyToPlay}), the + * players array can be fetched using this method. It will return an + * array containing the usernames of all of the players in the game, + * sized properly and with each player in the appropriate position. + */ + public String[] getPlayers () + { + // count up the players + int pcount = 0; + for (int i = 0; i < occupants.length; i++) { + if (!StringUtil.blank(occupants[i])) { + pcount++; + } + } + + // create and populate the players array + String[] players = new String[pcount]; + pcount = 0; + for (int i = 0; i < occupants.length; i++) { + if (!StringUtil.blank(occupants[i])) { + players[pcount++] = occupants[i]; + } + } + + return players; + } + + /** + * Requests to seat the specified user at the specified position in + * this table. + * + * @return null if the user was successfully seated, a string error + * code explaining the failure if the user was not able to be seated + * at that position. + */ + public String setOccupant (int position, String username) + { + // find out how many positions we have for occupation + int maxpos = _tconfig.getDesiredPlayers(); + // if there is no desired number of players, use the max + if (maxpos == -1) { + maxpos = _tconfig.getMaximumPlayers(); + } + + // make sure the requested position is a valid one + if (position >= maxpos) { + return INVALID_TABLE_POSITION; + } + + // make sure the requested position is not already occupied + if (!StringUtil.blank(occupants[position])) { + return TABLE_POSITION_OCCUPIED; + } + + // otherwise all is well, stick 'em in + occupants[position] = username; + return null; + } + + /** + * Returns true if this table has occupants in all of the desired + * positions and should be started. + */ + public boolean readyToStart () + { + return false; + } + // documentation inherited public Object getKey () { @@ -50,6 +162,8 @@ public class Table implements DSet.Element throws IOException { out.writeInt(getTableId()); + out.writeInt(lobbyOid); + out.writeInt(gameOid); ValueMarshaller.writeTo(out, occupants); ValueMarshaller.writeTo(out, config); } @@ -59,8 +173,11 @@ public class Table implements DSet.Element throws IOException { tableId = new Integer(in.readInt()); + gameOid = in.readInt(); + lobbyOid = in.readInt(); occupants = (String[])ValueMarshaller.readFrom(in); config = (GameConfig)ValueMarshaller.readFrom(in); + _tconfig = (TableConfig)config; } /** @@ -69,7 +186,15 @@ public class Table implements DSet.Element public String toString () { return "[tableId=" + tableId + + ", lobbyOid=" + lobbyOid + + ", gameOid=" + gameOid + ", occupants=" + StringUtil.toString(occupants) + ", config=" + config + "]"; } + + /** A casted reference of our game config object. */ + protected TableConfig _tconfig; + + /** A counter for assigning table ids. */ + protected static int _tableIdCounter = 0; } diff --git a/src/java/com/threerings/parlor/server/ParlorManager.java b/src/java/com/threerings/parlor/server/ParlorManager.java index ccb9cff4d..2936e212e 100644 --- a/src/java/com/threerings/parlor/server/ParlorManager.java +++ b/src/java/com/threerings/parlor/server/ParlorManager.java @@ -1,19 +1,26 @@ // -// $Id: ParlorManager.java,v 1.11 2001/10/19 02:04:29 mdb Exp $ +// $Id: ParlorManager.java,v 1.12 2001/10/19 22:02:36 mdb Exp $ package com.threerings.parlor.server; +import java.util.HashMap; + import com.samskivert.util.Config; import com.samskivert.util.HashIntMap; +import com.samskivert.util.StringUtil; import com.threerings.presents.server.InvocationManager; import com.threerings.presents.server.ServiceFailedException; import com.threerings.crowd.data.BodyObject; +import com.threerings.crowd.data.PlaceObject; import com.threerings.crowd.server.CrowdServer; +import com.threerings.crowd.server.PlaceManager; +import com.threerings.crowd.server.PlaceRegistry; import com.threerings.parlor.Log; import com.threerings.parlor.client.ParlorCodes; +import com.threerings.parlor.data.Table; import com.threerings.parlor.data.TableConfig; import com.threerings.parlor.data.TableLobbyObject; import com.threerings.parlor.game.GameConfig; @@ -27,7 +34,7 @@ import com.threerings.parlor.game.GameManager; * game. */ public class ParlorManager - implements ParlorCodes + implements ParlorCodes, PlaceRegistry.CreationObserver { /** * Initializes the parlor manager. This should be called by the server @@ -179,7 +186,7 @@ public class ParlorManager // started up (which is done by the place registry once the // game object creation has completed) GameManager gmgr = (GameManager) - CrowdServer.plreg.createPlace(invite.config); + CrowdServer.plreg.createPlace(invite.config, null); // provide the game manager with some initialization info String[] players = new String[] { @@ -216,7 +223,38 @@ public class ParlorManager GameConfig config) throws ServiceFailedException { - return -1; + // fetch the place object in which we'll be creating a table + try { + TableLobbyObject tlobj = (TableLobbyObject) + CrowdServer.omgr.getObject(placeOid); + + // make sure the game config implements TableConfig + if (!(config instanceof TableConfig)) { + Log.warning("Requested to matchmake a non-table game " + + "using the table services [creator=" + creator + + ", ploid=" + placeOid + + ", config=" + config + "]."); + throw new ServiceFailedException(INTERNAL_ERROR); + } + + // create a brand spanking new table + Table table = new Table(placeOid, config); + + // and stick it into the table lobby object + tlobj.addToTables(table); + + // also stick the table into our tables table + _tables.put(table.getTableId(), table); + + // finally let the caller know what the new table id is + return table.getTableId(); + + } catch (ClassCastException cce) { + Log.warning("Requested to create table in non-table-lobby " + + "[creator=" + creator + ", ploid=" + placeOid + + ", config=" + config + ", cce=" + cce + "]."); + throw new ServiceFailedException(INTERNAL_ERROR); + } } /** @@ -243,6 +281,104 @@ public class ParlorManager public void joinTable (BodyObject joiner, int tableId, int position) throws ServiceFailedException { + // look the table up + Table table = (Table)_tables.get(tableId); + if (table == null) { + throw new ServiceFailedException(NO_SUCH_TABLE); + } + + // make sure the lobby for this table is still around + TableLobbyObject tlobj = (TableLobbyObject) + CrowdServer.omgr.getObject(table.lobbyOid); + if (tlobj == null) { + Log.warning("User tried to join table whose lobby has " + + "disappeared [table=" + table + + ", joiner=" + joiner + "]."); + throw new ServiceFailedException(INTERNAL_ERROR); + } + + // request that the user be added to the table at that position + String error = table.setOccupant(position, joiner.username); + // if that failed, report the error + if (error != null) { + throw new ServiceFailedException(error); + } + + // determine whether or not it's time to start the game + if (table.readyToStart()) { + // create the game manager + GameManager gmgr = + createGameManager(table.config, table.getPlayers()); + + // and map this table to this game manager so that we can + // update the table with the game in progress once it's + // created + _pendingTables.put(gmgr, table); + } + + // update the table in the lobby + tlobj.updateTables(table); + } + + /** + * Called when we're ready to create a game (either an invitation has + * been accepted or a table is ready to start. If there is a problem + * creating the game manager, it will be reported in the logs. + * + * @return a reference to the newly created game manager or null if + * something choked during the creation or initialization process. + */ + protected GameManager createGameManager ( + GameConfig config, String[] players) + { + GameManager gmgr = null; + + try { + Log.info("Creating game manager [config=" + config + "]."); + + // create the game manager and begin it's initialization + // process. the game manager will take care of notifying the + // players that the game has been created once it has been + // started up (which is done by the place registry once the + // game object creation has completed) + gmgr = (GameManager)CrowdServer.plreg.createPlace(config, this); + + // provide the game manager with some initialization info + gmgr.setPlayers(players); + + } catch (Exception e) { + Log.warning("Unable to create game manager [config=" + config + + ", players=" + StringUtil.toString(players) + "]."); + Log.logStackTrace(e); + } + + return gmgr; + } + + /** + * Called by the place registry when the game object has been created + * and provided to the game manager. We use this to map game object + * oids back to table records when we create games from tables. + */ + public void placeCreated (PlaceObject plobj, PlaceManager pmgr) + { + // see if this place manager is associated with a table + Table table = (Table)_pendingTables.get(pmgr); + if (table != null) { + // update the table with the newly created game object + table.gameOid = plobj.getOid(); + + // and then update the lobby object that contains the table + TableLobbyObject tlobj = (TableLobbyObject) + CrowdServer.omgr.getObject(table.lobbyOid); + if (tlobj == null) { + Log.warning("Can't update in-play table as lobby's gone " + + "missing [table=" + table + + ", gameOid=" + plobj.getOid() + "]."); + return; + } + tlobj.updateTables(table); + } } /** @@ -293,6 +429,13 @@ public class ParlorManager /** The table of pending invitations. */ protected HashIntMap _invites = new HashIntMap(); + /** The table of pending tables. */ + protected HashIntMap _tables = new HashIntMap(); + + /** A table of tables that have games that have been created but not + * yet started. */ + protected HashMap _pendingTables = new HashMap(); + /** A counter used to generate unique identifiers for invitation * records. */ protected static int _nextInviteId = 0;