More progress on table lobby. Everything mostly works. Need to fix up some

boundary cases and deal with users and games going away.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@530 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2001-10-23 20:24:10 +00:00
parent 246987893d
commit 1849e90797
8 changed files with 648 additions and 20 deletions
@@ -1,5 +1,5 @@
//
// $Id: LobbyConfig.java,v 1.4 2001/10/11 21:08:21 mdb Exp $
// $Id: LobbyConfig.java,v 1.5 2001/10/23 20:24:10 mdb Exp $
package com.threerings.micasa.lobby;
@@ -7,12 +7,15 @@ import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import javax.swing.JComponent;
import javax.swing.JLabel;
import java.util.Properties;
import com.samskivert.util.StringUtil;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.parlor.game.GameConfig;
import com.threerings.micasa.util.MiCasaContext;
public class LobbyConfig extends PlaceConfig
{
@@ -28,6 +31,15 @@ public class LobbyConfig extends PlaceConfig
return "com.threerings.micasa.lobby.LobbyManager";
}
/**
* Derived classes override this function and create the appropriate
* matchmaking user interface component.
*/
public JComponent createMatchMakingView (MiCasaContext ctx)
{
return new JLabel("Match-making view goes here.");
}
/**
* Instantiates and returns a game config instance using the game
* config classname provided by the lobby configuration parameters.
@@ -1,5 +1,5 @@
//
// $Id: LobbyController.java,v 1.6 2001/10/11 21:08:21 mdb Exp $
// $Id: LobbyController.java,v 1.7 2001/10/23 20:24:10 mdb Exp $
package com.threerings.micasa.lobby;
@@ -33,7 +33,7 @@ public class LobbyController
protected PlaceView createPlaceView ()
{
return new LobbyPanel(_ctx);
return new LobbyPanel(_ctx, _config);
}
// documentation inherited
@@ -1,5 +1,5 @@
//
// $Id: LobbyPanel.java,v 1.6 2001/10/11 04:13:33 mdb Exp $
// $Id: LobbyPanel.java,v 1.7 2001/10/23 20:24:10 mdb Exp $
package com.threerings.micasa.lobby;
@@ -24,7 +24,7 @@ public class LobbyPanel
* Constructs a new lobby panel and the associated user interface
* elements.
*/
public LobbyPanel (MiCasaContext ctx)
public LobbyPanel (MiCasaContext ctx, LobbyConfig config)
{
// we want a five pixel border around everything
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
@@ -42,7 +42,7 @@ public class LobbyPanel
_main = new JPanel(gl);
// create our match-making view
_main.add(createMatchMakingView(ctx));
_main.add(config.createMatchMakingView(ctx));
// create a chat box and stick that in as well
_main.add(new ChatPanel(ctx));
@@ -55,16 +55,16 @@ public class LobbyPanel
gl.setOffAxisPolicy(GroupLayout.STRETCH);
JPanel sidePanel = new JPanel(gl);
// the sidebar contains a lobby selector...
// the sidebar contains a lobby info display...
// ...a lobby selector...
JLabel label = new JLabel("Select a lobby...");
sidePanel.add(label, GroupLayout.FIXED);
LobbySelector selector = new LobbySelector(ctx);
sidePanel.add(selector);
// ...a lobby info display...
// and an occupants list
label = new JLabel("Occupants");
label = new JLabel("People in lobby");
sidePanel.add(label, GroupLayout.FIXED);
_occupants = new OccupantList(ctx);
sidePanel.add(_occupants);
@@ -78,15 +78,6 @@ public class LobbyPanel
add(sidePanel, GroupLayout.FIXED);
}
/**
* Derived classes override this function and create the appropriate
* matchmaking user interface component.
*/
protected JComponent createMatchMakingView (MiCasaContext ctx)
{
return new JLabel("Match-making view goes here.");
}
// documentation inherited
public void willEnterPlace (PlaceObject plobj)
{
@@ -0,0 +1,211 @@
//
// $Id: TableItem.java,v 1.1 2001/10/23 20:24:10 mdb Exp $
package com.threerings.micasa.lobby.table;
import java.awt.Color;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import com.samskivert.util.StringUtil;
import com.threerings.crowd.data.BodyObject;
import com.threerings.parlor.client.TableManager;
import com.threerings.parlor.client.SeatednessObserver;
import com.threerings.parlor.data.Table;
import com.threerings.parlor.data.TableConfig;
import com.threerings.micasa.Log;
import com.threerings.micasa.util.MiCasaContext;
/**
* A table item displays the user interface for a single table (whether it
* be in-play or still being matchmade).
*/
public class TableItem
extends JPanel
implements ActionListener, SeatednessObserver
{
/** A reference to the table we are displaying. */
public Table table;
/**
* Creates a new table item to display and interact with the supplied
* table.
*/
public TableItem (MiCasaContext ctx, TableManager tmgr, Table table)
{
// keep track of these
_tmgr = tmgr;
// add ourselves as a seatedness observer
_tmgr.addSeatednessObserver(this);
// figure out who we are
_self = ((BodyObject)ctx.getClient().getClientObject()).username;
// grab the table config reference
_tconfig = (TableConfig)table.config;
// now create our user interface
setBorder(BorderFactory.createLineBorder(Color.black));
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
// create a label for the table
JLabel tlabel = new JLabel("Table " + table.tableId);
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.insets = new Insets(2, 0, 0, 0);
add(tlabel, gbc);
// we have one button for every "seat" at the table
int bcount = _tconfig.getDesiredPlayers();
if (bcount == -1) {
bcount = _tconfig.getMaximumPlayers();
}
// create blank buttons for now and then we'll update everything
// with the current state when we're done
gbc.weightx = 1.0;
gbc.insets = new Insets(2, 0, 2, 0);
_seats = new JButton[bcount];
for (int i = 0; i < bcount; i++) {
// create the button
_seats[i] = new JButton(JOIN_LABEL);
_seats[i].addActionListener(this);
// if we're on the left
if (i % 2 == 0) {
// if we're the last seat, then we've got an odd number
// and need to center this final seat
if (i == bcount-1) {
gbc.gridwidth = GridBagConstraints.REMAINDER;
} else {
gbc.gridwidth = 1;
}
} else {
gbc.gridwidth = GridBagConstraints.REMAINDER;
}
// adjust the insets of our last element
if (i == bcount-1) {
gbc.insets = new Insets(2, 0, 4, 0);
}
// and add the button with the configured constraints
add(_seats[i], gbc);
}
// and update ourselves based on the contents of the occupants
// list
tableUpdated(table);
}
/**
* Called when our table has been updated and we need to update the UI
* to reflect the new information.
*/
public void tableUpdated (Table table)
{
// grab this new table reference
this.table = table;
// first look to see if we're already sitting at a table
boolean isSeated = _tmgr.isSeated();
// now enable and label the buttons accordingly
int slength = _seats.length;
for (int i = 0; i < slength; i++) {
if (StringUtil.blank(table.occupants[i])) {
_seats[i].setText(JOIN_LABEL);
_seats[i].setEnabled(!isSeated);
_seats[i].setActionCommand("join");
} else if (table.occupants[i].equals(_self) &&
!table.inPlay()) {
_seats[i].setText(LEAVE_LABEL);
_seats[i].setEnabled(true);
_seats[i].setActionCommand("leave");
} else {
_seats[i].setText(table.occupants[i]);
_seats[i].setEnabled(false);
}
}
}
/**
* Called by the table list view prior to removing us. Here we clean
* up.
*/
public void tableRemoved ()
{
// no more observy
_tmgr.removeSeatednessObserver(this);
}
// documentation inherited
public void actionPerformed (ActionEvent event)
{
if (event.getActionCommand().equals("join")) {
// figure out what position this button is in
int position = -1;
for (int i = 0; i < _seats.length; i++) {
if (_seats[i] == event.getSource()) {
position = i;
break;
}
}
// sanity check
if (position == -1) {
Log.warning("Unable to figure out what position a <join> " +
"click came from [event=" + event + "].");
} else {
// otherwise, request to join the table at this position
_tmgr.joinTable(table.getTableId(), position);
}
} else {
// if we're not joining, we're leaving
_tmgr.leaveTable(table.getTableId());
}
}
// documentation inherited
public void seatednessDidChange (boolean isSeated)
{
// just update ourselves
tableUpdated(table);
}
/** Our username. */
protected String _self;
/** A reference to our table manager. */
protected TableManager _tmgr;
/** A casted reference to our table config object. */
protected TableConfig _tconfig;
/** We have a button for each "seat" at the table. */
protected JButton[] _seats;
/** The text shown for seats at which the user can join. */
protected static final String JOIN_LABEL = "<join>";
/** The text shown for the seat in which this user occupies and which
* lets her/him know that they can leave that seat by clicking. */
protected static final String LEAVE_LABEL = "<leave>";
}
@@ -0,0 +1,307 @@
//
// $Id: TableListView.java,v 1.1 2001/10/23 20:24:10 mdb Exp $
package com.threerings.micasa.lobby.table;
import java.util.Iterator;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.samskivert.swing.HGroupLayout;
import com.samskivert.swing.VGroupLayout;
import com.threerings.crowd.client.PlaceView;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.parlor.client.TableManager;
import com.threerings.parlor.client.TableObserver;
import com.threerings.parlor.client.SeatednessObserver;
import com.threerings.parlor.data.Table;
import com.threerings.parlor.data.TableConfig;
import com.threerings.parlor.game.GameConfig;
import com.threerings.micasa.Log;
import com.threerings.micasa.lobby.LobbyConfig;
import com.threerings.micasa.util.MiCasaContext;
/**
* A view that displays the tables in a table lobby. It displays two
* separate lists, one of tables being matchmade and another of games in
* progress. These tables are updated dynamically as they proceed through
* the matchmaking process. UI mechanisms for creating and joining tables
* are also provided.
*/
public class TableListView
extends JPanel
implements PlaceView, TableObserver, ActionListener, SeatednessObserver,
ChangeListener
{
/**
* Creates a new table list view, suitable for providing the user
* interface for table-style matchmaking in a table lobby.
*/
public TableListView (MiCasaContext ctx, LobbyConfig config)
{
// keep track of these
_config = config;
_ctx = ctx;
// create our table manager
_tmgr = new TableManager(ctx, TableLobbyObject.TABLES, this);
// add ourselves as a seatedness observer
_tmgr.addSeatednessObserver(this);
// set up a layout manager
HGroupLayout gl = new HGroupLayout(HGroupLayout.STRETCH);
gl.setOffAxisPolicy(HGroupLayout.STRETCH);
setLayout(gl);
// we have two lists of tables, one of tables being matchmade...
VGroupLayout pgl = new VGroupLayout(VGroupLayout.STRETCH);
pgl.setOffAxisPolicy(VGroupLayout.STRETCH);
JPanel panel = new JPanel(pgl);
panel.add(new JLabel("Pending tables"), VGroupLayout.FIXED);
VGroupLayout mgl = new VGroupLayout(VGroupLayout.NONE);
mgl.setOffAxisPolicy(VGroupLayout.STRETCH);
mgl.setJustification(VGroupLayout.TOP);
_matchList = new JPanel(mgl);
_matchList.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
panel.add(new JScrollPane(_matchList));
// create a prototype game config to configure our slider
try {
TableConfig tconfig = (TableConfig)config.getGameConfig();
JPanel cpanel =
new JPanel(new HGroupLayout(HGroupLayout.STRETCH));
cpanel.add(new JLabel("Seats:"), HGroupLayout.FIXED);
int min = tconfig.getMinimumPlayers();
_pslide = new JSlider(JSlider.HORIZONTAL, min,
tconfig.getMaximumPlayers(), min);
_pslide.addChangeListener(this);
cpanel.add(_pslide);
_pcount = new JLabel(Integer.toString(min));
cpanel.add(_pcount, HGroupLayout.FIXED);
_create = new JButton("Create table");
_create.addActionListener(this);
cpanel.add(_create, HGroupLayout.FIXED);
panel.add(cpanel, VGroupLayout.FIXED);
} catch (Exception e) {
Log.warning("Unable to obtain prototype game config to " +
"configure matchmaking interface.");
Log.logStackTrace(e);
}
add(panel);
// ...and one of games in progress
panel = new JPanel(pgl);
panel.add(new JLabel("Games in progress"), VGroupLayout.FIXED);
_playList = new JPanel(mgl);
_playList.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
panel.add(new JScrollPane(_playList));
add(panel);
}
// documentation inherited
public void willEnterPlace (PlaceObject place)
{
// pass the good word on to our table manager
_tmgr.willEnterPlace(place);
// iterate over the tables already active in this lobby and put
// them in their respective lists
TableLobbyObject tlobj = (TableLobbyObject)place;
Iterator iter = tlobj.tables.elements();
while (iter.hasNext()) {
tableAdded((Table)iter.next());
}
}
// documentation inherited
public void didLeavePlace (PlaceObject place)
{
// pass the good word on to our table manager
_tmgr.didLeavePlace(place);
// clear out our table lists
_matchList.removeAll();
_playList.removeAll();
}
// documentation inherited
public void tableAdded (Table table)
{
Log.info("Table added [table=" + table + "].");
// create a table item for this table and insert it into the
// appropriate list
JPanel panel = table.inPlay() ? _playList : _matchList;
panel.add(new TableItem(_ctx, _tmgr, table));
panel.revalidate();
panel.repaint();
}
// documentation inherited
public void tableUpdated (Table table)
{
Log.info("Table updated [table=" + table + "].");
// locate the table item associated with this table
TableItem item = getTableItem(table.getTableId());
if (item == null) {
Log.warning("Received table updated notification for " +
"unknown table [table=" + table + "].");
return;
}
// let the item perform any updates it finds necessary
item.tableUpdated(table);
// and we may need to move the item from the match to the in-play
// list if it just transitioned
if (table.gameOid != -1 && item.getParent() == _matchList) {
_matchList.remove(item);
_matchList.revalidate();
_matchList.repaint();
_playList.add(item);
_playList.revalidate();
_playList.repaint();
}
}
// documentation inherited
public void tableRemoved (int tableId)
{
Log.info("Table removed [tableId=" + tableId + "].");
// locate the table item associated with this table
TableItem item = getTableItem(tableId);
if (item == null) {
Log.warning("Received table removed notification for " +
"unknown table [tableId=" + tableId + "].");
return;
}
// remove this item from the user interface
JPanel panel = (JPanel)item.getParent();
panel.remove(item);
panel.revalidate();
panel.repaint();
// let the little fellow know that we gave him the boot
item.tableRemoved();
}
// documentation inherited
public void actionPerformed (ActionEvent event)
{
// the create table button was clicked. pass the word on to the
// table manager
GameConfig config = null;
try {
config = _config.getGameConfig();
} catch (Exception e) {
Log.warning("Unable to resolve game config class " +
"[lconfig=" + _config + ", error=" + e + "].");
return;
}
if (config instanceof TableConfig) {
// set the desired number of players
((TableConfig)config).setDesiredPlayers(_pslide.getValue());
}
// and create a table with these values
_tmgr.createTable(config);
}
// documentation inherited
public void seatednessDidChange (boolean isSeated)
{
// update the create table button
_create.setEnabled(!isSeated);
}
// documentation inherited
public void stateChanged (ChangeEvent e)
{
JSlider source = (JSlider)e.getSource();
if (!source.getValueIsAdjusting()) {
_pcount.setText(Integer.toString(source.getValue()));
}
}
/**
* Fetches the table item component associated with the specified
* table id.
*/
protected TableItem getTableItem (int tableId)
{
// first check the match list
int ccount = _matchList.getComponentCount();
for (int i = 0; i < ccount; i++) {
TableItem child = (TableItem)_matchList.getComponent(i);
if (child.table.getTableId() == tableId) {
return child;
}
}
// then the inplay list
ccount = _playList.getComponentCount();
for (int i = 0; i < ccount; i++) {
TableItem child = (TableItem)_playList.getComponent(i);
if (child.table.getTableId() == tableId) {
return child;
}
}
// sorry charlie
return null;
}
/** A reference to the client context. */
protected MiCasaContext _ctx;
/** A reference to the lobby config for the lobby in which we are
* doing table-style matchmaking. */
protected LobbyConfig _config;
/** A reference to our table manager. */
protected TableManager _tmgr;
/** The list of tables currently being matchmade. */
protected JPanel _matchList;
/** The list of tables that are in play. */
protected JPanel _playList;
/** Our create table button. */
protected JButton _create;
/** Our number of players slider. */
protected JSlider _pslide;
/** Our number of players indicator. */
protected JLabel _pcount;
}
@@ -0,0 +1,27 @@
//
// $Id: TableLobbyConfig.java,v 1.1 2001/10/23 20:24:10 mdb Exp $
package com.threerings.micasa.lobby.table;
import javax.swing.JComponent;
import com.threerings.micasa.lobby.LobbyConfig;
import com.threerings.micasa.util.MiCasaContext;
/**
* Instructs the lobby services to use a {@link TableListView} as the
* matchmaking component.
*/
public class TableLobbyConfig extends LobbyConfig
{
// documentation inherited
public String getManagerClassName ()
{
return "com.threerings.micasa.lobby.table.TableLobbyManager";
}
// documentation inherited
public JComponent createMatchMakingView (MiCasaContext ctx)
{
return new TableListView(ctx, this);
}
}
@@ -0,0 +1,19 @@
//
// $Id: TableLobbyManager.java,v 1.1 2001/10/23 20:24:10 mdb Exp $
package com.threerings.micasa.lobby.table;
import com.threerings.micasa.lobby.LobbyManager;
/**
* Extends lobby manager only to ensure that a table lobby object is used
* for table lobbies.
*/
public class TableLobbyManager extends LobbyManager
{
// documentation inherited
protected Class getPlaceObjectClass ()
{
return TableLobbyObject.class;
}
}
@@ -0,0 +1,61 @@
//
// $Id: TableLobbyObject.dobj,v 1.1 2001/10/23 20:24:10 mdb Exp $
package com.threerings.micasa.lobby.table;
import com.threerings.presents.dobj.DSet;
import com.threerings.parlor.data.Table;
import com.threerings.micasa.lobby.LobbyObject;
public class TableLobbyObject
extends LobbyObject
implements com.threerings.parlor.data.TableLobbyObject
{
/** The field name of the <code>tables</code> field. */
public static final String TABLES = "tables";
/** A set containing all of the tables being managed by this lobby. */
public DSet tables = new DSet(Table.class);
// documentation inherited
public DSet getTables ()
{
return tables;
}
/**
* Requests that the <code>tables</code> field be set to the specified
* value.
*/
public void setTables (DSet value)
{
requestAttributeChange(TABLES, value);
}
/**
* Requests that the specified element be added to the
* <code>tables</code> set.
*/
public void addToTables (Table table)
{
requestElementAdd(TABLES, table);
}
/**
* Requests that the specified element be updated in the
* <code>tables</code> set.
*/
public void updateTables (Table table)
{
requestElementUpdate(TABLES, table);
}
/**
* Requests that the element matching the supplied key be removed from
* the <code>tables</code> set.
*/
public void removeFromTables (Object key)
{
requestElementRemove(TABLES, key);
}
}