diff --git a/src/as/com/threerings/ezgame/PlayersFlexDisplay.as b/src/as/com/threerings/ezgame/PlayersFlexDisplay.as new file mode 100644 index 00000000..fde9ffad --- /dev/null +++ b/src/as/com/threerings/ezgame/PlayersFlexDisplay.as @@ -0,0 +1,189 @@ +// +// $Id$ + +package com.threerings.ezgame { + +import mx.core.ClassFactory; + +import mx.collections.ArrayCollection; +import mx.collections.Sort; + +import mx.containers.VBox; + +import mx.controls.List; + +/** + * A players display in flex. + * Feel free to subclass/rewrite/copy/alter/tweak/whatever as much as you want. + * Check out ScorePlayersFlexDisplay for an example of a subclass. + */ +public class PlayersFlexDisplay extends VBox +{ + public function PlayersFlexDisplay () + { + super(); + } + + /** + * Called to configure this display to start displaying. This is separated from the + * constructor to make subclassing easier. + */ + public function configure (gameCtrl :EZGameControl, playersOnly :Boolean = false) :void + { + _ctrl = gameCtrl; + _playersOnly = playersOnly; + _ourId = _ctrl.getMyId(); + + // set up the UI + createInterface(); + + // add listeners + _ctrl.addEventListener(StateChangedEvent.TURN_CHANGED, handleTurnChanged); + _ctrl.addEventListener(OccupantChangedEvent.OCCUPANT_ENTERED, handleOccupantEntered); + _ctrl.addEventListener(OccupantChangedEvent.OCCUPANT_LEFT, handleOccupantLeft); + + // set up the sort for the collection + var sort :Sort = new Sort(); + sort.compareFunction = sortFunction; + _players.sort = sort; + + // find all the current occupants and add them to our list of players + var list :Array; + if (_playersOnly) { + list = _ctrl.seating.getPlayerIds(); + + } else { + list = _ctrl.getOccupants(); + } + for each (var occupantId :int in list) { + _players.addItem(createRecord(occupantId)); + } + updateList(); + } + + /** + * Create the UI. + */ + protected function createInterface () :void + { + _list = new List(); + _list.itemRenderer = new ClassFactory(getRendererClass()); + _list.dataProvider = _players; + + addChild(_list); + } + + /** + * Return the class to use as our List itemRenderer. + */ + protected function getRendererClass () :Class + { + return SimplePlayerRenderer; + } + + /** + * Handle an update to the current turn. + * This is only applicable for turn-based games. + */ + protected function handleTurnChanged (event :StateChangedEvent) :void + { + // TODO + } + + /** + * Handle the addition of a new occupant to the game. + */ + protected function handleOccupantEntered (event :OccupantChangedEvent) :void + { + // if we're only displaying players, and this isn't, then don't show 'em! + if (_playersOnly && !event.player) { + return; + } + + _players.addItem(createRecord(event.occupantId)); + } + + /** + * Handle the removal of an occupant to the game. + */ + protected function handleOccupantLeft (event :OccupantChangedEvent) :void + { + for (var ii :int = _players.length - 1; ii >= 0; ii--) { + var rec :Object = _players.getItemAt(ii); + if (rec.id == event.occupantId) { + _players.removeItemAt(ii); + return; + } + } + + // don't worry if we never found the record.. + } + + /** + * Should be called when records are added to the list, or list data has changed such + * that it should be re-sorted. + */ + protected function updateList () :void + { + _players.refresh(); + } + + /** + * Create an associative hash to hold properties related to a player or occupant. + * This class assumes that 'id' will hold the occupantId, and that 'name' will hold + * the name. Other fields can be added and a custom renderer can be used to display those + * fields. + * + * @return an Object populated with the occupant's properties, or null if the occupant + * should be ommitted + */ + protected function createRecord (occupantId :int) :Object + { + var record :Object = new Object(); + record.id = occupantId; + record.name = _ctrl.getOccupantName(occupantId); + + return record; + } + + /** + * The sort function that will be used to display occupant records. + * + * @return -1 if rec1 should be first, 0 if they are equal (?), 1 if rec2 should be first. + */ + protected function sortFunction (rec1 :Object, rec2 :Object, fields :Array = null) :int + { + // make our record always on top.. + if (rec1.id == _ourId) { + return -1; + } + if (rec2.id == _ourId) { + return 1; + } + + // otherwise, sort by name + if (rec1.name < rec2.name) { + return -1; + } + if (rec1.name > rec2.name) { + return 1; + } + return 0; // huh.. same damn name + } + + /** The game control. */ + protected var _ctrl :EZGameControl; + + /** The occupantId of the occupant that's instatiated this widget. */ + protected var _ourId :int; + + /** Are we only displaying players (not occupants)? */ + protected var _playersOnly :Boolean; + + /** The List widget that displays the players. */ + protected var _list :List; + + /** The list of player data. */ + protected var _players :ArrayCollection = new ArrayCollection(); +} +} diff --git a/src/as/com/threerings/ezgame/ScorePlayerRenderer.as b/src/as/com/threerings/ezgame/ScorePlayerRenderer.as new file mode 100644 index 00000000..e19e6270 --- /dev/null +++ b/src/as/com/threerings/ezgame/ScorePlayerRenderer.as @@ -0,0 +1,28 @@ +// +// $Id$ + +package com.threerings.ezgame { + +import mx.controls.Label; + +public class ScorePlayerRenderer extends SimplePlayerRenderer +{ + override protected function createUI () :void + { + super.createUI(); + + addChild(_scoreLabel = new Label()); + _scoreLabel.maxWidth = 100; + } + + override protected function configureUI () :void + { + super.configureUI(); + + _scoreLabel.text = String(data.score); + } + + /* The label used to display the player's score. */ + protected var _scoreLabel :Label; +} +} diff --git a/src/as/com/threerings/ezgame/ScorePlayersFlexDisplay.as b/src/as/com/threerings/ezgame/ScorePlayersFlexDisplay.as new file mode 100644 index 00000000..cd0e55b9 --- /dev/null +++ b/src/as/com/threerings/ezgame/ScorePlayersFlexDisplay.as @@ -0,0 +1,79 @@ +// +// $Id$ + +package com.threerings.ezgame { + +/** + * Displays a player list with integer scores, sorting players according to their score. + */ +// TODO: retain scores for players that are absent? +public class ScorePlayersFlexDisplay extends PlayersFlexDisplay +{ + public function ScorePlayersFlexDisplay () + { + super(); + } + + /** + * Update the score for a single player. + */ + public function setScore (occupantId :int, score :int) :void + { + for (var ii :int = _players.length - 1; ii >= 0; ii--) { + var rec :Object = _players.getItemAt(ii); + if (rec.id == occupantId) { + rec.score = score; + updateList(); + return; + } + } + + // hmm, never found... + } + + /** + * Mass-update scores for players. + * + * @param mapping a hash mapping occupantId -> score. + */ + public function setScores (mapping :Object) :void + { + for (var ii :int = _players.length - 1; ii >= 0; ii--) { + var rec :Object = _players.getItemAt(ii); + trace("Examining " + rec.id); + if (String(rec.id) in mapping) { + rec.score = int(mapping[String(rec.id)]); + trace("Set score of " + rec.id + " to " + rec.score); + } + } + + updateList(); + } + + override protected function getRendererClass () :Class + { + return ScorePlayerRenderer; + } + + override protected function createRecord (occupantId :int) :Object + { + var record :Object = super.createRecord(occupantId); + record.score = 0; + return record; + } + + override protected function sortFunction (rec1 :Object, rec2 :Object, fields :Array = null) :int + { + // always sort on score + if (rec1.score > rec2.score) { + return -1; + } + if (rec1.score < rec2.score) { + return 1; + } + + // if two players have the same score then sort according to the superclass rules + return super.sortFunction(rec1, rec2, fields); + } +} +} diff --git a/src/as/com/threerings/ezgame/SimplePlayerRenderer.as b/src/as/com/threerings/ezgame/SimplePlayerRenderer.as new file mode 100644 index 00000000..4a053608 --- /dev/null +++ b/src/as/com/threerings/ezgame/SimplePlayerRenderer.as @@ -0,0 +1,72 @@ +// +// $Id$ + +package com.threerings.ezgame { + +import mx.containers.HBox; + +import mx.controls.Label; + +import mx.core.ScrollPolicy; + + +public class SimplePlayerRenderer extends HBox +{ + public function SimplePlayerRenderer () + { + super(); + + configureProperties(); + } + + override public function set data (value :Object) :void + { + super.data = value; + + if (processedDescriptors) { + configureUI(); + } + } + + override protected function createChildren () :void + { + super.createChildren(); + + createUI(); + + if (data != null) { + data = data; // re-set + } + } + + /** + * Configure any UI properties of this renderer. + */ + protected function configureProperties () :void + { + verticalScrollPolicy = ScrollPolicy.OFF; + horizontalScrollPolicy = ScrollPolicy.OFF; + } + + /** + * Create any UI elements needed. + */ + protected function createUI () :void + { + addChild(_nameLabel = new Label()); + _nameLabel.maxWidth = 100; + } + + /** + * Configure the actual UI elements with the data. + */ + protected function configureUI () :void + { + _nameLabel.text = String(data.name); + } + + /** The label used to display the player's name. */ + protected var _nameLabel :Label; +} + +}