Moved the serverless game stuff out of msoy and back into the vilya library.
We'll be using this in game gardens, at least. Note that the actionscript side currently doesn't compile because of limitations in building a .swc file, but that'll be fixed soon. git-svn-id: svn+ssh://src.earth.threerings.net/vilya/trunk@47 c613c5cb-e716-0410-b11b-feb51c14d237
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
package com.threerings.ezgame {
|
||||
|
||||
/**
|
||||
* A simple card deck that encodes cards as a string like "Ac" for the
|
||||
* ace of clubs, or "Td" for the 10 of diamonds.
|
||||
*/
|
||||
public class CardDeck
|
||||
{
|
||||
public function CardDeck (gameObj :GameObject, deckName :String = "deck")
|
||||
{
|
||||
_gameObj = gameObj;
|
||||
_deckName = deckName;
|
||||
|
||||
var deck :Array = new Array();
|
||||
for each (var rank :String in ["2", "3", "4", "5", "6", "7", "8",
|
||||
"9", "T", "J", "Q", "K", "A"]) {
|
||||
for each (var suit :String in ["c", "d", "h", "s"]) {
|
||||
deck.push(rank + suit);
|
||||
}
|
||||
}
|
||||
|
||||
_gameObj.setCollection(_deckName, deck);
|
||||
}
|
||||
|
||||
public function dealToPlayer (
|
||||
playerIdx :int, count :int, msgName :String) :void
|
||||
{
|
||||
// TODO: support the callback
|
||||
_gameObj.dealFromCollection(_deckName, count, msgName, null, playerIdx);
|
||||
}
|
||||
|
||||
public function dealToData (count :int, propName :String) :void
|
||||
{
|
||||
_gameObj.dealFromCollection(_deckName, count, propName, null);
|
||||
}
|
||||
|
||||
/** The game object. */
|
||||
protected var _gameObj :GameObject;
|
||||
|
||||
/** The name of our deck. */
|
||||
protected var _deckName :String;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.threerings.ezgame {
|
||||
|
||||
/**
|
||||
* This interface should be implemented by some class that lives on the
|
||||
* display list. When a DisplayObject that implements this interface
|
||||
* is added, it will be notified of the GameObject.
|
||||
*/
|
||||
public interface Game
|
||||
{
|
||||
/**
|
||||
* Called by the mysterious powers of the cosmos to initialize
|
||||
* your game with the GameObject.
|
||||
*/
|
||||
function setGameObject (gameObj :GameObject) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.threerings.ezgame {
|
||||
|
||||
import flash.events.IEventDispatcher;
|
||||
|
||||
/**
|
||||
* The game object that you'll be using to manage your game.
|
||||
*/
|
||||
public interface GameObject
|
||||
extends IEventDispatcher
|
||||
{
|
||||
/**
|
||||
* Data accessor.
|
||||
*/
|
||||
function get data () :Object;
|
||||
|
||||
/**
|
||||
* Get a property from data.
|
||||
*/
|
||||
function get (propName :String, index :int = -1) :Object;
|
||||
|
||||
/**
|
||||
* Set a property that will be distributed.
|
||||
*/
|
||||
function set (propName :String, value :Object, index :int = -1) :void;
|
||||
|
||||
/**
|
||||
* Set the specified collection to contain the specified values,
|
||||
* clearing any previous values.
|
||||
*/
|
||||
function setCollection (collName :String, values :Array) :void;
|
||||
|
||||
/**
|
||||
* Add to an existing collection. If it doesn't exist, it will
|
||||
* be created. The new values will be inserted randomly into the
|
||||
* collection.
|
||||
*/
|
||||
function addToCollection (collName :String, values :Array) :void;
|
||||
|
||||
/**
|
||||
* Pick (do not remove) the specified number of elements from a collection,
|
||||
* and distribute them to a specific player or set them as a property
|
||||
* in the game data.
|
||||
*/
|
||||
// TODO: a way to specify exclusive picks vs. duplicate-OK picks?
|
||||
function pickFromCollection (
|
||||
collName :String, count :int, msgOrPropName :String,
|
||||
playerIndex :int = -1) :void;
|
||||
|
||||
/**
|
||||
* Deal (remove) the specified number of elements from a collection,
|
||||
* and distribute them to a specific player or set them as a property
|
||||
* in the game data.
|
||||
*/
|
||||
// TODO: figure out the method signature of the callback
|
||||
function dealFromCollection (
|
||||
collName :String, count :int, msgOrPropName :String,
|
||||
callBack :Function = null, playerIndex :int = -1) :void;
|
||||
|
||||
/**
|
||||
* Merge the specified collection into the other collection.
|
||||
* The source collection will be destroyed. The elements from
|
||||
* The source collection will be shuffled and appended to the end
|
||||
* of the destination collection.
|
||||
*/
|
||||
function mergeCollection (srcColl :String, intoColl :String) :void;
|
||||
|
||||
/**
|
||||
* Send a "message" to other clients subscribed to the game.
|
||||
* These is similar to setting a property, except that the
|
||||
* value will not be saved- it will merely end up coming out
|
||||
* as a MessageReceivedEvent.
|
||||
*
|
||||
* @param playerIndex if -1, sends to all players, otherwise
|
||||
* the message will be private to just one player
|
||||
*/
|
||||
function sendMessage (
|
||||
messageName :String, value :Object, playerIndex :int = -1) :void;
|
||||
|
||||
/**
|
||||
* Send a message that will be heard by everyone in the game room,
|
||||
* even observers.
|
||||
*/
|
||||
function sendChat (msg :String) :void;
|
||||
|
||||
/**
|
||||
* Display the specified message immediately locally: not sent
|
||||
* to any other players or observers in the game room.
|
||||
*/
|
||||
function localChat (msg :String) :void;
|
||||
|
||||
/**
|
||||
* Get the player names, as an array.
|
||||
*/
|
||||
function getPlayerNames () :Array /* of String */;
|
||||
|
||||
/**
|
||||
* Get the index into the player names array of the current player,
|
||||
* or -1 if the user is not a player.
|
||||
*/
|
||||
function getMyIndex () :int;
|
||||
|
||||
/**
|
||||
* Get the turn holder's index, or -1 if it's nobody's turn.
|
||||
*/
|
||||
function getTurnHolderIndex () :int;
|
||||
|
||||
/**
|
||||
* Get the indexes of the winners
|
||||
*/
|
||||
function getWinnerIndexes () :Array /* of int */;
|
||||
|
||||
/**
|
||||
* A convenience method to just check if it's our turn.
|
||||
*/
|
||||
function isMyTurn () :Boolean;
|
||||
|
||||
/**
|
||||
* Is the game currently in play?
|
||||
*/
|
||||
function isInPlay () :Boolean;
|
||||
|
||||
/**
|
||||
* End the current turn. If no next player index is specified,
|
||||
* then the next player after the current one is used.
|
||||
*/
|
||||
function endTurn (optionalNextPlayerIndex :int = -1) :void;
|
||||
|
||||
/**
|
||||
* End the game. The specified player indexes are winners!
|
||||
*/
|
||||
function endGame (winnerIndex :int, ... rest) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.threerings.ezgame {
|
||||
|
||||
import flash.events.Event;
|
||||
|
||||
public class MessageReceivedEvent extends Event
|
||||
{
|
||||
/** The type of all MessageReceivedEvents. */
|
||||
public static const TYPE :String = "msgReceived";
|
||||
|
||||
/**
|
||||
* Access the message name.
|
||||
*/
|
||||
public function get name () :String
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the message value.
|
||||
*/
|
||||
public function get value () :Object
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
public function MessageReceivedEvent (messageName :String, value :Object)
|
||||
{
|
||||
super(TYPE);
|
||||
_name = messageName;
|
||||
_value = value;
|
||||
}
|
||||
|
||||
override public function toString () :String
|
||||
{
|
||||
return "[MessageReceivedEvent name=" + _name +
|
||||
", value=" + _value + "]";
|
||||
}
|
||||
|
||||
override public function clone () :Event
|
||||
{
|
||||
return new MessageReceivedEvent(_name, _value);
|
||||
}
|
||||
|
||||
protected var _name :String;
|
||||
protected var _value :Object;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.threerings.ezgame {
|
||||
|
||||
import flash.display.Sprite;
|
||||
|
||||
import flash.text.StyleSheet;
|
||||
import flash.text.TextField;
|
||||
import flash.text.TextFieldAutoSize;
|
||||
|
||||
/**
|
||||
* A sample component that displays the players of a game.
|
||||
* If the game has a turn holder, the current turn holder will be highlighted.
|
||||
*
|
||||
* This class demonstrates that the 'Game' interface may be implemented
|
||||
* by any DisplayObject that want access to the GameObject, not just the
|
||||
* actual DisplayObject that is displaying the game. Here, all we are
|
||||
* interested in is the names of the players and the current turn holder.
|
||||
*
|
||||
* You may use this, with any modifications you desire, in your game. Feel
|
||||
* free to copy/modify or extend this class.
|
||||
*/
|
||||
public class PlayersDisplay extends Sprite
|
||||
implements Game
|
||||
{
|
||||
// implementation of Game method
|
||||
public function setGameObject (gameObj :GameObject) :void
|
||||
{
|
||||
_gameObj = gameObj;
|
||||
_gameObj.addEventListener(StateChangedEvent.TURN_CHANGED, recheckTurn);
|
||||
_gameObj.addEventListener(StateChangedEvent.GAME_ENDED, recheckTurn);
|
||||
|
||||
setupPlayers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the player labels and configure the look of the entire UI.
|
||||
*/
|
||||
protected function setupPlayers () :void
|
||||
{
|
||||
var y :Number = BORDER;
|
||||
// create a label at the top, above the player names
|
||||
var label :TextField = new TextField();
|
||||
// damn stylesheet doesn't seem to actually -work-
|
||||
// var style :StyleSheet = new StyleSheet();
|
||||
// style.fontWeight = "bold";
|
||||
// style.color = "#0000FF";
|
||||
// style.fontFamily = "serif";
|
||||
// style.fontSize = 18;
|
||||
// label.styleSheet = style;
|
||||
label.autoSize = TextFieldAutoSize.LEFT;
|
||||
label.selectable = false;
|
||||
label.text = "Players";
|
||||
label.x = BORDER;
|
||||
label.y = y;
|
||||
addChild(label);
|
||||
y += label.textHeight;
|
||||
|
||||
var maxWidth :Number = label.textWidth;
|
||||
|
||||
// create a label for each player
|
||||
for each (var name :String in _gameObj.getPlayerNames()) {
|
||||
y += PAD;
|
||||
label = new TextField();
|
||||
label.autoSize = TextFieldAutoSize.LEFT;
|
||||
label.background = true;
|
||||
label.selectable = false;
|
||||
label.text = name;
|
||||
label.x = BORDER;
|
||||
label.y = y;
|
||||
addChild(label);
|
||||
y += label.textHeight;
|
||||
maxWidth = Math.max(maxWidth, label.textWidth);
|
||||
|
||||
_playerLabels.push(label);
|
||||
}
|
||||
|
||||
// make all the player labels the same width
|
||||
// (looks nice when highlighted)
|
||||
for each (label in _playerLabels) {
|
||||
label.autoSize = TextFieldAutoSize.NONE;
|
||||
label.width = maxWidth;
|
||||
}
|
||||
|
||||
// draw a blue rectangle around everything
|
||||
graphics.clear();
|
||||
graphics.lineStyle(1, 0x0000FF);
|
||||
graphics.drawRect(0, 0, maxWidth + (BORDER * 2), y + BORDER);
|
||||
|
||||
displayCurrentTurn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-set the background color for every player label, highlighting
|
||||
* only the player who has the turn.
|
||||
*/
|
||||
protected function displayCurrentTurn () :void
|
||||
{
|
||||
var idx :int = _gameObj.isInPlay() ? _gameObj.getTurnHolderIndex() : -1;
|
||||
for (var ii :int = 0; ii < _playerLabels.length; ii++) {
|
||||
var label :TextField = (_playerLabels[ii] as TextField);
|
||||
label.backgroundColor = (ii == idx) ? TURN_BACKGROUND
|
||||
: NORMAL_BACKGROUND;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registered to receive TURN_CHANGED and GAME_ENDED events
|
||||
* from the GameObject.
|
||||
*/
|
||||
protected function recheckTurn (event :StateChangedEvent) :void
|
||||
{
|
||||
displayCurrentTurn();
|
||||
}
|
||||
|
||||
/** Our game object. */
|
||||
protected var _gameObj :GameObject;
|
||||
|
||||
/** An array of labels, one for each player name. */
|
||||
protected var _playerLabels :Array = [];
|
||||
|
||||
/** The background color for players that doesn't have the turn. */
|
||||
protected static const NORMAL_BACKGROUND :uint = 0xFFFFFF;
|
||||
|
||||
/** The background color for players that do have the turn. */
|
||||
protected static const TURN_BACKGROUND :uint = 0xFF9999;
|
||||
|
||||
/** The number of pixels to border around all the names. */
|
||||
protected static const BORDER :int = 6;
|
||||
|
||||
/** The additional padding inserted between names. */
|
||||
protected static const PAD :int = 2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.threerings.ezgame {
|
||||
|
||||
import flash.events.Event;
|
||||
|
||||
/**
|
||||
* Property change events are dispatched after the property change was
|
||||
* validated on the server.
|
||||
*/
|
||||
public class PropertyChangedEvent extends Event
|
||||
{
|
||||
/** The type of a property change event. */
|
||||
public static const TYPE :String = "PropChanged";
|
||||
|
||||
/**
|
||||
* Get the name of the property that changed.
|
||||
*/
|
||||
public function get name () :String
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the property's new value.
|
||||
*/
|
||||
public function get newValue () :Object
|
||||
{
|
||||
return _newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the property's previous value (handy!).
|
||||
*/
|
||||
public function get oldValue () :Object
|
||||
{
|
||||
return _oldValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* If an array element was updated, get the index, or -1 if not applicable.
|
||||
*/
|
||||
public function get index () :int
|
||||
{
|
||||
return _index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function PropertyChangedEvent (
|
||||
propName :String, newValue :Object, oldValue :Object, index :int = -1)
|
||||
{
|
||||
super(TYPE);
|
||||
_name = propName;
|
||||
_newValue = newValue;
|
||||
_oldValue = oldValue;
|
||||
_index = index;
|
||||
}
|
||||
|
||||
override public function toString () :String
|
||||
{
|
||||
return "[PropertyChangedEvent name=" + _name + ", value=" + _newValue +
|
||||
((index < 0) ? "" : (", index=" + index)) + "]";
|
||||
}
|
||||
|
||||
override public function clone () :Event
|
||||
{
|
||||
return new PropertyChangedEvent(_name, _newValue, _oldValue, _index);
|
||||
}
|
||||
|
||||
/** Our implementation details. */
|
||||
protected var _name :String;
|
||||
protected var _newValue :Object;
|
||||
protected var _oldValue :Object;
|
||||
protected var _index :int;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.threerings.ezgame {
|
||||
|
||||
import flash.events.Event;
|
||||
|
||||
/**
|
||||
* Dispatched when the state of the game has changed.
|
||||
*/
|
||||
public class StateChangedEvent extends Event
|
||||
{
|
||||
/** Indicates that the game has transitioned to a started state. */
|
||||
public static const GAME_STARTED :String = "GameStarted";
|
||||
|
||||
/** Indicates that the game has transitioned to a ended state. */
|
||||
public static const GAME_ENDED :String = "GameEnded";
|
||||
|
||||
/** Indicates that the turn has changed. */
|
||||
// TODO: move to own event?
|
||||
public static const TURN_CHANGED :String = "TurnChanged";
|
||||
|
||||
public function StateChangedEvent (type :String)
|
||||
{
|
||||
super(type);
|
||||
}
|
||||
|
||||
override public function toString () :String
|
||||
{
|
||||
return "[StateChangedEvent type=" + type + "]";
|
||||
}
|
||||
|
||||
override public function clone () :Event
|
||||
{
|
||||
return new StateChangedEvent(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.threerings.ezgame.client {
|
||||
|
||||
import flash.events.Event;
|
||||
|
||||
import flash.utils.ByteArray;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.dobj.MessageAdapter;
|
||||
import com.threerings.presents.dobj.MessageListener;
|
||||
import com.threerings.presents.dobj.MessageEvent;
|
||||
|
||||
import com.threerings.crowd.client.PlaceView;
|
||||
import com.threerings.crowd.data.PlaceConfig;
|
||||
import com.threerings.crowd.data.PlaceObject;
|
||||
import com.threerings.crowd.util.CrowdContext;
|
||||
|
||||
import com.threerings.parlor.game.client.GameController;
|
||||
|
||||
import com.threerings.parlor.turn.client.TurnGameController;
|
||||
import com.threerings.parlor.turn.client.TurnGameControllerDelegate;
|
||||
|
||||
import com.threerings.ezgame.data.EZGameObject;
|
||||
import com.threerings.ezgame.data.PropertySetEvent;
|
||||
import com.threerings.ezgame.data.PropertySetListener;
|
||||
import com.threerings.ezgame.util.EZObjectMarshaller;
|
||||
|
||||
import com.threerings.ezgame.MessageReceivedEvent;
|
||||
import com.threerings.ezgame.PropertyChangedEvent;
|
||||
import com.threerings.ezgame.StateChangedEvent;
|
||||
|
||||
/**
|
||||
* A controller for flash games.
|
||||
*/
|
||||
public class EZGameController extends GameController
|
||||
implements TurnGameController, PropertySetListener, MessageListener
|
||||
{
|
||||
/** The implementation of the GameObject interface for users. */
|
||||
public var gameObjImpl :GameObjectImpl;
|
||||
|
||||
/**
|
||||
*/
|
||||
public function EZGameController ()
|
||||
{
|
||||
addDelegate(_turnDelegate = new TurnGameControllerDelegate(this));
|
||||
}
|
||||
|
||||
override public function willEnterPlace (plobj :PlaceObject) :void
|
||||
{
|
||||
_ezObj = (plobj as EZGameObject);
|
||||
gameObjImpl = new GameObjectImpl(_ctx, _ezObj);
|
||||
|
||||
_ctx.getClient().getClientObject().addListener(_userListener);
|
||||
|
||||
super.willEnterPlace(plobj);
|
||||
}
|
||||
|
||||
override public function didLeavePlace (plobj :PlaceObject) :void
|
||||
{
|
||||
super.didLeavePlace(plobj);
|
||||
|
||||
_ctx.getClient().getClientObject().removeListener(_userListener);
|
||||
|
||||
_ezObj = null;
|
||||
}
|
||||
|
||||
// from TurnGameController
|
||||
public function turnDidChange (turnHolder :Name) :void
|
||||
{
|
||||
dispatchUserEvent(
|
||||
new StateChangedEvent(StateChangedEvent.TURN_CHANGED));
|
||||
}
|
||||
|
||||
// from PropertySetListener
|
||||
public function propertyWasSet (event :PropertySetEvent) :void
|
||||
{
|
||||
// notify the user game
|
||||
dispatchUserEvent(new PropertyChangedEvent(
|
||||
event.getName(), event.getValue(), event.getOldValue(),
|
||||
event.getIndex()));
|
||||
}
|
||||
|
||||
// from MessageListener
|
||||
public function messageReceived (event :MessageEvent) :void
|
||||
{
|
||||
var name :String = event.getName();
|
||||
if (EZGameObject.USER_MESSAGE == name) {
|
||||
dispatchUserMessage(event.getArgs());
|
||||
|
||||
} else if (EZGameObject.GAME_CHAT == name) {
|
||||
// this is chat send by the game, let's route it like
|
||||
// localChat, which is also sent by the game
|
||||
gameObjImpl.localChat(String(event.getArgs()[0]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by our user listener when we receive a message event
|
||||
* on the user object.
|
||||
*/
|
||||
protected function messageReceivedOnUserObject (event :MessageEvent) :void
|
||||
{
|
||||
// see if it's a message about user games
|
||||
var msgName :String =
|
||||
EZGameObject.USER_MESSAGE + ":" + _ezObj.getOid();
|
||||
if (msgName == event.getName()) {
|
||||
dispatchUserMessage(event.getArgs());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the user message.
|
||||
*/
|
||||
protected function dispatchUserMessage (args :Array) :void
|
||||
{
|
||||
dispatchUserEvent(new MessageReceivedEvent(
|
||||
(args[0] as String),
|
||||
EZObjectMarshaller.decode(args[1])));
|
||||
}
|
||||
|
||||
override protected function createPlaceView (ctx :CrowdContext) :PlaceView
|
||||
{
|
||||
return new EZGamePanel(ctx, this);
|
||||
}
|
||||
|
||||
override protected function gameDidStart () :void
|
||||
{
|
||||
super.gameDidStart();
|
||||
dispatchUserEvent(
|
||||
new StateChangedEvent(StateChangedEvent.GAME_STARTED));
|
||||
}
|
||||
|
||||
override protected function gameDidEnd () :void
|
||||
{
|
||||
super.gameDidEnd();
|
||||
dispatchUserEvent(
|
||||
new StateChangedEvent(StateChangedEvent.GAME_ENDED));
|
||||
}
|
||||
|
||||
protected function dispatchUserEvent (event :Event) :void
|
||||
{
|
||||
gameObjImpl.dispatch(event);
|
||||
}
|
||||
|
||||
protected var _ezObj :EZGameObject;
|
||||
|
||||
protected var _turnDelegate :TurnGameControllerDelegate;
|
||||
|
||||
protected var _userListener :MessageAdapter =
|
||||
new MessageAdapter(messageReceivedOnUserObject);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.threerings.ezgame.client {
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.display.DisplayObjectContainer;
|
||||
|
||||
import flash.events.Event;
|
||||
|
||||
import flash.utils.Dictionary;
|
||||
|
||||
import mx.containers.Canvas;
|
||||
import mx.containers.VBox;
|
||||
|
||||
import mx.core.Container;
|
||||
import mx.core.IChildList;
|
||||
|
||||
import mx.utils.DisplayUtil;
|
||||
|
||||
import com.threerings.crowd.client.PlaceView;
|
||||
import com.threerings.crowd.data.PlaceObject;
|
||||
import com.threerings.crowd.util.CrowdContext;
|
||||
|
||||
import com.threerings.ezgame.data.EZGameConfig;
|
||||
import com.threerings.ezgame.data.EZGameObject;
|
||||
|
||||
import com.threerings.ezgame.Game;
|
||||
|
||||
public class EZGamePanel extends VBox
|
||||
implements PlaceView
|
||||
{
|
||||
public function EZGamePanel (ctx :CrowdContext, ctrl :EZGameController)
|
||||
{
|
||||
_ctx = ctx;
|
||||
_ctrl = ctrl;
|
||||
|
||||
// add a listener so that we hear about all new children
|
||||
addEventListener(Event.ADDED, childAdded);
|
||||
|
||||
// TODO: figure out how we're going to add the client game
|
||||
var cfg :EZGameConfig = (ctrl.getPlaceConfig() as EZGameConfig);
|
||||
//_gameView = new MsoySprite(cfg.game);
|
||||
//addChild(_gameView);
|
||||
|
||||
// TODO: move chat component into narya?
|
||||
//addChild(new ChatTextArea(ctx));
|
||||
}
|
||||
|
||||
// from PlaceView
|
||||
public function willEnterPlace (plobj :PlaceObject) :void
|
||||
{
|
||||
// don't start notifying anything of the game until we've
|
||||
// notified the game manager that we're in the game
|
||||
// (done in GameController, and it uses callLater, so we do it twice!)
|
||||
_ctx.getClient().callLater(function () :void {
|
||||
_ctx.getClient().callLater(function () :void {
|
||||
_ezObj = (plobj as EZGameObject);
|
||||
|
||||
notifyOfGame(this);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// from PlaceView
|
||||
public function didLeavePlace (plobj :PlaceObject) :void
|
||||
{
|
||||
_ezObj = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle ADDED events.
|
||||
*/
|
||||
protected function childAdded (event :Event) :void
|
||||
{
|
||||
if (_ezObj != null) {
|
||||
notifyOfGame(event.target as DisplayObject);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find any children of the specified object that implement
|
||||
* com.metasoy.game.Game and provide them with the GameObject.
|
||||
*/
|
||||
protected function notifyOfGame (root :DisplayObject) :void
|
||||
{
|
||||
DisplayUtil.walkDisplayObjects(root,
|
||||
function (disp :DisplayObject) :void
|
||||
{
|
||||
if (disp is Game) {
|
||||
// only notify the Game if we haven't seen it before
|
||||
if (null == _seenGames[disp]) {
|
||||
(disp as Game).setGameObject(_ctrl.gameObjImpl);
|
||||
_seenGames[disp] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected var _ctx :CrowdContext;
|
||||
protected var _ctrl :EZGameController;
|
||||
|
||||
/** A weak-key hash of the Game interfaces we've already seen. */
|
||||
protected var _seenGames :Dictionary = new Dictionary(true);
|
||||
|
||||
protected var _ezObj :EZGameObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.ezgame.client {
|
||||
|
||||
import flash.utils.ByteArray;
|
||||
|
||||
import com.threerings.io.TypedArray;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
import com.threerings.presents.client.InvocationService_ConfirmListener;
|
||||
import com.threerings.presents.client.InvocationService_InvocationListener;
|
||||
|
||||
/**
|
||||
* Provides services for ez games.
|
||||
*/
|
||||
public interface EZGameService extends InvocationService
|
||||
{
|
||||
/**
|
||||
* Request to set the specified property.
|
||||
*/
|
||||
function setProperty (
|
||||
client :Client, propName :String, value :Object, index :int,
|
||||
listener :InvocationService_InvocationListener) :void;
|
||||
|
||||
/**
|
||||
* Request to end the turn, possibly futzing the next turn holder unless
|
||||
* -1 is specified for the nextPlayerIndex.
|
||||
*/
|
||||
function endTurn (
|
||||
client :Client, nextPlayerIndex :int,
|
||||
listener :InvocationService_InvocationListener) :void;
|
||||
|
||||
/**
|
||||
* Request to end the game, with the specified player indices assigned
|
||||
* as winners.
|
||||
*/
|
||||
function endGame (
|
||||
client :Client, winners :TypedArray /* of int */,
|
||||
listener :InvocationService_InvocationListener) :void;
|
||||
|
||||
/**
|
||||
* Request to send a private message to one other player in
|
||||
* the game.
|
||||
*/
|
||||
function sendMessage (
|
||||
client :Client, msgName :String, value :Object, playerIdx :int,
|
||||
listener :InvocationService_InvocationListener) :void;
|
||||
|
||||
/**
|
||||
* Add to the specified named collection.
|
||||
*
|
||||
* @param clearExisting if true, wipe the old contents.
|
||||
*/
|
||||
function addToCollection (
|
||||
client :Client, collName :String, data :TypedArray /* of ByteArray */,
|
||||
clearExisting :Boolean, listener :InvocationService_InvocationListener)
|
||||
:void;
|
||||
|
||||
/**
|
||||
* Merge the specified collection into the other.
|
||||
*/
|
||||
function mergeCollection (
|
||||
client :Client, srcColl :String, intoColl :String,
|
||||
listener :InvocationService_InvocationListener) :void;
|
||||
|
||||
/**
|
||||
* Pick or deal some number of elements from the specified collection,
|
||||
* and either set a property in the flash object, or delivery the
|
||||
* picks to the specified player index via a game message.
|
||||
*/
|
||||
function getFromCollection (
|
||||
client :Client, collName :String, consume :Boolean, count :int,
|
||||
msgOrPropName :String, playerIndex :int,
|
||||
listener :InvocationService_ConfirmListener) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.threerings.ezgame.client {
|
||||
|
||||
import flash.errors.IllegalOperationError;
|
||||
|
||||
import flash.utils.Proxy;
|
||||
|
||||
import flash.utils.flash_proxy;
|
||||
|
||||
use namespace flash_proxy;
|
||||
|
||||
public class GameData extends Proxy
|
||||
{
|
||||
public function GameData (gameObjImpl :GameObjectImpl, props :Object)
|
||||
{
|
||||
_gameObjImpl = gameObjImpl;
|
||||
_props = props;
|
||||
}
|
||||
|
||||
public function hasOwnProperty (propName :String) :Boolean
|
||||
{
|
||||
// pass-through
|
||||
return _props.hasOwnProperty(propName);
|
||||
}
|
||||
|
||||
public function propertyIsEnumerable (propName :String) :Boolean
|
||||
{
|
||||
// pass-through
|
||||
return _props.propertyIsEnumerable(propName);
|
||||
}
|
||||
|
||||
public function setPropertyIsEnumerable (
|
||||
propName :String, isEnum :Boolean = true) :void
|
||||
{
|
||||
// pass-through
|
||||
_props.setPropertyIsEnumerable(propName, isEnum);
|
||||
}
|
||||
|
||||
override flash_proxy function callProperty (propName :*, ... rest) :*
|
||||
{
|
||||
// don't allow function calls
|
||||
throw new IllegalOperationError();
|
||||
}
|
||||
|
||||
override flash_proxy function getDescendants (name :*) :*
|
||||
{
|
||||
// we don't need this XML business
|
||||
throw new IllegalOperationError();
|
||||
}
|
||||
|
||||
override flash_proxy function isAttribute (name :*) :Boolean
|
||||
{
|
||||
// we don't need this XML business
|
||||
throw new IllegalOperationError();
|
||||
}
|
||||
|
||||
override flash_proxy function getProperty (propName :*) :*
|
||||
{
|
||||
// pass-through
|
||||
return _props[propName];
|
||||
}
|
||||
|
||||
override flash_proxy function hasProperty (propName :*) :Boolean
|
||||
{
|
||||
// pass-through
|
||||
return (_props[propName] !== undefined);
|
||||
}
|
||||
|
||||
override flash_proxy function setProperty (propName :*, value :*) :void
|
||||
{
|
||||
_gameObjImpl.set(String(propName), value);
|
||||
}
|
||||
|
||||
override flash_proxy function deleteProperty (propName :*) :Boolean
|
||||
{
|
||||
var hasProp :Boolean = hasProperty(propName);
|
||||
_gameObjImpl.set(String(propName), null);
|
||||
return hasProp;
|
||||
}
|
||||
|
||||
override flash_proxy function nextNameIndex (index :int) :int
|
||||
{
|
||||
// possibly set up the property list on the first call
|
||||
if (index == 0) {
|
||||
_propertyList = [];
|
||||
for (var prop :String in _props) {
|
||||
_propertyList.push(prop);
|
||||
}
|
||||
}
|
||||
|
||||
// return a 1-based index to indicate that there is a property
|
||||
if (index < _propertyList.length) {
|
||||
return index + 1;
|
||||
|
||||
} else {
|
||||
// we're done, clear the prop list
|
||||
_propertyList = null;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
override flash_proxy function nextName (index :int) :String
|
||||
{
|
||||
// the index is 1-based, so subtract one
|
||||
return (_propertyList[index - 1] as String);
|
||||
}
|
||||
|
||||
override flash_proxy function nextValue (index :int) :*
|
||||
{
|
||||
return _props[nextName(index)];
|
||||
}
|
||||
|
||||
/** The GameObject that controls things. */
|
||||
protected var _gameObjImpl :GameObjectImpl;
|
||||
|
||||
/** The object we're proxying. */
|
||||
protected var _props :Object;
|
||||
|
||||
/** Used temporarily while iterating over our names or values. */
|
||||
protected var _propertyList :Array;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
package com.threerings.ezgame.client {
|
||||
|
||||
import flash.errors.IllegalOperationError;
|
||||
|
||||
import flash.events.Event;
|
||||
import flash.events.EventDispatcher;
|
||||
|
||||
import flash.utils.IExternalizable;
|
||||
import flash.utils.ByteArray;
|
||||
|
||||
import com.threerings.io.TypedArray;
|
||||
|
||||
import com.threerings.util.ClassUtil;
|
||||
import com.threerings.util.MessageBundle;
|
||||
import com.threerings.util.Name;
|
||||
import com.threerings.util.StringUtil;
|
||||
|
||||
import com.threerings.presents.client.ConfirmAdapter;
|
||||
import com.threerings.presents.client.InvocationService_ConfirmListener;
|
||||
|
||||
import com.threerings.crowd.data.BodyObject;
|
||||
import com.threerings.crowd.util.CrowdContext;
|
||||
|
||||
import com.threerings.ezgame.data.EZGameObject;
|
||||
import com.threerings.ezgame.data.PropertySetEvent;
|
||||
import com.threerings.ezgame.util.EZObjectMarshaller;
|
||||
|
||||
import com.threerings.ezgame.GameObject;
|
||||
import com.threerings.ezgame.PropertyChangedEvent;
|
||||
|
||||
public class GameObjectImpl extends EventDispatcher
|
||||
implements GameObject
|
||||
{
|
||||
public function GameObjectImpl (ctx :CrowdContext, ezObj :EZGameObject)
|
||||
{
|
||||
_ctx = ctx;
|
||||
_ezObj = ezObj;
|
||||
_gameData = new GameData(this, _ezObj.getUserProps());
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function get data () :Object
|
||||
{
|
||||
return _gameData;
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function get (propName :String, index :int = -1) :Object
|
||||
{
|
||||
var value :Object = data[propName];
|
||||
if (index >= 0) {
|
||||
if (value is Array) {
|
||||
return (value as Array)[index];
|
||||
|
||||
} else {
|
||||
throw new ArgumentError("Property " + propName +
|
||||
" is not an array.");
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function set (propName :String, value :Object, index :int = -1) :void
|
||||
{
|
||||
validatePropertyChange(propName, value, index);
|
||||
|
||||
var encoded :Object = EZObjectMarshaller.encode(value);
|
||||
_ezObj.ezGameService.setProperty(
|
||||
_ctx.getClient(), propName, encoded, index,
|
||||
createLoggingListener("setProperty"));
|
||||
|
||||
// set it immediately in the game object
|
||||
_ezObj.applyPropertySet(propName, value, index);
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function setCollection (collName :String, values :Array) :void
|
||||
{
|
||||
populateCollection(collName, values, true);
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function addToCollection (collName :String, values :Array) :void
|
||||
{
|
||||
populateCollection(collName, values, false);
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function pickFromCollection (
|
||||
collName :String, count :int, msgOrPropName :String,
|
||||
playerIndex :int = -1) :void
|
||||
{
|
||||
getFromCollection(collName, count, msgOrPropName, playerIndex,
|
||||
false, null);
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function dealFromCollection (
|
||||
collName :String, count :int, msgOrPropName :String,
|
||||
callback :Function = null, playerIndex :int = -1) :void
|
||||
{
|
||||
getFromCollection(collName, count, msgOrPropName, playerIndex,
|
||||
true, callback);
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function mergeCollection (srcColl :String, intoColl :String) :void
|
||||
{
|
||||
validateName(srcColl);
|
||||
validateName(intoColl);
|
||||
_ezObj.ezGameService.mergeCollection(_ctx.getClient(),
|
||||
srcColl, intoColl, createLoggingListener("mergeCollection"));
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function sendMessage (
|
||||
messageName :String, value :Object, playerIndex :int = -1) :void
|
||||
{
|
||||
validateName(messageName);
|
||||
validateValue(value);
|
||||
|
||||
var encoded :Object = EZObjectMarshaller.encode(value);
|
||||
_ezObj.ezGameService.sendMessage(_ctx.getClient(),
|
||||
messageName, encoded, playerIndex,
|
||||
createLoggingListener("sendMessage"));
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function sendChat (msg :String) :void
|
||||
{
|
||||
validateChat(msg);
|
||||
// Post a message to the game object, the controller
|
||||
// will listen and call localChat().
|
||||
_ezObj.postMessage(EZGameObject.GAME_CHAT, [ msg ]);
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function localChat (msg :String) :void
|
||||
{
|
||||
validateChat(msg);
|
||||
// The sendChat() messages will end up being routed
|
||||
// through this method on each client.
|
||||
// TODO: make this look distinct from other system chat
|
||||
_ctx.getChatDirector().displayInfo(null, MessageBundle.taint(msg));
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function getPlayerNames () :Array
|
||||
{
|
||||
var names :Array = new Array();
|
||||
for each (var name :Name in _ezObj.players) {
|
||||
names.push((name == null) ? null : name.toString());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function getMyIndex () :int
|
||||
{
|
||||
return _ezObj.getPlayerIndex(getUsername());
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function getTurnHolderIndex () :int
|
||||
{
|
||||
return _ezObj.getPlayerIndex(_ezObj.turnHolder);
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function getWinnerIndexes () :Array /* of int */
|
||||
{
|
||||
var arr :Array = new Array();
|
||||
if (_ezObj.winners != null) {
|
||||
for (var ii :int = 0; ii < _ezObj.winners.length; ii++) {
|
||||
if (_ezObj.winners[ii]) {
|
||||
arr.push(ii);
|
||||
}
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function isMyTurn () :Boolean
|
||||
{
|
||||
return getUsername().equals(_ezObj.turnHolder);
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function isInPlay () :Boolean
|
||||
{
|
||||
return _ezObj.isInPlay();
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function endTurn (nextPlayerIndex :int = -1) :void
|
||||
{
|
||||
_ezObj.ezGameService.endTurn(_ctx.getClient(), nextPlayerIndex,
|
||||
createLoggingListener("endTurn"));
|
||||
}
|
||||
|
||||
// from GameObject
|
||||
public function endGame (winnerIndex :int, ... rest) :void
|
||||
{
|
||||
var winners :TypedArray = TypedArray.create(int);
|
||||
winners.push(winnerIndex);
|
||||
while (rest.length > 0) {
|
||||
winners.push(int(rest.shift()));
|
||||
}
|
||||
_ezObj.ezGameService.endGame(_ctx.getClient(), winners,
|
||||
createLoggingListener("endGame"));
|
||||
}
|
||||
|
||||
override public function willTrigger (type :String) :Boolean
|
||||
{
|
||||
throw new IllegalOperationError();
|
||||
}
|
||||
|
||||
override public function dispatchEvent (event :Event) :Boolean
|
||||
{
|
||||
// Ideally we want to not be an IEventDispatcher so that people
|
||||
// won't try to do this on us, but if we do that, then some other
|
||||
// object will be the target during dispatch, and that's confusing.
|
||||
// It's really nice to be able to
|
||||
throw new IllegalOperationError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Secret function to dispatch property changed events.
|
||||
*/
|
||||
internal function dispatch (event :Event) :void
|
||||
{
|
||||
try {
|
||||
super.dispatchEvent(event);
|
||||
} catch (err :Error) {
|
||||
var log :Log = Log.getLog(this);
|
||||
log.warning("Error dispatching event to user game.");
|
||||
log.logStackTrace(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to get our name.
|
||||
*/
|
||||
private function getUsername () :Name
|
||||
{
|
||||
var body :BodyObject =
|
||||
(_ctx.getClient().getClientObject() as BodyObject);
|
||||
return body.getVisibleName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a listener for service requests.
|
||||
*/
|
||||
private function createLoggingListener (
|
||||
service :String) :InvocationService_ConfirmListener
|
||||
{
|
||||
return new ConfirmAdapter(function (cause :String) :void {
|
||||
Log.getLog(GameObject).warning("Service failure " +
|
||||
"[service=" + service + ", cause=" + cause + "].");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for setCollection and addToCollection.
|
||||
*/
|
||||
private function populateCollection (
|
||||
collName :String, values :Array, clearExisting :Boolean) :void
|
||||
{
|
||||
validateName(collName);
|
||||
if (values == null) {
|
||||
throw new ArgumentError("Collection values may not be null.");
|
||||
}
|
||||
validateValue(values);
|
||||
|
||||
var encodedValues :TypedArray =
|
||||
(EZObjectMarshaller.encode(values) as TypedArray);
|
||||
|
||||
_ezObj.ezGameService.addToCollection(
|
||||
_ctx.getClient(), collName, encodedValues, clearExisting,
|
||||
createLoggingListener("populateCollection"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for pickFromCollection and dealFromCollection.
|
||||
*/
|
||||
private function getFromCollection(
|
||||
collName :String, count :int, msgOrPropName :String, playerIndex :int,
|
||||
consume :Boolean, callback :Function) :void
|
||||
{
|
||||
validateName(collName);
|
||||
validateName(msgOrPropName);
|
||||
if (count < 1) {
|
||||
throw new ArgumentError("Must retrieve at least one element!");
|
||||
}
|
||||
|
||||
var listener :InvocationService_ConfirmListener;
|
||||
if (callback != null) {
|
||||
// TODO: Figure out the method sig of the callback, and what it
|
||||
// means
|
||||
var fn :Function = function (cause :String = null) :void {
|
||||
if (cause == null) {
|
||||
callback(count);
|
||||
} else {
|
||||
callback(parseInt(cause));
|
||||
}
|
||||
};
|
||||
listener = new ConfirmAdapter(fn, fn);
|
||||
|
||||
} else {
|
||||
listener = createLoggingListener("getFromCollection");
|
||||
}
|
||||
|
||||
_ezObj.ezGameService.getFromCollection(
|
||||
_ctx.getClient(), collName, consume, count, msgOrPropName,
|
||||
playerIndex, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the property name / value are valid.
|
||||
*/
|
||||
private function validatePropertyChange (
|
||||
propName :String, value :Object, index :int) :void
|
||||
{
|
||||
validateName(propName);
|
||||
|
||||
// check that we're setting an array element on an array
|
||||
if (index >= 0) {
|
||||
if (!(_gameData[propName] is Array)) {
|
||||
throw new ArgumentError("Property " + propName +
|
||||
" is not an Array.");
|
||||
}
|
||||
}
|
||||
|
||||
// validate the value too
|
||||
validateValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the specified name is valid.
|
||||
*/
|
||||
private function validateName (name :String) :void
|
||||
{
|
||||
if (name == null) {
|
||||
throw new ArgumentError(
|
||||
"Property, message, and collection names must not be null.");
|
||||
}
|
||||
}
|
||||
|
||||
private function validateChat (msg :String) :void
|
||||
{
|
||||
if (StringUtil.isBlank(msg)) {
|
||||
throw new ArgumentError(
|
||||
"Empty chat may not be displayed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the value is legal to be streamed to other clients.
|
||||
*/
|
||||
private function validateValue (value :Object) :void
|
||||
{
|
||||
if (value == null) {
|
||||
return;
|
||||
|
||||
} else if (value is IExternalizable) {
|
||||
throw new ArgumentError(
|
||||
"IExternalizable is not yet supported");
|
||||
|
||||
} else if (value is Array) {
|
||||
if (ClassUtil.getClass(value) != Array) {
|
||||
// We can't allow arrays to be serialized as IExternalizables
|
||||
// because we need to know element values (opaquely) on the
|
||||
// server. Also, we don't allow other types because we wouldn't
|
||||
// create the right class on the other side.
|
||||
throw new ArgumentError(
|
||||
"Custom array subclasses are not supported");
|
||||
}
|
||||
// then, continue on with the sub-properties check (below)
|
||||
|
||||
} else {
|
||||
var type :String = typeof(value);
|
||||
if (type == "number" || type == "string" || type == "boolean" ) {
|
||||
// kosher!
|
||||
return;
|
||||
}
|
||||
if (ClassUtil.getClass(value) != Object) {
|
||||
throw new ArgumentError(
|
||||
"Non-simple properties may not be set.");
|
||||
}
|
||||
// fall through and verify the object's sub-properties
|
||||
}
|
||||
|
||||
// check sub-properties (of arrays and objects)
|
||||
for each (var arrValue :Object in (value as Array)) {
|
||||
validateValue(arrValue);
|
||||
}
|
||||
}
|
||||
|
||||
protected var _ctx :CrowdContext;
|
||||
|
||||
protected var _ezObj :EZGameObject;
|
||||
|
||||
protected var _gameData :GameData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.ezgame.data {
|
||||
|
||||
import com.threerings.util.MessageBundle;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
|
||||
import com.threerings.crowd.client.PlaceController;
|
||||
|
||||
import com.threerings.parlor.game.client.GameConfigurator;
|
||||
import com.threerings.parlor.game.data.GameConfig;
|
||||
import com.threerings.parlor.game.data.PartyGameConfig;
|
||||
import com.threerings.parlor.game.data.PartyGameCodes;
|
||||
|
||||
import com.threerings.ezgame.client.EZGameController;
|
||||
|
||||
/**
|
||||
* A game config for a simple multiplayer ez game.
|
||||
*/
|
||||
public /* abstract */ class EZGameConfig extends GameConfig
|
||||
implements PartyGameConfig
|
||||
{
|
||||
/** A creator-submitted name of the game. */
|
||||
public var gameName :String;
|
||||
|
||||
override public function createController () :PlaceController
|
||||
{
|
||||
return new EZGameController();
|
||||
}
|
||||
|
||||
override public function getBundleName () :String
|
||||
{
|
||||
return "general";
|
||||
}
|
||||
|
||||
// TODO
|
||||
//override public function createConfigurator () :GameConfigurator
|
||||
|
||||
override public function getGameName () :String
|
||||
{
|
||||
return MessageBundle.taint(gameName);
|
||||
}
|
||||
|
||||
// from PartyGameConfig
|
||||
public function getPartyGameType () :int
|
||||
{
|
||||
// TODO
|
||||
return PartyGameCodes.NOT_PARTY_GAME;
|
||||
}
|
||||
|
||||
override public function equals (other :Object) :Boolean
|
||||
{
|
||||
if (!super.equals(other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var that :EZGameConfig = (other as EZGameConfig);
|
||||
return (this.gameName === that.gameName);
|
||||
}
|
||||
|
||||
override public function hashCode () :int
|
||||
{
|
||||
return super.hashCode(); // TODO: incorporate gamename?
|
||||
}
|
||||
|
||||
override public function writeObject (out :ObjectOutputStream) :void
|
||||
{
|
||||
super.writeObject(out);
|
||||
|
||||
out.writeField(gameName);
|
||||
}
|
||||
|
||||
override public function readObject (ins :ObjectInputStream) :void
|
||||
{
|
||||
super.readObject(ins);
|
||||
|
||||
gameName = (ins.readField(String) as String);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.ezgame.data {
|
||||
|
||||
import flash.utils.ByteArray;
|
||||
|
||||
import com.threerings.util.langBoolean;
|
||||
import com.threerings.util.Integer;
|
||||
|
||||
import com.threerings.io.TypedArray;
|
||||
|
||||
import com.threerings.ezgame.client.EZGameService;
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
import com.threerings.presents.client.InvocationService_ConfirmListener;
|
||||
import com.threerings.presents.client.InvocationService_InvocationListener;
|
||||
import com.threerings.presents.data.InvocationMarshaller;
|
||||
import com.threerings.presents.data.InvocationMarshaller_ConfirmMarshaller;
|
||||
import com.threerings.presents.data.InvocationMarshaller_ListenerMarshaller;
|
||||
import com.threerings.presents.dobj.InvocationResponseEvent;
|
||||
|
||||
/**
|
||||
* Provides the implementation of the {@link EZGameService} 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 EZGameMarshaller extends InvocationMarshaller
|
||||
implements EZGameService
|
||||
{
|
||||
/** The method id used to dispatch {@link #addToCollection} requests. */
|
||||
public static const ADD_TO_COLLECTION :int = 1;
|
||||
|
||||
// documentation inherited from interface
|
||||
public function addToCollection (arg1 :Client, arg2 :String, arg3 :TypedArray /* of ByteArray */, arg4 :Boolean, arg5 :InvocationService_InvocationListener) :void
|
||||
{
|
||||
var listener5 :InvocationMarshaller_ListenerMarshaller = new InvocationMarshaller_ListenerMarshaller();
|
||||
listener5.listener = arg5;
|
||||
sendRequest(arg1, ADD_TO_COLLECTION, [
|
||||
arg2, arg3, langBoolean.valueOf(arg4), listener5
|
||||
]);
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #endGame} requests. */
|
||||
public static const END_GAME :int = 2;
|
||||
|
||||
// documentation inherited from interface
|
||||
public function endGame (arg1 :Client, arg2 :TypedArray, arg3 :InvocationService_InvocationListener) :void
|
||||
{
|
||||
var listener3 :InvocationMarshaller_ListenerMarshaller = new InvocationMarshaller_ListenerMarshaller();
|
||||
listener3.listener = arg3;
|
||||
sendRequest(arg1, END_GAME, [
|
||||
arg2, listener3
|
||||
]);
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #endTurn} requests. */
|
||||
public static const END_TURN :int = 3;
|
||||
|
||||
// documentation inherited from interface
|
||||
public function endTurn (arg1 :Client, arg2 :int, arg3 :InvocationService_InvocationListener) :void
|
||||
{
|
||||
var listener3 :InvocationMarshaller_ListenerMarshaller = new InvocationMarshaller_ListenerMarshaller();
|
||||
listener3.listener = arg3;
|
||||
sendRequest(arg1, END_TURN, [
|
||||
Integer.valueOf(arg2), listener3
|
||||
]);
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #getFromCollection} requests. */
|
||||
public static const GET_FROM_COLLECTION :int = 4;
|
||||
|
||||
// documentation inherited from interface
|
||||
public function getFromCollection (arg1 :Client, arg2 :String, arg3 :Boolean, arg4 :int, arg5 :String, arg6 :int, arg7 :InvocationService_ConfirmListener) :void
|
||||
{
|
||||
var listener7 :InvocationMarshaller_ConfirmMarshaller = new InvocationMarshaller_ConfirmMarshaller();
|
||||
listener7.listener = arg7;
|
||||
sendRequest(arg1, GET_FROM_COLLECTION, [
|
||||
arg2, langBoolean.valueOf(arg3), Integer.valueOf(arg4), arg5, Integer.valueOf(arg6), listener7
|
||||
]);
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #mergeCollection} requests. */
|
||||
public static const MERGE_COLLECTION :int = 5;
|
||||
|
||||
// documentation inherited from interface
|
||||
public function mergeCollection (arg1 :Client, arg2 :String, arg3 :String, arg4 :InvocationService_InvocationListener) :void
|
||||
{
|
||||
var listener4 :InvocationMarshaller_ListenerMarshaller = new InvocationMarshaller_ListenerMarshaller();
|
||||
listener4.listener = arg4;
|
||||
sendRequest(arg1, MERGE_COLLECTION, [
|
||||
arg2, arg3, listener4
|
||||
]);
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #sendMessage} requests. */
|
||||
public static const SEND_MESSAGE :int = 6;
|
||||
|
||||
// documentation inherited from interface
|
||||
public function sendMessage (arg1 :Client, arg2 :String, arg3 :Object, arg4 :int, arg5 :InvocationService_InvocationListener) :void
|
||||
{
|
||||
var listener5 :InvocationMarshaller_ListenerMarshaller = new InvocationMarshaller_ListenerMarshaller();
|
||||
listener5.listener = arg5;
|
||||
sendRequest(arg1, SEND_MESSAGE, [
|
||||
arg2, arg3, Integer.valueOf(arg4), listener5
|
||||
]);
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #setProperty} requests. */
|
||||
public static const SET_PROPERTY :int = 7;
|
||||
|
||||
// documentation inherited from interface
|
||||
public function setProperty (arg1 :Client, arg2 :String, arg3 :Object, arg4 :int, arg5 :InvocationService_InvocationListener) :void
|
||||
{
|
||||
var listener5 :InvocationMarshaller_ListenerMarshaller = new InvocationMarshaller_ListenerMarshaller();
|
||||
listener5.listener = arg5;
|
||||
sendRequest(arg1, SET_PROPERTY, [
|
||||
arg2, arg3, Integer.valueOf(arg4), listener5
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package com.threerings.ezgame.data {
|
||||
|
||||
import flash.events.Event;
|
||||
|
||||
import flash.utils.ByteArray;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
import com.threerings.io.TypedArray;
|
||||
|
||||
import com.threerings.parlor.game.data.GameObject;
|
||||
import com.threerings.parlor.turn.data.TurnGameObject;
|
||||
|
||||
import com.threerings.ezgame.util.EZObjectMarshaller;
|
||||
|
||||
public class EZGameObject extends GameObject
|
||||
implements TurnGameObject
|
||||
{
|
||||
/** The identifier for a MessageEvent containing a user message. */
|
||||
public static const USER_MESSAGE :String = "Umsg";
|
||||
|
||||
/** The identifier for a MessageEvent containing game-system chat. */
|
||||
public static const GAME_CHAT :String = "Uchat";
|
||||
|
||||
// AUTO-GENERATED: FIELDS START
|
||||
/** The field name of the <code>turnHolder</code> field. */
|
||||
public static const TURN_HOLDER :String = "turnHolder";
|
||||
|
||||
/** The field name of the <code>ezGameService</code> field. */
|
||||
public static const EZ_GAME_SERVICE :String = "ezGameService";
|
||||
// AUTO-GENERATED: FIELDS END
|
||||
|
||||
/** The current turn holder. */
|
||||
public var turnHolder :Name;
|
||||
|
||||
/** The service interface for requesting special things from the server. */
|
||||
public var ezGameService :EZGameMarshaller;
|
||||
|
||||
/**
|
||||
* Access the underlying user properties.
|
||||
*/
|
||||
public function getUserProps () :Object
|
||||
{
|
||||
return _props;
|
||||
}
|
||||
|
||||
// from TurnGameObject
|
||||
public function getTurnHolderFieldName () :String
|
||||
{
|
||||
return TURN_HOLDER;
|
||||
}
|
||||
|
||||
// from TurnGameObject
|
||||
public function getTurnHolder () :Name
|
||||
{
|
||||
return turnHolder;
|
||||
}
|
||||
|
||||
// from TurnGameObject
|
||||
public function getPlayers () :TypedArray /* of Name */
|
||||
{
|
||||
return players;
|
||||
}
|
||||
|
||||
// AUTO-GENERATED: METHODS START
|
||||
/**
|
||||
* Requests that the <code>turnHolder</code> field be set to the
|
||||
* specified value. The local value will be updated immediately and an
|
||||
* event will be propagated through the system to notify all listeners
|
||||
* that the attribute did change. Proxied copies of this object (on
|
||||
* clients) will apply the value change when they received the
|
||||
* attribute changed notification.
|
||||
*/
|
||||
public function setTurnHolder (value :Name) :void
|
||||
{
|
||||
var ovalue :Name = this.turnHolder;
|
||||
requestAttributeChange(
|
||||
TURN_HOLDER, value, ovalue);
|
||||
this.turnHolder = value;
|
||||
}
|
||||
// AUTO-GENERATED: METHODS END
|
||||
|
||||
/**
|
||||
* Called by a PropertySetEvent to enact a property change.
|
||||
* @return the old value
|
||||
*/
|
||||
public function applyPropertySet (
|
||||
propName :String, value :Object, index :int) :Object
|
||||
{
|
||||
var oldValue :Object = _props[propName];
|
||||
if (index >= 0) {
|
||||
// set an array element
|
||||
var arr :Array = (oldValue as Array);
|
||||
if (arr == null) {
|
||||
arr = [];
|
||||
_props[propName] = arr;
|
||||
}
|
||||
oldValue = arr[index];
|
||||
arr[index] = value;
|
||||
|
||||
} else if (value != null) {
|
||||
// normal property set
|
||||
_props[propName] = value;
|
||||
|
||||
} else {
|
||||
// remove a property
|
||||
delete _props[propName];
|
||||
}
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
override public function writeObject (out :ObjectOutputStream) :void
|
||||
{
|
||||
super.writeObject(out);
|
||||
|
||||
throw new Error("Un-needed");
|
||||
}
|
||||
|
||||
override public function readObject (ins :ObjectInputStream) :void
|
||||
{
|
||||
super.readObject(ins);
|
||||
|
||||
// first read any regular bits
|
||||
turnHolder = (ins.readObject() as Name);
|
||||
ezGameService = (ins.readObject() as EZGameMarshaller);
|
||||
|
||||
// then user properties
|
||||
var count :int = ins.readInt();
|
||||
while (count-- > 0) {
|
||||
var key :String = ins.readUTF();
|
||||
var value :Object = EZObjectMarshaller.decode(ins.readObject());
|
||||
_props[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** The raw properties set by the game. */
|
||||
protected var _props :Object = new Object();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.threerings.ezgame.data {
|
||||
|
||||
import flash.utils.ByteArray;
|
||||
import flash.utils.IExternalizable;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
import com.threerings.io.Streamer;
|
||||
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.dobj.NamedEvent;
|
||||
|
||||
import com.threerings.ezgame.util.EZObjectMarshaller;
|
||||
|
||||
/**
|
||||
* Represents a property change on the actionscript object we
|
||||
* use in FlashGameObject.
|
||||
*/
|
||||
public class PropertySetEvent extends NamedEvent
|
||||
{
|
||||
/**
|
||||
* Create a PropertySetEvent.
|
||||
*/
|
||||
public function PropertySetEvent () // unserialize-only
|
||||
{
|
||||
super(0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value that was set for the property.
|
||||
*/
|
||||
public function getValue () :Object
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the old value.
|
||||
*/
|
||||
public function getOldValue () :Object
|
||||
{
|
||||
return _oldValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index, or -1 if not applicable.
|
||||
*/
|
||||
public function getIndex () :int
|
||||
{
|
||||
return _index;
|
||||
}
|
||||
|
||||
override public function applyToObject (target :DObject) :Boolean
|
||||
{
|
||||
_oldValue =
|
||||
EZGameObject(target).applyPropertySet(_name, _data, _index);
|
||||
return true;
|
||||
}
|
||||
|
||||
override protected function notifyListener (listener :Object) :void
|
||||
{
|
||||
if (listener is PropertySetListener) {
|
||||
(listener as PropertySetListener).propertyWasSet(this);
|
||||
}
|
||||
}
|
||||
|
||||
override public function readObject (ins :ObjectInputStream) :void
|
||||
{
|
||||
super.readObject(ins);
|
||||
|
||||
_index = ins.readInt();
|
||||
_data = EZObjectMarshaller.decode(ins.readObject());
|
||||
}
|
||||
|
||||
/** The index of the property, if applicable. */
|
||||
protected var _index :int;
|
||||
|
||||
/** The client-side data that is assigned to this property. */
|
||||
protected var _data :Object;
|
||||
|
||||
/** The old value. */
|
||||
protected var _oldValue :Object;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.threerings.ezgame.data {
|
||||
|
||||
import com.threerings.presents.dobj.ChangeListener;
|
||||
|
||||
/**
|
||||
* A listener that will be notified of PropertySet events.
|
||||
*/
|
||||
public interface PropertySetListener extends ChangeListener
|
||||
{
|
||||
/**
|
||||
* Called when a property was set on a flash game object.
|
||||
* This will be called <em>after</em> the event has been applied
|
||||
* to the object.
|
||||
*/
|
||||
function propertyWasSet (event :PropertySetEvent) :void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.threerings.ezgame.util {
|
||||
|
||||
import flash.net.ObjectEncoding;
|
||||
|
||||
import flash.system.ApplicationDomain;
|
||||
|
||||
import flash.utils.ByteArray;
|
||||
import flash.utils.Endian;
|
||||
|
||||
import com.threerings.io.TypedArray;
|
||||
|
||||
/**
|
||||
* Utility methods for transferring flash properties via
|
||||
* the presents dobj system.
|
||||
*/
|
||||
public class EZObjectMarshaller
|
||||
{
|
||||
/**
|
||||
* Encode the specified object as either a byte[] or a byte[][] if it
|
||||
* is an array. The specific mechanism of encoding is not important,
|
||||
* as long as decode returns a clone of the original object.
|
||||
*
|
||||
* Currently, cycles in the object graph are preserved on the other end.
|
||||
* TODO: serialize IExternalizable implementations, and take
|
||||
* into account the ApplicationDomain.
|
||||
*/
|
||||
public static function encode (
|
||||
obj :Object, encodeArrayElements :Boolean = true) :Object
|
||||
{
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
if (encodeArrayElements && obj is Array) {
|
||||
var src :Array = (obj as Array);
|
||||
var dest :TypedArray = TypedArray.create(ByteArray);
|
||||
for each (var o :Object in src) {
|
||||
dest.push(encode(o, false));
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
// TODO: Our own encoding, that takes into account
|
||||
// the ApplicationDomain
|
||||
var bytes :ByteArray = new ByteArray();
|
||||
bytes.endian = Endian.BIG_ENDIAN;
|
||||
bytes.objectEncoding = ObjectEncoding.AMF3;
|
||||
bytes.writeObject(obj);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static function decode (encoded :Object) :Object
|
||||
{
|
||||
if (encoded == null) {
|
||||
return null;
|
||||
}
|
||||
if (encoded is TypedArray) {
|
||||
var src :TypedArray = (encoded as TypedArray);
|
||||
var dest :Array = [];
|
||||
for each (var b :ByteArray in src) {
|
||||
dest.push(decode(b));
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
var bytes :ByteArray = (encoded as ByteArray);
|
||||
|
||||
// TODO: Our own decoding, that takes into account
|
||||
// the ApplicationDomain
|
||||
bytes.endian = Endian.BIG_ENDIAN;
|
||||
bytes.objectEncoding = ObjectEncoding.AMF3;
|
||||
return bytes.readObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user