diff --git a/src/as/com/threerings/parlor/client/DefaultFlexTableConfigurator.as b/src/as/com/threerings/parlor/client/DefaultFlexTableConfigurator.as new file mode 100644 index 00000000..86275562 --- /dev/null +++ b/src/as/com/threerings/parlor/client/DefaultFlexTableConfigurator.as @@ -0,0 +1,123 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.client { + +import mx.controls.CheckBox; +import mx.controls.HSlider; +import mx.controls.Label; + +import com.threerings.parlor.data.TableConfig; +import com.threerings.parlor.util.ParlorContext; + +import com.threerings.parlor.game.client.FlexGameConfigurator; +import com.threerings.parlor.game.data.GameConfig; + +/** + * Provides a default implementation of a TableConfigurator for + * a Swing interface. + */ +public class DefaultFlexTableConfigurator extends TableConfigurator +{ + /** + * Create a TableConfigurator that allows for the specified configuration + * parameters. + */ + public function DefaultSwingTableConfigurator ( + desiredPlayers :int, minPlayers :int = -1, maxPlayers :int = -1, + allowPrivate :Boolean = false) + { + if (minPlayers < 0) { + minPlayers = desiredPlayers; + } + if (maxPlayers < 0) { + maxPlayers = desiredPlayers; + } + + _config.minimumPlayerCount = minPlayers; + + // create a slider for players, if applicable + if (minPlayers != maxPlayers) { + _playerSlider = new HSlider(); + _playerSlider.value = desiredPlayers; + _playerSlider.minimum = minPlayers; + _playerSlider.maximum = maxPlayers; + + } else { + _config.desiredPlayerCount = desiredPlayers; + } + + // create up the checkbox for private games, if applicable + if (allowPrivate) { + _privateCheck = new CheckBox(); + } + } + + // documentation inherited + protected function createConfigInterface () :void + { + super.createConfigInterface(); + + var gconf :FlexGameConfigurator = + (_gameConfigurator as FlexGameConfigurator); + + if (_playerSlider != null) { + // TODO: proper translation + var playerLabel :Label = new Label(); + playerLabel.text = "Players:"; + gconf.addControl(playerLabel, _playerSlider); + } + + if (_privateCheck != null) { + // TODO: proper translation + var privateLabel :Label = new Label(); + privateLabel.text = "Private:"; + gconf.addControl(privateLabel, _privateCheck); + } + } + + // documentation inherited + override public function isEmpty () :Boolean + { + return (_playerSlider == null) && (_privateCheck == null); + } + + // documentation inherited + override protected function flushTableConfig() :void + { + super.flushTableConfig(); + + if (_playerSlider != null) { + _config.desiredPlayerCount = _playerSlider.value; + } + if (_privateCheck != null) { + _config.privateTable = _privateCheck.selected; + } + } + + /** A slider for configuring the number of players at the table. */ + protected var _playerSlider :HSlider; + + /** A checkbox to allow the table creator to specify if the table is + * private. */ + protected var _privateCheck :CheckBox; +} +} diff --git a/src/as/com/threerings/parlor/client/GameReadyObserver.as b/src/as/com/threerings/parlor/client/GameReadyObserver.as new file mode 100644 index 00000000..856a341a --- /dev/null +++ b/src/as/com/threerings/parlor/client/GameReadyObserver.as @@ -0,0 +1,44 @@ +// +// $Id: GameReadyObserver.java 4191 2006-06-13 22:42:20Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.client { + +/** + * Used to inform interested parties when the {@link ParlorDirector} + * receives a game ready notification. The observers can ratify the + * decision to head directly into the game or can take responsibility + * themselves for doing so. + */ +public interface GameReadyObserver +{ + /** + * Called when a game ready notification is received. + * + * @param gameOid the place oid of the ready game. + * + * @return if the observer returns true from this method, the parlor + * director assumes they will take care of entering the game room + * after performing processing of their own. If all observers return + * false, the director will enter the game room automatically. + */ + function receivedGameReady (gameOid :int) :Boolean; +} +} diff --git a/src/as/com/threerings/parlor/client/Invitation.as b/src/as/com/threerings/parlor/client/Invitation.as new file mode 100644 index 00000000..7fab2ca3 --- /dev/null +++ b/src/as/com/threerings/parlor/client/Invitation.as @@ -0,0 +1,214 @@ +// +// $Id: Invitation.java 3381 2005-03-03 19:36:34Z mdb $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.client { + +import com.threerings.util.Name; + +import com.threerings.parlor.data.ParlorCodes; +import com.threerings.parlor.game.data.GameConfig; +import com.threerings.parlor.util.ParlorContext; + +/** + * The invitation class is used to track information related to + * outstanding invitations generated by or targeted to this client. + */ +public class Invitation + implements ParlorCodes, ParlorService_InviteListener +{ + /** The unique id for this invitation (as assigned by the + * server). This is -1 until we receive an acknowledgement from + * the server that our invitation was delivered. */ + public var inviteId :int = -1; + + /** The name of the other user involved in this invitation. */ + public var opponent :Name; + + /** The configuration of the game to be created. */ + public var config :GameConfig; + + /** Constructs a new invitation record. */ + public function Invitation ( + ctx :ParlorContext, pservice :ParlorService, + opponent :Name, config :GameConfig, + observer :InvitationResponseObserver) + { + _ctx = ctx; + _pservice = pservice; + _observer = observer; + this.opponent = opponent; + this.config = config; + } + + /** + * Accepts this invitation. + */ + public function accept () :void + { + // generate the invocation service request + _pservice.respond(_ctx.getClient(), inviteId, + INVITATION_ACCEPTED, null, this); + } + + /** + * Refuses this invitation. + * + * @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 function refuse (message :String) :void + { + // generate the invocation service request + _pservice.respond(_ctx.getClient(), inviteId, + INVITATION_REFUSED, message, this); + } + + /** + * Cancels this invitation. + */ + public function cancel () :void + { + // if the invitation has not yet been acknowleged by the + // server, we make a note that it should be cancelled when we + // do receive the acknowlegement + if (inviteId == -1) { + _cancelled = true; + + } else { + // otherwise, generate the invocation service request + _pservice.cancel(_ctx.getClient(), inviteId, this); + // and remove it from the pending table + _ctx.getParlorDirector().clearInvitation(this); + } + } + + /** + * Counters this invitation with an invitation with different game + * configuration parameters. + * + * @param config the updated game configuration. + * @param observer the entity that will be notified if this + * counter-invitation is accepted, refused or countered. + */ + public function counter ( + config :GameConfig, observer :InvitationResponseObserver) :void + { + // update our observer (who will eventually be hearing back from + // the other client about their counter-invitation) + _observer = observer; + + // generate the invocation service request + _pservice.respond(_ctx.getClient(), inviteId, + INVITATION_COUNTERED, config, this); + } + + // documentation inherited from interface + public function inviteReceived (inviteId :int) :void + { + // fill in our invitation id + this.inviteId = inviteId; + + // if the invitation was cancelled before we heard back about + // it, we need to send off a cancellation request now + if (_cancelled) { + _pservice.cancel(_ctx.getClient(), inviteId, this); + } else { + // otherwise, put it in the pending invites table + _ctx.getParlorDirector().registerInvitation(this); + } + } + + // documentation inherited from interface + public function requestFailed (reason :String) :void + { + // let the observer know what's up + _observer.invitationRefused(this, reason); + } + + /** + * Called by the parlor director when we receive a response to an + * invitation initiated by this client. + */ + protected function receivedResponse (code :int, arg :Object) :void + { + // make sure we have an observer to notify + if (_observer == null) { + Log.getLog(this).warning("No observer registered for invitation " + + this + "."); + return; + } + + // notify the observer + try { + switch (code) { + case INVITATION_ACCEPTED: + _observer.invitationAccepted(this); + break; + + case INVITATION_REFUSED: + _observer.invitationRefused(this, (arg as String)); + break; + + case INVITATION_COUNTERED: + _observer.invitationCountered(this, (arg as GameConfig)); + break; + } + + } catch (err :Error) { + var log :Log = Log.getLog(this); + log.warning("Invitation response observer choked on response " + + "[code=" + code + ", arg=" + arg + + ", invite=" + this + "]."); + log.logStackTrace(e); + } + + // unless the invitation was countered, we can remove it from the + // pending table because it's resolved + if (code != INVITATION_COUNTERED) { + _ctx.getParlorDirector().clearInvitation(this); + } + } + + /** Returns a string representation of this invitation record. */ + public function toString () :String + { + return "[inviteId=" + inviteId + ", opponent=" + opponent + + ", config=" + config + ", observer=" + _observer + + ", cancelled=" + _cancelled + "]"; + } + + /** Provides access to client services. */ + protected var _ctx :ParlorContext; + + /** Provides access to parlor services. */ + protected var _pservice :ParlorService; + + /** The entity to notify when we receive a response for this + * invitation. */ + protected var _observer :InvitationResponseObserver; + + /** A flag indicating that we were requested to cancel this + * invitation before we even heard back with an acknowledgement + * that it was received by the server. */ + protected var _cancelled :Boolean = false; +} +} diff --git a/src/as/com/threerings/parlor/client/InvitationHandler.as b/src/as/com/threerings/parlor/client/InvitationHandler.as new file mode 100644 index 00000000..69b76fc4 --- /dev/null +++ b/src/as/com/threerings/parlor/client/InvitationHandler.as @@ -0,0 +1,46 @@ +// +// $Id: InvitationHandler.java 4191 2006-06-13 22:42:20Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.client { + +/** + * A client entity that wishes to handle invitations received by other + * clients should implement this interface and register itself with the + * parlor director. It will subsequently be notified of any incoming + * invitations. It is also responsible for handling cancelled invitations. + */ +public interface InvitationHandler +{ + /** + * Called when an invitation is received from another player. + * + * @param invite the received invitation. + */ + function invitationReceived (invite :Invitation) :void; + + /** + * Called when an invitation is cancelled by the inviting player. + * + * @param invite the cancelled invitation. + */ + function invitationCancelled (invite :Invitation) :void; +} +} diff --git a/src/as/com/threerings/parlor/client/InvitationResponseObserver.as b/src/as/com/threerings/parlor/client/InvitationResponseObserver.as new file mode 100644 index 00000000..f049f90b --- /dev/null +++ b/src/as/com/threerings/parlor/client/InvitationResponseObserver.as @@ -0,0 +1,62 @@ +// +// $Id: InvitationResponseObserver.java 3381 2005-03-03 19:36:34Z mdb $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.client { + +import com.threerings.parlor.game.data.GameConfig; + +/** + * A client entity that wishes to generate invitations for games must + * 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. + */ +public interface InvitationResponseObserver +{ + /** + * Called if the invitation was accepted. + * + * @param invite the invitation for which we received a response. + */ + function invitationAccepted (invite :Invitation) :void; + + /** + * Called if the invitation was refused. + * + * @param invite the invitation for which we received a response. + * @param message a message provided by the invited user explaining + * the reason for their refusal, or the empty string if no message was + * provided. + */ + function invitationRefused (invite :Invitation, message :String) :void; + + /** + * Called if the invitation was countered with an alternate game + * configuration. + * + * @param invite the invitation for which we received a response. + * @param config the game configuration proposed by the invited + * player. + */ + function invitationCountered (invite :Invitation, config :GameConfig) :void; +} +} diff --git a/src/as/com/threerings/parlor/client/ParlorDecoder.as b/src/as/com/threerings/parlor/client/ParlorDecoder.as new file mode 100644 index 00000000..4dd9d60c --- /dev/null +++ b/src/as/com/threerings/parlor/client/ParlorDecoder.as @@ -0,0 +1,103 @@ +// +// $Id: ParlorDecoder.java 4145 2006-05-24 01:24:24Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.client { + +import com.threerings.parlor.client.ParlorReceiver; +import com.threerings.parlor.game.data.GameConfig; +import com.threerings.presents.client.InvocationDecoder; +import com.threerings.util.Name; + +/** + * Dispatches calls to a {@link ParlorReceiver} instance. + */ +public class ParlorDecoder extends InvocationDecoder +{ + /** The generated hash code used to identify this receiver class. */ + public static const RECEIVER_CODE :String = "5ef9ee0d359c42a9024498ee9aad119a"; + + /** The method id used to dispatch {@link ParlorReceiver#gameIsReady} + * notifications. */ + public static const GAME_IS_READY :int = 1; + + /** The method id used to dispatch {@link ParlorReceiver#receivedInvite} + * notifications. */ + public static const RECEIVED_INVITE :int = 2; + + /** The method id used to dispatch {@link ParlorReceiver#receivedInviteCancellation} + * notifications. */ + public static const RECEIVED_INVITE_CANCELLATION :int = 3; + + /** The method id used to dispatch {@link ParlorReceiver#receivedInviteResponse} + * notifications. */ + public static const RECEIVED_INVITE_RESPONSE :int = 4; + + /** + * Creates a decoder that may be registered to dispatch invocation + * service notifications to the specified receiver. + */ + public function ParlorDecoder (receiver :ParlorReceiver) + { + this.receiver = receiver; + } + + // documentation inherited + override public function getReceiverCode () :String + { + return RECEIVER_CODE; + } + + // documentation inherited + override public function dispatchNotification ( + methodId :int, args :Array) :void + { + var prec :ParlorReceiver = (receiver as ParlorReceiver); + switch (methodId) { + case GAME_IS_READY: + prec.gameIsReady((args[0] as Integer).value); + return; + + case RECEIVED_INVITE: + prec.receivedInvite( + (args[0] as Integer).value, (args[1] as Name), + (args[2] as GameConfig) + ); + return; + + case RECEIVED_INVITE_CANCELLATION: + prec.receivedInviteCancellation( + (args[0] as Integer).value + ); + return; + + case RECEIVED_INVITE_RESPONSE: + prec.receivedInviteResponse( + (args[0] as Integer).value, (args[1] as Integer).value, args[2] + ); + return; + + default: + super.dispatchNotification(methodId, args); + return; + } + } +} +} diff --git a/src/as/com/threerings/parlor/client/ParlorDirector.as b/src/as/com/threerings/parlor/client/ParlorDirector.as new file mode 100644 index 00000000..764cff21 --- /dev/null +++ b/src/as/com/threerings/parlor/client/ParlorDirector.as @@ -0,0 +1,252 @@ +// +// $Id: ParlorDirector.java 3381 2005-03-03 19:36:34Z mdb $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.client { + +import com.threerings.util.ArrayUtil; +import com.threerings.util.HashMap; +import com.threerings.util.Name; + +import com.threerings.presents.client.BasicDirector; +import com.threerings.presents.client.Client; +import com.threerings.presents.client.InvocationService; + +import com.threerings.parlor.Log; +import com.threerings.parlor.data.ParlorCodes; +import com.threerings.parlor.game.data.GameConfig; +import com.threerings.parlor.util.ParlorContext; + +/** + * The parlor director manages the client side of the game configuration + * and matchmaking processes. It is also the entity that is listening for + * game start notifications which it then dispatches the client entity + * that will actually create and display the user interface for the game + * that started. + */ +public class ParlorDirector extends BasicDirector + implements ParlorCodes, ParlorReceiver +{ + /** + * Constructs a parlor director and provides it with the parlor + * context that it can use to access the client services that it needs + * to provide its own services. Only one parlor director should be + * active in the client at one time and it should be made available + * via the parlor context. + * + * @param ctx the parlor context in use by the client. + */ + public function ParlorDirector (ctx :ParlorContext) + { + super(ctx); + _pctx = ctx; + + // register ourselves with the invocation director as a parlor + // notification receiver + _pctx.getClient().getInvocationDirector().registerReceiver( + new ParlorDecoder(this)); + } + + /** + * Sets the invitation handler, which is the entity that will be + * notified when we receive incoming invitation notifications and when + * invitations have been cancelled. + * + * @param handler our new invitation handler. + */ + public function setInvitationHandler (handler :InvitationHandler) :void + { + _handler = handler; + } + + /** + * Adds the specified observer to the list of entities that are + * notified when we receive a game ready notification. + */ + public function addGameReadyObserver (observer :GameReadyObserver) :void + { + _grobs.add(observer); + } + + /** + * Removes the specified observer from the list of entities that are + * notified when we receive a game ready notification. + */ + public function removeGameReadyObserver (observer :GameReadyObserver) :void + { + ArrayUtil.removeFirst(_grobs, observer); + } + + /** + * Requests that the named user be invited to a game described by the + * supplied game config. + * + * @param invitee the user to invite. + * @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, refused or countered. + * + * @return an invitation object that can be used to manage the + * outstanding invitation. + */ + public function invite ( + invitee :Name, config :GameConfig, + observer :InvitationResponseObserver) :Invitation + { + // create the invitation record + var invite :Invitation = new Invitation( + _ctx, _pservice, invitee, config, observer); + // submit the invitation request to the server + _pservice.invite(_ctx.getClient(), invitee, config, invite); + // and return the invitation to the caller + return invite; + } + + /** + * Requests that the specified single player game be started. + * + * @param config the configuration of the single player game to be + * started. + * @param listener a listener to be informed of failure if the game + * cannot be started. + */ + public function startSolitaire ( + config :GameConfig, listener :InvocationService.ConfirmListener) :void + { + _pservice.startSolitaire(_ctx.getClient(), config, listener); + } + + // documentation inherited + public function clientDidLogoff (event :ClientEvent) :void + { + super.clientDidLogoff(event); + _pservice = null; + _pendingInvites.clear(); + } + + // documentation inherited + protected function fetchServices (client :Client) :void + { + // get a handle on our parlor services + _pservice = (client.requireService(ParlorService) as ParlorService); + } + + // documentation inherited from interface + public function gameIsReady (gameOid :int) :void + { + Log.getLog(this).info("Handling game ready [goid=" + gameOid + "]."); + + // see what our observers have to say about it + var handled :Boolean = false; + for (var ii :int = 0; ii < _grobs.length; ii++) { + var grob :GameReadyObserver = (_grobs[ii] as GameReadyObserver); + handled = grob.receivedGameReady(gameOid) || handled; + } + + // if none of the observers took matters into their own hands, + // then we'll head on over to the game room ourselves + if (!handled) { + _ctx.getLocationDirector().moveTo(gameOid); + } + } + + // documentation inherited from interface + public function receivedInvite ( + remoteId :int, inviter :Name, config :GameConfig) :void + { + // create an invitation record for this invitation + invite :Invitation = new Invitation( + _ctx, _pservice, inviter, config, null); + invite.inviteId = remoteId; + + // put it in the pending invitations table + _pendingInvites.put(remoteId, invite); + + try { + // notify the invitation handler of the incoming invitation + _handler.invitationReceived(invite); + + } catch (err :Error) { + var log :Log = Log.getLog(this); + log.warning("Invitation handler choked on invite " + + "notification " + invite + "."); + log.logStackTrace(err); + } + } + + // documentation inherited from interface + public function receivedInviteResponse ( + remoteId :int, code :int, arg :Object) :void + { + // look up the invitation record for this invitation + var invite :Invitation = (_pendingInvites.get(remoteId) as Invitation); + if (invite == null) { + Log.getLog(this).warning("Have no record of invitation for " + + "which we received a response?! [remoteId=" + remoteId + + ", code=" + code + ", arg=" + arg + "]."); + + } else { + invite.receivedResponse(code, arg); + } + } + + // documentation inherited from interface + public function receivedInviteCancellation (remoteId :int) :void + { + // TBD + } + + /** + * Register a new invitation in our pending invitations table. The + * invitation will call this when it knows its invitation id. + */ + protected function registerInvitation (invite :Invitation) :void + { + _pendingInvites.put(invite.inviteId, invite); + } + + /** + * Called by an invitation when it knows it is no longer and can be + * cleared from the pending invitations table. + */ + protected function clearInvitation (invite :Invitation) :void + { + _pendingInvites.remove(invite.inviteId); + } + + /** An active parlor context. */ + protected var _pctx :ParlorContext; + + /** Provides access to parlor server side services. */ + protected var _pservice :ParlorService; + + /** The entity that has registered itself to handle incoming + * invitation notifications. */ + protected var _handler :InvitationHandler; + + /** A table of acknowledged (but not yet accepted or refused) + * invitation requests, keyed on invitation id. */ + protected var _pendingInvites :HashMap = new HashMap(); + + /** We notify the entities on this list when we get a game ready + * notification. */ + protected var _grobs :Array = new Array(); +} diff --git a/src/as/com/threerings/parlor/client/ParlorReceiver.as b/src/as/com/threerings/parlor/client/ParlorReceiver.as new file mode 100644 index 00000000..ed4199ae --- /dev/null +++ b/src/as/com/threerings/parlor/client/ParlorReceiver.as @@ -0,0 +1,85 @@ +// +// $Id: ParlorReceiver.java 3381 2005-03-03 19:36:34Z mdb $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.client { + +import com.threerings.util.Name; + +import com.threerings.presents.client.InvocationReceiver; + +import com.threerings.parlor.data.ParlorCodes; +import com.threerings.parlor.game.data.GameConfig; + +/** + * Defines, for the parlor services, a set of notifications delivered + * asynchronously by the server to the client. These are handled by the + * {@link ParlorDirector}. + */ +public interface ParlorReceiver extends InvocationReceiver +{ + /** + * Dispatched to the client when a game in which they are a + * participant is ready for play. The client will then enter the game + * room which will trigger the loading of the appropriate game UI code + * and generally get things started. + * + * @param gameOid the object id of the game object. + */ + function gameIsReady (gameOid :int) :void; + + /** + * Called by the invocation services when another user has invited us + * to play a game. + * + * @param remoteId the unique indentifier for this invitation (used + * when countering or responding). + * @param inviter the username of the inviting user. + * @param config the configuration information for the game to which + * we've been invited. + */ + function receivedInvite (remoteId :int, inviter :Name, config :GameConfig); + + /** + * Called by the invocation services when another user has responded + * to our invitation by either accepting, refusing or countering it. + * + * @param remoteId the indentifier for the invitation on question. + * @param code the response code, either {@link + * ParlorCodes#INVITATION_ACCEPTED} or {@link + * ParlorCodes#INVITATION_REFUSED} or {@link + * ParlorCodes#INVITATION_COUNTERED}. + * @param arg in the case of a refused invitation, a string + * containing a message provided by the invited user explaining the + * 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. + */ + function receivedInviteResponse (remoteId :int, code :int, arg :Object); + + /** + * Called by the invocation services when an outstanding invitation + * has been cancelled by the inviting user. + * + * @param remoteId the indentifier of the cancelled invitation. + */ + function receivedInviteCancellation (remoteId :int); +} +} diff --git a/src/as/com/threerings/parlor/client/ParlorService.as b/src/as/com/threerings/parlor/client/ParlorService.as new file mode 100644 index 00000000..c9d1d578 --- /dev/null +++ b/src/as/com/threerings/parlor/client/ParlorService.as @@ -0,0 +1,151 @@ +// +// $Id: ParlorService.java 3525 2005-04-26 02:38:42Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.client { + +import com.threerings.util.Name; + +import com.threerings.presents.client.Client; +import com.threerings.presents.client.InvocationService; + +import com.threerings.parlor.data.TableConfig; + +import com.threerings.parlor.game.data.GameConfig; + +/** + * Provides an interface to the various parlor invocation services. + * Presently these services are limited to the various matchmaking + * mechanisms. It is unlikely that client code will want to make direct + * use of this class, instead they would make use of the programmatic + * interface provided by the {@link ParlorDirector}. + */ +public interface ParlorService extends InvocationService +{ + /** + * You probably don't want to call this directly, but want to generate + * your invitation request via {@link ParlorDirector#invite}. Requests + * that an invitation be delivered to the named user, requesting that + * they join the inviting user in a game, the details of which are + * specified in the supplied game config object. + * + * @param client a connected, operational client instance. + * @param invitee the username of the user to be invited. + * @param config a game config object detailing the type and + * configuration of the game to be created. + * @param listener will receive and process the response. + */ + function invite ( + client :Client, invitee :Name, config :GameConfig, + listener :InviteListener) :void; + + /** + * You probably don't want to call this directly, but want to call one + * of {@link Invitation#accept}, {@link Invitation#refuse}, or {@link + * Invitation#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 + * this invitation. + * @param code the response code to use in responding to the + * invitation. + * @param arg the argument associated with the response (a string + * message from the player explaining why the response was refused in + * the case of an invitation refusal or an updated game configuration + * object in the case of a counter-invitation, or null in the case of + * an accepted invitation). + * @param listener will receive and process the response. + */ + function respond ( + client :Client, inviteId :int, code :int, arg :Object, + listener :InvocationListener) :void; + + /** + * You probably don't want to call this directly, but want to call + * {@link Invitation#cancel}. Requests that an outstanding + * invitation be cancelled. + * + * @param client a connected, operational client instance. + * @param inviteId the unique id previously assigned by the server to + * this invitation. + * @param listener will receive and process the response. + */ + function cancel ( + client :Client, inviteId :int, listener :InvocationListener) :void; + + /** + * You probably don't want to call this directly, but want to call + * {@link TableDirector#createTable}. Requests that a new table be + * created. + * + * @param client a connected, operational client instance. + * @param lobbyOid the oid of the lobby that will contain the newly + * created table. + * @param tableConfig the table configuration parameters. + * @param config the game config for the game to be matchmade by the + * table. + * @param listener will receive and process the response. + */ + function createTable ( + client :Client, lobbyOid :int, tableConfig :TableConfig, + config :GameConfig, listener :TableListener) :void; + + /** + * You probably don't want to call this directly, but want to call + * {@link TableDirector#joinTable}. Requests that the current user + * be added to the specified table at the specified position. + * + * @param client a connected, operational client instance. + * @param lobbyOid the oid of the lobby that contains the table. + * @param tableId the unique id of the table to which this user wishes + * to be added. + * @param position the position at the table to which this user desires + * to be added. + * @param listener will receive and process the response. + */ + function joinTable ( + client :Client, lobbyOid :int, tableId :int, + position :int, listener :InvocationListener) :void; + + /** + * You probably don't want to call this directly, but want to call + * {@link TableDirector#leaveTable}. Requests that the current user be + * removed from the specified table. + * + * @param client a connected, operational client instance. + * @param lobbyOid the oid of the lobby that contains the table. + * @param tableId the unique id of the table from which this user + * wishes to be removed. + * @param listener will receive and process the response. + */ + function leaveTable ( + client :Client, lobbyOid :int, tableId :int, + listener :InvocationListener) :void; + + /** + * Requests to start a single player game with the specified game + * configuration. + */ + function startSolitaire ( + client :Client, config :GameConfig, + listener :ConfirmListener) :void; +} +} diff --git a/src/as/com/threerings/parlor/client/ParlorService_InviteListener.as b/src/as/com/threerings/parlor/client/ParlorService_InviteListener.as new file mode 100644 index 00000000..d96e084d --- /dev/null +++ b/src/as/com/threerings/parlor/client/ParlorService_InviteListener.as @@ -0,0 +1,15 @@ +package com.threerings.parlor.client { + +import com.threerings.presents.client.InvocationListener; + +/** + * Used to communicate responses to {@link #invite} requests. + */ +public interface ParlorService_InviteListener extends InvocationListener +{ + /** + * Called in response to a successful {@link #invite} request. + */ + function inviteReceived (inviteId :int) :void; +} +} diff --git a/src/as/com/threerings/parlor/client/ParlorService_TableListener.as b/src/as/com/threerings/parlor/client/ParlorService_TableListener.as new file mode 100644 index 00000000..d499034e --- /dev/null +++ b/src/as/com/threerings/parlor/client/ParlorService_TableListener.as @@ -0,0 +1,13 @@ +package com.threerings.parlor.client { + +import com.threerings.presents.client.InvocationListener; + +/** + * Used to communicate responses to {@link #createTable}, {@link + * #joinTable}, and {@link #leaveTable} requests. + */ +public interface ParlorService_TableListener extends InvocationListener +{ + function tableCreated (tableId :int) :void; +} +} diff --git a/src/as/com/threerings/parlor/client/SeatednessObserver.as b/src/as/com/threerings/parlor/client/SeatednessObserver.as new file mode 100644 index 00000000..d5ff079c --- /dev/null +++ b/src/as/com/threerings/parlor/client/SeatednessObserver.as @@ -0,0 +1,39 @@ +// +// $Id: SeatednessObserver.java 4191 2006-06-13 22:42:20Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.client { + +/** + * Entites that wish to hear about when we sit down at a table or stand up + * from a table can implement this interface and register themselves with + * the {@link TableDirector}. + */ +public interface SeatednessObserver +{ + /** + * Called when this client sits down at or stands up from a table. + * + * @param isSeated true if the client is now seated at a table, false + * if they are now no longer seated at a table. + */ + function seatednessDidChange (isSeated :Boolean) :void; +} +} diff --git a/src/as/com/threerings/parlor/client/TableConfigurator.as b/src/as/com/threerings/parlor/client/TableConfigurator.as new file mode 100644 index 00000000..2e70506c --- /dev/null +++ b/src/as/com/threerings/parlor/client/TableConfigurator.as @@ -0,0 +1,125 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.client { + +import com.threerings.parlor.util.ParlorContext; + +import com.threerings.parlor.data.TableConfig; + +import com.threerings.parlor.game.client.GameConfigurator; + +/** + * This should be implemented some user-interface element that allows + * the user to configure whichever TableConfig options are relevant. + */ +public /*abstract*/ class TableConfigurator +{ + /** + * Create a TableConfigurator. + */ + public function TableConfigurator () + { + _config = createTableConfig(); + } + + /** + * Optionally, you can set a pre-configured TableConfig + * that will be used to initialize the display parameters (if possible). + * This should be called prior to init(). + */ + public function setTableConfig (config :TableConfig) :void + { + if (config != null) { + _config = config; + } + } + + /** + * Initialize the TableConfigurator. + * + * At the time this is called, the GameConfigurator should have + * already been initialized. + */ + public function init ( + ctx :ParlorContext, gameConfigurator :GameConfigurator) :void + { + _ctx = ctx; + _gameConfigurator = gameConfigurator; + createConfigInterface(); + } + + /** + * Create the table config object that will be used. + */ + protected function createTableConfig () :TableConfig + { + return new TableConfig(); + } + + /** + * Create the config interface. + */ + protected function createConfigInterface () :void + { + // nothing by default + } + + /** + * If true, the TableConfigurator is empty, which doesn't mean that + * it will not return a TableConfig object (for it must), but rather + * that there are no user-editable options being presented in the + * config interface. + */ + public /*abstract*/ function isEmpty () :Boolean + { + return true; + } + + /** + * Return the fully configured table config according to the currently + * configured user interface elements. + */ + public function getTableConfig () :TableConfig + { + flushTableConfig(); + return _config; + } + + /** + * Derived classes will want to override this method, flushing + * values from the user interface to the table config object. + */ + protected function flushTableConfig () :void + { + // nothing by default + } + + /** Provides access to client services. */ + protected var _ctx :ParlorContext; + + /** The config we're configurating. */ + protected var _config :TableConfig; + + /** The game configurator, which we may wish to reference. */ + protected var _gameConfigurator :GameConfigurator; +} +} diff --git a/src/as/com/threerings/parlor/client/TableDirector.as b/src/as/com/threerings/parlor/client/TableDirector.as new file mode 100644 index 00000000..2a280a43 --- /dev/null +++ b/src/as/com/threerings/parlor/client/TableDirector.as @@ -0,0 +1,367 @@ +// +// $Id: TableDirector.java 3758 2005-11-10 23:18:58Z mdb $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.client { + +import com.threerings.presents.client.BasicDirector; +import com.threerings.presents.client.Client; +import com.threerings.presents.client.ClientEvent; + +import com.threerings.presents.dobj.EntryAddedEvent; +import com.threerings.presents.dobj.EntryRemovedEvent; +import com.threerings.presents.dobj.EntryUpdatedEvent; +import com.threerings.presents.dobj.SetListener; + +import com.threerings.crowd.data.BodyObject; +import com.threerings.crowd.data.PlaceObject; + +import com.threerings.parlor.data.Table; +import com.threerings.parlor.data.TableConfig; +import com.threerings.parlor.data.TableLobbyObject; +import com.threerings.parlor.game.data.GameConfig; +import com.threerings.parlor.util.ParlorContext; + +/** + * As tables are created and managed within the scope of a place (a + * lobby), we want to fold the table management functionality into the + * standard hierarchy of place controllers that deal with place-related + * functionality on the client. Thus, instead of forcing places that + * expect to have tables to extend a TableLobbyController or + * something similar, we instead provide the table director which can be + * instantiated by the place controller (or specific table related views) + * to handle the table matchmaking services. + * + *

Entites that do so, will need to implement the {@link + * TableObserver} interface so that the table director can notify them + * when table related things happen. + * + *

The table services expect that the place object being used as a + * lobby in which the table matchmaking takes place implements the {@link + * TableLobbyObject} interface. + */ +public class TableDirector extends BasicDirector + implements SetListener, ParlorService_TableListener +{ + private static const log :Log = Log.getLog(TableDirector); + + /** + * Creates a new table director to manage tables with the specified + * observer which will receive callbacks when interesting table + * related things happen. + * + * @param ctx the parlor context in use by the client. + * @param tableField the field name of the distributed set that + * contains the tables we will be managing. + * @param observer the entity that will receive callbacks when things + * happen to the tables. + */ + public function TableDirector ( + ctx :ParlorContext, tableField :String, observer :TableObserver) + { + super(ctx); + + // keep track of this stuff + _pctx = ctx; + _tableField = tableField; + _observer = observer; + } + + /** + * This must be called by the entity that uses the table director when + * the using entity prepares to enter and display a place. It is + * assumed that the client is already subscribed to the provided place + * object. + */ + public function willEnterPlace (place :PlaceObject) :void + { + // add ourselves as a listener to the place object + place.addListener(this); + + // and remember this for later + _lobby = place; + } + + /** + * This must be called by the entity that uses the table director when + * the using entity has left and is done displaying a place. + */ + public function didLeavePlace (place :PlaceObject) :void + { + // remove our listenership + place.removeListener(this); + + // clear out our lobby reference + _lobby = null; + } + + /** + * Requests that the specified observer be added to the list of + * observers that are notified when this client sits down at or stands + * up from a table. + */ + public function addSeatednessObserver (observer :SeatednessObserver) :void + { + _seatedObservers.push(observer); + } + + /** + * Requests that the specified observer be removed from to the list of + * observers that are notified when this client sits down at or stands + * up from a table. + */ + public function removeSeatednessObserver ( + observer :SeatednessObserver) :void + { + ArrayUtil.removeFirst(_seatedObservers, observer); + } + + /** + * Returns true if this client is currently seated at a table, false + * if they are not. + */ + public function isSeated () :Boolean + { + return (_ourTable != null); + } + + /** + * Sends a request to create a table with the specified game + * configuration. This user will become the owner of this table and + * will be added to the first position in the table. The response will + * be communicated via the {@link TableObserver} interface. + */ + public function createTable ( + tableConfig :TableConfig, config :GameConfig) :void + { + // if we're already in a table, refuse the request + if (_ourTable != null) { + log.warning("Ignoring request to create table as we're " + + "already in a table [table=" + _ourTable + "]."); + return; + } + + // make sure we're currently in a place + if (_lobby == null) { + log.warning("Requested to create a table but we're not " + + "currently in a place [config=" + config + "]."); + return; + } + + // go ahead and issue the create request + _pservice.createTable(_pctx.getClient(), _lobby.getOid(), tableConfig, + config, this); + } + + /** + * Sends a request to join the specified table at the specified + * position. The response will be communicated via the {@link + * TableObserver} interface. + */ + public function joinTable (tableId :int, position :int) :void + { + // if we're already in a table, refuse the request + if (_ourTable != null) { + log.warning("Ignoring request to join table as we're " + + "already in a table [table=" + _ourTable + "]."); + return; + } + + // make sure we're currently in a place + if (_lobby == null) { + log.warning("Requested to join a table but we're not " + + "currently in a place [tableId=" + tableId + "]."); + return; + } + + // issue the join request + _pservice.joinTable(_pctx.getClient(), _lobby.getOid(), tableId, + position, this); + } + + /** + * Sends a request to leave the specified table at which we are + * presumably seated. The response will be communicated via the {@link + * TableObserver} interface. + */ + public function leaveTable (tableId :int) :void + { + // make sure we're currently in a place + if (_lobby == null) { + log.warning("Requested to leave a table but we're not " + + "currently in a place [tableId=" + tableId + "]."); + return; + } + + // issue the leave request + _pservice.leaveTable(_pctx.getClient(), _lobby.getOid(), tableId, this); + } + + // documentation inherited + override public function clientDidLogoff (event :ClientEvent) :void + { + super.clientDidLogoff(event); + _pservice = null; + _lobby = null; + _ourTable = null; + } + + // documentation inherited + protected function fetchServices (Client client) :void + { + // get a handle on our parlor services + _pservice = (client.requireService(ParlorService) as ParlorService); + } + + // documentation inherited + public function entryAdded (event :EntryAddedEvent) :void + { + if (event.getName() == _tableField) { + var table :Table = (event.getEntry() as Table); + + // check to see if we just joined a table + checkSeatedness(table); + + // now let the observer know what's up + _observer.tableAdded(table); + } + } + + // documentation inherited + public function entryUpdated (event :EntryUpdatedEvent) :void + { + if (event.getName() == _tableField) { + var table :Table = (event.getEntry() as Table); + + // check to see if we just joined or left a table + checkSeatedness(table); + + // now let the observer know what's up + _observer.tableUpdated(table); + } + } + + // documentation inherited + public function entryRemoved (event :EntryRemovedEvent) :void + { + if (event.getName() == _tableField) { + var tableId :int = (event.getKey() as int); + + // check to see if our table just disappeared + if (_ourTable != null && tableId == _ourTable.tableId) { + _ourTable = null; + notifySeatedness(false); + } + + // now let the observer know what's up + _observer.tableRemoved(tableId); + } + } + + // documentation inherited from interface + public function tableCreated (tableId :int) :void + { + // nothing much to do here + log.info("Table creation succeeded [tableId=" + tableId + "]."); + } + + // documentation inherited from interface + public function requestFailed (reason :String) :void + { + log.warning("Table creation failed [reason=" + reason + "]."); + } + + /** + * Checks to see if we're a member of this table and notes it as our + * table, if so. + */ + protected function checkSeatedness (table :Table) :void + { + var oldTable :Table = _ourTable; + + // if this is the same table as our table, clear out our table + // reference and allow it to be added back if we are still in the + // table + if (table.equals(_ourTable)) { + _ourTable = null; + } + + // look for our username in the occupants array + var self :BodyObject = + (_pctx.getClient().getClientObject() as BodyObject); + for (var ii :int = 0; ii < table.occupants.length; ii++) { + if (self.getVisibleName().equals(table.occupants[i])) { + _ourTable = table; + break; + } + } + + // if nothing changed, bail now + if (oldTable == _ourTable || + (oldTable != null && oldTable.equals(_ourTable))) { + return; + } + + // otherwise notify the observers + notifySeatedness(_ourTable != null); + } + + /** + * Notifies the seatedness observers of a seatedness change. + */ + protected function notifySeatedness (isSeated :Boolean) :void + { + var slength :int = _seatedObservers.length; + for (var ii :int = 0; ii < slength; ii++) { + var observer :SeatednessObserver = + (_seatedObservers[ii] as SeatednessObserver); + try { + observer.seatednessDidChange(isSeated); + } catch (err :Error) { + log.warning("Observer choked in seatednessDidChange() " + + "[observer=" + observer + "]."); + log.logStackTrace(err); + } + } + } + + /** A context by which we can access necessary client services. */ + protected var _pctx :ParlorContext; + + /** Parlor server-side services. */ + protected var _pservice :ParlorService; + + /** The place object in which we're currently managing tables. */ + protected var _lobby :PlaceObject; + + /** The field name of the distributed set that contains our tables. */ + protected var _tableField :String; + + /** The entity that we talk to when table stuff happens. */ + protected var _observer :TableObserver; + + /** The table of which we are a member if any. */ + protected var _ourTable :Table; + + /** An array of entities that want to hear about when we stand up or + * sit down. */ + protected var _seatedObservers :Array = new Array(); +} +} diff --git a/src/as/com/threerings/parlor/client/TableObserver.as b/src/as/com/threerings/parlor/client/TableObserver.as new file mode 100644 index 00000000..bffc44bc --- /dev/null +++ b/src/as/com/threerings/parlor/client/TableObserver.as @@ -0,0 +1,49 @@ +// +// $Id: TableObserver.java 4191 2006-06-13 22:42:20Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.client { + +import com.threerings.parlor.data.Table; + +/** + * The {@link TableDirector} converts distributed object events into + * higher level callbacks to implementers of this interface, which are + * expected to render these events sensically in a user interface. + */ +public interface TableObserver +{ + /** + * Called when a new table is created. + */ + function tableAdded (table :Table) :void; + + /** + * Called when something has changed about a table (occupant list + * updated, state changed from matchmaking to in-play, etc.). + */ + function tableUpdated (table :Table) :void; + + /** + * Called when a table goes away. + */ + function tableRemoved (tableId :int) :void; +} +} diff --git a/src/as/com/threerings/parlor/data/ParlorCodes.as b/src/as/com/threerings/parlor/data/ParlorCodes.as new file mode 100644 index 00000000..9d3cdc8f --- /dev/null +++ b/src/as/com/threerings/parlor/data/ParlorCodes.as @@ -0,0 +1,63 @@ +// +// $Id: ParlorCodes.java 3359 2005-02-19 22:38:06Z mdb $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.data { + +import com.threerings.presents.data.InvocationCodes; + +/** + * Contains codes used by the parlor invocation services. + */ +public class ParlorCodes extends InvocationCodes +{ + /** The response code for an accepted invitation. */ + public static const INVITATION_ACCEPTED :int = 0; + + /** The response code for a refused invitation. */ + public static const INVITATION_REFUSED :int = 1; + + /** The response code for a countered invitation. */ + public static const INVITATION_COUNTERED :int = 2; + + /** An error code explaining that an invitation was rejected because + * the invited user was not online at the time the invitation was + * received. */ + public static const INVITEE_NOT_ONLINE :String = "m.invitee_not_online"; + + /** An error code returned when a user requests to join a table that + * doesn't exist. */ + public static const NO_SUCH_TABLE :String = "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 const INVALID_TABLE_POSITION :String = + "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 const TABLE_POSITION_OCCUPIED :String = + "m.table_position_occupied"; + + /** An error code returned when a user requests to leave a table that + * they were not sitting at in the first place. */ + public static const NOT_AT_TABLE :String = "m.not_at_table"; +} +} diff --git a/src/as/com/threerings/parlor/data/ParlorMarshaller.as b/src/as/com/threerings/parlor/data/ParlorMarshaller.as new file mode 100644 index 00000000..b259855b --- /dev/null +++ b/src/as/com/threerings/parlor/data/ParlorMarshaller.as @@ -0,0 +1,143 @@ +// +// $Id: ParlorMarshaller.java 4145 2006-05-24 01:24:24Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.data { + +import com.threerings.parlor.client.ParlorService; +import com.threerings.parlor.data.TableConfig; +import com.threerings.parlor.game.data.GameConfig; +import com.threerings.presents.client.Client; +import com.threerings.presents.client.InvocationService; +import com.threerings.presents.client.InvocationService_InvocationListener; +import com.threerings.presents.data.InvocationMarshaller; +import com.threerings.presents.dobj.InvocationResponseEvent; +import com.threerings.util.Name; + +/** + * Provides the implementation of the {@link ParlorService} interface + * that marshalls the arguments and delivers the request to the provider + * on the server. Also provides an implementation of the response listener + * interfaces that marshall the response arguments and deliver them back + * to the requesting client. + */ +public class ParlorMarshaller extends InvocationMarshaller + implements ParlorService +{ + /** The method id used to dispatch {@link #cancel} requests. */ + public static const CANCEL :int = 1; + + // documentation inherited from interface + public function cancel (arg1 :Client, arg2 :int, arg3 :InvocationService_InvocationListener) :void + { + var listener3 :InvocationMarshaller_ListenerMarshaller = + new InvocationMarshaller_ListenerMarshaller(); + listener3.listener = arg3; + sendRequest(arg1, CANCEL, [ + Integer.valueOf(arg2), listener3 + ]); + } + + /** The method id used to dispatch {@link #createTable} requests. */ + public static const CREATE_TABLE :int = 2; + + // documentation inherited from interface + public function createTable (arg1 :Client, arg2 :int, arg3 :TableConfig, arg4 :GameConfig, arg5 :ParlorService_TableListener) :void + { + var listener5 :ParlorMarshaller_TableMarshaller = + new ParlorMarshaller_TableMarshaller(); + listener5.listener = arg5; + sendRequest(arg1, CREATE_TABLE, [ + Integer.valueOf(arg2), arg3, arg4, listener5 + ]); + } + + /** The method id used to dispatch {@link #invite} requests. */ + public static const INVITE :int = 3; + + // documentation inherited from interface + public function invite (arg1 :Client, arg2 :Name, arg3 :GameConfig, arg4 :ParlorService_InviteListener) :void + { + var listener4 :ParlorMarshaller_InviteMarshaller = + new ParlorMarshaller_InviteMarshaller(); + listener4.listener = arg4; + sendRequest(arg1, INVITE, [ + arg2, arg3, listener4 + ]); + } + + /** The method id used to dispatch {@link #joinTable} requests. */ + public static const JOIN_TABLE :int = 4; + + // documentation inherited from interface + public function joinTable (arg1 :Client, arg2 :int, arg3 :int, arg4 :int, arg5 :InvocationService_InvocationListener) :void + { + var listener5 :InvocationMarshaller_ListenerMarshaller listener5 = + new InvocationMarshaller_ListenerMarshaller(); + listener5.listener = arg5; + sendRequest(arg1, JOIN_TABLE, [ + Integer.valueOf(arg2), Integer.valueOf(arg3), Integer.valueOf(arg4), listener5 + ]); + } + + /** The method id used to dispatch {@link #leaveTable} requests. */ + public static const LEAVE_TABLE :int = 5; + + // documentation inherited from interface + public function leaveTable (arg1 :Client, arg2 :int, arg3 :int, arg4 :InvocationService_InvocationListener) :void + { + var listener4 :InvocationMarshaller_ListenerMarshaller = + new InvocationMarshaller_ListenerMarshaller(); + listener4.listener = arg4; + sendRequest(arg1, LEAVE_TABLE, [ + Integer.valueOf(arg2), Integer.valueOf(arg3), listener4 + ]); + } + + /** The method id used to dispatch {@link #respond} requests. */ + public static const RESPOND :int = 6; + + // documentation inherited from interface + public function respond (arg1 :Client, arg2 :int, arg3 :int, arg4 :Object, arg5 :InvocationService_InvocationListener) :void + { + var listener5 :InvocationMarshaller_ListenerMarshaller = + new InvocationMarshallerListenerMarshaller(); + listener5.listener = arg5; + sendRequest(arg1, RESPOND, [ + Integer.valueOf(arg2), Integer.valueOf(arg3), arg4, listener5 + ]); + } + + /** The method id used to dispatch {@link #startSolitaire} requests. */ + public static const START_SOLITAIRE :int = 7; + + // documentation inherited from interface + public function startSolitaire (arg1 :Client, arg2 :GameConfig, arg3 :InvocationService_ConfirmListener) :void + { + var listener3 :InvocationMarshaller_ConfirmMarshaller = + new InvocationMarshaller_ConfirmMarshaller(); + listener3.listener = arg3; + sendRequest(arg1, START_SOLITAIRE, [ + arg2, listener3 + ]); + } + +} +} diff --git a/src/as/com/threerings/parlor/data/ParlorMarshaller_InviteMarshaller.as b/src/as/com/threerings/parlor/data/ParlorMarshaller_InviteMarshaller.as new file mode 100644 index 00000000..e909ec8e --- /dev/null +++ b/src/as/com/threerings/parlor/data/ParlorMarshaller_InviteMarshaller.as @@ -0,0 +1,39 @@ +package com.threerings.parlor.data { + +import com.threerings.util.Integer; + +import com.threerings.presents.data.InvocationMarshaller_ListenerMarshaller; + +public class ParlorMarshaller_InviteMarshaller + extends InvocationMarshaller_ListenerMarshaller + implements InviteListener +{ + /** The method id used to dispatch {@link #inviteReceived} + * responses. */ + public static const INVITE_RECEIVED :int = 1; + + // documentation inherited from interface + public function inviteReceived (arg1 :int) :void + { + _invId = null; + omgr.postEvent(new InvocationResponseEvent( + callerOid, requestId, INVITE_RECEIVED, + [ Integer.valueOf(arg1) ])); + } + + // documentation inherited + override public function dispatchResponse (methodId :int, args :Array) :void + { + switch (methodId) { + case INVITE_RECEIVED: + (listener as InviteListener).inviteReceived( + (args[0] as Integer).value); + return; + + default: + super.dispatchResponse(methodId, args); + return; + } + } +} +} diff --git a/src/as/com/threerings/parlor/data/ParlorMarshaller_TableMarshaller.as b/src/as/com/threerings/parlor/data/ParlorMarshaller_TableMarshaller.as new file mode 100644 index 00000000..cfc3f129 --- /dev/null +++ b/src/as/com/threerings/parlor/data/ParlorMarshaller_TableMarshaller.as @@ -0,0 +1,39 @@ +package com.threerings.parlor.data { + +import com.threerings.util.Integer; + +import com.threerings.presents.data.InvocationMarshaller_ListenerMarshaller; + +public class ParlorMarshaller_TableMarshaller + extends InvocationMarshaller_ListenerMarshaller + implements TableListener +{ + /** The method id used to dispatch {@link #tableCreated} + * responses. */ + public static const TABLE_CREATED :int = 1; + + // documentation inherited from interface + public function tableCreated (arg1 :int) :void + { + _invId = null; + omgr.postEvent(new InvocationResponseEvent( + callerOid, requestId, TABLE_CREATED, + [ Integer.valueOf(arg1) ])); + } + + // documentation inherited + override public function dispatchResponse (methodId :int, args :Array) :void + { + switch (methodId) { + case TABLE_CREATED: + (listener as TableListener).tableCreated( + (args[0] as Integer).value); + return; + + default: + super.dispatchResponse(methodId, args); + return; + } + } +} +} diff --git a/src/as/com/threerings/parlor/data/Table.as b/src/as/com/threerings/parlor/data/Table.as new file mode 100644 index 00000000..6773a088 --- /dev/null +++ b/src/as/com/threerings/parlor/data/Table.as @@ -0,0 +1,378 @@ +// +// $Id: Table.java 4191 2006-06-13 22:42:20Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.data { + +import com.threerings.util.Hashable; +import com.threerings.util.Name; +import com.threerings.util.StringBuilder; + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; + +import com.threerings.presents.dobj.DSet; +import com.threerings.presents.dobj.DSet_Entry; + +import com.threerings.crowd.data.BodyObject; + +import com.threerings.parlor.data.ParlorCodes; +import com.threerings.parlor.game.data.GameConfig; +import com.threerings.parlor.game.data.PartyGameConfig; + +/** + * This class represents a table that is being used to matchmake a game by + * the Parlor services. + */ +public class Table + implements Hashable, DSet_Entry, ParlorCodes +{ + /** The unique identifier for this table. */ + public var tableId :int; + + /** The object id of the lobby object with which this table is + * associated. */ + public var lobbyOid :int; + + /** The oid of the game that was created from this table or -1 if the + * table is still in matchmaking mode. */ + public var gameOid :int = -1; + + /** An array of the usernames of the occupants of this table (some + * slots may not be filled), or null if a party game. */ + public var occupants :TypedArray /* of Name */; + + /** The body oids of the occupants of this table, or null if a party game. + * (This is not propagated to remote instances.) */ + public var bodyOids :TypedArray /* of int */; + + /** The game config for the game that is being matchmade. */ + public var config :GameConfig; + + /** The table configuration object. */ + public var tconfig :TableConfig; + + /** Suitable for unserialization. */ + public function Table () + { + } + + /** + * Returns true if there is no one sitting at this table. + */ + public function isEmpty () :Boolean + { + for (var ii :int = 0; ii < bodyOids.length; ii++) { + if ((bodyOids[ii] as int) !== 0) { + return false; + } + } + return true; + } + + /** + * Count the number of players currently occupying this table. + */ + public function getOccupiedCount () :int + { + var count :int = 0; + for (var ii :int = 0; ii < occupants.length; ii++) { + if (occupants[ii] != null) { + count++; + } + } + return count; + } + + /** + * Once a table is ready to play (see {@link #mayBeStarted} and {@link + * #shouldBeStarted}), 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 function getPlayers () :Array + { + if (isPartyGame()) { + return occupants; + } + + // create and populate the players array + var players :Array = new Array(); + for (var ii :int = 0; ii < occupants.length; ii++) { + if (occupants[ii] != null) { + players.push(occupants[ii]); + } + } + + return players; + } + + /** + * For a team game, get the team member indices of the compressed + * players array returned by getPlayers(). + */ + public function getTeamMemberIndices () :Array /* of Array of int */ + { + var teams :Array = tconfig.teamMemberIndices; + if (teams == null) { + return null; + } + +// TODO + // compress the team indexes down + ArrayIntSet set = new ArrayIntSet(); + int[][] newTeams = new int[teams.length][]; + Name[] players = getPlayers(); + for (int ii=0; ii < teams.length; ii++) { + set.clear(); + for (int jj=0; jj < teams[ii].length; jj++) { + Name occ = occupants[teams[ii][jj]]; + if (occ != null) { + set.add(ListUtil.indexOf(players, occ)); + } + } + newTeams[ii] = set.toIntArray(); + } + + return newTeams; + } + + /** + * Return true if the game is a party game. + */ + public function isPartyGame () :Boolean + { + return (PartyGameConfig.NOT_PARTY_GAME != getPartyGameType()); + } + + /** + * Get the type of party game being played at this table, or + * PartyGameConfig.NOT_PARTY_GAME. + */ + public function getPartyGameType () :int + { + if (config is PartyGameConfig) { + return (config as PartyGameConfig).getPartyGameType(); + + } else { + return PartyGameConfig.NOT_PARTY_GAME; + } + } + + /** + * Requests to seat the specified user at the specified position in + * this table. + * + * @param position the position in which to seat the user. + * @param occupant the occupant to set. + * + * @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 function setOccupant (position :int, occupant :BodyObject) :String + { + // make sure the requested position is a valid one + if (position >= tconfig.desiredPlayerCount || position < 0) { + return INVALID_TABLE_POSITION; + } + + // make sure the requested position is not already occupied + if (occupants[position] != null) { + return TABLE_POSITION_OCCUPIED; + } + + // otherwise all is well, stick 'em in + setOccupantPos(position, occupant); + return null; + } + + /** + * This method is used for party games, it does no bounds checking + * or verification of the player's ability to join, if you are unsure + * you should call 'setOccupant'. + */ + public function setOccupantPos (position :int, occupant :BodyObject) :void + { + occupants[position] = occupant.getVisibleName(); + bodyOids[position] = occupant.getOid(); + } + + /** + * Requests that the specified user be removed from their seat at this + * table. + * + * @return true if the user was seated at the table and has now been + * removed, false if the user was never seated at the table in the + * first place. + */ + public function clearOccupant (username :Name) :void + { + var dex :int = ArrayUtil.indexOf(occupants, username); + if (dex != -1) { + clearOccupantPos(dex); + return true; + } + return false; + } + + /** + * Requests that the user identified by the specified body object id + * be removed from their seat at this table. + * + * @return true if the user was seated at the table and has now been + * removed, false if the user was never seated at the table in the + * first place. + */ + public function clearOccupant (bodyOid :int) :Boolean + { + var dex :int = ArrayUtil.indexOf(bodyOids, bodyOid); + if (dex != -1) { + clearOccupantPos(dex); + return true; + } + return false; + } + + /** + * Called to clear an occupant at the specified position. + * Only call this method if you know what you're doing. + */ + public function clearOccupantPos (position :int) :void + { + occupants[position] = null; + bodyOids[position] = 0; + } + + /** + * Returns true if this table has a sufficient number of occupants + * that the game can be started. + */ + public function mayBeStarted () :Boolean + { + if (tconfig.teamMemberIndices == null) { + // for a normal game, just check to see if we're past the minimum + return tconfig.minimumPlayerCount <= getOccupiedCount(); + + } else { + // for a team game, make sure each team has the minimum players + var teams :Array = tconfig.teamMemberIndices; + for (var ii :int = 0; ii < teams.length; ii++) { + var teamCount :int = 0; + var members :Array = (teams[ii] as Array); + for (var jj :int = 0; jj < members.length; jj++) { + if (occupants[members[jj]] != null) { + teamCount++; + } + } + if (teamCount < tconfig.minimumPlayerCount) { + return false; + } + } + return true; + } + } + + /** + * Returns true if sufficient seats are occupied that the game should + * be automatically started. + */ + public function shouldBeStarted () :Boolean + { + return tconfig.desiredPlayerCount <= getOccupiedCount(); + } + + /** + * Returns true if this table is in play, false if it is still being + * matchmade. + */ + public function inPlay () :Boolean + { + return gameOid != -1; + } + + // documentation inherited + public function getKey () :Object + { + return tableId; + } + + // from Hashable + public function equals (other :Object) :Boolean + { + return (other is Table) && + (tableId == ((Table) other).tableId); + } + + // from Hashable + public function hashCode () :int + { + return tableId; + } + + // from Streamable + public function writeObject (out :ObjectOutputStream) :void + { + out.writeInt(tableId); + out.writeInt(lobbyOid); + out.writeInt(gameOid); + out.writeObject(occupants); + out.writeObject(config); + out.writeObject(tconfig); + } + + // from Streamable + public function readObject (ins :ObjectInputStream) :void + { + tableId = ins.readInt(); + lobbyOid = ins.readInt(); + gameOid = ins.readInt(); + occupants = (ins.readObject() as TypedArray); + config = (ins.readObject() as GameConfig); + tconfig = (ins.readObject() as TableConfig); + } + + /** + * Generates a string representation of this table instance. + */ + public function toString () :String + { + var buf :StringBuilder = new StringBuilder(); + buf.append(ClassUtil.shortClassName(this)); + buf.append(" ["); + toStringBuf(buf); + buf.append("]"); + return buf.toString(); + } + + /** + * Helper method for toString, ripe for overrideability. + */ + protected function toStringBuf (buf :StringBuilder) :void + { + buf.append("tableId=").append(tableId); + buf.append(", lobbyOid=").append(lobbyOid); + buf.append(", gameOid=").append(gameOid); + buf.append(", occupants=").append(occupants.join()); + buf.append(", config=").append(config); + } +} +} diff --git a/src/as/com/threerings/parlor/data/TableConfig.as b/src/as/com/threerings/parlor/data/TableConfig.as new file mode 100644 index 00000000..a85173e7 --- /dev/null +++ b/src/as/com/threerings/parlor/data/TableConfig.as @@ -0,0 +1,72 @@ +// +// $Id: TableConfig.java 3604 2005-06-17 20:25:28Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.data { + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; +import com.threerings.io.Streamable; +import com.threerings.io.TypedArray; + +/** + * Table configuration parameters for a game that is to be matchmade + * using the table services. + */ +public class TableConfig + implements Streamable +{ + /** The total number of players that are desired for the table, + * or -1 for a party game. For team games, this should be set to the + * total number of players overall, as teams may be unequal. */ + public var desiredPlayerCount :int; + + /** The minimum number of players needed overall (or per-team if a + * team-based game) for the game to start at the creator's discretion. */ + public var minimumPlayerCount :int; + + /** If non-null, indicates that this is a team-based game and contains + * the team assignments for each player. For example, a game with + * three players in two teams- players 0 and 2 versus player 1- would + * have { {0, 2}, {1} }; */ + public var teamMemberIndices :TypedArray; + + /** Whether the table is "private". */ + public var privateTable :Boolean; + + // from Streamable + public function writeObject (out :ObjectOutputStream) :void + { + out.writeInt(desiredPlayerCount); + out.writeInt(minimumPlayerCount); + out.writeField(teamMemberIndices); + out.writeBoolean(privateTable); + } + + // from Streamable + public function readObject (ins :ObjectInputStream) :void + { + desiredPlayerCount = ins.readInt(); + minimumPlayerCount = ins.readInt(); + teamMemberIndices = (ins.readField("[[I") as TypedArray); + privateTable = ins.readBoolean(); + } +} +} diff --git a/src/as/com/threerings/parlor/data/TableLobbyObject.as b/src/as/com/threerings/parlor/data/TableLobbyObject.as new file mode 100644 index 00000000..6664b98d --- /dev/null +++ b/src/as/com/threerings/parlor/data/TableLobbyObject.as @@ -0,0 +1,57 @@ +// +// $Id: TableLobbyObject.java 4145 2006-05-24 01:24:24Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.data { + +import com.threerings.presents.dobj.DSet; + +/** + * This interface must be implemented by the place object used by a lobby + * that wishes to make use of the table services. + */ +public interface TableLobbyObject +{ + /** + * Returns a reference to the distributed set instance that will be + * holding the tables. + */ + function getTables () :DSet; + + /** + * Adds the supplied table instance to the tables set (using the + * appropriate distributed object mechanisms). + */ + function addToTables (table :Table) :void; + + /** + * Updates the value of the specified table instance in the tables + * distributed set (using the appropriate distributed object + * mechanisms). + */ + function updateTables (table :Table) :void; + + /** + * Removes the table instance that matches the specified key from the + * tables set (using the appropriate distributed object mechanisms). + */ + function removeFromTables (key :Object) :void; +} +} diff --git a/src/as/com/threerings/parlor/game/client/FlexGameConfigurator.as b/src/as/com/threerings/parlor/game/client/FlexGameConfigurator.as new file mode 100644 index 00000000..311da5ba --- /dev/null +++ b/src/as/com/threerings/parlor/game/client/FlexGameConfigurator.as @@ -0,0 +1,77 @@ +// +// $Id: GameConfigurator.java 3590 2005-06-08 23:20:18Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.game.client; + +import mx.core.Container; +import mx.core.UIComponent; + +import mx.containers.Grid; +import mx.containers.GridItem; +import mx.containers.GridRow; + +import com.threerings.parlor.game.data.GameConfig; +import com.threerings.parlor.util.ParlorContext; + +/** + * Provides the base from which interfaces can be built to configure games + * prior to starting them. Derived classes would extend the base + * configurator adding interface elements and wiring them up properly to + * allow the user to configure an instance of their game. + * + *

Clients that use the game configurator will want to instantiate one + * based on the class returned from the {@link GameConfig} and then + * initialize it with a call to {@link #init}. + */ +public /*abstract*/ class FlexGameConfigurator extends GameConfigurator +{ + /** + * Get the Container that contains the UI elements for this configurator. + */ + public function getContainer () :Container + { + return _grid; + } + + /** + * Add a control to the interface. This should be the standard way + * that configurator controls are added, but note also that external + * entities may add their own controls that are related to the game, + * but do not directly alter the game config, so that all the controls + * are added in a uniform manner and are well aligned. + */ + public function addControl ( + label :UIComponent, control :UIComponent) :void + { + var row :GridRow = new GridRow(); + var labelItem :GridItem = new GridItem(); + var controlItem :GridItem = new GridItem(); + + labelItem.addChild(label); + controlItem.addChild(control); + row.addChild(labelItem); + row.addChild(controlItem); + _grid.addChild(row); + } + + /** The grid on which the config options are placed. */ + protected var _grid :Grid = new Grid(); +} diff --git a/src/as/com/threerings/parlor/game/client/GameConfigurator.as b/src/as/com/threerings/parlor/game/client/GameConfigurator.as new file mode 100644 index 00000000..ef778850 --- /dev/null +++ b/src/as/com/threerings/parlor/game/client/GameConfigurator.as @@ -0,0 +1,104 @@ +// +// $Id: GameConfigurator.java 3590 2005-06-08 23:20:18Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.game.client { + +import com.threerings.parlor.game.data.GameConfig; +import com.threerings.parlor.util.ParlorContext; + +/** + * Provides the base from which interfaces can be built to configure games + * prior to starting them. Derived classes would extend the base + * configurator adding interface elements and wiring them up properly to + * allow the user to configure an instance of their game. + * + *

Clients that use the game configurator will want to instantiate one + * based on the class returned from the {@link GameConfig} and then + * initialize it with a call to {@link #init}. + */ +public /*abstract*/ class GameConfigurator +{ + /** + * Initializes this game configurator, creates its user interface + * elements and prepares it for display. + */ + public function init (ctx :ParlorContext) :void + { + // save this for later + _ctx = ctx; + + // create our interface elements + createConfigInterface(); + } + + /** + * The default implementation creates nothing. + */ + protected function createConfigInterface () :void + { + } + + /** + * Provides this configurator with its configuration. It should set up + * all of its user interface elements to reflect the configuration. + */ + public function setGameConfig (config :GameConfig) :void + { + _config = config; + // set up the user interface + gotGameConfig(); + } + + /** + * Derived classes will likely want to override this method and + * configure their user interface elements accordingly. + */ + protected function gotGameConfig () :void + { + } + + /** + * Obtains a configured game configuration. + */ + public function getGameConfig () :GameConfig + { + // flush our changes to the config object + flushGameConfig(); + return _config; + } + + /** + * Derived classes will want to override this method, flushing values + * from the user interface to the game config object so that it is + * properly configured prior to being returned to the {@link + * #getGameConfig} caller. + */ + protected function flushGameConfig () :void + { + } + + /** Provides access to client services. */ + protected var _ctx :ParlorContext; + + /** Our game configuration. */ + protected var _config :GameConfig; +} +} diff --git a/src/as/com/threerings/parlor/game/client/GameController.as b/src/as/com/threerings/parlor/game/client/GameController.as new file mode 100644 index 00000000..46f0711d --- /dev/null +++ b/src/as/com/threerings/parlor/game/client/GameController.as @@ -0,0 +1,337 @@ +// +// $Id: GameController.java 3804 2006-01-13 01:52:36Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.game.client { + +import com.threerings.presents.dobj.AttributeChangeListener; +import com.threerings.presents.dobj.AttributeChangedEvent; + +import com.threerings.crowd.client.PlaceController; +import com.threerings.crowd.data.BodyObject; +import com.threerings.crowd.data.PlaceConfig; +import com.threerings.crowd.data.PlaceObject; +import com.threerings.crowd.util.CrowdContext; + +import com.threerings.parlor.Log; +import com.threerings.parlor.game.data.GameCodes; +import com.threerings.parlor.game.data.GameConfig; +import com.threerings.parlor.game.data.GameObject; +import com.threerings.parlor.game.data.PartyGameConfig; +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 /*abstract*/ class GameController extends PlaceController + implements AttributeChangeListener +{ + protected static const log :Log = Log.getLog(GameController); + + /** + * 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 ctx the client context. + * @param config the configuration of the game we are intended to + * control. + */ + override public function init (ctx :CrowdContext, config :PlaceConfig) :void + { + // cast our references before we call super.init() so that when + // super.init() calls createPlaceView(), we have our casted + // references already in place + _ctx = (ctx as ParlorContext); + _config = (config as GameConfig); + + super.init(ctx, config); + } + + /** + * Adds this controller as a listener to the game object (thus derived + * classes need not do so) and lets the game manager know that we are + * now ready to go. + */ + override public function willEnterPlace (plobj :PlaceObject) :void + { + super.willEnterPlace(plobj); + + // obtain a casted reference + _gobj = (plobj as GameObject); + + // if this place object is not our current location we'll need to + // add it as an auxiliary chat source + var bobj :BodyObject = + (_ctx.getClient().getClientObject() as BodyObject); + if (bobj.location != plobj.getOid()) { + _ctx.getChatDirector().addAuxiliarySource( + _gobj, GameCodes.GAME_CHAT_TYPE); + } + + // and add ourselves as a listener + _gobj.addListener(this); + + // we don't want to claim to be finished until any derived classes + // that overrode this method have executed, so we'll queue up a + // runnable here that will let the game manager know that we're + // ready on the next pass through the distributed event loop + log.info("Entering game " + _gobj.which() + "."); + if (_gobj.getPlayerIndex(bobj.getVisibleName()) != -1) { + // finally let the game manager know that we're ready + // to roll + _ctx.getClient().callLater(playerReady); + } + } + + /** + * Removes our listener registration from the game object and cleans + * house. + */ + override public function didLeavePlace (plobj :PlaceObject) :void + { + super.didLeavePlace(plobj); + + _ctx.getChatDirector().removeAuxiliarySource(_gobj); + + // unlisten to the game object + _gobj.removeListener(this); + _gobj = null; + } + + /** + * Returns whether the game is over. + */ + public function isGameOver () :Boolean + { + var gameOver :Boolean = (_gobj == null) || + (_gobj.state != GameObject.IN_PLAY); + return (_gameOver || gameOver); + } + + /** + * Sets the client game over override. This is used in situations + * where we determine that the game is over before the server has + * informed us of such. + */ + public function setGameOver (gameOver :Boolean) :void + { + _gameOver = gameOver; + } + + /** + * Calls {@link #gameWillReset}, ends the current game (locally, it + * does not tell the server to end the game), and waits to receive a + * reset notification (which is simply an event setting the game state + * to IN_PLAY even though it's already set to + * IN_PLAY) from the server which will start up a new + * game. Derived classes should override {@link #gameWillReset} to + * perform any game-specific animations. + */ + public function resetGame () :void + { + // let derived classes do their thing + gameWillReset(); + + // end the game until we receive a new board + setGameOver(true); + } + + /** + * Returns the unique round identifier for the current round. + */ + public function getRoundId () :int + { + return (_gobj == null) ? -1 : _gobj.roundId; + } + + /** + * Handles basic game controller action events. Derived classes should + * be sure to call super.handleAction for events they + * don't specifically handle. + */ + override public function handleAction (cmd :String, arg :Object) :Boolean + { + return super.handleAction(cmd, arg); + } + + /** + * A way for controllers to display a game-related system message. + */ + public function systemMessage (bundle :String, msg :String) :void + { + _ctx.getChatDirector().displayInfo( + bundle, msg, GameCodes.GAME_CHAT_TYPE); + } + + // from interface AttributeChangeListener + public function attributeChanged (event :AttributeChangedEvent) :void + { + // deal with game state changes + var name :String = event.getName(); + if (GameObject.STATE.equals(name)) { + var newState :int = int(event.getValue()); + if (!stateDidChange(newState)) { + log.warning("Game transitioned to unknown state " + + "[gobj=" + _gobj + ", state=" + newState + "]."); + } + } + } + + /** + * Derived classes can override this method if they add additional game + * states and should handle transitions to those states, returning true to + * indicate they were handled and calling super for the normal game states. + */ + protected function stateDidChange (state :int) :Boolean + { + switch (state) { + case GameObject.IN_PLAY: + gameDidStart(); + return true; + case GameObject.GAME_OVER: + gameDidEnd(); + return true; + case GameObject.CANCELLED: + gameWasCancelled(); + return true; + } + return false; + } + + /** + * Called after we've entered the game and everything has initialized + * to notify the server that we, as a player, are ready to play. + */ + protected function playerReady () :void + { + log.info("Reporting ready " + _gobj.which() + "."); + _gobj.gameService.playerReady(_ctx.getClient()); + } + + /** + * 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 function gameDidStart () :void + { + if (_gobj == null) { + log.info("Received gameDidStart() after leaving game room."); + return; + } + + // clear out our game over flag + setGameOver(false); + +/* TODO + // let our delegates do their business + applyToDelegates(new DelegateOp() { + public void apply (PlaceControllerDelegate delegate) { + ((GameControllerDelegate)delegate).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 function gameDidEnd () :void + { +/* TODO + // let our delegates do their business + applyToDelegates(new DelegateOp() { + public void apply (PlaceControllerDelegate delegate) { + ((GameControllerDelegate)delegate).gameDidEnd(); + } + }); +*/ + } + + /** + * Called when the game was cancelled for some reason. + */ + protected function gameWasCancelled () :void + { +/* + // let our delegates do their business + applyToDelegates(new DelegateOp() { + public void apply (PlaceControllerDelegate delegate) { + ((GameControllerDelegate)delegate).gameWasCancelled(); + } + }); +*/ + } + + /** + * Called to give derived classes a chance to display animations, send + * a final packet, or do any other business they care to do when the + * game is about to reset. + */ + protected function gameWillReset () :void + { +/* TODO + // let our delegates do their business + applyToDelegates(new DelegateOp() { + public void apply (PlaceControllerDelegate delegate) { + ((GameControllerDelegate)delegate).gameWillReset(); + } + }); +*/ + } + + /** + * Used to determine if this game is a party game. + */ + protected function isPartyGame () :Boolean + { + return (_config is PartyGameConfig) && + (PartyGameConfig(_config).getPartyGameType() != + PartyGameConfig.NOT_PARTY_GAME); + } + + /** A reference to the active parlor context. */ + protected var _pctx :ParlorContext; + + /** Our game configuration information. */ + protected var _config :GameConfig; + + /** A reference to the game object for the game that we're + * controlling. */ + protected var _gobj :GameObject; + + /** A local flag overriding the game over state for situations where + * the client knows the game is over before the server has + * transitioned the game object accordingly. */ + protected var _gameOver :Boolean; +} +} diff --git a/src/as/com/threerings/parlor/game/client/GameService.as b/src/as/com/threerings/parlor/game/client/GameService.as new file mode 100644 index 00000000..8458f5f7 --- /dev/null +++ b/src/as/com/threerings/parlor/game/client/GameService.as @@ -0,0 +1,39 @@ +// +// $Id: GameService.java 3600 2005-06-15 23:46:31Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.game.client { + +import com.threerings.presents.client.Client; +import com.threerings.presents.client.InvocationService; + +/** + * Provides services used by game clients to request that actions be taken + * by the game manager. + */ +public interface GameService extends InvocationService +{ + /** + * Lets the game manager know that the calling player is in the game + * room and ready to play. + */ + function playerReady (client :Client) :void; +} +} diff --git a/src/as/com/threerings/parlor/game/data/GameAI.as b/src/as/com/threerings/parlor/game/data/GameAI.as new file mode 100644 index 00000000..68b8e13c --- /dev/null +++ b/src/as/com/threerings/parlor/game/data/GameAI.as @@ -0,0 +1,47 @@ +// +// $Id: GameAI.java 3399 2005-03-12 07:37:34Z mdb $ + +package com.threerings.parlor.game.data { + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; +import com.threerings.io.Streamable; + +/** + * Represents attributes of an AI player. + */ +public class GameAI + implements Streamable +{ + /** The "personality" of the AI, which can be interpreted by + * each puzzle. */ + public var personality :int; + + /** The skill level of the AI. */ + public var skill :int; + + /** + * Constructs an AI with the specified (game-interpreted) skill and + * personality. + */ + public function GameAI (personality :int = 0, skill :int = 0) + { + this.personality = personality; + this.skill = skill; + } + + // from Streamable + public function writeObject (out :ObjectOutputStream) :void + { + out.writeInt(personality); + out.writeInt(skill); + } + + // from Streamable + public function readObject (ins :ObjectInputStream) :void + { + personality = ins.readInt(); + skill = ins.readInt(); + } +} +} diff --git a/src/as/com/threerings/parlor/game/data/GameCodes.as b/src/as/com/threerings/parlor/game/data/GameCodes.as new file mode 100644 index 00000000..938a8dfd --- /dev/null +++ b/src/as/com/threerings/parlor/game/data/GameCodes.as @@ -0,0 +1,45 @@ +// +// $Id: PuzzleCodes.java 3184 2004-10-28 19:20:27Z mdb $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.game.data { + +import com.threerings.presents.data.InvocationCodes; + +/** + * Constants relating to the game services. + */ +public class GameCodes extends InvocationCodes +{ + /** The message bundle identifier for general game messages. */ + public static const GAME_MESSAGE_BUNDLE :String = "game.general"; + + /** The name of the message event to a placeObject that a player + * was knocked out of a puzzle. */ + public static const PLAYER_KNOCKED_OUT :String = "playerKnocked"; + + /** The name of the message event to a placeObject that reports + * the winners and losers of a game. */ + public static const WINNERS_AND_LOSERS :String = "winnersAndLosers"; + + /** A chat type for chatting on the game object. */ + public static const GAME_CHAT_TYPE :String = "gameChat"; +} +} diff --git a/src/as/com/threerings/parlor/game/data/GameConfig.as b/src/as/com/threerings/parlor/game/data/GameConfig.as new file mode 100644 index 00000000..87336a27 --- /dev/null +++ b/src/as/com/threerings/parlor/game/data/GameConfig.as @@ -0,0 +1,162 @@ +// +// $Id: GameConfig.java 4026 2006-04-18 01:32:41Z mdb $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.game.data { + +import com.threerings.util.ClassUtil; +import com.threerings.util.Cloneable; +import com.threerings.util.Hashable; +import com.threerings.util.Name; + +import com.threerings.io.TypedArray; + +import com.threerings.crowd.data.PlaceConfig; + +import com.threerings.parlor.client.TableConfigurator; +import com.threerings.parlor.game.client.GameConfigurator; + +/** + * 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 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 #createController} 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. + */ +public /*abstract*/ class GameConfig extends PlaceConfig + implements Cloneable, Hashable +{ + /** The usernames of the players involved in this game, or an empty + * array if such information is not needed by this particular game. */ + public var players :TypedArray = TypedArray.create(Name); + + /** Indicates whether or not this game is rated. */ + public var rated :Boolean = true; + + /** Configurations for AIs to be used in this game. Slots with real + * players should be null and slots with AIs should contain + * configuration for those AIs. A null array indicates no use of AIs + * at all. */ + public var ais :TypedArray = TypedArray.create(GameAI); + + /** + * Returns the message bundle identifier for the bundle that should be + * used to translate the translatable strings used to describe the + * game config parameters. + */ + public function getBundleName () :String + { + Log.getLog(this).warning("this method is actually abstract."); + } + + /** + * Creates a configurator that can be used to create a user interface + * for configuring this instance prior to starting the game. If no + * configuration is necessary, this method should return null. + */ + public function createConfigurator () :GameConfigurator + + /** + * Creates a table configurator for initializing 'table' properties + * of the game. The default implementation returns null. + */ + public function createTableConfigurator () :TableConfigurator + { + return null; + } + + /** + * Returns a translatable label describing this game. + */ + public function getGameName () :String + { + // the whole getRatingTypeId(), getGameName(), getBundleName() + // business should be cleaned up. we should have getGameIdent() + // and everything should have a default implementation using that + return "m." + getBundleName(); + } + + /** + * Returns the game rating type, if the system uses such things. + */ + public function getRatingTypeId () :int + { + return -1; + } + + /** + * Returns an Array of strings that describe the configuration of this + * game. Default implementation returns an empty array. + */ + public function getDescription () :Array + { + return new Array(); // nothing by default + } + + /** + * Returns true if this game config object is equal to the supplied + * object (meaning it is also a game config object and its + * configuration settings are the same as ours). + */ + public function equals (other :Object) :Boolean + { + // make sure they're of the same class + if (ClassUtil.getClassName(other) == ClassUtil.getClassName(this)) { + var that :GameConfig = GameConfig(other); + return this.rated == that.rated; + + } else { + return false; + } + } + + /** + * Computes a hashcode for this game config object that supports our + * {@link #equals} implementation. Objects that are equal should have + * the same hashcode. + */ + public function hashCode () :int + { + // look ma, it's so sophisticated! + return StringUtil.hashCode(ClassUtil.getClassName(this)) + + (rated ? 1 : 0); + } + + // from Cloneable + public function clone () :Object + { + var copy :GameConfig = ClassUtil.newInstance(this); + copy.players = this.players; + copy.rated = this.rated; + copy.ais = this.ais; + + return copy; + } +} +} diff --git a/src/as/com/threerings/parlor/game/data/GameMarshaller.as b/src/as/com/threerings/parlor/game/data/GameMarshaller.as new file mode 100644 index 00000000..cb125bcf --- /dev/null +++ b/src/as/com/threerings/parlor/game/data/GameMarshaller.as @@ -0,0 +1,49 @@ +// +// $Id: GameMarshaller.java 4145 2006-05-24 01:24:24Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.game.data { + +import com.threerings.parlor.game.client.GameService; +import com.threerings.presents.client.Client; +import com.threerings.presents.data.InvocationMarshaller; +import com.threerings.presents.dobj.InvocationResponseEvent; + +/** + * Provides the implementation of the {@link GameService} interface + * that marshalls the arguments and delivers the request to the provider + * on the server. Also provides an implementation of the response listener + * interfaces that marshall the response arguments and deliver them back + * to the requesting client. + */ +public class GameMarshaller extends InvocationMarshaller + implements GameService +{ + /** The method id used to dispatch {@link #playerReady} requests. */ + public static const PLAYER_READY :int = 1; + + // documentation inherited from interface + public function playerReady (arg1 :Client) :void + { + sendRequest(arg1, PLAYER_READY, [ ]); + } + +} +} diff --git a/src/as/com/threerings/parlor/game/data/GameObject.as b/src/as/com/threerings/parlor/game/data/GameObject.as new file mode 100644 index 00000000..bf04f9dd --- /dev/null +++ b/src/as/com/threerings/parlor/game/data/GameObject.as @@ -0,0 +1,442 @@ +// +// $Id: GameObject.java 4191 2006-06-13 22:42:20Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.game.data { + +import com.threerings.util.Integer; +import com.threerings.util.langBoolean; +import com.threerings.util.Name; + +import com.threerings.io.TypedArray; + +import com.threerings.crowd.data.PlaceObject; + +/** + * A game object hosts the shared data associated with a game played by + * one or more players. The game object extends the place object so that + * the game can act as a place where players actually go when playing the + * game. Only very basic information is maintained in the base game + * object. It serves as the base for a hierarchy of game object + * derivatives that handle basic gameplay for a suite of different game + * types (ie. turn based games, party games, board games, card games, + * etc.). + */ +public class GameObject extends PlaceObject +{ + // AUTO-GENERATED: FIELDS START + /** The field name of the gameService field. */ + public static const GAME_SERVICE :String = "gameService"; + + /** The field name of the state field. */ + public static const STATE :String = "state"; + + /** The field name of the isRated field. */ + public static const IS_RATED :String = "isRated"; + + /** The field name of the isPrivate field. */ + public static const IS_PRIVATE :String = "isPrivate"; + + /** The field name of the players field. */ + public static const PLAYERS :String = "players"; + + /** The field name of the winners field. */ + public static const WINNERS :String = "winners"; + + /** The field name of the roundId field. */ + public static const ROUND_ID :String = "roundId"; + + /** The field name of the playerStatus field. */ + public static const PLAYER_STATUS :String = "playerStatus"; + // AUTO-GENERATED: FIELDS END + + /** 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 const PRE_GAME :int = 0; + + /** A game state constant indicating that the game is in play. */ + public static const IN_PLAY :int = 1; + + /** A game state constant indicating that the game ended normally. */ + public static const GAME_OVER :int = 2; + + /** A game state constant indicating that the game was cancelled. */ + public static const CANCELLED :int = 3; + + /** The player status constant for a player whose game is in play. */ + public static const PLAYER_IN_PLAY :int = 0; + + /** The player status constant for a player whose has been knocked out + * of the game. NOTE: This can include a player choosing to leave a + * game prematurely. */ + public static const PLAYER_LEFT_GAME :int = 1; + + /** Provides general game invocation services. */ + public var gameService :GameMarshaller; + + /** The game state, one of {@link #PRE_GAME}, {@link #IN_PLAY}, + * {@link #GAME_OVER}, or {@link #CANCELLED}. */ + public var state :int = PRE_GAME; + + /** Indicates whether or not this game is rated. */ + public var isRated :Boolean; + + /** Indicates whether the game is "private". */ + public var isPrivate :Boolean; + + /** The usernames of the players involved in this game. */ + public var players :TypedArray; /* of Name */ + + /** Whether each player in the game is a winner, or null + * if the game is not yet over. */ + public var winners :TypedArray; /* of Boolean */ + + /** The unique round identifier for the current round. */ + public var roundId :int; + + /** If null, indicates that all present players are active, or for + * more complex games can be non-null to indicate the current status + * of each player in the game. The status value is one of + * {@link #PLAYER_LEFT_GAME} or {@link #PLAYER_IN_PLAY}. */ + public var playerStatus :TypedArray; /* of int */ + + /** + * Returns the number of players in the game. + */ + public function getPlayerCount () :int + { + var count :int = 0; + var size :int = players.length; + for (var ii :int = 0; ii < size; ii++) { + if (players[ii] != null) { + count++; + } + } + return count; + } + + /** + * Returns the number of active players in the game. + */ + public function getActivePlayerCount () :int + { + var count :int = 0; + var size :int = players.length; + for (var ii :int = 0; ii < size; ii++) { + if (isActivePlayer(ii)) { + count++; + } + } + return count; + } + + /** + * Returns whether the given player is still an active player in + * the game. (Ie. whether or not they are still participating.) + */ + public function isActivePlayer (pidx :int) :Boolean + { + return isOccupiedPlayer(pidx) && + (playerStatus == null || isActivePlayerStatus(playerStatus[pidx])); + } + + /** + * Returns the player index of the given user in the game, or + * -1 if the player is not involved in the game. + */ + public function getPlayerIndex (username :Name) :int + { + return ArrayUtil.indexOf(players, username); + } + + /** + * Returns whether the game is in play. A game that is not in play + * could either be awaiting players, ended, or cancelled. + */ + public function isInPlay () :Boolean + { + return (state == IN_PLAY); + } + + /** + * Returns whether the given player index in the game is occupied. + */ + public function isOccupiedPlayer (pidx :int) :Boolean + { + return (pidx >= 0 && pidx < players.length) && (players[pidx] != null); + } + + /** + * Returns whether the given player index is a winner, or false if the + * winners are not yet assigned. + */ + public function isWinner (pidx :int) :Boolean + { + return (winners != null) && winners[pidx]; + } + + /** + * Returns the number of winners for this game, or 0 if + * the winners array is not populated, e.g., the game is not yet over. + */ + public function getWinnerCount () :int + { + int count = 0; + if (winners != null) { + for (var ii :int = 0; ii < winners.length; ii++) { + if (winners[ii]) { + count++; + } + } + } + return count; + } + + /** + * Returns true if the game is ended in a draw. + */ + public function isDraw () :Boolean + { + return getWinnerCount() == getPlayerCount(); + } + + /** + * Returns the winner index of the first winning player for this game, + * or -1 if there are no winners or the winners array is + * not yet assigned. This is only likely to be useful for games that + * are known to have a single winner. + */ + public function getWinnerIndex () :int + { + return ArrayUtil.indexOf(winners, true); + } + + /** + * Returns the type of party game being played or NOT_PARTY_GAME. + * {@link PartyGameConfig}. + */ + public function getPartyGameType () :int + { + return PartyGameConfig.NOT_PARTY_GAME; + } + + /** + * Used by {@link #isActivePlayer} to determine if the supplied status is + * associated with an active player (one that has not resigned from the + * game and/or left the game room). + */ + protected function isActivePlayerStatus (playerStatus :int) :Boolean + { + return playerStatus == PLAYER_IN_PLAY; + } + + override protected function whichBuf (buf :StringBuilder) :void + { + super.whichBuf(buf); + buf.append(players.join()); + buf.append(":").append(state); + } + + // AUTO-GENERATED: METHODS START + /** + * Requests that the gameService 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 function setGameService (value :GameMarshaller) :void + { + var ovalue :GameMarshaller = this.gameService; + requestAttributeChange( + GAME_SERVICE, value, ovalue); + this.gameService = value; + } + + /** + * Requests that the state 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 function setState (value :int) :void + { + var ovalue :int = this.state; + requestAttributeChange( + STATE, Integer.valueOf(value), Integer.valueOf(ovalue)); + this.state = value; + } + + /** + * Requests that the isRated 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 function setIsRated (value :Boolean) :void + { + var ovalue :Boolean = this.isRated; + requestAttributeChange( + IS_RATED, langBoolean.valueOf(value), langBoolean.valueOf(ovalue)); + this.isRated = value; + } + + /** + * Requests that the isPrivate 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 function setIsPrivate (value :Boolean) :void + { + var ovalue :Boolean = this.isPrivate; + requestAttributeChange( + IS_PRIVATE, langBoolean.valueOf(value), + langBoolean.valueOf(ovalue)); + this.isPrivate = value; + } + + /** + * Requests that the players 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 function setPlayers (value :TypedArray) :void + { + var ovalue :TypedArray = this.players; + requestAttributeChange( + PLAYERS, value, ovalue); + this.players = (value == null) ? null : (value.clone() as TypedArray); + } + + /** + * Requests that the indexth element of + * players 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 function setPlayersAt (value :Name, index :int) :void + { + var ovalue :Name = (this.players[index] as Name); + requestElementUpdate( + PLAYERS, index, value, ovalue); + this.players[index] = value; + } + + /** + * Requests that the winners 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 function setWinners (value :TypedArray) :void + { + var ovalue :TypedArray = this.winners; + requestAttributeChange( + WINNERS, value, ovalue); + this.winners = (value == null) ? null : (value.clone() as TypedArray); + } + + /** + * Requests that the indexth element of + * winners 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 function setWinnersAt (value :Boolean, index :int) :void + { + var ovalue :Boolean = (this.winners[index] as Boolean); + requestElementUpdate( + WINNERS, index, langBoolean.valueOf(value), + langBoolean.valueOf(ovalue)); + this.winners[index] = value; + } + + /** + * Requests that the roundId 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 function setRoundId (value :int) :void + { + var ovalue :int = this.roundId; + requestAttributeChange( + ROUND_ID, Integer.valueOf(value), Integer.valueOf(ovalue)); + this.roundId = value; + } + + /** + * Requests that the playerStatus 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 function setPlayerStatus (value :TypedArray) :void + { + var ovalue :TypedArray = this.playerStatus; + requestAttributeChange( + PLAYER_STATUS, value, ovalue); + this.playerStatus = (value == null) ? null + : (value.clone() as TypedArray); + } + + /** + * Requests that the indexth element of + * playerStatus 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 function setPlayerStatusAt (value :int, index :int) :void + { + var ovalue :int = (this.playerStatus[index] as int); + requestElementUpdate( + PLAYER_STATUS, index, Integer.valueOf(value), + Integer.valueOf(ovalue)); + this.playerStatus[index] = value; + } + // AUTO-GENERATED: METHODS END +} +} diff --git a/src/as/com/threerings/parlor/game/data/PartyGameConfig.as b/src/as/com/threerings/parlor/game/data/PartyGameConfig.as new file mode 100644 index 00000000..f678d3a3 --- /dev/null +++ b/src/as/com/threerings/parlor/game/data/PartyGameConfig.as @@ -0,0 +1,51 @@ +// +// $Id: PartyGameConfig.java 3804 2006-01-13 01:52:36Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.game.data { + +/** + * Provides additional information for party games. + * A party game is a game in which the players are not set prior to starting, + * but players can come and go at will during the normal progression of + * the game. + */ +public interface PartyGameConfig +{ + /** Party game constant indicating that this game, while it does + * implement PartyGameConfig, is not currently being played in party + * mode. */ + public static const NOT_PARTY_GAME :int = 0; + + /** Party game constant indicating that we're in a party game in which + * players must sit at an available seat to play, otherwise they're + * an observer. */ + public static const SEATED_PARTY_GAME :int = 1; + + /** Party game constant indicating that everyone in the game place is + * a "player", meaning that they do not need to claim a seat to play. */ + public static const FREE_FOR_ALL_PARTY_GAME :int = 2; + + /** + * Get the type of party game being played. + */ + function getPartyGameType () :int; +} +} diff --git a/src/as/com/threerings/parlor/util/ParlorContext.as b/src/as/com/threerings/parlor/util/ParlorContext.as new file mode 100644 index 00000000..4f4fb5e4 --- /dev/null +++ b/src/as/com/threerings/parlor/util/ParlorContext.as @@ -0,0 +1,38 @@ +// +// $Id: ParlorContext.java 4191 2006-06-13 22:42:20Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.parlor.util { + +import com.threerings.crowd.util.CrowdContext; +import com.threerings.parlor.client.ParlorDirector; + +/** + * The parlor context provides access to the various managers, etc. that + * are needed by the parlor client code. + */ +public interface ParlorContext extends CrowdContext +{ + /** + * Returns a reference to the parlor director. + */ + function getParlorDirector () :ParlorDirector; +} +}