From 1e7b01ad854afa69d741e4d156a9877fc22d0cf6 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Mon, 1 Oct 2001 06:19:15 +0000 Subject: [PATCH] Yet more Parlor progress. Added GameController which manages the flow of the game on the client side and have started to wire up the DObject stuff. Whee! git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@366 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../client/InvitationResponseObserver.java | 14 +- .../parlor/client/ParlorDirector.java | 110 +++++++++++--- .../parlor/client/ParlorService.java | 13 +- .../threerings/parlor/game/GameConfig.java | 82 ++++++++-- .../parlor/game/GameController.java | 141 ++++++++++++++++++ .../threerings/parlor/game/GameObject.dobj | 39 ++++- 6 files changed, 354 insertions(+), 45 deletions(-) create mode 100644 src/java/com/threerings/parlor/game/GameController.java diff --git a/src/java/com/threerings/parlor/client/InvitationResponseObserver.java b/src/java/com/threerings/parlor/client/InvitationResponseObserver.java index 59b3f08a3..fcc7348c2 100644 --- a/src/java/com/threerings/parlor/client/InvitationResponseObserver.java +++ b/src/java/com/threerings/parlor/client/InvitationResponseObserver.java @@ -1,5 +1,5 @@ // -// $Id: InvitationResponseObserver.java,v 1.3 2001/10/01 05:07:13 mdb Exp $ +// $Id: InvitationResponseObserver.java,v 1.4 2001/10/01 06:19:15 mdb Exp $ package com.threerings.parlor.client; @@ -7,7 +7,7 @@ import com.threerings.parlor.data.GameConfig; /** * A client entity that wishes to generate invitations for games must - * implement this interface. An invitation can be accepted, rejected or + * implement this interface. An invitation can be accepted, refused or * countered. A countered invitation is one where the game configuration * is adjusted by the invited player and proposed back to the inviting * player. @@ -23,15 +23,15 @@ public interface InvitationResponseObserver public void invitationAccepted (int inviteId); /** - * Called if the invitation was rejected. + * Called if the invitation was refused. * * @param inviteId the unique id of the invitation for which we * received a response. - * @param message a message provided by the rejecting user explaining - * the reason for their rejection, or the empty string if no message - * was provided. + * @param message a message provided by the invited user explaining + * the reason for their refusal, or the empty string if no message was + * provided. */ - public void invitationRejected (int inviteId, String message); + public void invitationRefused (int inviteId, String message); /** * Called if the invitation was countered with an alternate game diff --git a/src/java/com/threerings/parlor/client/ParlorDirector.java b/src/java/com/threerings/parlor/client/ParlorDirector.java index 1e5701836..92a6a29ba 100644 --- a/src/java/com/threerings/parlor/client/ParlorDirector.java +++ b/src/java/com/threerings/parlor/client/ParlorDirector.java @@ -1,5 +1,5 @@ // -// $Id: ParlorDirector.java,v 1.2 2001/10/01 05:07:13 mdb Exp $ +// $Id: ParlorDirector.java,v 1.3 2001/10/01 06:19:15 mdb Exp $ package com.threerings.parlor.client; @@ -54,7 +54,7 @@ public class ParlorDirector * @param config the configuration of the game to which the user is * being invited. * @param observer the entity that will be notified if this invitation - * is accepted, rejected or countered. + * is accepted, refused or countered. * * @return a unique id associated with this invitation that can be * used to discern between various outstanding invitations by the @@ -73,6 +73,51 @@ public class ParlorDirector return invite.inviteId; } + /** + * Accept an invitation. + * + * @param inviteId the id of the invitation to accept. + */ + public void accept (int inviteId) + { + Invitation invite = getInviteByLocalId(inviteId); + if (invite == null) { + // complain if we didn't find a matching invitation + Log.warning("Received request to accept non-existent " + + "invitation [inviteId=" + inviteId + "]."); + return; + } + + // generate the invocation service request + ParlorService.respond(_ctx.getClient(), invite.remoteId, + ParlorService.INVITATION_ACCEPTED, + null, this); + } + + /** + * Refuse an invitation. + * + * @param inviteId the id of the invitation to accept. + * @param message the message to deliver to the inviting user + * explaining the reason for the refusal or null if no message is to + * be provided. + */ + public void refuse (int inviteId, String message) + { + Invitation invite = getInviteByLocalId(inviteId); + if (invite == null) { + // complain if we didn't find a matching invitation + Log.warning("Received request to accept non-existent " + + "invitation [inviteId=" + inviteId + "]."); + return; + } + + // generate the invocation service request + ParlorService.respond(_ctx.getClient(), invite.remoteId, + ParlorService.INVITATION_REFUSED, + message, this); + } + /** * Counters a received invitation with an invitation with different * game configuration parameters. @@ -80,13 +125,11 @@ public class ParlorDirector * @param inviteId the id of the received invitation. * @param config the updated game configuration. * @param observer the entity that will be notified if this - * counter-invitation is accepted, rejected or countered. + * counter-invitation is accepted, refused or countered. */ public void counter (int inviteId, GameConfig config, InvitationResponseObserver observer) { - // look up the invitation record (oh the two separate key spaces - // humanity) Invitation invite = getInviteByLocalId(inviteId); if (invite == null) { // complain if we didn't find a matching invitation @@ -173,16 +216,16 @@ public class ParlorDirector /** * Called by the invocation services when another user has responded - * to our invitation by either accepting, rejecting or countering it. + * to our invitation by either accepting, refusing or countering it. * * @param remoteId the unique indentifier for the invitation. * @param code the response code, either {@link * ParlorService#INVITATION_ACCEPTED} or {@link - * ParlorService#INVITATION_REJECTED} or {@link + * ParlorService#INVITATION_REFUSED} or {@link * ParlorService#INVITATION_COUNTERED}. - * @param arg in the case of a rejected invitation, a string + * @param arg in the case of a refused invitation, a string * containing a message provided by the invited user explaining the - * reason for rejection (the empty string if no explanation was + * reason for refusal (the empty string if no explanation was * provided). In the case of a countered invitation, a new game config * object with the modified game configuration. */ @@ -204,18 +247,48 @@ public class ParlorDirector invite + "."); return; } + + // notify the observer + try { + switch (code) { + case ParlorService.INVITATION_ACCEPTED: + invite.observer.invitationAccepted(invite.inviteId); + break; + + case ParlorService.INVITATION_REFUSED: + invite.observer.invitationRefused( + invite.inviteId, (String)arg); + break; + + case ParlorService.INVITATION_COUNTERED: + invite.observer.invitationCountered( + invite.inviteId, (GameConfig)arg); + break; + } + + } catch (Exception e) { + Log.warning("Invitation response observer choked on response " + + "[code=" + code + ", arg=" + arg + + ", invite=" + invite + "]."); + Log.logStackTrace(e); + } + + // unless the invitation was countered, we can remove it from the + // pending table because it's resolved + if (code != ParlorService.INVITATION_COUNTERED) { + _pendingInvites.remove(remoteId); + } } /** - * Called by the invocation services when another user has invited us - * to play a game. + * Called by the invocation services when an outstanding invitation + * has been cancelled by the inviting user. * - * @param inviter the username of the inviting user. - * @param config the configuration information for the game to which - * we've been invited. + * @param remoteId the unique indentifier for the invitation. */ - public void handleCancelInviteNotification (String inviter, GameConfig config) + public void handleCancelInviteNotification (int remoteId) { + // TBD } /** @@ -253,13 +326,16 @@ public class ParlorDirector /** * Called by the invocation services when an invitation request failed - * or was rejected for some reason. + * or was rejected for some reason (this is different than an + * invitation being refused by the invitee which is handled by {@link + * #handleRespondInviteNotification}). * * @param invid the invocation id of the invitation request. - * @param reason a reason code explaining the rejection or failure. + * @param reason a reason code explaining the failure. */ public void handleInviteFailed (int invid, String reason) { + // TBD } /** diff --git a/src/java/com/threerings/parlor/client/ParlorService.java b/src/java/com/threerings/parlor/client/ParlorService.java index c55475dfc..03f82433b 100644 --- a/src/java/com/threerings/parlor/client/ParlorService.java +++ b/src/java/com/threerings/parlor/client/ParlorService.java @@ -1,5 +1,5 @@ // -// $Id: ParlorService.java,v 1.2 2001/10/01 05:07:13 mdb Exp $ +// $Id: ParlorService.java,v 1.3 2001/10/01 06:19:15 mdb Exp $ package com.threerings.parlor.client; @@ -39,8 +39,8 @@ public class ParlorService /** The response code for an accepted invitation. */ public static final int INVITATION_ACCEPTED = 0; - /** The response code for a rejected invitation. */ - public static final int INVITATION_REJECTED = 1; + /** The response code for a refused invitation. */ + public static final int INVITATION_REFUSED = 1; /** The response code for a countered invitation. */ public static final int INVITATION_COUNTERED = 2; @@ -72,9 +72,10 @@ public class ParlorService } /** - * You probably don't want to call this directly, but want to call - * {@link ParlorDirector#counter}. Requests that a counter-invitation be - * delivered with the specified parameters. + * You probably don't want to call this directly, but want to call one + * of {@link ParlorDirector#accept}, {@link ParlorDirector#refuse}, or + * {@link ParlorDirector#counter}. Requests that an invitation + * response be delivered with the specified parameters. * * @param client a connected, operational client instance. * @param inviteId the unique id previously assigned by the server to diff --git a/src/java/com/threerings/parlor/game/GameConfig.java b/src/java/com/threerings/parlor/game/GameConfig.java index 6ef02d6c6..1af4c9283 100644 --- a/src/java/com/threerings/parlor/game/GameConfig.java +++ b/src/java/com/threerings/parlor/game/GameConfig.java @@ -1,5 +1,5 @@ // -// $Id: GameConfig.java,v 1.1 2001/10/01 02:56:35 mdb Exp $ +// $Id: GameConfig.java,v 1.2 2001/10/01 06:19:15 mdb Exp $ package com.threerings.parlor.data; @@ -12,33 +12,87 @@ import com.threerings.cocktail.cher.io.Streamable; /** * The game config class encapsulates the configuration information for a * particular type of game. The hierarchy of game config objects mimics - * the hierarchy of game managers and the client provides a game config - * object to the game manager at game creation time which is used to set - * the game's configuration options. + * the hierarchy of game managers and controllers. Both the game manager + * and game controller are provided with the game config object when the + * game is created. + * + *

