diff --git a/build.xml b/build.xml index 1aa635e1..a203b761 100644 --- a/build.xml +++ b/build.xml @@ -93,11 +93,16 @@ classname="com.threerings.presents.tools.GenActionScriptTask"/> - - - - - + + + + + + + + + + diff --git a/src/as/com/threerings/ezgame/client/EZGameConfigurator.as b/src/as/com/threerings/ezgame/client/EZGameConfigurator.as index 7d3d8219..382f5dd8 100644 --- a/src/as/com/threerings/ezgame/client/EZGameConfigurator.as +++ b/src/as/com/threerings/ezgame/client/EZGameConfigurator.as @@ -27,179 +27,117 @@ import mx.controls.ComboBox; import mx.controls.HSlider; import mx.controls.Label; +import com.threerings.util.StreamableHashMap; import com.threerings.util.StringUtil; import com.threerings.flex.LabeledSlider; import com.threerings.parlor.game.client.FlexGameConfigurator; +import com.threerings.ezgame.data.ChoiceParameter; import com.threerings.ezgame.data.EZGameConfig; +import com.threerings.ezgame.data.Parameter; +import com.threerings.ezgame.data.RangeParameter; +import com.threerings.ezgame.data.ToggleParameter; /** * Adds custom configuration of options specified in XML. */ public class EZGameConfigurator extends FlexGameConfigurator { - /** - * Set XML game configuration options. - */ - public function setXMLConfig (config :XML) :void + // from GameConfigurator + override protected function gotGameConfig () :void { - var log :Log = Log.getLog(this); + super.gotGameConfig(); - for each (var param :XML in config..params.children()) { - if (StringUtil.isBlank(param.@ident)) { - log.warning("Bad configuration option: no 'ident' in " + - param + ", ignoring."); - continue; - } - switch (param.localName().toLowerCase()) { - case "range": - addRangeOption(param, log); - break; + var params :Array = (_config as EZGameConfig).getGameDefinition().params; + if (params == null) { + return; + } - case "choice": - addChoiceOption(param, log); - break; + for each (var param :Parameter in params) { + if (param is RangeParameter) { + var range :RangeParameter = (param as RangeParameter); + var slider :HSlider = new HSlider(); + slider.minimum = range.minimum; + slider.maximum = range.maximum; + slider.value = range.start; + slider.liveDragging = true; + slider.snapInterval = 1; + addLabeledControl(param, new LabeledSlider(slider)); - case "toggle": - addToggleOption(param, log); - break; + } else if (param is ChoiceParameter) { + var choice :ChoiceParameter = (param as ChoiceParameter); + var startDex :int = choice.choices.indexOf(choice.start); + if (startDex == -1) { + Log.getLog(this).warning( + "Start value does not appear in list of choices [param=" + choice + "]."); + } else { + var combo :ComboBox = new ComboBox(); + combo.dataProvider = choice.choices; + combo.selectedIndex = startDex; + addLabeledControl(param, combo); + } - default: - log.warning("Unknown XML configuration option: " + param + - ", ignoring."); - break; + } else if (param is ToggleParameter) { + var check :CheckBox = new CheckBox(); + check.selected = (param as ToggleParameter).start; + addLabeledControl(param, check); + + } else { + Log.getLog(this).warning("Unknown parameter in config [param=" + param + "]."); } } } - /** - * Add a control that came from parsing our custom option XML. - */ - protected function addXMLControl (spec :XML, control :UIComponent) :void - { - var ident :String = String(spec.@ident); - var name :String = String(spec.@name); - var tip :String = String(spec.@tip); - if (StringUtil.isBlank(name)) { - name = ident; - } - - var lbl :Label = new Label(); - lbl.text = name + ":"; - lbl.styleName = "lobbyLabel"; - lbl.toolTip = tip == "" ? name : tip; - control.toolTip = tip == "" ? name : tip; - - addControl(lbl, control); - _customConfigs.push(ident, control); - } - - /** - * Adds a 'range' option specified in XML - */ - protected function addRangeOption (spec :XML, log :Log) :void - { - var min :Number = Number(spec.@minimum); - var max :Number = Number(spec.@maximum); - var start :Number = Number(spec.@start); - if (isNaN(min) || isNaN(max) || isNaN(start)) { - log.warning("Unable to parse range specification. " + - "Required numeric values: minimum, maximum, start " + - "[xml=" + spec + "]."); - return; - } - - var slider :HSlider = new HSlider(); - slider.minimum = min; - slider.maximum = max; - slider.value = start; - slider.liveDragging = true; - slider.snapInterval = 1; - - addXMLControl(spec, new LabeledSlider(slider)); - } - - /** - * Adds a 'choice' option specified in XML - */ - protected function addChoiceOption (spec :XML, log :Log) :void - { - if (spec.@choices.length == 0 || spec.@start.length == 0) { - log.warning("Unable to parse choice specification. " + - "Required 'choices' or 'start' is missing. " + - "[xml=" + spec + "]."); - return; - } - - var choiceString :String = String(spec.@choices); - var start :String = String(spec.@start); - - var choices :Array = choiceString.split(","); - var startDex :int = choices.indexOf(start); - if (startDex == -1) { - log.warning("Choice start value does not appear in list of choices "+ - "[xml=" + spec + "]."); - return; - } - - var box :ComboBox = new ComboBox(); - box.dataProvider = choices; - box.selectedIndex = startDex; - - addXMLControl(spec, box); - } - - /** - * Adds a 'toggle' option specified in XML - */ - protected function addToggleOption (spec :XML, log :Log) :void - { - if (spec.@start.length == 0) { - log.warning("Unable to parse toggle specification. " + - "Required 'start' is missing. " + - "[xml=" + spec + "]."); - return; - } - - var startStr :String = String(spec.@start).toLowerCase(); - - var box :CheckBox = new CheckBox(); - box.selected = ("true" == startStr); - - addXMLControl(spec, box); - } - override protected function flushGameConfig () :void { super.flushGameConfig(); - + // if there were any custom XML configs, flush those as well. if (_customConfigs.length > 0) { - var options :Object = {}; + var params :StreamableHashMap = new StreamableHashMap(); for (var ii :int = 0; ii < _customConfigs.length; ii += 2) { var ident :String = String(_customConfigs[ii]); var control :UIComponent = (_customConfigs[ii + 1] as UIComponent); if (control is LabeledSlider) { - options[ident] = (control as LabeledSlider).slider.value; + params.put(ident, (control as LabeledSlider).slider.value); } else if (control is CheckBox) { - options[ident] = (control as CheckBox).selected; + params.put(ident, (control as CheckBox).selected); } else if (control is ComboBox) { - options[ident] = (control as ComboBox).value; + params.put(ident, (control as ComboBox).value); } else { Log.getLog(this).warning("Unknow custom config type " + control); } } - (_config as EZGameConfig).customConfig = options; + (_config as EZGameConfig).params = params; } } + /** + * Add a control that came from parsing our custom option XML. + */ + protected function addLabeledControl (param :Parameter, control :UIComponent) :void + { + if (StringUtil.isBlank(param.name)) { + param.name = param.ident; + } + + var lbl :Label = new Label(); + lbl.text = param.name + ":"; + lbl.styleName = "lobbyLabel"; + lbl.toolTip = param.tip; + control.toolTip = param.tip; + + addControl(lbl, control); + _customConfigs.push(param.ident, control); + } + /** Contains pairs of identString, control, identString, control.. */ protected var _customConfigs :Array = []; } diff --git a/src/as/com/threerings/ezgame/client/EZGamePanel.as b/src/as/com/threerings/ezgame/client/EZGamePanel.as index 801d9f69..3e911087 100644 --- a/src/as/com/threerings/ezgame/client/EZGamePanel.as +++ b/src/as/com/threerings/ezgame/client/EZGamePanel.as @@ -63,12 +63,11 @@ public class EZGamePanel extends Canvas _ezObj = (plobj as EZGameObject); backend = createBackend(); - _gameView = new GameContainer(cfg.gameMedia); + _gameView = new GameContainer(cfg.getGameDefinition().getMediaPath(cfg.getGameId())); _gameView.percentWidth = 100; _gameView.percentHeight = 100; backend.setSharedEvents( - Loader(_gameView.getMediaContainer().getMedia()). - contentLoaderInfo.sharedEvents); + Loader(_gameView.getMediaContainer().getMedia()).contentLoaderInfo.sharedEvents); backend.setContainer(_gameView); addChild(_gameView); } diff --git a/src/as/com/threerings/ezgame/client/GameControlBackend.as b/src/as/com/threerings/ezgame/client/GameControlBackend.as index b1a560c8..42ad96a8 100644 --- a/src/as/com/threerings/ezgame/client/GameControlBackend.as +++ b/src/as/com/threerings/ezgame/client/GameControlBackend.as @@ -47,6 +47,7 @@ import com.threerings.util.Iterator; import com.threerings.util.MessageBundle; import com.threerings.util.Name; import com.threerings.util.StringUtil; +import com.threerings.util.Wrapped; import com.threerings.presents.client.ConfirmAdapter; import com.threerings.presents.client.ResultWrapper; @@ -176,7 +177,14 @@ public class GameControlBackend // straight data o["gameData"] = _gameData; - o["gameConfig"] = (_ctrl.getPlaceConfig() as EZGameConfig).customConfig; + + // convert our game config from a HashMap to a Dictionary + var gameConfig :Object = {}; + var cfg :EZGameConfig = (_ctrl.getPlaceConfig() as EZGameConfig); + cfg.params.forEach(function (key :Object, value :Object) :void { + gameConfig[key] = (value is Wrapped) ? Wrapped(value).unwrap() : value; + }); + o["gameConfig"] = gameConfig; // functions o["setProperty_v1"] = setProperty_v1; @@ -388,7 +396,7 @@ public class GameControlBackend var uc :UserCookie = (_ezObj.userCookies.get(playerId) as UserCookie); if (uc != null) { - callback(uc.cookie); + callback(EZObjectMarshaller.decode(uc.cookie)); return; } } @@ -901,7 +909,7 @@ public class GameControlBackend delete _cookieCallbacks[cookie.playerId]; for each (var fn :Function in arr) { try { - fn(cookie.cookie); + fn(EZObjectMarshaller.decode(cookie.cookie)); } catch (err :Error) { // cope } diff --git a/src/as/com/threerings/ezgame/data/ChoiceParameter.as b/src/as/com/threerings/ezgame/data/ChoiceParameter.as new file mode 100644 index 00000000..5fe9334e --- /dev/null +++ b/src/as/com/threerings/ezgame/data/ChoiceParameter.as @@ -0,0 +1,65 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.data { + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; +import com.threerings.io.TypedArray; + +/** + * Models a parameter that allows the selection of one of a list of choices (specified as strings). + */ +public class ChoiceParameter extends Parameter +{ + /** The set of choices available for this parameter. */ + public var choices :TypedArray; + + /** The starting selection. */ + public var start :String; + + public function ChoiceParameter () + { + } + + // documentation inherited + override public function getDefaultValue () :Object + { + return start; + } + + // from interface Streamable + override public function readObject (ins :ObjectInputStream) :void + { + super.readObject(ins); + choices = (ins.readObject() as TypedArray); + start = (ins.readField(String) as String); + } + + // from interface Streamable + override public function writeObject (out :ObjectOutputStream) :void + { + super.writeObject(out); + out.writeObject(choices); + out.writeField(start); + } +} +} diff --git a/src/as/com/threerings/ezgame/data/EZGameConfig.as b/src/as/com/threerings/ezgame/data/EZGameConfig.as index bd28aa00..5e60bf9b 100644 --- a/src/as/com/threerings/ezgame/data/EZGameConfig.as +++ b/src/as/com/threerings/ezgame/data/EZGameConfig.as @@ -23,6 +23,7 @@ package com.threerings.ezgame.data { import com.threerings.util.Hashable; import com.threerings.util.MessageBundle; +import com.threerings.util.StreamableHashMap; import com.threerings.io.ObjectInputStream; import com.threerings.io.ObjectOutputStream; @@ -39,53 +40,45 @@ import com.threerings.ezgame.util.EZObjectMarshaller; * A game config for a simple multiplayer ez game. */ public class EZGameConfig extends GameConfig - implements Hashable { - /** The name of the game. */ - public var name :String; + /** Our configuration parameters. These will be seeded with the defaults from the game + * definition and then configured by the player in the lobby. */ + public var params :StreamableHashMap = new StreamableHashMap(); - /** If non-zero, a game id used to persistently identify the game. - * This could be thought of as a new-style rating id. */ - public var persistentGameId:int; - - /** The media for the game. In flash, this is the URL to the SWF file. */ - public var gameMedia :String; - - /** The game type. */ - public var gameType :int = SEATED_GAME; - - /** Any custom configuration options. */ - public var customConfig :Object; - - public function EZGameConfig () + public function EZGameConfig (gameId :int = 0, gameDef :GameDefinition = null) { - // nothing needed + _gameId = gameId; + _gameDef = gameDef; } - override public function getGameType () :int + /** Returns the game definition associated with this config instance. */ + public function getGameDefinition () :GameDefinition { - return gameType; + return _gameDef; } - // from abstract GameConfig - override public function getBundleName () :String + // from GameConfig + override public function getGameId () :int { - return "general"; + return _gameId; } - // TODO - //override public function createConfigurator () :GameConfigurator - - - override public function getGameName () :String + // from GameConfig + override public function getGameIdent () :String { - return MessageBundle.taint(name); + return _gameDef.ident; } - // from PlaceConfig - override public function createController () :PlaceController + // from GameConfig + override public function getMatchType () :int { - return new EZGameController(); + return _gameDef.match.getMatchType(); + } + + // from GameConfig + override public function createConfigurator () :GameConfigurator + { + return null; } // from abstract PlaceConfig @@ -94,46 +87,40 @@ public class EZGameConfig extends GameConfig throw new Error("Not implemented."); } - override public function hashCode () :int - { - return super.hashCode() ^ persistentGameId; - } - - override public function equals (other :Object) :Boolean - { - if (!super.equals(other)) { - return false; - } - - var that :EZGameConfig = (other as EZGameConfig); - return (this.persistentGameId == that.persistentGameId) && - (this.gameMedia === that.gameMedia); - } - // from interface Streamable override public function readObject (ins :ObjectInputStream) :void { super.readObject(ins); - - name = (ins.readField(String) as String) - persistentGameId = ins.readInt(); - gameMedia = (ins.readField(String) as String); - gameType = ins.readByte(); - - customConfig = EZObjectMarshaller.decode(ins.readObject()); + params = (ins.readObject() as StreamableHashMap); + _gameId = ins.readInt(); + _gameDef = (ins.readObject() as GameDefinition); } // from interface Streamable override public function writeObject (out :ObjectOutputStream) :void { super.writeObject(out); - - out.writeField(name); - out.writeInt(persistentGameId); - out.writeField(gameMedia); - out.writeByte(gameType); - - out.writeObject(EZObjectMarshaller.encode(customConfig, false)); + out.writeObject(params); + out.writeInt(_gameId); + out.writeObject(_gameDef); } + + /** Returns true if this is a party game, false otherwise. */ + public function isPartyGame () :Boolean + { + return getMatchType() == PARTY; + } + + // from PlaceConfig + override public function createController () :PlaceController + { + return new EZGameController(); + } + + /** Our game's unique id. */ + protected var _gameId :int; + + /** Our game definition. */ + protected var _gameDef :GameDefinition; } } diff --git a/src/as/com/threerings/ezgame/data/GameDefinition.as b/src/as/com/threerings/ezgame/data/GameDefinition.as new file mode 100644 index 00000000..aa6c7edd --- /dev/null +++ b/src/as/com/threerings/ezgame/data/GameDefinition.as @@ -0,0 +1,108 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.data { + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; +import com.threerings.io.Streamable; +import com.threerings.io.TypedArray; + +/** + * Contains the information about a game as described by the game definition XML file. + */ +public /*abstract*/ class GameDefinition + implements Streamable +{ + /** A string identifier for the game. */ + public var ident :String; + + /** The class name of the GameController derivation that we use to bootstrap on + * the client. */ + public var controller :String; + + /** The class name of the GameManager derivation that we use to manage the game on + * the server. */ + public var manager :String; + + /** The MD5 digest of the game media file. */ + public var digest :String; + + /** The configuration of the match-making mechanism. */ + public var match :MatchConfig; + + /** Parameters used to configure the game itself. */ + public var params :TypedArray; + + public function GameDefinition () + { + } + + /** + * Provides the path to this game's media (a jar file or an SWF). + * + * @param gameId the unique id of the game provided when this game definition was registered + * with the system, or -1 if we're running in test mode. + */ + public function getMediaPath (gameId :int) :String + { + throw new Error("abstract"); + } + + /** + * Returns true if a single player can play this game (possibly against AI opponents), or if + * opponents are needed. + */ + public function isSinglePlayerPlayable () :Boolean + { + throw new Error("Not implemented"); + } + + /** Generates a string representation of this instance. */ + public function toString () :String + { + return "[ident=" + ident + ", ctrl=" + controller + ", mgr=" + manager + + ", match=" + match + ", params=" + params + ", digest=" + digest + "]"; + } + + // from interface Streamable + public function readObject (ins :ObjectInputStream) :void + { + ident = (ins.readField(String) as String); + controller = (ins.readField(String) as String); + manager = (ins.readField(String) as String); + digest = (ins.readField(String) as String); + match = (ins.readObject() as MatchConfig); + params = (ins.readObject() as TypedArray); + } + + // from interface Streamable + public function writeObject (out :ObjectOutputStream) :void + { + out.writeField(ident); + out.writeField(controller); + out.writeField(manager); + out.writeField(digest); + out.writeObject(match); + out.writeObject(params); + } +} +} diff --git a/src/as/com/threerings/ezgame/data/MatchConfig.as b/src/as/com/threerings/ezgame/data/MatchConfig.as new file mode 100644 index 00000000..e688a9d5 --- /dev/null +++ b/src/as/com/threerings/ezgame/data/MatchConfig.as @@ -0,0 +1,50 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.data { + +import com.threerings.io.SimpleStreamableObject; + +import com.threerings.parlor.game.data.GameConfig; + +/** + * Used to configure the match-making interface for a game. Particular match-making mechanisms + * extend this class and specify their own special configuration parameters. + */ +public /*abstract*/ class MatchConfig extends SimpleStreamableObject +{ + public function MatchConfig () + { + } + + /** Returns the matchmaking type to use for this game, e.g. {@link GameConfig.SEATED_GAME}. */ + public function getMatchType () :int + { + throw new Error("Abstract"); + } + + /** Returns the minimum number of players needed to play this game. */ + public function getMinimumPlayers () :int + { + throw new Error("Abstract"); + } +} +} diff --git a/src/as/com/threerings/ezgame/data/Parameter.as b/src/as/com/threerings/ezgame/data/Parameter.as new file mode 100644 index 00000000..5a333318 --- /dev/null +++ b/src/as/com/threerings/ezgame/data/Parameter.as @@ -0,0 +1,81 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.data { + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; +import com.threerings.io.Streamable; + +/** + * Defines a configuration parameter for a game. Various derived classes exist that define + * particular types of configuration parameters including choices, toggles, ranges, etc. + */ +public /*abstract*/ class Parameter + implements Streamable +{ + /** A string identifier that names this parameter. */ + public var ident :String; + + /** A human readable name for this configuration parameter. */ + public var name :String; + + /** A human readable tooltip to display when the mouse is hovered over this configuration + * parameter. */ + public var tip :String; + + public function Parameter () + { + } + + public function toString () :String + { + return ident; + } + + public function getDefaultValue () :Object + { + throw new Error("Abstract"); + } + + /** Returns the translation key for this parameter's label. */ + public function getLabel () :String + { + return "m." + ident; + } + + // from interface Streamable + public function readObject (ins :ObjectInputStream) :void + { + ident = (ins.readField(String) as String); + name = (ins.readField(String) as String); + tip = (ins.readField(String) as String); + } + + // from interface Streamable + public function writeObject (out :ObjectOutputStream) :void + { + out.writeField(ident); + out.writeField(name); + out.writeField(tip); + } +} +} diff --git a/src/as/com/threerings/ezgame/data/PropertySetEvent.as b/src/as/com/threerings/ezgame/data/PropertySetEvent.as index a7a3ec3a..87b1334f 100644 --- a/src/as/com/threerings/ezgame/data/PropertySetEvent.as +++ b/src/as/com/threerings/ezgame/data/PropertySetEvent.as @@ -53,8 +53,7 @@ public class PropertySetEvent extends NamedEvent override public function applyToObject (target :DObject) :Boolean { // since we're in actionscript, we're always on the client - _oldValue = - EZGameObject(target).applyPropertySet(_name, _data, _index); + _oldValue = EZGameObject(target).applyPropertySet(_name, _data, _index); return true; } @@ -63,7 +62,7 @@ public class PropertySetEvent extends NamedEvent */ public function getValue () :Object { - return _data; + return EZObjectMarshaller.decode(_data); } /** @@ -87,7 +86,7 @@ public class PropertySetEvent extends NamedEvent { super.readObject(ins); _index = ins.readInt(); - _data = EZObjectMarshaller.decode(ins.readObject()); + _data = (ins.readObject() as Object); } // from interface Streamable diff --git a/src/as/com/threerings/ezgame/data/RangeParameter.as b/src/as/com/threerings/ezgame/data/RangeParameter.as new file mode 100644 index 00000000..22130cd8 --- /dev/null +++ b/src/as/com/threerings/ezgame/data/RangeParameter.as @@ -0,0 +1,69 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.data { + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; + +/** + * Models a parameter that can contain an integer value in a specified range. + */ +public class RangeParameter extends Parameter +{ + /** The minimum value of this parameter. */ + public var minimum :int; + + /** The maximum value of this parameter. */ + public var maximum :int; + + /** The starting value for this parameter. */ + public var start :int; + + public function RangeParameter () + { + } + + // documentation inherited + override public function getDefaultValue () :Object + { + return start; + } + + // from interface Streamable + override public function readObject (ins :ObjectInputStream) :void + { + super.readObject(ins); + minimum = ins.readInt(); + maximum = ins.readInt(); + start = ins.readInt(); + } + + // from interface Streamable + override public function writeObject (out :ObjectOutputStream) :void + { + super.writeObject(out); + out.writeInt(minimum); + out.writeInt(maximum); + out.writeInt(start); + } +} +} diff --git a/src/as/com/threerings/ezgame/data/TableMatchConfig.as b/src/as/com/threerings/ezgame/data/TableMatchConfig.as new file mode 100644 index 00000000..1ef84236 --- /dev/null +++ b/src/as/com/threerings/ezgame/data/TableMatchConfig.as @@ -0,0 +1,82 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.data { + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; + +import com.threerings.parlor.game.data.GameConfig; + +/** + * Extends {@link MatchConfig} with information about match-making in the table style. + */ +public class TableMatchConfig extends MatchConfig +{ + /** The minimum number of seats at this table. */ + public var minSeats :int; + + /** The starting setting for the number of seats at this table. */ + public var startSeats :int; + + /** The maximum number of seats at this table. */ + public var maxSeats :int; + + /** This is set to true if this is a party game. */ + public var isPartyGame :Boolean; + + public function TableMatchConfig () + { + } + + // from MatchConfig + override public function getMatchType () :int + { + return isPartyGame ? GameConfig.PARTY : GameConfig.SEATED_GAME; + } + + // from MatchConfig + override public function getMinimumPlayers () :int + { + return minSeats; + } + + // from interface Streamable + override public function readObject (ins :ObjectInputStream) :void + { + super.readObject(ins); + minSeats = ins.readInt(); + startSeats = ins.readInt(); + maxSeats = ins.readInt(); + isPartyGame = ins.readBoolean(); + } + + // from interface Streamable + override public function writeObject (out :ObjectOutputStream) :void + { + super.writeObject(out); + out.writeInt(minSeats); + out.writeInt(startSeats); + out.writeInt(maxSeats); + out.writeBoolean(isPartyGame); + } +} +} diff --git a/src/as/com/threerings/ezgame/data/ToggleParameter.as b/src/as/com/threerings/ezgame/data/ToggleParameter.as new file mode 100644 index 00000000..03007da1 --- /dev/null +++ b/src/as/com/threerings/ezgame/data/ToggleParameter.as @@ -0,0 +1,59 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.data { + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; + +/** + * Models a parameter that allows the toggling of a single value. + */ +public class ToggleParameter extends Parameter +{ + /** The starting state for this parameter. */ + public var start :Boolean; + + public function ToggleParameter () + { + } + + // documentation inherited + override public function getDefaultValue () :Object + { + return start; + } + + // from interface Streamable + override public function readObject (ins :ObjectInputStream) :void + { + super.readObject(ins); + start = ins.readBoolean(); + } + + // from interface Streamable + override public function writeObject (out :ObjectOutputStream) :void + { + super.writeObject(out); + out.writeBoolean(start); + } +} +} diff --git a/src/as/com/threerings/ezgame/data/UserCookie.as b/src/as/com/threerings/ezgame/data/UserCookie.as index 9d5def20..18a2e6f2 100644 --- a/src/as/com/threerings/ezgame/data/UserCookie.as +++ b/src/as/com/threerings/ezgame/data/UserCookie.as @@ -28,16 +28,23 @@ import com.threerings.io.ObjectOutputStream; import com.threerings.presents.dobj.DSet_Entry; -import com.threerings.ezgame.util.EZObjectMarshaller; - +/** + * Represents a user's game-specific cookie data. + */ public class UserCookie implements DSet_Entry { /** The id of the player that has this cookie. */ public var playerId :int; - /** The decoded cookie value. */ - public var cookie :Object; + /** The cookie value. */ + public var cookie :ByteArray; + + public function UserCookie (playerId :int = 0, cookie :ByteArray = null) + { + this.playerId = playerId; + this.cookie = cookie; + } // from DSet_Entry public function getKey () :Object @@ -49,14 +56,14 @@ public class UserCookie public function readObject (ins :ObjectInputStream) :void { playerId = ins.readInt(); - var ba :ByteArray = (ins.readField(ByteArray) as ByteArray); - cookie = EZObjectMarshaller.decode(ba); + cookie = (ins.readField(ByteArray) as ByteArray); } // from superinterface Streamable public function writeObject (out :ObjectOutputStream) :void { - throw new Error(); + out.writeInt(playerId); + out.writeField(cookie); } } } diff --git a/src/as/com/threerings/parlor/data/Table.as b/src/as/com/threerings/parlor/data/Table.as index d4a7e91b..eb62a51c 100644 --- a/src/as/com/threerings/parlor/data/Table.as +++ b/src/as/com/threerings/parlor/data/Table.as @@ -40,8 +40,7 @@ import com.threerings.parlor.data.ParlorCodes; import com.threerings.parlor.game.data.GameConfig; /** - * This class represents a table that is being used to matchmake a game by - * the Parlor services. + * This class represents a table that is being used to matchmake a game by the Parlor services. */ public class Table implements DSet_Entry, Hashable @@ -49,28 +48,25 @@ public class Table /** The unique identifier for this table. */ public var tableId :int; - /** The object id of the lobby object with which this table is - * associated. */ + /** 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. */ + /** 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. */ + /** 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; - /** The body oids of the occupants of this table, or null if a party game. - * (This is not propagated to remote instances.) */ - public /*transient*/ var bodyOids :TypedArray; + /** 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; - /** For a running game, the total number of players. For FFA party games, - * this is everyone. */ + /** For a running game, the total number of players. For FFA party games, this is everyone. */ public var playerCount :int; - /** For a running game, the total number of watchers. For FFA party games, - * this is always 0. */ + /** For a running game, the total number of watchers. For FFA party games, this is always 0. */ public var watcherCount :int; /** The game config for the game that is being matchmade. */ @@ -84,6 +80,25 @@ public class Table { } + /** + * 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 + { + // 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; + } + /** * Count the number of players currently occupying this table. */ @@ -101,28 +116,8 @@ public class Table } /** - * 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 - { - // 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(). + * For a team game, get the team member indices of the compressed players array returned by + * getPlayers(). */ public function getTeamMemberIndices () :TypedArray /* of Array of int */ { @@ -150,98 +145,13 @@ public class Table return newTeams; } -// /** -// * 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 ParlorCodes.INVALID_TABLE_POSITION; -// } -// -// // make sure the requested position is not already occupied -// if (occupants[position] != null) { -// return ParlorCodes.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) :Boolean -// { -// 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 clearOccupantByOid (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. + * Returns true if this table has a sufficient number of occupants that the game can be + * started. */ public function mayBeStarted () :Boolean { - switch (config.getGameType()) { + switch (config.getMatchType()) { case GameConfig.SEATED_CONTINUOUS: case GameConfig.PARTY: return true; @@ -270,25 +180,23 @@ public class Table } } -// /** -// * Returns true if sufficient seats are occupied that the game should -// * be automatically started. -// */ -// public function shouldBeStarted () :Boolean -// { -// switch (config.getGameType()) { -// case GameConfig.SEATED_CONTINUOUS: -// case GameConfig.PARTY: -// return true; -// -// default: -// return (tconfig.desiredPlayerCount <= getOccupiedCount()); -// } -// } + /** + * Returns true if sufficient seats are occupied that the game should be automatically started. + */ + public function shouldBeStarted () :Boolean + { + switch (config.getMatchType()) { + case GameConfig.SEATED_CONTINUOUS: + case GameConfig.PARTY: + return true; + + default: + return (tconfig.desiredPlayerCount <= getOccupiedCount()); + } + } /** - * Returns true if this table is in play, false if it is still being - * matchmade. + * Returns true if this table is in play, false if it is still being matchmade. */ public function inPlay () :Boolean { @@ -304,8 +212,7 @@ public class Table // from Hashable public function equals (other :Object) :Boolean { - return (other is Table) && - (tableId == (other as Table).tableId); + return (other is Table) && (tableId == (other as Table).tableId); } /** @@ -327,19 +234,6 @@ public class Table return tableId; } -// /** -// * 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; -// } - // from Streamable public function readObject (ins :ObjectInputStream) :void { @@ -356,13 +250,14 @@ public class Table // from Streamable public function writeObject (out :ObjectOutputStream) :void { - throw new Error(); -// out.writeInt(tableId); -// out.writeInt(lobbyOid); -// out.writeInt(gameOid); -// out.writeObject(occupants); -// out.writeObject(config); -// out.writeObject(tconfig); + out.writeInt(tableId); + out.writeInt(lobbyOid); + out.writeInt(gameOid); + out.writeObject(occupants); + out.writeShort(playerCount); + out.writeShort(watcherCount); + out.writeObject(config); + out.writeObject(tconfig); } /** @@ -373,7 +268,7 @@ public class Table buf.append("tableId=").append(tableId); buf.append(", lobbyOid=").append(lobbyOid); buf.append(", gameOid=").append(gameOid); - buf.append(", occupants=").append(occupants.join()); + buf.append(", occupants=").append(occupants == null ? "" : 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 index 3bf33bfb..b99aa7fc 100644 --- a/src/as/com/threerings/parlor/data/TableConfig.as +++ b/src/as/com/threerings/parlor/data/TableConfig.as @@ -27,24 +27,21 @@ import com.threerings.io.SimpleStreamableObject; import com.threerings.io.TypedArray; /** - * Table configuration parameters for a game that is to be matchmade - * using the table services. + * Table configuration parameters for a game that is to be matchmade using the table services. */ public class TableConfig extends SimpleStreamableObject { - /** The total number of players that are desired for the table. - * For team games, this should be set to the total number of players - * overall, as teams may be unequal. */ + /** The total number of players that are desired for the table. 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. */ + /** 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} }; */ + /** 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". */ diff --git a/src/as/com/threerings/parlor/game/client/FlexGameConfigurator.as b/src/as/com/threerings/parlor/game/client/FlexGameConfigurator.as index e54ad241..eb78b6ea 100644 --- a/src/as/com/threerings/parlor/game/client/FlexGameConfigurator.as +++ b/src/as/com/threerings/parlor/game/client/FlexGameConfigurator.as @@ -31,14 +31,12 @@ 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. + * 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}. + *

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 { @@ -54,14 +52,12 @@ public /*abstract*/ class FlexGameConfigurator extends GameConfigurator } /** - * 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. + * 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 + public function addControl (label :UIComponent, control :UIComponent) :void { var item :HBox = new HBox(); item.width = 225; diff --git a/src/as/com/threerings/parlor/game/client/GameConfigurator.as b/src/as/com/threerings/parlor/game/client/GameConfigurator.as index 70edb9a2..7426c2fd 100644 --- a/src/as/com/threerings/parlor/game/client/GameConfigurator.as +++ b/src/as/com/threerings/parlor/game/client/GameConfigurator.as @@ -25,20 +25,18 @@ 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. + * 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}. + *

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. + * Initializes this game configurator, creates its user interface elements and prepares it for + * display. */ public function init (ctx :ParlorContext) :void { @@ -57,8 +55,8 @@ public /*abstract*/ class GameConfigurator } /** - * Provides this configurator with its configuration. It should set up - * all of its user interface elements to reflect the configuration. + * 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 { @@ -68,8 +66,8 @@ public /*abstract*/ class GameConfigurator } /** - * Derived classes will likely want to override this method and - * configure their user interface elements accordingly. + * Derived classes will likely want to override this method and configure their user interface + * elements accordingly. */ protected function gotGameConfig () :void { @@ -86,10 +84,9 @@ public /*abstract*/ class GameConfigurator } /** - * 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. + * 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 { diff --git a/src/as/com/threerings/parlor/game/client/GameController.as b/src/as/com/threerings/parlor/game/client/GameController.as index 335e5c3b..26f43187 100644 --- a/src/as/com/threerings/parlor/game/client/GameController.as +++ b/src/as/com/threerings/parlor/game/client/GameController.as @@ -294,9 +294,9 @@ public /*abstract*/ class GameController extends PlaceController /** * Convenience method to determine the type of game. */ - protected function getGameType () :int + protected function getMatchType () :int { - return _gconfig.getGameType(); + return _gconfig.getMatchType(); } /** A reference to the active parlor context. */ diff --git a/src/as/com/threerings/parlor/game/data/GameConfig.as b/src/as/com/threerings/parlor/game/data/GameConfig.as index fa116cee..f96314b0 100644 --- a/src/as/com/threerings/parlor/game/data/GameConfig.as +++ b/src/as/com/threerings/parlor/game/data/GameConfig.as @@ -37,48 +37,43 @@ 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 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. + *

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 { - /** Game type constant: a game that is started with a list of players, - * and those are the only players that may play. */ + /** Game type constant: a game that is started with a list of players, and those are the only + * players that may play. */ public static const SEATED_GAME :int = 0; - /** Game type constant: a game that starts immediately, but only has - * a certain number of player slots. Users enter the game room, and - * then choose where to sit. */ + /** Game type constant: a game that starts immediately, but only has a certain number of player + * slots. Users enter the game room, and then choose where to sit. */ public static const SEATED_CONTINUOUS :int = 1; - /** Game type constant: a game that starts immediately, and every - * user that enters is a player. */ + /** Game type constant: a game that starts immediately, and every user that enters is a + * player. */ public static const PARTY :int = 2; - /** The usernames of the players involved in this game, or an empty - * array if such information is not needed by this particular game. */ + /** 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. */ + /** 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); public function GameConfig () @@ -87,27 +82,31 @@ public /*abstract*/ class GameConfig extends PlaceConfig } /** - * Get the type of game. + * Returns a numeric identifier for this game class. This may be used to track persisent + * information on a per-game basis. */ - public function getGameType () :int + public function getGameId () :int { - return SEATED_GAME; + throw new Error("abstract"); } - /** - * 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 /*abstract*/ function getBundleName () :String + public function getGameIdent () :String { throw new Error("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. + * Get the type of game. + */ + public function getMatchType () :int + { + return SEATED_GAME; + } + + /** + * 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 /*abstract*/ function createConfigurator () :GameConfigurator { @@ -115,8 +114,8 @@ public /*abstract*/ class GameConfig extends PlaceConfig } /** - * Creates a table configurator for initializing 'table' properties - * of the game. The default implementation returns null. + * Creates a table configurator for initializing 'table' properties of the game. The default + * implementation returns null. */ public function createTableConfigurator () :TableConfigurator { @@ -124,34 +123,13 @@ public /*abstract*/ class GameConfig extends PlaceConfig } /** - * 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; - } - - /** - * Computes a hashcode for this game config object that supports our - * {@link #equals} implementation. Objects that are equal should have - * the same hashcode. + * 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); + return StringUtil.hashCode(ClassUtil.getClassName(this)) + (rated ? 1 : 0); } // from Cloneable @@ -161,30 +139,27 @@ public /*abstract*/ class GameConfig extends PlaceConfig copy.players = this.players; copy.rated = this.rated; copy.ais = this.ais; - return copy; } /** - * 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). + * 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.isSameClass(other, this)) { var that :GameConfig = GameConfig(other); - return this.rated == that.rated; - + return this.getGameId() == that.getGameId() && this.rated == that.rated; } else { return false; } } /** - * Returns an Array of strings that describe the configuration of this - * game. Default implementation returns an empty array. + * Returns an Array of strings that describe the configuration of this game. Default + * implementation returns an empty array. */ public function getDescription () :Array { diff --git a/src/java/com/threerings/ezgame/Log.java b/src/java/com/threerings/ezgame/Log.java new file mode 100644 index 00000000..87c819ff --- /dev/null +++ b/src/java/com/threerings/ezgame/Log.java @@ -0,0 +1,58 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Contains a reference to the log object used by this package. + */ +public class Log +{ + /** We dispatch our log messages through this logger. */ + public static Logger log = Logger.getLogger("com.threerings.ezgame"); + + /** Convenience function. */ + public static void debug (String message) + { + log.fine(message); + } + + /** Convenience function. */ + public static void info (String message) + { + log.info(message); + } + + /** Convenience function. */ + public static void warning (String message) + { + log.warning(message); + } + + /** Convenience function. */ + public static void logStackTrace (Throwable t) + { + log.log(Level.WARNING, t.getMessage(), t); + } +} diff --git a/src/java/com/threerings/ezgame/client/EZGameConfigurator.java b/src/java/com/threerings/ezgame/client/EZGameConfigurator.java new file mode 100644 index 00000000..963888a9 --- /dev/null +++ b/src/java/com/threerings/ezgame/client/EZGameConfigurator.java @@ -0,0 +1,309 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.client; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; + +import java.util.logging.Level; + +import org.apache.commons.io.IOUtils; + +import com.samskivert.swing.HGroupLayout; +import com.samskivert.swing.SimpleSlider; + +import com.threerings.util.MessageBundle; +import com.threerings.util.MessageManager; + +import com.threerings.parlor.game.client.SwingGameConfigurator; +import com.threerings.parlor.game.data.GameAI; + +import com.threerings.ezgame.data.AIParameter; +import com.threerings.ezgame.data.ChoiceParameter; +import com.threerings.ezgame.data.EZGameConfig; +import com.threerings.ezgame.data.FileParameter; +import com.threerings.ezgame.data.GameDefinition; +import com.threerings.ezgame.data.Parameter; +import com.threerings.ezgame.data.RangeParameter; +import com.threerings.ezgame.data.ToggleParameter; +import com.threerings.ezgame.util.EZGameContext; + +import static com.threerings.ezgame.Log.log; + +/** + * Handles the configuration of an EZ game. This works in conjunction with the {@link EZGameConfig} + * to provide a generic mechanism for defining and obtaining game configuration settings. + */ +public class EZGameConfigurator extends SwingGameConfigurator +{ + // documentation inherited + protected void gotGameConfig () + { + super.gotGameConfig(); + + EZGameContext ctx = (EZGameContext)_ctx; + EZGameConfig config = (EZGameConfig)_config; + GameDefinition gamedef = config.getGameDefinition(); + + // create our parameter editors + if (_editors == null) { + MessageBundle msgs = ctx.getMessageManager().getBundle(config.getGameIdent()); + _editors = new ParamEditor[gamedef.params.length]; + for (int ii = 0; ii < _editors.length; ii++) { + _editors[ii] = createEditor(ctx, msgs, gamedef.params[ii]); + addControl(new JLabel(msgs.get(gamedef.params[ii].getLabel())), + (JComponent) _editors[ii]); + } + } + + // now read our parameters + for (int ii = 0; ii < gamedef.params.length; ii++) { + _editors[ii].readParameter(gamedef.params[ii], config); + } + } + + // documentation inherited + protected void flushGameConfig () + { + super.flushGameConfig(); + + EZGameConfig config = (EZGameConfig)_config; + GameDefinition gamedef = config.getGameDefinition(); + for (int ii = 0; ii < gamedef.params.length; ii++) { + _editors[ii].writeParameter(gamedef.params[ii], config); + } + } + + protected ParamEditor createEditor (EZGameContext ctx, MessageBundle msgs, Parameter param) + { + if (param instanceof AIParameter) { + return new AIEditor((AIParameter)param); + } else if (param instanceof RangeParameter) { + return new RangeEditor((RangeParameter)param); + } else if (param instanceof ToggleParameter) { + return new ToggleEditor((ToggleParameter)param); + } else if (param instanceof ChoiceParameter) { + return new ChoiceEditor(msgs, (ChoiceParameter)param); + } else if (param instanceof FileParameter) { + return new FileEditor(ctx, (FileParameter)param); + } else { + log.warning("Unknown parameter type! " + param + "."); + return null; + } + } + + /** Provides a uniform interface to our UI components. */ + protected static interface ParamEditor + { + public void readParameter (Parameter param, EZGameConfig config); + public void writeParameter (Parameter param, EZGameConfig config); + } + + protected class RangeEditor extends SimpleSlider implements ParamEditor + { + public RangeEditor (RangeParameter param) + { + super(null, param.minimum, param.maximum, param.start); + } + + public void readParameter (Parameter param, EZGameConfig config) + { + setValue((Integer)config.params.get(param.ident)); + } + + public void writeParameter (Parameter param, EZGameConfig config) + { + config.params.put(param.ident, getValue()); + } + } + + protected class ToggleEditor extends JPanel implements ParamEditor + { + public ToggleEditor (ToggleParameter param) + { + setLayout(new HGroupLayout(HGroupLayout.NONE, HGroupLayout.LEFT)); + add(_box = new JCheckBox(null, null, param.start)); + } + + public void readParameter (Parameter param, EZGameConfig config) + { + _box.setSelected((Boolean)config.params.get(param.ident)); + } + + public void writeParameter (Parameter param, EZGameConfig config) + { + config.params.put(param.ident, _box.isSelected()); + } + + protected JCheckBox _box; + } + + protected class ChoiceEditor extends JPanel implements ParamEditor + { + public ChoiceEditor (MessageBundle msgs, ChoiceParameter param) + { + setLayout(new HGroupLayout(HGroupLayout.NONE, HGroupLayout.LEFT)); + Choice[] choices = new Choice[param.choices.length]; + Choice selection = null; + for (int ii = 0; ii < choices.length; ii++) { + String choice = param.choices[ii]; + choices[ii] = new Choice(); + choices[ii].choice = choice; + choices[ii].label = msgs.get(param.getChoiceLabel(ii)); + if (choice.equals(param.start)) { + selection = choices[ii]; + } + } + add(_combo = new JComboBox(choices)); + if (selection != null) { + _combo.setSelectedItem(selection); + } + } + + public void readParameter (Parameter param, EZGameConfig config) + { + Choice selected = new Choice(); + selected.choice = (String)config.params.get(param.ident); + _combo.setSelectedItem(selected); + } + + public void writeParameter (Parameter param, EZGameConfig config) + { + config.params.put(param.ident, ((Choice)_combo.getSelectedItem()).choice); + } + + protected JComboBox _combo; + } + + protected static class Choice + { + public String choice; + public String label; + + public String toString () { + return label; + } + + public boolean equals (Object other) { + return (other instanceof Choice) && choice.equals(((Choice) other).choice); + } + } + + protected class FileEditor extends JPanel + implements ParamEditor, ActionListener + { + public FileEditor (EZGameContext ctx, FileParameter param) + { + _ctx = ctx; + _param = param; + setLayout(new HGroupLayout(HGroupLayout.NONE, HGroupLayout.LEFT)); + String label = ctx.getMessageManager().getBundle( + MessageManager.GLOBAL_BUNDLE).get("m.file_unset"); + add(_show = new JButton(label)); + _show.addActionListener(this); + } + + public void readParameter (Parameter param, EZGameConfig config) + { + // nothing doing + } + + public void writeParameter (Parameter param, EZGameConfig config) + { + if (_data != null) { + config.params.put(param.ident, _data); + } + } + + public void actionPerformed (ActionEvent event) + { + if (event.getSource() == _show) { + if (_chooser == null) { + _chooser = new JFileChooser(); + } + + int rv = _chooser.showOpenDialog(this); + if (rv == JFileChooser.APPROVE_OPTION) { + File file = _chooser.getSelectedFile(); + + try { + if (_param.binary) { + _data = IOUtils.toByteArray(new FileInputStream(file)); + } else { + _data = IOUtils.toString(new FileReader(file)); + } + _show.setText(file.getName()); + + } catch (IOException ioe) { + String msg = MessageBundle.tcompose( + "m.file_read_failure", ioe.getMessage()); + _ctx.getChatDirector().displayFeedback(null, msg); + log.warning("Failed to read '" + file + "': " + ioe.getMessage()); + } + } + } + } + + protected EZGameContext _ctx; + protected FileParameter _param; + protected JButton _show; + protected JFileChooser _chooser; + protected Object _data; + } + + protected class AIEditor extends SimpleSlider implements ParamEditor + { + public AIEditor (AIParameter param) + { + super(null, 0, param.maximum, 0); + } + + public void readParameter (Parameter param, EZGameConfig config) + { + setValue((config.ais == null) ? 0 : config.ais.length); + } + + public void writeParameter (Parameter param, EZGameConfig config) + { + config.ais = new GameAI[getValue()]; + for (int ii = 0; ii < config.ais.length; ii++) { + // TODO: allow specification of difficulty and personality + config.ais[ii] = new GameAI(0, 0); + } + } + } + + protected ParamEditor[] _editors; +} diff --git a/src/java/com/threerings/ezgame/client/EZGamePanel.java b/src/java/com/threerings/ezgame/client/EZGamePanel.java index 4644faef..3b8a252f 100644 --- a/src/java/com/threerings/ezgame/client/EZGamePanel.java +++ b/src/java/com/threerings/ezgame/client/EZGamePanel.java @@ -38,7 +38,6 @@ 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; @@ -56,16 +55,17 @@ public class EZGamePanel extends JPanel // add a listener so that we hear about all new children addContainerListener(this); - EZGameConfig cfg = (EZGameConfig) ctrl.getPlaceConfig(); - try { - _gameView = (Component) Class.forName(cfg.gameMedia).newInstance(); - add(_gameView); - } catch (RuntimeException re) { - throw re; +// TODO: sort out if and how EZ games will create their views +// EZGameConfig cfg = (EZGameConfig) ctrl.getPlaceConfig(); +// try { +// _gameView = (Component) Class.forName(cfg.gameMedia).newInstance(); +// add(_gameView); +// } catch (RuntimeException re) { +// throw re; - } catch (Exception e) { - throw new RuntimeException(e); - } +// } catch (Exception e) { +// throw new RuntimeException(e); +// } // TODO: Add a standard chat display? //addChild(new ChatDisplayBox(ctx)); diff --git a/src/java/com/threerings/ezgame/data/AIParameter.java b/src/java/com/threerings/ezgame/data/AIParameter.java new file mode 100644 index 00000000..bb6a1286 --- /dev/null +++ b/src/java/com/threerings/ezgame/data/AIParameter.java @@ -0,0 +1,49 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.data; + +import com.threerings.util.ActionScript; + +/** + * Models a parameter that is used to configure AIs. + */ +@ActionScript(omit=true) +public class AIParameter extends Parameter +{ + /** Indicates the maximum number of AIs in the game. */ + public int maximum; + + // TODO: allow specification of difficulty range + // TODO: allow specification of personality types + + @Override // documentation inherited + public String getLabel () + { + return "m.ai_" + ident; + } + + @Override // documentation inherited + public Object getDefaultValue () + { + return 0; + } +} diff --git a/src/java/com/threerings/ezgame/data/ChoiceParameter.java b/src/java/com/threerings/ezgame/data/ChoiceParameter.java new file mode 100644 index 00000000..4a8aeddc --- /dev/null +++ b/src/java/com/threerings/ezgame/data/ChoiceParameter.java @@ -0,0 +1,57 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.data; + +import com.threerings.util.ActionScript; + +/** + * Models a parameter that allows the selection of one of a list of choices (specified as strings). + */ +public class ChoiceParameter extends Parameter +{ + /** The set of choices available for this parameter. */ + public String[] choices; + + /** The starting selection. */ + public String start; + + /** + * Returns the translation key for the specified choice. + */ + @ActionScript(omit=true) + public String getChoiceLabel (int index) + { + return "m.choice_" + choices[index]; + } + + @Override @ActionScript(omit=true) // documentation inherited + public String getLabel () + { + return "m.choice_" + ident; + } + + @Override // documentation inherited + public Object getDefaultValue () + { + return start; + } +} diff --git a/src/java/com/threerings/ezgame/data/EZGameConfig.java b/src/java/com/threerings/ezgame/data/EZGameConfig.java index bb40e48b..cdcdbb65 100644 --- a/src/java/com/threerings/ezgame/data/EZGameConfig.java +++ b/src/java/com/threerings/ezgame/data/EZGameConfig.java @@ -21,90 +21,80 @@ package com.threerings.ezgame.data; -import com.threerings.util.MessageBundle; - -import com.threerings.crowd.client.PlaceController; +import com.threerings.util.StreamableHashMap; import com.threerings.parlor.game.client.GameConfigurator; import com.threerings.parlor.game.data.GameConfig; -import com.threerings.ezgame.client.EZGameController; +import com.threerings.ezgame.client.EZGameConfigurator; /** * A game config for a simple multiplayer game. */ public class EZGameConfig extends GameConfig { - /** The name of the game. */ - public String name; + /** Our configuration parameters. These will be seeded with the defaults from the game + * definition and then configured by the player in the lobby. */ + public StreamableHashMap params = new StreamableHashMap(); - /** If non-zero, a game id used to persistently identify the game. - * This could be thought of as a new-style rating id. */ - public int persistentGameId; - - /** The media for the game. In flash this is the URL to the SWF file. - * In Java, this will be a class name, or maybe a Jar. TODO? */ - public String gameMedia; - - /** The game type. */ - public byte gameType = SEATED_GAME; - - /** Custom configuration for the game. May only be interpretable on the - * client. */ - public Object customConfig; - - @Override - public byte getGameType () + /** A zero argument constructor used when unserializing. */ + public EZGameConfig () { - return gameType; } - // from abstract GameConfig - public String getBundleName () + /** Constructs a game config based on the supplied game definition. */ + public EZGameConfig (int gameId, GameDefinition gameDef) { - return "general"; + _gameId = gameId; + _gameDef = gameDef; + + // set the default values for our parameters + for (int ii = 0; ii < gameDef.params.length; ii++) { + params.put(gameDef.params[ii].ident, gameDef.params[ii].getDefaultValue()); + } } - // from abstract GameConfig + /** + * Returns the non-changing metadata that defines this game. + */ + public GameDefinition getGameDefinition () + { + return _gameDef; + } + + @Override // from GameConfig + public int getGameId () + { + return _gameId; + } + + @Override // from GameConfig + public String getGameIdent () + { + return _gameDef.ident; + } + + @Override // from GameConfig + public int getMatchType () + { + return _gameDef.match.getMatchType(); + } + + @Override // from GameConfig public GameConfigurator createConfigurator () { - // TODO - return null; + return new EZGameConfigurator(); } - @Override - public String getGameName () - { - return MessageBundle.taint(name); - } - - @Override // from PlaceConfig - public PlaceController createController () - { - return new EZGameController(); - } - - // from abstract PlaceConfig + @Override // from GameConfig public String getManagerClassName () { - return "com.threerings.ezgame.server.EZGameManager"; + return _gameDef.manager; } - @Override - public boolean equals (Object other) - { - if (!super.equals(other)) { - return false; - } + /** Our game's unique id. */ + protected int _gameId; - EZGameConfig that = (EZGameConfig) other; - return this.persistentGameId == that.persistentGameId && - this.gameMedia.equals(that.gameMedia); - } - - @Override - public int hashCode () - { - return super.hashCode() ^ persistentGameId; - } + /** Our game definition. */ + protected GameDefinition _gameDef; } diff --git a/src/java/com/threerings/ezgame/data/FileParameter.java b/src/java/com/threerings/ezgame/data/FileParameter.java new file mode 100644 index 00000000..599de7c9 --- /dev/null +++ b/src/java/com/threerings/ezgame/data/FileParameter.java @@ -0,0 +1,52 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.data; + +import com.threerings.util.ActionScript; + +/** + * Models a paramter that can be used to load the contents of a file into a byte array or string + * and ship it to the server. + */ +@ActionScript(omit=true) +public class FileParameter extends Parameter +{ + /** Whether or not the contents of the file should be supplied as binary data. If false, the + * file will be loaded as text in the platform default encoding . */ + public boolean binary = false; + + @Override // documentation inherited + public String getLabel () + { + return "m.file_" + ident; + } + + @Override // documentation inherited + public Object getDefaultValue () + { + if (binary) { + return new byte[0]; + } else { + return ""; + } + } +} diff --git a/src/java/com/threerings/ezgame/data/GameDefinition.java b/src/java/com/threerings/ezgame/data/GameDefinition.java new file mode 100644 index 00000000..dd3634ba --- /dev/null +++ b/src/java/com/threerings/ezgame/data/GameDefinition.java @@ -0,0 +1,101 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.data; + +import java.util.ArrayList; + +import com.samskivert.util.StringUtil; + +import com.threerings.io.Streamable; +import com.threerings.util.ActionScript; + +/** + * Contains the information about a game as described by the game definition XML file. + */ +public abstract class GameDefinition implements Streamable +{ + /** A string identifier for the game. */ + public String ident; + + /** The class name of the GameController derivation that we use to bootstrap on + * the client. */ + public String controller; + + /** The class name of the GameManager derivation that we use to manage the game on + * the server. */ + public String manager; + + /** The MD5 digest of the game media file. */ + public String digest; + + /** The configuration of the match-making mechanism. */ + public MatchConfig match; + + /** Parameters used to configure the game itself. */ + public Parameter[] params; + + /** + * Provides the path to this game's media (a jar file or an SWF). + * + * @param gameId the unique id of the game provided when this game definition was registered + * with the system, or -1 if we're running in test mode. + */ + public abstract String getMediaPath (int gameId); + + /** + * Returns true if a single player can play this game (possibly against AI opponents), or if + * opponents are needed. + */ + public boolean isSinglePlayerPlayable () + { + // maybe it's just single player no problem + int minPlayers = 2; + if (match != null) { + minPlayers = match.getMinimumPlayers(); + if (minPlayers <= 1) { + return true; + } + } + + // or maybe it has AIs + int aiCount = 0; + for (Parameter param : params) { + if (param instanceof AIParameter) { + aiCount = ((AIParameter)param).maximum; + } + } + return (minPlayers - aiCount) <= 1; + } + + /** Called when parsing a game definition from XML. */ + @ActionScript(omit=true) + public void setParams (ArrayList list) + { + params = list.toArray(new Parameter[list.size()]); + } + + /** Generates a string representation of this instance. */ + public String toString () + { + return StringUtil.fieldsToString(this); + } +} diff --git a/src/java/com/threerings/ezgame/data/MatchConfig.java b/src/java/com/threerings/ezgame/data/MatchConfig.java new file mode 100644 index 00000000..1bf8a689 --- /dev/null +++ b/src/java/com/threerings/ezgame/data/MatchConfig.java @@ -0,0 +1,39 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.data; + +import com.threerings.io.SimpleStreamableObject; + +import com.threerings.parlor.game.data.GameConfig; + +/** + * Used to configure the match-making interface for a game. Particular match-making mechanisms + * extend this class and specify their own special configuration parameters. + */ +public abstract class MatchConfig extends SimpleStreamableObject +{ + /** Returns the matchmaking type to use for this game, e.g. {@link GameConfig.SEATED_GAME}. */ + public abstract int getMatchType (); + + /** Returns the minimum number of players needed to play this game. */ + public abstract int getMinimumPlayers (); +} diff --git a/src/java/com/threerings/ezgame/data/Parameter.java b/src/java/com/threerings/ezgame/data/Parameter.java new file mode 100644 index 00000000..18239ebb --- /dev/null +++ b/src/java/com/threerings/ezgame/data/Parameter.java @@ -0,0 +1,55 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.data; + +import com.samskivert.util.StringUtil; + +import com.threerings.io.Streamable; + +/** + * Defines a configuration parameter for a game. Various derived classes exist that define + * particular types of configuration parameters including choices, toggles, ranges, etc. + */ +public abstract class Parameter implements Streamable +{ + /** A string identifier that names this parameter. */ + public String ident; + + /** A human readable name for this configuration parameter. */ + public String name; + + /** A human readable tooltip to display when the mouse is hovered over this configuration + * parameter. */ + public String tip; + + /** Returns the translation key for this parameter's label. */ + public abstract String getLabel (); + + /** Returns the default value of this parameter. */ + public abstract Object getDefaultValue (); + + /** Generates a string representation of this instance. */ + public String toString () + { + return StringUtil.fieldsToString(this); + } +} diff --git a/src/java/com/threerings/ezgame/data/PropertySetEvent.java b/src/java/com/threerings/ezgame/data/PropertySetEvent.java index 7831667d..804be14b 100644 --- a/src/java/com/threerings/ezgame/data/PropertySetEvent.java +++ b/src/java/com/threerings/ezgame/data/PropertySetEvent.java @@ -27,6 +27,7 @@ import com.threerings.io.ObjectInputStream; import com.threerings.io.ObjectOutputStream; import com.threerings.io.Streamable; import com.threerings.io.Streamer; +import com.threerings.util.ActionScript; import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.NamedEvent; @@ -34,8 +35,7 @@ import com.threerings.presents.dobj.NamedEvent; import com.threerings.ezgame.util.EZObjectMarshaller; /** - * Represents a property change on the actionscript object we - * use in EZGameObject. + * Represents a property change on the actionscript object we use in EZGameObject. */ public class PropertySetEvent extends NamedEvent { @@ -47,17 +47,16 @@ public class PropertySetEvent extends NamedEvent /** * Create a PropertySetEvent. */ - public PropertySetEvent ( - int targetOid, String propName, Object value, int index, Object oldValue) + public PropertySetEvent (int targetOid, String propName, Object value, int index, Object ovalue) { super(targetOid, propName); _data = value; _index = index; - _oldValue = oldValue; + _oldValue = ovalue; } /** - * Get the value that was set for the property. + * Returns the value that was set for the property. */ public Object getValue () { @@ -65,7 +64,7 @@ public class PropertySetEvent extends NamedEvent } /** - * Get the old value. + * Returns the old value. */ public Object getOldValue () { @@ -73,7 +72,7 @@ public class PropertySetEvent extends NamedEvent } /** - * Get the index, or -1 if not applicable. + * Returns the index, or -1 if not applicable. */ public int getIndex () { @@ -102,7 +101,7 @@ public class PropertySetEvent extends NamedEvent } } - @Override + @Override @ActionScript(name="toStringBuf") protected void toString (StringBuilder buf) { buf.append("PropertySetEvent "); diff --git a/src/java/com/threerings/ezgame/data/RangeParameter.java b/src/java/com/threerings/ezgame/data/RangeParameter.java new file mode 100644 index 00000000..6cba6813 --- /dev/null +++ b/src/java/com/threerings/ezgame/data/RangeParameter.java @@ -0,0 +1,51 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.data; + +import com.threerings.util.ActionScript; + +/** + * Models a parameter that can contain an integer value in a specified range. + */ +public class RangeParameter extends Parameter +{ + /** The minimum value of this parameter. */ + public int minimum; + + /** The maximum value of this parameter. */ + public int maximum; + + /** The starting value for this parameter. */ + public int start; + + @Override @ActionScript(omit=true) // documentation inherited + public String getLabel () + { + return "m.range_" + ident; + } + + @Override // documentation inherited + public Object getDefaultValue () + { + return start; + } +} diff --git a/src/java/com/threerings/ezgame/data/TableMatchConfig.java b/src/java/com/threerings/ezgame/data/TableMatchConfig.java new file mode 100644 index 00000000..af201938 --- /dev/null +++ b/src/java/com/threerings/ezgame/data/TableMatchConfig.java @@ -0,0 +1,54 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.data; + +import com.threerings.parlor.game.data.GameConfig; + +/** + * Extends {@link MatchConfig} with information about match-making in the table style. + */ +public class TableMatchConfig extends MatchConfig +{ + /** The minimum number of seats at this table. */ + public int minSeats; + + /** The starting setting for the number of seats at this table. */ + public int startSeats; + + /** The maximum number of seats at this table. */ + public int maxSeats; + + /** This is set to true if this is a party game. */ + public boolean isPartyGame; + + @Override // from MatchConfig + public int getMatchType () + { + return isPartyGame ? GameConfig.PARTY : GameConfig.SEATED_GAME; + } + + @Override // from MatchConfig + public int getMinimumPlayers () + { + return minSeats; + } +} diff --git a/src/java/com/threerings/ezgame/data/ToggleParameter.java b/src/java/com/threerings/ezgame/data/ToggleParameter.java new file mode 100644 index 00000000..6bbcbcc5 --- /dev/null +++ b/src/java/com/threerings/ezgame/data/ToggleParameter.java @@ -0,0 +1,45 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.data; + +import com.threerings.util.ActionScript; + +/** + * Models a parameter that allows the toggling of a single value. + */ +public class ToggleParameter extends Parameter +{ + /** The starting state for this parameter. */ + public boolean start; + + @Override @ActionScript(omit=true) // documentation inherited + public String getLabel () + { + return "m.toggle_" + ident; + } + + @Override // documentation inherited + public Object getDefaultValue () + { + return start; + } +} diff --git a/src/java/com/threerings/ezgame/server/EZGameManager.java b/src/java/com/threerings/ezgame/server/EZGameManager.java index 2f965b6b..b6c5afe2 100644 --- a/src/java/com/threerings/ezgame/server/EZGameManager.java +++ b/src/java/com/threerings/ezgame/server/EZGameManager.java @@ -58,9 +58,8 @@ import com.threerings.parlor.game.server.GameManager; import com.threerings.parlor.turn.server.TurnGameManager; -import com.threerings.ezgame.data.EZGameConfig; -import com.threerings.ezgame.data.EZGameObject; import com.threerings.ezgame.data.EZGameMarshaller; +import com.threerings.ezgame.data.EZGameObject; import com.threerings.ezgame.data.PropertySetEvent; import com.threerings.ezgame.data.UserCookie; @@ -355,7 +354,7 @@ public class EZGameManager extends GameManager throw new InvocationException(INTERNAL_ERROR); } - gcm.getCookie(getPersistentGameId(), body, new ResultListener() { + gcm.getCookie(_gameconfig.getGameId(), body, new ResultListener() { public void requestCompleted (byte[] result) { // Result may be null: that's ok, it means we've looked up the user's // nonexistant cookie. Only set the cookie if the playerIndex is still in the @@ -391,7 +390,7 @@ public class EZGameManager extends GameManager _gameObj.addToUserCookies(cookie); } - gcm.setCookie(getPersistentGameId(), caller, value); + gcm.setCookie(_gameconfig.getGameId(), caller, value); } /** @@ -442,26 +441,13 @@ public class EZGameManager extends GameManager new PropertySetEvent(_gameObj.getOid(), propName, value, index, oldValue)); } - /** - * Get the game id of this ezgame, as set in the config. - */ - protected int getPersistentGameId () - throws InvocationException - { - int id = ((EZGameConfig) _config).persistentGameId; - if (id == 0) { - throw new InvocationException("Persistent game id not set."); - } - return id; - } - /** * Validate that the specified user has access to do things in the game. */ protected void validateUser (ClientObject caller) throws InvocationException { - switch (getGameType()) { + switch (getMatchType()) { case GameConfig.PARTY: return; // always validate. @@ -495,7 +481,7 @@ public class EZGameManager extends GameManager protected BodyObject getPlayerByOid (int oid) { // verify that they're a player - switch (getGameType()) { + switch (getMatchType()) { case GameConfig.PARTY: // all occupants are players in a party game break; diff --git a/src/java/com/threerings/ezgame/util/EZGameContext.java b/src/java/com/threerings/ezgame/util/EZGameContext.java new file mode 100644 index 00000000..2bb2cdd8 --- /dev/null +++ b/src/java/com/threerings/ezgame/util/EZGameContext.java @@ -0,0 +1,35 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.util; + +import com.threerings.util.MessageManager; + +import com.threerings.parlor.util.ParlorContext; + +/** + * Extends the Parlor context with bits needed by the EZ game framework. + */ +public interface EZGameContext extends ParlorContext +{ + /** Returns a message manager that can be used to translate strings. */ + public MessageManager getMessageManager (); +} diff --git a/src/java/com/threerings/ezgame/xml/GameParser.java b/src/java/com/threerings/ezgame/xml/GameParser.java new file mode 100644 index 00000000..5a91cc6e --- /dev/null +++ b/src/java/com/threerings/ezgame/xml/GameParser.java @@ -0,0 +1,203 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// 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.ezgame.xml; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; + +import java.util.ArrayList; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import org.apache.commons.digester.Digester; +import org.apache.commons.digester.ObjectCreateRule; +import org.apache.commons.digester.Rule; + +import com.samskivert.util.StringUtil; +import com.samskivert.xml.SetFieldRule; +import com.samskivert.xml.SetNextFieldRule; +import com.samskivert.xml.SetPropertyFieldsRule; + +import com.threerings.ezgame.data.AIParameter; +import com.threerings.ezgame.data.ChoiceParameter; +import com.threerings.ezgame.data.FileParameter; +import com.threerings.ezgame.data.GameDefinition; +import com.threerings.ezgame.data.MatchConfig; +import com.threerings.ezgame.data.RangeParameter; +import com.threerings.ezgame.data.TableMatchConfig; +import com.threerings.ezgame.data.ToggleParameter; +import com.threerings.parlor.game.data.GameConfig; + +/** + * Parses the XML definition of a game. + */ +public class GameParser +{ + public GameParser () + { + // create and configure our digester + _digester = new Digester() { + public void fatalError (SAXParseException exception) + throws SAXException { + // the standard digester needlessly logs a fatal warning here + if (errorHandler != null) { + errorHandler.fatalError(exception); + } + } + }; + + // add the rules to parse the GameDefinition and its fields + _digester.addObjectCreate("game", getGameDefinitionClass()); + _digester.addRule("game/ident", new SetFieldRule("ident")); + _digester.addRule("game/controller", new SetFieldRule("controller")); + _digester.addRule("game/manager", new SetFieldRule("manager")); + + _digester.addRule("game/match", new Rule() { + public void begin (String namespace, String name, Attributes attrs) throws Exception { + String type = attrs.getValue("type"); + if (StringUtil.isBlank(type)) { + String errmsg = " block missing type attribute."; + throw new Exception(errmsg); + } + addMatchParsingRules(digester, type); + } + public void end (String namespace, String name) throws Exception { + MatchConfig match = (MatchConfig)digester.pop(); + ((GameDefinition)digester.peek()).match = match; + } + }); + + // these rules handle customization parameters + _digester.addRule("game/params", new ObjectCreateRule(ArrayList.class)); + _digester.addSetNext("game/params", "setParams", ArrayList.class.getName()); + addParameter("game/params/ai", AIParameter.class); + addParameter("game/params/range", RangeParameter.class); + addParameter("game/params/choice", ChoiceParameter.class); + addParameter("game/params/toggle", ToggleParameter.class); + addParameter("game/params/file", FileParameter.class); + + // add a rule to put the parsed definition onto our list + _digester.addSetNext("game", "add", Object.class.getName()); + } + + /** + * Parses a game definition from the supplied XML file. + * + * @exception IOException thrown if an error occurs reading the file. + * @exception SAXException thrown if an error occurs parsing the XML. + */ + public GameDefinition parseGame (File source) + throws IOException, SAXException + { + return parseGame(new FileReader(source)); + } + + /** + * Parses a game definition from the supplied XML source. + * + * @exception IOException thrown if an error occurs reading the file. + * @exception SAXException thrown if an error occurs parsing the XML. + */ + public GameDefinition parseGame (Reader source) + throws IOException, SAXException + { + // make sure nothing is lingering on the stack from a previous failure + _digester.clear(); + // push an array list on the digester which will receive the parsed game definition + ArrayList list = new ArrayList(); + _digester.push(list); + _digester.parse(source); + return (list.size() > 0) ? (GameDefinition)list.get(0) : null; + } + + /** + * Returns the {@link GameDefinition} class (or derived class) to use when parsing our game + * definition. + */ + protected String getGameDefinitionClass () + { + return GameDefinition.class.getName(); + } + + protected void addParameter (String path, Class pclass) + { + _digester.addRule(path, new ObjectCreateRule(pclass)); + _digester.addRule(path, new SetPropertyFieldsRule()); + _digester.addSetNext(path, "add", Object.class.getName()); + } + + /** + * Adds the rules needed to parse a custom match config, as well as the {@link MatchConfig} + * derived instance itself, based on the supplied type. + */ + protected void addMatchParsingRules (Digester digester, String type) + throws Exception + { + int itype = -1; + try { + itype = Integer.valueOf(type); + } catch (Exception e) { + for (int ii = 0; ii < GameConfig.TYPE_STRINGS.length; ii++) { + if (GameConfig.TYPE_STRINGS[ii].equals(type)) { + itype = ii; + break; + } + } + } + + switch (itype) { + case GameConfig.SEATED_GAME: + digester.push(createMatchConfig()); + digester.addRule("game/match/min_seats", new SetFieldRule("minSeats")); + digester.addRule("game/match/max_seats", new SetFieldRule("maxSeats")); + digester.addRule("game/match/start_seats", new SetFieldRule("startSeats")); + break; + + case GameConfig.PARTY: + // party games are handled by a specially configured table + TableMatchConfig config = createMatchConfig(); + config.minSeats = config.maxSeats = config.startSeats = 1; + config.isPartyGame = true; + digester.push(config); + break; + + case GameConfig.SEATED_CONTINUOUS: + // TODO + + default: + String errmsg = "Unknown match-making config type '" + type + "'."; + throw new Exception(errmsg); + } + } + + protected TableMatchConfig createMatchConfig () + { + return new TableMatchConfig(); + } + + /** Used to process XML descriptions. */ + protected Digester _digester; +} diff --git a/src/java/com/threerings/parlor/data/ParlorCodes.java b/src/java/com/threerings/parlor/data/ParlorCodes.java index fc3af508..2d08dc66 100644 --- a/src/java/com/threerings/parlor/data/ParlorCodes.java +++ b/src/java/com/threerings/parlor/data/ParlorCodes.java @@ -40,24 +40,20 @@ public interface ParlorCodes extends InvocationCodes /** The response code for a countered invitation. */ public static final int INVITATION_COUNTERED = 2; - /** An error code explaining that an invitation was rejected because - * the invited user was not online at the time the invitation was - * received. */ + /** 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 final String INVITEE_NOT_ONLINE = "m.invitee_not_online"; - /** An error code returned when a user requests to join a table that - * doesn't exist. */ + /** An error code returned when a user requests to join a table that doesn't exist. */ public static final String NO_SUCH_TABLE = "m.no_such_table"; - /** An error code returned when a user requests to join a table at a - * position that is not valid. */ - public static final String INVALID_TABLE_POSITION = - "m.invalid_table_position"; + /** An error code returned when a user requests to join a table at a position that is not + * valid. */ + public static final String INVALID_TABLE_POSITION = "m.invalid_table_position"; - /** An error code returned when a user requests to join a table in a - * position that is already occupied. */ - public static final String TABLE_POSITION_OCCUPIED = - "m.table_position_occupied"; + /** An error code returned when a user requests to join a table in a position that is already + * occupied. */ + public static final String TABLE_POSITION_OCCUPIED = "m.table_position_occupied"; /** An error code returned when a user requests to leave a table that * they were not sitting at in the first place. */ diff --git a/src/java/com/threerings/parlor/data/Table.java b/src/java/com/threerings/parlor/data/Table.java index f26182cf..c5f5db43 100644 --- a/src/java/com/threerings/parlor/data/Table.java +++ b/src/java/com/threerings/parlor/data/Table.java @@ -36,8 +36,7 @@ import com.threerings.parlor.data.ParlorCodes; import com.threerings.parlor.game.data.GameConfig; /** - * This class represents a table that is being used to matchmake a game by - * the Parlor services. + * This class represents a table that is being used to matchmake a game by the Parlor services. */ public class Table implements DSet.Entry, ParlorCodes @@ -45,28 +44,25 @@ public class Table /** The unique identifier for this table. */ public int tableId; - /** The object id of the lobby object with which this table is - * associated. */ + /** The object id of the lobby object with which this table is associated. */ public int lobbyOid; - /** The oid of the game that was created from this table or -1 if the - * table is still in matchmaking mode. */ + /** The oid of the game that was created from this table or -1 if the table is still in + * matchmaking mode. */ public int gameOid = -1; - /** An array of the usernames of the occupants of this table (some - * slots may not be filled), or null if a party game. */ + /** An array of the usernames of the occupants of this table (some slots may not be filled), or + * null if a party game. */ public Name[] occupants; - /** The body oids of the occupants of this table, or null if a party game. - * (This is not propagated to remote instances.) */ + /** The body oids of the occupants of this table, or null if a party game. (This is not + * propagated to remote instances.) */ public transient int[] bodyOids; - /** For a running game, the total number of players. For FFA party games, - * this is everyone. */ + /** For a running game, the total number of players. For FFA party games, this is everyone. */ public short playerCount; - /** For a running game, the total number of watchers. For FFA party games, - * this is always 0. */ + /** For a running game, the total number of watchers. For FFA party games, this is always 0. */ public short watcherCount; /** The game config for the game that is being matchmade. */ @@ -83,15 +79,13 @@ public class Table } /** - * Initializes a new table instance, and assigns it the next monotonically - * increasing table id. + * Initializes a new table instance, and assigns it the next monotonically increasing table id. * - * @param lobbyOid the object id of the lobby in which this table is - * to live. + * @param lobbyOid the object id of the lobby in which this table is to live. * @param tconfig the table configuration for this table. - * @param config the configuration of the game being matchmade by this - * table. + * @param config the configuration of the game being matchmade by this table. */ + @ActionScript(omit=true) public void init (int lobbyOid, TableConfig tconfig, GameConfig config) { // assign a unique table id @@ -105,7 +99,7 @@ public class Table this.config = config; // make room for the maximum number of players - if (config.getGameType() != GameConfig.PARTY) { + if (config.getMatchType() != GameConfig.PARTY) { occupants = new Name[tconfig.desiredPlayerCount]; bodyOids = new int[occupants.length]; @@ -121,6 +115,7 @@ public class Table /** * Returns true if there is no one sitting at this table. */ + @ActionScript(omit=true) public boolean isEmpty () { for (int i = 0; i < bodyOids.length; i++) { @@ -148,21 +143,20 @@ public class Table } /** - * 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 + * 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 Name[] getPlayers () { // seated party games need a spot for every seat - if (GameConfig.SEATED_CONTINUOUS == config.getGameType()) { + if (GameConfig.SEATED_CONTINUOUS == config.getMatchType()) { return new Name[tconfig.desiredPlayerCount]; } - // FFA party games have 0-length players array, and non-party - // games will have the players who are ready-to-go for the game start. + // FFA party games have 0-length players array, and non-party games will have the players + // who are ready-to-go for the game start. Name[] players = new Name[getOccupiedCount()]; if (occupants != null) { for (int ii = 0, dex = 0; ii < occupants.length; ii++) { @@ -176,8 +170,8 @@ public class Table } /** - * For a team game, get the team member indices of the compressed - * players array returned by getPlayers(). + * For a team game, get the team member indices of the compressed players array returned by + * getPlayers(). */ public int[][] getTeamMemberIndices () { @@ -205,16 +199,15 @@ public class Table } /** - * Requests to seat the specified user at the specified position in - * this table. + * 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. + * @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. */ + @ActionScript(omit=true) public String setOccupant (int position, BodyObject occupant) { // make sure the requested position is a valid one @@ -233,10 +226,10 @@ public class Table } /** - * 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'. + * 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'. */ + @ActionScript(omit=true) public void setOccupantPos (int position, BodyObject occupant) { occupants[position] = occupant.getVisibleName(); @@ -244,13 +237,12 @@ public class Table } /** - * Requests that the specified user be removed from their seat at this - * table. + * 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. + * @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. */ + @ActionScript(omit=true) public boolean clearOccupant (Name username) { if (occupants != null) { @@ -265,13 +257,13 @@ public class Table } /** - * Requests that the user identified by the specified body object id - * be removed from their seat at this table. + * 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. + * @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. */ + @ActionScript(omit=true) public boolean clearOccupantByOid (int bodyOid) { if (bodyOids != null) { @@ -286,9 +278,10 @@ public class Table } /** - * Called to clear an occupant at the specified position. - * Only call this method if you know what you're doing. + * Called to clear an occupant at the specified position. Only call this method if you know + * what you're doing. */ + @ActionScript(omit=true) public void clearOccupantPos (int position) { occupants[position] = null; @@ -296,12 +289,12 @@ public class Table } /** - * Returns true if this table has a sufficient number of occupants - * that the game can be started. + * Returns true if this table has a sufficient number of occupants that the game can be + * started. */ public boolean mayBeStarted () { - switch (config.getGameType()) { + switch (config.getMatchType()) { case GameConfig.SEATED_CONTINUOUS: case GameConfig.PARTY: return true; @@ -330,12 +323,11 @@ public class Table } /** - * Returns true if sufficient seats are occupied that the game should - * be automatically started. + * Returns true if sufficient seats are occupied that the game should be automatically started. */ public boolean shouldBeStarted () { - switch (config.getGameType()) { + switch (config.getMatchType()) { case GameConfig.SEATED_CONTINUOUS: case GameConfig.PARTY: return true; @@ -346,8 +338,7 @@ public class Table } /** - * Returns true if this table is in play, false if it is still being - * matchmade. + * Returns true if this table is in play, false if it is still being matchmade. */ public boolean inPlay () { @@ -363,8 +354,7 @@ public class Table // documentation inherited public boolean equals (Object other) { - return (other instanceof Table) && - (tableId == ((Table) other).tableId); + return (other instanceof Table) && (tableId == ((Table) other).tableId); } // documentation inherited diff --git a/src/java/com/threerings/parlor/data/TableConfig.java b/src/java/com/threerings/parlor/data/TableConfig.java index 7679bedb..aa320180 100644 --- a/src/java/com/threerings/parlor/data/TableConfig.java +++ b/src/java/com/threerings/parlor/data/TableConfig.java @@ -24,24 +24,21 @@ package com.threerings.parlor.data; import com.threerings.io.SimpleStreamableObject; /** - * Table configuration parameters for a game that is to be matchmade - * using the table services. + * Table configuration parameters for a game that is to be matchmade using the table services. */ public class TableConfig extends SimpleStreamableObject { - /** The total number of players that are desired for the table. - * For team games, this should be set to the total number of players - * overall, as teams may be unequal. */ + /** The total number of players that are desired for the table. For team games, this should be + * set to the total number of players overall, as teams may be unequal. */ public int desiredPlayerCount; - /** 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. */ + /** 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 int minimumPlayerCount; - /** 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} }; */ + /** 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 int[][] teamMemberIndices; /** Whether the table is "private". */ diff --git a/src/java/com/threerings/parlor/game/client/GameConfigurator.java b/src/java/com/threerings/parlor/game/client/GameConfigurator.java index 388d945c..51d59193 100644 --- a/src/java/com/threerings/parlor/game/client/GameConfigurator.java +++ b/src/java/com/threerings/parlor/game/client/GameConfigurator.java @@ -25,20 +25,18 @@ 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. + * 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}. + *

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. + * Initializes this game configurator, creates its user interface elements and prepares it for + * display. */ public void init (ParlorContext ctx) { @@ -57,8 +55,8 @@ public abstract class GameConfigurator } /** - * Provides this configurator with its configuration. It should set up - * all of its user interface elements to reflect the configuration. + * Provides this configurator with its configuration. It should set up all of its user + * interface elements to reflect the configuration. */ public void setGameConfig (GameConfig config) { @@ -68,8 +66,8 @@ public abstract class GameConfigurator } /** - * Derived classes will likely want to override this method and - * configure their user interface elements accordingly. + * Derived classes will likely want to override this method and configure their user interface + * elements accordingly. */ protected void gotGameConfig () { @@ -86,10 +84,9 @@ public abstract class GameConfigurator } /** - * 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. + * 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 void flushGameConfig () { diff --git a/src/java/com/threerings/parlor/game/client/GameController.java b/src/java/com/threerings/parlor/game/client/GameController.java index 2d68a1e6..d17c7f22 100644 --- a/src/java/com/threerings/parlor/game/client/GameController.java +++ b/src/java/com/threerings/parlor/game/client/GameController.java @@ -40,24 +40,21 @@ import com.threerings.parlor.game.data.GameObject; import com.threerings.parlor.util.ParlorContext; /** - * The game controller manages the flow and control of a game on the - * client side. This class serves as the root of a hierarchy of controller - * classes that aim to provide functionality shared between various - * similar games. The base controller provides functionality for starting - * and ending the game and for calculating ratings adjustements when a - * game ends normally. It also handles the basic house keeping like - * subscription to the game object and dispatch of commands and - * distributed object events. + * 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 { /** - * 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. + * 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 @@ -65,9 +62,8 @@ public abstract class GameController extends PlaceController */ public void init (CrowdContext ctx, PlaceConfig config) { - // cast our references before we call super.init() so that when - // super.init() calls createPlaceView(), we have our casted - // references already in place + // cast our references before we call super.init() so that when super.init() calls + // createPlaceView(), we have our casted references already in place _ctx = (ParlorContext)ctx; _config = (GameConfig)config; @@ -75,9 +71,8 @@ public abstract class GameController extends PlaceController } /** - * 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. + * 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. */ public void willEnterPlace (PlaceObject plobj) { @@ -86,27 +81,24 @@ public abstract class GameController extends PlaceController // obtain a casted reference _gobj = (GameObject)plobj; - // if this place object is not our current location we'll need to - // add it as an auxiliary chat source + // if this place object is not our current location we'll need to add it as an auxiliary + // chat source BodyObject bobj = (BodyObject)_ctx.getClient().getClientObject(); if (bobj.location != plobj.getOid()) { - _ctx.getChatDirector().addAuxiliarySource( - _gobj, GameCodes.GAME_CHAT_TYPE); + _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 + // 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) { _ctx.getClient().getRunQueue().postRunnable(new Runnable() { public void run () { - // finally let the game manager know that we're ready - // to roll + // finally let the game manager know that we're ready to roll playerReady(); } }); @@ -114,8 +106,7 @@ public abstract class GameController extends PlaceController } /** - * Removes our listener registration from the game object and cleans - * house. + * Removes our listener registration from the game object and cleans house. */ public void didLeavePlace (PlaceObject plobj) { @@ -131,9 +122,9 @@ public abstract class GameController extends PlaceController /** * Convenience method to determine the type of game. */ - public byte getGameType () + public int getMatchType () { - return _config.getGameType(); + return _config.getMatchType(); } /** @@ -141,15 +132,13 @@ public abstract class GameController extends PlaceController */ public boolean isGameOver () { - boolean gameOver = (_gobj == null) || - (_gobj.state != GameObject.IN_PLAY); + boolean gameOver = (_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. + * 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 void setGameOver (boolean gameOver) { @@ -157,13 +146,11 @@ public abstract class GameController extends PlaceController } /** - * 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. + * 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 void resetGame () { @@ -183,9 +170,8 @@ public abstract class GameController extends PlaceController } /** - * Handles basic game controller action events. Derived classes should - * be sure to call super.handleAction for events they - * don't specifically handle. + * Handles basic game controller action events. Derived classes should be sure to call + * super.handleAction for events they don't specifically handle. */ public boolean handleAction (ActionEvent action) { @@ -197,8 +183,7 @@ public abstract class GameController extends PlaceController */ public void systemMessage (String bundle, String msg) { - _ctx.getChatDirector().displayInfo( - bundle, msg, GameCodes.GAME_CHAT_TYPE); + _ctx.getChatDirector().displayInfo(bundle, msg, GameCodes.GAME_CHAT_TYPE); } // documentation inherited @@ -208,16 +193,16 @@ public abstract class GameController extends PlaceController if (event.getName().equals(GameObject.STATE)) { int newState = event.getIntValue(); if (!stateDidChange(newState)) { - Log.warning("Game transitioned to unknown state " + - "[gobj=" + _gobj + ", state=" + 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. + * 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 boolean stateDidChange (int state) { @@ -236,8 +221,8 @@ public abstract class GameController extends PlaceController } /** - * Called after we've entered the game and everything has initialized - * to notify the server that we, as a player, are ready to play. + * 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 void playerReady () { @@ -246,9 +231,8 @@ public abstract class GameController extends PlaceController } /** - * 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. + * Called when the game transitions to the IN_PLAY state. This happens when all of + * the players have arrived and the server starts the game. */ protected void gameDidStart () { @@ -269,9 +253,8 @@ public abstract class GameController extends PlaceController } /** - * 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). + * Called when the game transitions to the GAME_OVER state. This happens when the + * game reaches some end condition by normal means (is not cancelled or aborted). */ protected void gameDidEnd () { @@ -297,9 +280,8 @@ public abstract class GameController extends PlaceController } /** - * 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. + * 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 void gameWillReset () { @@ -317,12 +299,10 @@ public abstract class GameController extends PlaceController /** Our game configuration information. */ protected GameConfig _config; - /** A reference to the game object for the game that we're - * controlling. */ + /** A reference to the game object for the game that we're controlling. */ protected GameObject _gobj; - /** 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. */ + /** 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 boolean _gameOver; } diff --git a/src/java/com/threerings/parlor/game/data/GameConfig.java b/src/java/com/threerings/parlor/game/data/GameConfig.java index 0b4c24b2..a8085041 100644 --- a/src/java/com/threerings/parlor/game/data/GameConfig.java +++ b/src/java/com/threerings/parlor/game/data/GameConfig.java @@ -31,74 +31,81 @@ 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 + * 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. + *

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 { - /** Game type constant: a game that is started with a list of players, - * and those are the only players that may play. */ - public static final byte SEATED_GAME = 0; + /** Matchmaking type constant: a game that is started with a list of players, and those are the + * only players that may play. */ + public static final int SEATED_GAME = 0; - /** Game type constant: a game that starts immediately, but only has - * a certain number of player slots. Users enter the game room, and - * then choose where to sit. */ - public static final byte SEATED_CONTINUOUS = 1; + /** Matchmaking type constant: a game that starts immediately, but only has a certain number of + * player slots. Users enter the game room, and then choose where to sit. */ + public static final int SEATED_CONTINUOUS = 1; - /** Game type constant: a game that starts immediately, and every - * user that enters is a player. */ - public static final byte PARTY = 2; + /** Matchmaking type constant: a game that starts immediately, and every user that enters is a + * player. */ + public static final int PARTY = 2; - /** The usernames of the players involved in this game, or an empty - * array if such information is not needed by this particular game. */ + /** Maps the matchmaking type codes into strings used in our XML configuration. */ + public static final String[] TYPE_STRINGS = { "table", "entersit", "party" }; + + /** The usernames of the players involved in this game, or an empty array if such information + * is not needed by this particular game. */ public Name[] players = new Name[0]; /** Indicates whether or not this game is rated. */ public boolean rated = 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. */ + /** 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 GameAI[] ais = new GameAI[0]; /** - * Get the type of game. + * Returns a numeric identifier for this game class. This may be used to track persisent + * information on a per-game basis. */ - public byte getGameType () + public abstract int getGameId (); + + /** + * Returns a string identifier for this game class (e.g. "spades"). This may be used to + * identify a message bundle for which to obtain translations for this game configuration and + * to look up the name of the game in said bundle. + */ + public abstract String getGameIdent (); + + /** + * Returns the matchmaking type of this game: {@link #SEATED_GAME}, etc. + */ + public int getMatchType () { return SEATED_GAME; } /** - * Returns the message bundle identifier for the bundle that should be - * used to translate the translatable strings used to describe the - * game config parameters. + * 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 abstract String getBundleName (); + public GameConfigurator createConfigurator () + { + return null; + } /** - * 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 abstract GameConfigurator createConfigurator (); - - /** - * Creates a table configurator for initializing 'table' properties - * of the game. The default implementation returns null. + * Creates a table configurator for initializing 'table' properties of the game. The default + * implementation returns null. */ public TableConfigurator createTableConfigurator () { @@ -106,27 +113,8 @@ public abstract class GameConfig extends PlaceConfig implements Cloneable } /** - * Returns a translatable label describing this game. - */ - public String getGameName () - { - // 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 byte getRatingTypeId () - { - return (byte)-1; - } - - /** - * Returns a List of strings that describe the configuration of this - * game. Default implementation returns an empty list. + * Returns a List of strings that describe the configuration of this game. Default + * implementation returns an empty list. */ public List getDescription () { @@ -134,26 +122,21 @@ public abstract class GameConfig extends PlaceConfig implements Cloneable } /** - * 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). + * Returns true if this game config object is equal to the supplied object (meaning it is a + * game config for the same game and its configuration settings are the same as ours). */ public boolean equals (Object other) { - // make sure they're of the same class - if (other.getClass() == this.getClass()) { - GameConfig that = (GameConfig) other; - return this.rated == that.rated; - - } else { + if (!(other instanceof GameConfig)) { return false; } + GameConfig that = (GameConfig) other; + return (this.getGameId() == that.getGameId()) && (this.rated == that.rated); } /** - * Computes a hashcode for this game config object that supports our - * {@link #equals} implementation. Objects that are equal should have - * the same hashcode. + * Computes a hashcode for this game config object that supports our {@link #equals} + * implementation. Objects that are equal should have the same hashcode. */ public int hashCode () { diff --git a/src/java/com/threerings/parlor/game/server/GameManager.java b/src/java/com/threerings/parlor/game/server/GameManager.java index 24622944..e46cd344 100644 --- a/src/java/com/threerings/parlor/game/server/GameManager.java +++ b/src/java/com/threerings/parlor/game/server/GameManager.java @@ -58,20 +58,17 @@ import com.threerings.parlor.server.ParlorSender; import com.threerings.util.MessageBundle; /** - * The game manager handles the server side management of a game. It - * manipulates the game state in accordance with the logic of the game - * flow and generally manages the whole game playing process. + * The game manager handles the server side management of a game. It manipulates the game state in + * accordance with the logic of the game flow and generally manages the whole game playing process. * - *

The game manager extends the place manager because games are - * implicitly played in a location, the players of the game implicitly - * bodies in that location. + *

The game manager extends the place manager because games are implicitly played in a + * location, the players of the game implicitly bodies in that location. */ public class GameManager extends PlaceManager implements ParlorCodes, GameCodes { /** - * Returns the configuration object for the game being managed by this - * manager. + * Returns the configuration object for the game being managed by this manager. */ public GameConfig getGameConfig () { @@ -81,19 +78,19 @@ public class GameManager extends PlaceManager /** * A convenience method for getting the game type. */ - public byte getGameType () + public int getMatchType () { - return _gameconfig.getGameType(); + return _gameconfig.getMatchType(); } /** - * Adds the given player to the game at the first available player - * index. This should only be called before the game is started, and - * is most likely to be used to add players to party games. + * Adds the given player to the game at the first available player index. This should only be + * called before the game is started, and is most likely to be used to add players to party + * games. * * @param player the username of the player to add to this game. - * @return the player index at which the player was added, or - * -1 if the player could not be added to the game. + * @return the player index at which the player was added, or -1 if the player + * could not be added to the game. */ public int addPlayer (Name player) { @@ -108,10 +105,9 @@ public class GameManager extends PlaceManager // sanity-check the player index if (pidx == -1) { - Log.warning("Couldn't find free player index for player " + - "[game=" + where() + ", player=" + player + - ", players=" + StringUtil.toString(_gameobj.players) + - "]."); + Log.warning("Couldn't find free player index for player [game=" + where() + + ", player=" + player + + ", players=" + StringUtil.toString(_gameobj.players) + "]."); return -1; } @@ -120,9 +116,8 @@ public class GameManager extends PlaceManager } /** - * Adds the given player to the game at the specified player index. - * This should only be called before the game is started, and is most - * likely to be used to add players to party games. + * Adds the given player to the game at the specified player index. This should only be called + * before the game is started, and is most likely to be used to add players to party games. * * @param player the username of the player to add to this game. * @param pidx the player index at which the player is to be added. @@ -132,34 +127,31 @@ public class GameManager extends PlaceManager { // make sure the specified player index is valid if (pidx < 0 || pidx >= getPlayerSlots()) { - Log.warning("Attempt to add player at an invalid index " + - "[game=" + where() + ", player=" + player + - ", pidx=" + pidx + "]."); + Log.warning("Attempt to add player at an invalid index [game=" + where() + + ", player=" + player + ", pidx=" + pidx + "]."); return false; } // make sure the player index is available if (_gameobj.players[pidx] != null) { - Log.warning("Attempt to add player at occupied index " + - "[game=" + where() + ", player=" + player + - ", pidx=" + pidx + "]."); + Log.warning("Attempt to add player at occupied index [game=" + where() + + ", player=" + player + ", pidx=" + pidx + "]."); return false; } - // make sure the player isn't already somehow a part of the game - // to avoid any potential badness that might ensue if we added - // them more than once + // make sure the player isn't already somehow a part of the game to avoid any potential + // badness that might ensue if we added them more than once if (_gameobj.getPlayerIndex(player) != -1) { - Log.warning("Attempt to add player to game that they're already " + - "playing [game=" + where() + ", player=" + player + "]."); + Log.warning("Attempt to add player to game that they're already playing " + + "[game=" + where() + ", player=" + player + "]."); return false; } // get the player's body object BodyObject bobj = CrowdServer.lookupBody(player); if (bobj == null) { - Log.warning("Unable to get body object while adding player " + - "[game=" + where() + ", player=" + player + "]."); + Log.warning("Unable to get body object while adding player [game=" + where() + + ", player=" + player + "]."); return false; } @@ -179,9 +171,9 @@ public class GameManager extends PlaceManager } /** - * Removes the given player from the game. This is most likely to be - * used to allow players involved in a party game to leave the game - * early-on if they realize they'd rather not play for some reason. + * Removes the given player from the game. This is most likely to be used to allow players + * involved in a party game to leave the game early-on if they realize they'd rather not play + * for some reason. * * @param player the username of the player to remove from this game. * @return true if the player was successfully removed, false if not. @@ -193,10 +185,9 @@ public class GameManager extends PlaceManager // sanity-check the player index if (pidx == -1) { - Log.warning("Attempt to remove non-player from players list " + - "[game=" + where() + ", player=" + player + - ", players=" + StringUtil.toString(_gameobj.players) + - "]."); + Log.warning("Attempt to remove non-player from players list [game=" + where() + + ", player=" + player + + ", players=" + StringUtil.toString(_gameobj.players) + "]."); return false; } @@ -221,9 +212,8 @@ public class GameManager extends PlaceManager } /** - * Replaces the player at the specified index and calls {@link - * #playerWasReplaced} to let derived classes and delegates know - * what's going on. + * Replaces the player at the specified index and calls {@link #playerWasReplaced} to let + * derived classes and delegates know what's going on. */ public void replacePlayer (final int pidx, final Name player) { @@ -236,15 +226,14 @@ public class GameManager extends PlaceManager // notify our delegates applyToDelegates(new DelegateOp() { public void apply (PlaceManagerDelegate delegate) { - ((GameManagerDelegate)delegate).playerWasReplaced( - pidx, oplayer, player); + ((GameManagerDelegate)delegate).playerWasReplaced(pidx, oplayer, player); } }); } /** - * Returns the user object for the player with the specified index or - * null if the player at that index is not online. + * Returns the user object for the player with the specified index or null if the player at + * that index is not online. */ public BodyObject getPlayer (int playerIdx) { @@ -259,9 +248,8 @@ public class GameManager extends PlaceManager } /** - * Sets the specified player as an AI with the specified - * configuration. It is assumed that this will be set soon after the - * player names for all AIs present in the game. (It should be done + * Sets the specified player as an AI with the specified configuration. It is assumed that this + * will be set soon after the player names for all AIs present in the game. (It should be done * before human players start trickling into the game.) * * @param pidx the player index of the AI. @@ -288,8 +276,8 @@ public class GameManager extends PlaceManager } /** - * Returns the name of the player with the specified index or null if - * no player exists at that index. + * Returns the name of the player with the specified index or null if no player exists at that + * index. */ public Name getPlayerName (int index) { @@ -297,8 +285,8 @@ public class GameManager extends PlaceManager } /** - * Returns the player index of the given user in the game, or - * -1 if the player is not involved in the game. + * Returns the player index of the given user in the game, or -1 if the player is + * not involved in the game. */ public int getPlayerIndex (Name username) { @@ -306,14 +294,12 @@ public class GameManager extends PlaceManager } /** - * Get the player index of the specified oid, or -1 if the oid is - * not a player or is a player that is not presently in the game. + * Get the player index of the specified oid, or -1 if the oid is not a player or is a player + * that is not presently in the game. */ public int getPresentPlayerIndex (int bodyOid) { - return (_playerOids == null) - ? -1 - : IntListUtil.indexOf(_playerOids, bodyOid); + return (_playerOids == null) ? -1 : IntListUtil.indexOf(_playerOids, bodyOid); } /** @@ -349,13 +335,11 @@ public class GameManager extends PlaceManager } /** - * Returns whether the player at the specified player index is actively - * playing the game + * Returns whether the player at the specified player index is actively playing the game */ public boolean isActivePlayer (int pidx) { - return _gameobj.isActivePlayer(pidx) && - (getPlayerOid(pidx) > 0 || isAI(pidx)); + return _gameobj.isActivePlayer(pidx) && (getPlayerOid(pidx) > 0 || isAI(pidx)); } /** @@ -377,14 +361,12 @@ public class GameManager extends PlaceManager /** * Sends a system message to the players in the game room. * - * @param waitForStart if true, the message will not be sent until the - * game has started. + * @param waitForStart if true, the message will not be sent until the game has started. */ public void systemMessage ( String msgbundle, String msg, boolean waitForStart) { - if (waitForStart && - ((_gameobj == null) || (_gameobj.state == GameObject.PRE_GAME))) { + if (waitForStart && ((_gameobj == null) || (_gameobj.state == GameObject.PRE_GAME))) { // queue up the message. if (_startmsgs == null) { _startmsgs = new ArrayList>(); @@ -398,23 +380,20 @@ public class GameManager extends PlaceManager } /** - * This is called when the game is ready to start (all players - * involved have delivered their "am ready" notifications). It calls - * {@link #gameWillStart}, sets the necessary wheels in motion and - * then calls {@link #gameDidStart}. Derived classes should override - * one or both of the calldown functions (rather than this function) - * if they need to do things before or after the game starts. + * This is called when the game is ready to start (all players involved have delivered their + * "am ready" notifications). It calls {@link #gameWillStart}, sets the necessary wheels in + * motion and then calls {@link #gameDidStart}. Derived classes should override one or both of + * the calldown functions (rather than this function) if they need to do things before or after + * the game starts. * - * @return true if the game was started, false if it could not be - * started because it was already in play or because all players have - * not yet reported in. + * @return true if the game was started, false if it could not be started because it was + * already in play or because all players have not yet reported in. */ public boolean startGame () { // complain if we're already started if (_gameobj.state == GameObject.IN_PLAY) { - Log.warning("Requested to start an already in-play game " + - "[game=" + where() + "]."); + Log.warning("Requested to start an already in-play game [game=" + where() + "]."); Thread.dumpStack(); return false; } @@ -424,27 +403,24 @@ public class GameManager extends PlaceManager // make sure everyone has turned up if (!allPlayersReady()) { - Log.warning("Requested to start a game that is still " + - "awaiting players [game=" + where() + - ", pnames=" + StringUtil.toString(_gameobj.players) + + Log.warning("Requested to start a game that is still awaiting players " + + "[game=" + where() + ", pnames=" + StringUtil.toString(_gameobj.players) + ", poids=" + StringUtil.toString(_playerOids) + "]."); return false; } - // if we're still waiting for a call to endGame() to propagate, - // queue up a runnable to start the game which will allow the - // endGame() to propagate before we start things up + // if we're still waiting for a call to endGame() to propagate, queue up a runnable to + // start the game which will allow the endGame() to propagate before we start things up if (_committedState == GameObject.IN_PLAY) { if (_postponedStart) { - // We've already tried postponing once, doesn't do us any - // good to throw ourselves into a frenzy trying again. - Log.warning("Tried to postpone the start of a still-ending game " + - "multiple times [game=" + where() + "]."); + // We've already tried postponing once, doesn't do us any good to throw ourselves + // into a frenzy trying again. + Log.warning("Tried to postpone the start of a still-ending game multiple times " + + "[game=" + where() + "]."); _postponedStart = false; return false; } - Log.info("Postponing start of still-ending game " + - "[game=" + where() + "]."); + Log.info("Postponing start of still-ending game [game=" + where() + "]."); _postponedStart = true; // TEMP: track down weirdness final Exception firstCall = new Exception(); @@ -500,30 +476,27 @@ public class GameManager extends PlaceManager if (shouldEndGame()) { endGame(); } else { - // otherwise report that the player was knocked out to other - // people in his/her room + // otherwise report that the player was knocked out to other people in his/her room reportPlayerKnockedOut(pidx); } } /** - * Called when the game is known to be over. This will call some - * calldown functions to determine the winner of the game and then - * transition the game to the {@link GameObject#GAME_OVER} state. + * Called when the game is known to be over. This will call some calldown functions to + * determine the winner of the game and then transition the game to the {@link + * GameObject#GAME_OVER} state. */ public void endGame () { // TEMP: debug pending rating repeat bug if (_gameEndTracker.checkCall( - "Requested to end already ended game " + - "[game=" + where() + "].")) { + "Requested to end already ended game [game=" + where() + "].")) { return; } // END TEMP if (!_gameobj.isInPlay()) { - Log.info("Refusing to end game that was not in play " + - "[game=" + where() + "]."); + Log.info("Refusing to end game that was not in play [game=" + where() + "]."); return; } @@ -544,22 +517,19 @@ public class GameManager extends PlaceManager _gameobj.commitTransaction(); } - // wait until we hear the game state transition on the game object - // to invoke our game over code so that we can be sure that any - // final events dispatched on the game object prior to the call to - // endGame() have been dispatched + // wait until we hear the game state transition on the game object to invoke our game over + // code so that we can be sure that any final events dispatched on the game object prior to + // the call to endGame() have been dispatched } /** * Sets the state of the game to {@link GameObject#CANCELLED}. * - * @return true if the game was cancelled, false if it was already over or - * cancelled. + * @return true if the game was cancelled, false if it was already over or cancelled. */ public boolean cancelGame () { - if (_gameobj.state != GameObject.GAME_OVER && - _gameobj.state != GameObject.CANCELLED) { + if (_gameobj.state != GameObject.GAME_OVER && _gameobj.state != GameObject.CANCELLED) { _gameobj.setState(GameObject.CANCELLED); return true; } @@ -567,10 +537,9 @@ public class GameManager extends PlaceManager } /** - * Returns whether game conclusion antics such as rating updates - * should be performed when an in-play game is ended. Derived classes - * may wish to override this method to customize the conditions under - * which the game is concluded. + * Returns whether game conclusion antics such as rating updates should be performed when an + * in-play game is ended. Derived classes may wish to override this method to customize the + * conditions under which the game is concluded. */ public boolean shouldConcludeGame () { @@ -578,13 +547,11 @@ public class GameManager extends PlaceManager } /** - * Called when the game is to be reset to its starting state in - * preparation for a new game without actually ending the current - * game. It calls {@link #gameWillReset} followed by the standard game - * start processing ({@link #gameWillStart} and {@link - * #gameDidStart}). Derived classes should override these calldown - * functions (rather than this function) if they need to do things - * before or after the game resets. + * Called when the game is to be reset to its starting state in preparation for a new game + * without actually ending the current game. It calls {@link #gameWillReset} followed by the + * standard game start processing ({@link #gameWillStart} and {@link #gameDidStart}). Derived + * classes should override these calldown functions (rather than this function) if they need to + * do things before or after the game resets. */ public void resetGame () { @@ -597,21 +564,20 @@ public class GameManager extends PlaceManager } /** - * Called by the client when the player is ready for the game to start. - * This method is dispatched dynamically by {@link #messageReceived}. + * Called by the client when the player is ready for the game to start. This method is + * dispatched dynamically by {@link #messageReceived}. */ public void playerReady (BodyObject caller) { // get the user's player index int pidx = _gameobj.getPlayerIndex(caller.getVisibleName()); if (pidx == -1) { - // only complain if this is not a party game, since it's - // perfectly normal to receive a player ready notification - // from a user entering a party game in which they're not yet + // only complain if this is not a party game, since it's perfectly normal to receive a + // player ready notification from a user entering a party game in which they're not yet // a participant if (needsNoShowTimer()) { - Log.warning("Received playerReady() from non-player? " + - "[game=" + where() + ", who=" + caller.who() + "]."); + Log.warning("Received playerReady() from non-player? [game=" + where() + + ", who=" + caller.who() + "]."); } return; } @@ -626,8 +592,8 @@ public class GameManager extends PlaceManager } /** - * Returns true if all (non-AI) players have delivered their {@link - * #playerReady} notifications, false if they have not. + * Returns true if all (non-AI) players have delivered their {@link #playerReady} + * notifications, false if they have not. */ public boolean allPlayersReady () { @@ -640,10 +606,9 @@ public class GameManager extends PlaceManager } /** - * Returns true if the player at the specified slot is ready (or if - * there is meant to be no player in that slot), false if there is - * meant to be a player in the specified slot and they have not yet - * reported that they are ready. + * Returns true if the player at the specified slot is ready (or if there is meant to be no + * player in that slot), false if there is meant to be a player in the specified slot and they + * have not yet reported that they are ready. */ public boolean playerIsReady (int pidx) { @@ -653,20 +618,19 @@ public class GameManager extends PlaceManager } /** - * Returns true if this game requires a no-show timer. The default - * implementation returns true for non-party games and false for party - * games. Derived classes may wish to change or augment this behavior. + * Returns true if this game requires a no-show timer. The default implementation returns true + * for non-party games and false for party games. Derived classes may wish to change or augment + * this behavior. */ protected boolean needsNoShowTimer () { - return (getGameType() == GameConfig.SEATED_GAME); + return (getMatchType() == GameConfig.SEATED_GAME); } /** - * Derived classes that need their AIs to be ticked periodically - * should override this method and return true. Many AIs can act - * entirely in reaction to game state changes and need no periodic - * ticking which is why ticking is disabled by default. + * Derived classes that need their AIs to be ticked periodically should override this method + * and return true. Many AIs can act entirely in reaction to game state changes and need no + * periodic ticking which is why ticking is disabled by default. * * @see #tickAIs */ @@ -676,9 +640,8 @@ public class GameManager extends PlaceManager } /** - * Called when a player was added to the game. Derived classes may - * override this method to perform any game-specific actions they - * desire, but should be sure to call + * Called when a player was added to the game. Derived classes may override this method to + * perform any game-specific actions they desire, but should be sure to call * super.playerWasAdded(). * * @param player the username of the player added to the game. @@ -689,22 +652,19 @@ public class GameManager extends PlaceManager } /** - * Called when a player was removed from the game. Derived classes - * may override this method to perform any game-specific actions they - * desire, but should be sure to call + * Called when a player was removed from the game. Derived classes may override this method to + * perform any game-specific actions they desire, but should be sure to call * super.playerWasRemoved(). * * @param player the username of the player removed from the game. - * @param pidx the player index of the player before they were removed - * from the game. + * @param pidx the player index of the player before they were removed from the game. */ protected void playerWasRemoved (Name player, int pidx) { } /** - * Called when a player has been replaced via a call to {@link - * #replacePlayer}. + * Called when a player has been replaced via a call to {@link #replacePlayer}. */ protected void playerWasReplaced (int pidx, Name oldPlayer, Name newPlayer) { @@ -717,14 +677,12 @@ public class GameManager extends PlaceManager { BodyObject user = getPlayer(pidx); if (user == null) { - // body object can be null for ai players - return; + return; // body object can be null for ai players } DObject place = CrowdServer.omgr.getObject(user.location); if (place != null) { - place.postMessage(PLAYER_KNOCKED_OUT, - new Object[] { new int[] { user.getOid() } }); + place.postMessage(PLAYER_KNOCKED_OUT, new Object[] { new int[] { user.getOid() } }); } } @@ -770,8 +728,8 @@ public class GameManager extends PlaceManager // set up an initial player status array _gameobj.setPlayerStatus(new int[getPlayerSlots()]); - // save off the number of players so that we needn't repeatedly - // iterate through the player name array server-side unnecessarily + // save off the number of players so that we needn't repeatedly iterate through the player + // name array server-side unnecessarily _playerCount = _gameobj.getPlayerCount(); // instantiate a player oid array which we'll fill in later @@ -780,8 +738,8 @@ public class GameManager extends PlaceManager // give delegates a chance to do their thing super.didStartup(); - // let the players of this game know that we're ready to roll (if - // we have a specific set of players) + // let the players of this game know that we're ready to roll (if we have a specific set of + // players) for (int ii = 0; ii < getPlayerSlots(); ii++) { // skip non-existent players and AIs if (!_gameobj.isOccupiedPlayer(ii) || isAI(ii)) { @@ -790,8 +748,7 @@ public class GameManager extends PlaceManager BodyObject bobj = CrowdServer.lookupBody(_gameobj.players[ii]); if (bobj == null) { - Log.warning("Unable to deliver game ready to non-existent " + - "player [game=" + where() + + Log.warning("Unable to deliver game ready to non-existent player [game=" + where() + ", player=" + _gameobj.players[ii] + "]."); continue; } @@ -841,22 +798,21 @@ public class GameManager extends PlaceManager endPlayerGame(pidx); } - // then complete the bodyLeft() processing which may result in a call - // to placeBecameEmpty() which will shut the game down + // then complete the bodyLeft() processing which may result in a call to placeBecameEmpty() + // which will shut the game down super.bodyLeft(bodyOid); } /** - * When a game room becomes empty, we cancel the game if it's still in - * progress and close down the game room. + * When a game room becomes empty, we cancel the game if it's still in progress and close down + * the game room. */ protected void placeBecameEmpty () { // Log.info("Game room empty. Going away. [game=" + where() + "]."); // if we're in play then move to game over - if (_gameobj.state != GameObject.PRE_GAME && - _gameobj.state != GameObject.GAME_OVER && + if (_gameobj.state != GameObject.PRE_GAME && _gameobj.state != GameObject.GAME_OVER && _gameobj.state != GameObject.CANCELLED) { _gameobj.setState(GameObject.GAME_OVER); // and shutdown directly @@ -870,23 +826,21 @@ public class GameManager extends PlaceManager } /** - * Called when all players have arrived in the game room. By default, - * this starts up the game, but a manager may wish to override this - * and start the game according to different criterion. + * Called when all players have arrived in the game room. By default, this starts up the game, + * but a manager may wish to override this and start the game according to different criterion. */ protected void playersAllHere () { // if we're a seated game and we haven't already started, start. - if ((getGameType() == GameConfig.SEATED_GAME) && (_gameobj.state == GameObject.PRE_GAME)) { + if ((getMatchType() == GameConfig.SEATED_GAME) && (_gameobj.state == GameObject.PRE_GAME)) { startGame(); } } /** - * Called after the no-show delay has expired following the delivery - * of notifications to all players that the game is ready. - * Note: this is not called for party games. Those games have - * a human who decides when to start the game. + * Called after the no-show delay has expired following the delivery of notifications to all + * players that the game is ready. Note: this is not called for party games. Those + * games have a human who decides when to start the game. */ protected void checkForNoShows () { @@ -914,14 +868,13 @@ public class GameManager extends PlaceManager } /** - * This is called when some, but not all, players failed to show up - * for a game. The default implementation simply cancels the game. + * This is called when some, but not all, players failed to show up for a game. The default + * implementation simply cancels the game. */ protected void handlePartialNoShow () { - // mark the no-show players; this will cause allPlayersReady() to - // think that everyone has arrived, but still allow us to tell who - // has not shown up in gameDidStart() + // mark the no-show players; this will cause allPlayersReady() to think that everyone has + // arrived, but still allow us to tell who has not shown up in gameDidStart() int humansHere = 0; for (int ii = 0; ii < _playerOids.length; ii++) { if (_playerOids[ii] == 0) { @@ -938,9 +891,8 @@ public class GameManager extends PlaceManager cancelGame(); } else { - // go ahead and report that everyone is ready (which will start the - // game); gameDidStart() will take care of giving the boot to - // anyone who isn't around + // go ahead and report that everyone is ready (which will start the game); + // gameDidStart() will take care of giving the boot to anyone who isn't around Log.info("Forcing start of partial no-show game [game=" + where() + ", poids=" + StringUtil.toString(_playerOids) + "]."); playersAllHere(); @@ -948,8 +900,8 @@ public class GameManager extends PlaceManager } /** - * @return true if we should start the game even without any humans. - * Default implementation always returns false. + * @return true if we should start the game even without any humans. Default implementation + * always returns false. */ protected boolean startWithoutHumans () { @@ -957,11 +909,9 @@ public class GameManager extends PlaceManager } /** - * Called when the game is about to start, but before the game start - * notification has been delivered to the players. Derived classes - * should override this if they need to perform some pre-start - * activities, but should be sure to call - * super.gameWillStart(). + * Called when the game is about to start, but before the game start notification has been + * delivered to the players. Derived classes should override this if they need to perform some + * pre-start activities, but should be sure to call super.gameWillStart(). */ protected void gameWillStart () { @@ -977,8 +927,8 @@ public class GameManager extends PlaceManager } /** - * Called when the game state changes. This happens after the attribute - * change event has propagated. + * Called when the game state changes. This happens after the attribute change event has + * propagated. * * @param state the new game state. * @param oldState the previous game state. @@ -991,8 +941,8 @@ public class GameManager extends PlaceManager break; case GameObject.GAME_OVER: - // we do some jiggery pokery to allow derived game objects to have - // different notions of what it means to be in play + // we do some jiggery pokery to allow derived game objects to have different notions of + // what it means to be in play _gameobj.state = oldState; boolean wasInPlay = _gameobj.isInPlay(); _gameobj.state = state; @@ -1016,11 +966,10 @@ public class GameManager extends PlaceManager } /** - * Called after the game start notification was dispatched. Derived - * classes can override this to put whatever wheels they might need - * into motion now that the game is started (if anything other than - * transitioning the game to {@link GameObject#IN_PLAY} is necessary), - * but should be sure to call super.gameDidStart(). + * Called after the game start notification was dispatched. Derived classes can override this + * to put whatever wheels they might need into motion now that the game is started (if anything + * other than transitioning the game to {@link GameObject#IN_PLAY} is necessary), but should be + * sure to call super.gameDidStart(). */ protected void gameDidStart () { @@ -1034,8 +983,7 @@ public class GameManager extends PlaceManager // inform the players of any pending messages. if (_startmsgs != null) { for (Tuple mtup : _startmsgs) { - systemMessage(mtup.left, // bundle - mtup.right); // message + systemMessage(mtup.left, /* bundle */ mtup.right /* message */); } _startmsgs = null; } @@ -1045,8 +993,7 @@ public class GameManager extends PlaceManager AIGameTicker.registerAIGame(this); } - // any players who have not claimed that they are ready should now - // be given le boote royale + // any players who have not claimed that they are ready should now be given le boote royale for (int ii = 0; ii < _playerOids.length; ii++) { if (_playerOids[ii] == -1) { Log.info("Booting no-show player [game=" + where() + @@ -1058,8 +1005,7 @@ public class GameManager extends PlaceManager } /** - * Called by the {@link AIGameTicker} if we're registered as an AI - * game. + * Called by the {@link AIGameTicker} if we're registered as an AI game. */ protected void tickAIs () { @@ -1097,10 +1043,9 @@ public class GameManager extends PlaceManager } /** - * Called when a player has been marked as knocked out but before the - * knock-out status update has been sent to the players. Any status - * information that needs be updated in light of the knocked out - * player can be updated here. + * Called when a player has been marked as knocked out but before the knock-out status update + * has been sent to the players. Any status information that needs be updated in light of the + * knocked out player can be updated here. */ protected void playerGameDidEnd (int pidx) { @@ -1109,13 +1054,11 @@ public class GameManager extends PlaceManager } /** - * Called when a player leaves the game in order to determine whether - * the game should be ended based on its current state, which will - * include updated player status for the player in question. The - * default implementation returns true if the game is in play and - * there is only one player left. Derived classes may wish to - * override this method in order to customize the required end-game - * conditions. + * Called when a player leaves the game in order to determine whether the game should be ended + * based on its current state, which will include updated player status for the player in + * question. The default implementation returns true if the game is in play and there is only + * one player left. Derived classes may wish to override this method in order to customize the + * required end-game conditions. */ protected boolean shouldEndGame () { @@ -1123,11 +1066,10 @@ public class GameManager extends PlaceManager } /** - * Assigns the final winning status for each player to their respect - * player index in the supplied array. This will be called by {@link - * #endGame} when the game is over. The default implementation marks - * no players as winners. Derived classes should override this method - * in order to customize the winning conditions. + * Assigns the final winning status for each player to their respect player index in the + * supplied array. This will be called by {@link #endGame} when the game is over. The default + * implementation marks no players as winners. Derived classes should override this method in + * order to customize the winning conditions. */ protected void assignWinners (boolean[] winners) { @@ -1135,11 +1077,9 @@ public class GameManager extends PlaceManager } /** - * Called when the game is about to end, but before the game end - * notification has been delivered to the players. Derived classes - * should override this if they need to perform some pre-end - * activities, but should be sure to call - * super.gameWillEnd(). + * Called when the game is about to end, but before the game end notification has been + * delivered to the players. Derived classes should override this if they need to perform some + * pre-end activities, but should be sure to call super.gameWillEnd(). */ protected void gameWillEnd () { @@ -1152,9 +1092,8 @@ public class GameManager extends PlaceManager } /** - * Called after the game has transitioned to the {@link - * GameObject#GAME_OVER} state. Derived classes should override this - * to perform any post-game activities, but should be sure to call + * Called after the game has transitioned to the {@link GameObject#GAME_OVER} state. Derived + * classes should override this to perform any post-game activities, but should be sure to call * super.gameDidEnd(). */ protected void gameDidEnd () @@ -1181,15 +1120,13 @@ public class GameManager extends PlaceManager } /** - * Called to let the manager know that the game was cancelled (and may be - * about to be shutdown if there's no one in the room). In the base - * framework a game will only be canceled if no one shows up, so {@link - * #gameWillStart}, etc. will never have been called and thus {@link - * #gameWillEnd}, etc. will not be called. However, if a game chooses to - * cancel itself for whatever reason, no effort will be made to call {@link - * #endGame} and the game ending call backs so that game can override this - * method to do anything it needs. Note that {@link #didShutdown} will be - * called in every case and that's generally the best place to free + * Called to let the manager know that the game was cancelled (and may be about to be shutdown + * if there's no one in the room). In the base framework a game will only be canceled if no one + * shows up, so {@link #gameWillStart}, etc. will never have been called and thus {@link + * #gameWillEnd}, etc. will not be called. However, if a game chooses to cancel itself for + * whatever reason, no effort will be made to call {@link #endGame} and the game ending call + * backs so that game can override this method to do anything it needs. Note that {@link + * #didShutdown} will be called in every case and that's generally the best place to free * resources so this method may not be needed. */ protected void gameWasCancelled () @@ -1198,8 +1135,7 @@ public class GameManager extends PlaceManager } /** - * Report winner and loser oids to each room that any of the - * winners/losers is in. + * Report winner and loser oids to each room that any of the winners/losers is in. */ protected void reportWinnersAndLosers () { @@ -1218,8 +1154,7 @@ public class GameManager extends PlaceManager } } - Object[] args = - new Object[] { winners.toIntArray(), losers.toIntArray() }; + Object[] args = new Object[] { winners.toIntArray(), losers.toIntArray() }; // now send a message event to each room for (int ii=0, nn = places.size(); ii < nn; ii++) { @@ -1231,10 +1166,9 @@ public class GameManager extends PlaceManager } /** - * Called when the game is about to reset, but before the board has - * been re-initialized or any other clearing out of game data has - * taken place. Derived classes should override this if they need to - * perform some pre-reset activities. + * Called when the game is about to reset, but before the board has been re-initialized or any + * other clearing out of game data has taken place. Derived classes should override this if + * they need to perform some pre-reset activities. */ protected void gameWillReset () { @@ -1250,8 +1184,8 @@ public class GameManager extends PlaceManager } /** - * Gives game managers an opportunity to perform periodic processing - * that is not driven by events generated by the player. + * Gives game managers an opportunity to perform periodic processing that is not driven by + * events generated by the player. */ protected void tick (long tickStamp) { @@ -1259,8 +1193,7 @@ public class GameManager extends PlaceManager } /** - * Called periodically to call {@link #tick} on all registered game - * managers. + * Called periodically to call {@link #tick} on all registered game managers. */ protected static void tickAllGames () { @@ -1271,8 +1204,7 @@ public class GameManager extends PlaceManager try { gmgr.tick(now); } catch (Exception e) { - Log.warning( - "Game manager choked during tick [gmgr=" + gmgr + "]."); + Log.warning("Game manager choked during tick [gmgr=" + gmgr + "]."); Log.logStackTrace(e); } } @@ -1298,12 +1230,11 @@ public class GameManager extends PlaceManager } /** Listens for game state changes. */ - protected AttributeChangeListener _stateListener = - new AttributeChangeListener() { + protected AttributeChangeListener _stateListener = new AttributeChangeListener() { public void attributeChanged (AttributeChangedEvent event) { if (event.getName().equals(GameObject.STATE)) { stateDidChange(_committedState = event.getIntValue(), - ((Integer)event.getOldValue()).intValue()); + ((Integer)event.getOldValue()).intValue()); } } }; @@ -1320,19 +1251,17 @@ public class GameManager extends PlaceManager /** The oids of our player and AI body objects. */ protected int[] _playerOids; - /** If AIs are present, contains their configuration, or null at human - * player indexes. */ + /** If AIs are present, contains their configuration, or null at human player indexes. */ protected GameAI[] _AIs; - /** If non-null, contains bundles and messages that should be sent as - * system messages once the game has started. */ + /** If non-null, contains bundles and messages that should be sent as system messages once the + * game has started. */ protected ArrayList> _startmsgs; /** Our delegate operator to tick AIs. */ protected TickAIDelegateOp _tickAIOp; - /** The state of the game that has been propagated to our - * subscribers. */ + /** The state of the game that has been propagated to our subscribers. */ protected int _committedState; /** TEMP: debugging the pending rating double release bug. */ @@ -1342,14 +1271,13 @@ public class GameManager extends PlaceManager protected boolean _postponedStart = false; /** A list of all currently active game managers. */ - protected static ArrayList _managers = - new ArrayList(); + protected static ArrayList _managers = new ArrayList(); /** The interval for the game manager tick. */ protected static Interval _tickInterval; - /** We give players 30 seconds to turn up in a game; after that, - * they're considered a no show. */ + /** We give players 30 seconds to turn up in a game; after that, they're considered a no + * show. */ protected static final long NOSHOW_DELAY = 30 * 1000L; /** The delay in milliseconds between ticking of all game managers. */ diff --git a/tests/build.xml b/tests/build.xml index 6a76a00f..64d63cdc 100644 --- a/tests/build.xml +++ b/tests/build.xml @@ -96,7 +96,7 @@ + debug="on" optimize="off" deprecation="on"> diff --git a/tests/src/java/com/threerings/parlor/TestConfig.java b/tests/src/java/com/threerings/parlor/TestConfig.java index ff2212ec..26525d67 100644 --- a/tests/src/java/com/threerings/parlor/TestConfig.java +++ b/tests/src/java/com/threerings/parlor/TestConfig.java @@ -21,6 +21,7 @@ package com.threerings.parlor; +import com.threerings.crowd.client.PlaceController; import com.threerings.parlor.game.client.GameConfigurator; import com.threerings.parlor.game.data.GameConfig; @@ -29,19 +30,24 @@ public class TestConfig extends GameConfig /** The foozle parameter. */ public int foozle; + public int getGameId () + { + return 0; + } + + public String getGameIdent () + { + return "test"; + } + public GameConfigurator createConfigurator () { return null; } - public Class getControllerClass () + public PlaceController createController () { - return TestController.class; - } - - public String getBundleName () - { - return "test"; + return new TestController(); } public String getManagerClassName () diff --git a/tests/src/java/com/threerings/whirled/TestConfig.java b/tests/src/java/com/threerings/whirled/TestConfig.java index d420d796..6a7ecb23 100644 --- a/tests/src/java/com/threerings/whirled/TestConfig.java +++ b/tests/src/java/com/threerings/whirled/TestConfig.java @@ -21,14 +21,15 @@ package com.threerings.whirled; +import com.threerings.crowd.client.PlaceController; import com.threerings.crowd.data.PlaceConfig; import com.threerings.whirled.server.SceneManager; public class TestConfig extends PlaceConfig { - public Class getControllerClass () + public PlaceController createController () { - return TestController.class; + return new TestController(); } public String getManagerClassName ()