The game config object is also the mechanism used to instantiate + * the appropriate game manager and controller. Every game must have an + * associated game config derived class that overrides {@link + * #getControllerClass} and {@link getManagerClassName}, returning the + * appropriate game controller and manager class for that game. Thus the + * entire chain of events that causes a particular game to be created is + * the construction of the appropriate game config instance which is + * provided to the server as part of an invitation or via some other + * matchmaking mechanism. * *

A game that has specific configuration needs would extend this * class (or an appropriate subclass) adding it's configuration - * information, and also overriding {@link #writeTo} and {@link #readFrom} - * to provide code to effect the necessary serialization and - * unserialization of the added fields. - * - *

The implementation of the client-side of the game can either use - * the code provided for obtaining configuration parameters from the user, - * or provide its own specialized configuration interface. It need only - * produce a game config object when the configuration process is complete - * and provide that to the server along with the game creation request. + * information and overriding {@link #writeTo} and {@link #readFrom} to + * provide code to serialize and unserialize the additional fields. */ -public class GameConfig implements Streamable +public abstract class GameConfig implements Streamable { + /** Indicates whether or not this game is rated. */ + public boolean rated = false; + + /** + * Returns the class that should be used to create a controller for + * this game. The controller class must derive from {@link + * com.threerings.parlor.client.GameController}. + */ + public abstract Class getControllerClass (); + + /** + * Returns the name of the class that should be used to create a + * manager for this game. The manager class must derive from {@link + * com.threerings.parlor.server.GameManager}. Note: this + * method differs from {@link #getControllerClass} because we want to + * avoid compile time linkage of the game config object (which is used + * on the client) to server code. This allows a code optimizer (DashO + * Pro, for example) to remove the server code from the client, + * knowing that it is never used. + */ + public abstract String getManagerClassName (); + // documentation inherited public void writeTo (DataOutputStream out) throws IOException { + out.writeBoolean(rated); } // documentation inherited public void readFrom (DataInputStream in) throws IOException { + rated = in.readBoolean(); + } + + /** + * Generates a string representation of this object by calling the + * overridable {@link #toString(StringBuffer)} which builds up the + * string in a manner friendly to derived classes. + */ + public String toString () + { + StringBuffer buf = new StringBuffer(); + buf.append("["); + toString(buf); + buf.append("]"); + return buf.toString(); + } + + /** + * An extensible mechanism for generating a string representation of + * this object. Derived classes should override this method, calling + * super and then appending their own data to the supplied string + * buffer. The regular {@link #toString} function will call this + * derived function to generate its string. + */ + protected void toString (StringBuffer buf) + { + buf.append("type=").append(getClass().getName()); + buf.append("rated=").append(rated); } } diff --git a/src/java/com/threerings/parlor/game/GameController.java b/src/java/com/threerings/parlor/game/GameController.java new file mode 100644 index 000000000..3e93662d8 --- /dev/null +++ b/src/java/com/threerings/parlor/game/GameController.java @@ -0,0 +1,141 @@ +// +// $Id: GameController.java,v 1.1 2001/10/01 06:19:15 mdb Exp $ + +package com.threerings.parlor.client; + +import java.awt.event.ActionEvent; + +import com.samskivert.swing.Controller; + +import com.threerings.cocktail.cher.dobj.*; + +import com.threerings.parlor.Log; +import com.threerings.parlor.data.GameConfig; +import com.threerings.parlor.data.GameObject; +import com.threerings.parlor.util.ParlorContext; + +/** + * The game controller manages the flow and control of a game on the + * client side. This class serves as the root of a hierarchy of controller + * classes that aim to provide functionality shared between various + * similar games. The base controller provides functionality for starting + * and ending the game and for calculating ratings adjustements when a + * game ends normally. It also handles the basic house keeping like + * subscription to the game object and dispatch of commands and + * distributed object events. + */ +public class GameController extends Controller implements Subscriber +{ + /** + * Initializes this game controller with the game configuration that + * was established during the match making process. Derived classes + * may want to override this method to initialize themselves with + * game-specific configuration parameters but they should be sure to + * call super.init in such cases. + * + * @param gameOid the object id of the game object for the game we are + * intended to control. + * @param config the configuration of the game we are intended to + * control. + */ + public void init (ParlorContext ctx, int gameOid, GameConfig config) + { + // keep a reference to our context + _ctx = ctx; + + // subscribe to the game object + _ctx.getDObjectManager().subscribeToObject(gameOid, this); + + // keep the config around for later + _config = config; + } + + /** + * Handles basic game controller action events. Derived classes should + * be sure to call super.handleAction for events they + * don't specifically handle. + */ + public boolean handleAction (ActionEvent action) + { + return false; + } + + // documentation inherited + public void objectAvailable (DObject object) + { + // keep a reference around to the game object + _gobj = (GameObject)object; + } + + // documentation inherited + public void requestFailed (int oid, ObjectAccessException cause) + { + Log.warning("Unable to subscribe to game object!? [oid=" + oid + + ", cause=" + cause + "]."); + } + + // documentation inherited + public boolean handleEvent (DEvent event, DObject target) + { + if (event instanceof AttributeChangedEvent) { + AttributeChangedEvent ace = (AttributeChangedEvent)event; + + // deal with game state changes + if (ace.getName().equals(GameObject.STATE)) { + switch (ace.getIntValue()) { + case GameObject.IN_PLAY: + gameDidStart(); + break; + case GameObject.GAME_OVER: + gameDidEnd(); + break; + case GameObject.CANCELLED: + gameWasCancelled(); + break; + default: + Log.warning("Game transitioned to unknown state " + + "[gobj=" + _gobj + + ", state=" + ace.getIntValue() + "]."); + break; + } + } + } + + return true; + } + + /** + * Called when the game transitions to the IN_PLAY + * state. This happens when all of the players have arrived and the + * server starts the game. + */ + protected void gameDidStart () + { + } + + /** + * Called when the game transitions to the GAME_OVER + * state. This happens when the game reaches some end condition by + * normal means (is not cancelled or aborted). + */ + protected void gameDidEnd () + { + } + + /** + * Called when the game was cancelled for some reason. + */ + protected void gameWasCancelled () + { + } + + /** A reference to the active parlor context. */ + protected ParlorContext _ctx; + + /** Our game configuration information. */ + protected GameConfig _config; + + /** A reference to the game object for the game that we're + * controlling. */ + protected GameObject _gobj; +} diff --git a/src/java/com/threerings/parlor/game/GameObject.dobj b/src/java/com/threerings/parlor/game/GameObject.dobj index 0993741aa..4745fedcf 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.1 2001/10/01 02:56:35 mdb Exp $ +// $Id: GameObject.dobj,v 1.2 2001/10/01 06:19:15 mdb Exp $ package com.threerings.parlor.data; @@ -17,6 +17,43 @@ import com.threerings.cocktail.party.data.PlaceObject; */ public class GameObject extends PlaceObject { + /** The field name of the state field. */ + public static final String STATE = "state"; + + /** The field name of the isRated field. */ + public static final String IS_RATED = "isRated"; + + /** 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; + + /** A game state constant indicating that the game is in play. */ + public static final int IN_PLAY = 1; + + /** A game state constant indicating that the game ended normally. */ + public static final int GAME_OVER = 2; + + /** A game state constant indicating that the game was cancelled. */ + public static final int CANCELLED = 3; + + /** The game state, one of {@link #AWAITING_PLAYERS}, {@link #IN_PLAY}, + * {@link #GAME_OVER}, or {@link #CANCELLED}. */ + public int state; + /** Indicates whether or not this game is rated. */ public boolean isRated; + + /** Requests that the state field be set to the specified + * value. */ + public void setState (int state) + { + requestAttributeChange(STATE, new Integer(state)); + } + + /** Requests that the isRated field be set to the + * specified value. */ + public void setIsRated (boolean isRated) + { + requestAttributeChange(IS_RATED, new Boolean(isRated)); + } }