Initial version of game simulator to facilitate game testing. Runs a

client and game server in a single process, creating as many Simulant
players to round out the game as desired.  Still more work to do, but
currently creates simulants, starts up the game, and awaits players.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@837 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Walter Korman
2001-12-19 09:32:02 +00:00
parent 6d350588c2
commit 3e1e1dea83
12 changed files with 879 additions and 0 deletions
@@ -0,0 +1,118 @@
//
// $Id: ClientController.java,v 1.1 2001/12/19 09:32:02 shaper Exp $
package com.threerings.micasa.simulator.client;
import java.awt.event.ActionEvent;
import com.samskivert.swing.Controller;
import com.samskivert.util.StringUtil;
import com.threerings.presents.dobj.*;
import com.threerings.presents.client.*;
import com.threerings.crowd.client.*;
import com.threerings.crowd.data.*;
import com.threerings.parlor.game.GameConfig;
import com.threerings.micasa.Log;
import com.threerings.micasa.simulator.util.SimulatorContext;
import com.threerings.micasa.simulator.data.SimulatorInfo;
/**
* Responsible for top-level control of the client user interface.
*/
public class ClientController
extends Controller
implements ClientObserver
{
/**
* Creates a new client controller. The controller will set everything
* up in preparation for logging on.
*/
public ClientController (SimulatorContext ctx, SimulatorFrame frame)
{
// we'll want to keep these around
_ctx = ctx;
_frame = frame;
// we want to know about logon/logoff
_ctx.getClient().addObserver(this);
}
// documentation inherited
public boolean handleAction (ActionEvent action)
{
String cmd = action.getActionCommand();
if (cmd.equals("logoff")) {
// request that we logoff
_ctx.getClient().logoff(true);
return true;
}
Log.info("Unhandled action: " + action);
return false;
}
// documentation inherited
public void clientDidLogon (Client client)
{
Log.info("Client did logon [client=" + client + "].");
// keep the body object around for stuff
_body = (BodyObject)client.getClientObject();
// get a handle on the simulator info
SimulatorInfo siminfo = _ctx.getSimulatorInfo();
// create the game config object
GameConfig config = null;
try {
config = (GameConfig)
Class.forName(siminfo.gameConfigClass).newInstance();
} catch (Exception e) {
Log.warning("Failed to instantiate game config " +
"[class=" + siminfo.gameConfigClass + "].");
return;
}
// send the game creation request
SimulatorDirector.createGame(
client, config, siminfo.simClass, siminfo.playerCount);
// our work here is done, as the location manager will move us
// into the game room straightaway
}
// documentation inherited
public void clientFailedToLogon (Client client, Exception cause)
{
Log.info("Client failed to logon [client=" + client +
", cause=" + cause + "].");
}
// documentation inherited
public void clientConnectionFailed (Client client, Exception cause)
{
Log.info("Client connection failed [client=" + client +
", cause=" + cause + "].");
}
// documentation inherited
public boolean clientWillLogoff (Client client)
{
Log.info("Client will logoff [client=" + client + "].");
return true;
}
// documentation inherited
public void clientDidLogoff (Client client)
{
Log.info("Client did logoff [client=" + client + "].");
}
protected SimulatorContext _ctx;
protected SimulatorFrame _frame;
protected BodyObject _body;
}
@@ -0,0 +1,141 @@
//
// $Id: SimulatorApp.java,v 1.1 2001/12/19 09:32:02 shaper Exp $
package com.threerings.micasa.simulator.client;
import com.samskivert.swing.util.SwingUtil;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.ClientAdapter;
import com.threerings.presents.net.Credentials;
import com.threerings.presents.net.UsernamePasswordCreds;
import com.threerings.micasa.Log;
import com.threerings.micasa.simulator.data.SimulatorInfo;
import com.threerings.micasa.simulator.server.SimulatorServer;
/**
* The simulator application is a test harness to facilitate development
* and debugging of games.
*/
public class SimulatorApp
{
public void init (String[] args) throws Exception
{
// create the server
SimulatorServer server = new SimulatorServer();
server.init();
_serverThread = new ServerThread(server);
// create a frame
_frame = new SimulatorFrame();
// create the simulator info object
SimulatorInfo siminfo = new SimulatorInfo();
siminfo.gameConfigClass = args[0];
siminfo.simClass = args[1];
siminfo.playerCount = getInt(
System.getProperty("playercount"), DEFAULT_PLAYER_COUNT);
// create our client instance
_client = new SimulatorClient(_frame, siminfo);
}
public void run ()
{
// size and display the window
int wid = getInt(System.getProperty("width"), 800);
int hei = getInt(System.getProperty("height"), 600);
_frame.setSize(wid, hei);
SwingUtil.centerWindow(_frame);
_frame.show();
// start up the server
_serverThread.start();
// start up the client
Client client = _client.getContext().getClient();
// we're connecting to our own server
client.setServer("localhost", Client.DEFAULT_SERVER_PORT);
// we want to exit when we logged off or failed to log on
client.addObserver(new ClientAdapter() {
public void clientFailedToLogon (Client c, Exception cause) {
System.exit(0);
}
public void clientDidLogoff (Client c) {
System.exit(0);
}
});
// configure the client with some credentials and logon
String username = System.getProperty("username");
if (username == null) {
username =
"bob" + ((int)(Math.random() * Integer.MAX_VALUE) % 500);
}
// create and set our credentials
Credentials creds = new UsernamePasswordCreds(username, "test");
client.setCredentials(creds);
client.logon();
}
public static void main (String[] args)
{
if (args.length < 2) {
String msg = "Usage:\n" +
" java com.threerings.simulator.SimulatorApp " +
"<game config class name> <simulant class name>\n" +
"Optional properties:\n" +
" -Dusername=<user>\n" +
" -Dplayercount=<number>\n" +
" -Dwidth=<width>\n" +
" -Dheight=<height>";
System.out.println(msg);
return;
}
SimulatorApp app = new SimulatorApp();
try {
app.init(args);
} catch (Exception e) {
Log.warning("Error initializing application.");
Log.logStackTrace(e);
}
app.run();
}
protected int getInt (String value, int defval)
{
try {
return Integer.parseInt(value);
} catch (NumberFormatException nfe) {
return defval;
}
}
protected static class ServerThread extends Thread
{
public ServerThread (SimulatorServer server)
{
_server = server;
}
public void run ()
{
_server.run();
}
protected SimulatorServer _server;
}
/** The default number of players in the game. */
protected static final int DEFAULT_PLAYER_COUNT = 2;
protected SimulatorClient _client;
protected SimulatorFrame _frame;
protected ServerThread _serverThread;
}
@@ -0,0 +1,149 @@
//
// $Id: SimulatorClient.java,v 1.1 2001/12/19 09:32:02 shaper Exp $
package com.threerings.micasa.simulator.client;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import com.samskivert.swing.Controller;
import com.samskivert.util.Config;
import com.threerings.presents.client.Client;
import com.threerings.presents.dobj.DObjectManager;
import com.threerings.crowd.client.LocationDirector;
import com.threerings.crowd.client.OccupantManager;
import com.threerings.crowd.client.PlaceView;
import com.threerings.parlor.client.ParlorDirector;
import com.threerings.micasa.Log;
import com.threerings.micasa.simulator.data.SimulatorInfo;
import com.threerings.micasa.simulator.util.SimulatorContext;
public class SimulatorClient
implements Client.Invoker
{
public SimulatorClient (SimulatorFrame frame, SimulatorInfo siminfo)
throws IOException
{
// create our context
_ctx = new SimulatorContextImpl();
// create the handles on our various services
_config = new Config();
_client = new Client(null, this);
// create our managers and directors
_locdir = new LocationDirector(_ctx);
_occmgr = new OccupantManager(_ctx);
_pardtr = new ParlorDirector(_ctx);
// for test purposes, hardcode the server info
_client.setServer("localhost", 4007);
// keep this for later
_frame = frame;
_siminfo = siminfo;
// log off when they close the window
_frame.addWindowListener(new WindowAdapter() {
public void windowClosing (WindowEvent evt) {
// if we're logged on, log off
if (_client.loggedOn()) {
_client.logoff(true);
}
}
});
// create our client controller and stick it in the frame
Controller ctrl = new ClientController(_ctx, _frame);
_frame.setController(ctrl);
}
/**
* Returns a reference to the context in effect for this client. This
* reference is valid for the lifetime of the application.
*/
public SimulatorContext getContext ()
{
return _ctx;
}
// documentation inherited
public void invokeLater (Runnable run)
{
// queue it on up on the swing thread
SwingUtilities.invokeLater(run);
}
/**
* The context implementation. This provides access to all of the
* objects and services that are needed by the operating client.
*/
protected class SimulatorContextImpl implements SimulatorContext
{
public Config getConfig ()
{
return _config;
}
public Client getClient ()
{
return _client;
}
public DObjectManager getDObjectManager ()
{
return _client.getDObjectManager();
}
public LocationDirector getLocationDirector ()
{
return _locdir;
}
public OccupantManager getOccupantManager ()
{
return _occmgr;
}
public ParlorDirector getParlorDirector ()
{
return _pardtr;
}
public void setPlaceView (PlaceView view)
{
// stick the place view into our frame
_frame.setPanel((JPanel)view);
}
public SimulatorFrame getFrame ()
{
return _frame;
}
public SimulatorInfo getSimulatorInfo ()
{
return _siminfo;
}
}
protected SimulatorContext _ctx;
protected SimulatorFrame _frame;
protected SimulatorInfo _siminfo;
protected Config _config;
protected Client _client;
protected LocationDirector _locdir;
protected OccupantManager _occmgr;
protected ParlorDirector _pardtr;
}
@@ -0,0 +1,14 @@
//
// $Id: SimulatorCodes.java,v 1.1 2001/12/19 09:32:02 shaper Exp $
package com.threerings.micasa.simulator.client;
public interface SimulatorCodes
{
/** The module name for the simulator services. */
public static final String MODULE_NAME = "simulator";
/** The message identifier for a create table request. */
public static final String CREATE_GAME_REQUEST = "CreateGame";
}
@@ -0,0 +1,31 @@
//
// $Id: SimulatorDirector.java,v 1.1 2001/12/19 09:32:02 shaper Exp $
package com.threerings.micasa.simulator.client;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationDirector;
import com.threerings.parlor.game.GameConfig;
public class SimulatorDirector implements SimulatorCodes
{
/**
* Requests that a new game be created.
*
* @param client a connected, operational client instance.
* @param config the game config for the game to be created.
* @param simClass the class name of the simulant to create.
* @param playerCount the number of players in the game.
*
* @return the invocation request id of the generated request.
*/
public static int createGame (
Client client, GameConfig config, String simClass, int playerCount)
{
InvocationDirector invdir = client.getInvocationDirector();
Object[] args = new Object[] {
config, simClass, new Integer(playerCount) };
return invdir.invoke(MODULE_NAME, CREATE_GAME_REQUEST, args, null);
}
}
@@ -0,0 +1,57 @@
//
// $Id: SimulatorFrame.java,v 1.1 2001/12/19 09:32:02 shaper Exp $
package com.threerings.micasa.simulator.client;
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import com.samskivert.swing.Controller;
import com.samskivert.swing.ControllerProvider;
/**
* Contains the user interface for the Simulator client application.
*/
public class SimulatorFrame
extends JFrame implements ControllerProvider
{
/**
* Constructs the top-level Simulator client frame.
*/
public SimulatorFrame ()
{
super("Simulator");
}
/**
* Sets the panel that makes up the entire client display.
*/
public void setPanel (JPanel panel)
{
// remove the old panel
getContentPane().removeAll();
// add the new one
getContentPane().add(panel, BorderLayout.CENTER);
// swing doesn't properly repaint after adding/removing children
validate();
}
/**
* Sets the controller for the outermost scope. This controller will
* handle all actions that aren't handled by controllers of tigher
* scope.
*/
public void setController (Controller controller)
{
_controller = controller;
}
// documentation inherited
public Controller getController ()
{
return _controller;
}
protected Controller _controller;
}
@@ -0,0 +1,22 @@
//
// $Id: SimulatorInfo.java,v 1.1 2001/12/19 09:32:02 shaper Exp $
package com.threerings.micasa.simulator.data;
public class SimulatorInfo
{
/** The game config classname. */
public String gameConfigClass;
/** The simulant classname. */
public String simClass;
/** The number of players in the game. */
public int playerCount;
public String toString ()
{
return "[gameConfigClass=" + gameConfigClass +
", simClass=" + simClass + ", playerCount=" + playerCount + "]";
}
}
@@ -0,0 +1,27 @@
//
// $Id: Simulant.java,v 1.1 2001/12/19 09:32:02 shaper Exp $
package com.threerings.micasa.simulator.client;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.PlaceObject;
public abstract class Simulant
{
/**
* Sets the body object associated with this simulant.
*/
public void setBodyObject (BodyObject self)
{
_self = self;
}
/**
* Called when the simulant is about to enter the room in which it
* will be doing all of its business.
*/
public abstract void willEnterPlace (PlaceObject plobj);
/** Our body object. */
protected BodyObject _self;
}
@@ -0,0 +1,201 @@
//
// $Id: SimulatorManager.java,v 1.1 2001/12/19 09:32:02 shaper Exp $
package com.threerings.micasa.simulator.server;
import java.util.ArrayList;
import com.samskivert.util.Config;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.server.InvocationManager;
import com.threerings.presents.server.InvocationProvider;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.server.LocationProvider;
import com.threerings.crowd.server.PlaceManager;
import com.threerings.crowd.server.PlaceRegistry;
import com.threerings.crowd.server.PlaceRegistry.CreationObserver;
import com.threerings.parlor.game.GameConfig;
import com.threerings.parlor.game.GameManager;
import com.threerings.parlor.game.GameObject;
import com.threerings.micasa.Log;
import com.threerings.micasa.simulator.client.Simulant;
import com.threerings.micasa.simulator.client.SimulatorCodes;
/**
* The simulator manager is responsible for handling the simulator
* services on the server side.
*/
public class SimulatorManager implements SimulatorCodes
{
/**
* Initializes the simulator manager manager. This should be called by
* the server that is making use of the simulator services on the
* single instance of simulator manager that it has created.
*
* @param config the configuration object in use by this server.
* @param invmgr a reference to the invocation manager in use by this
* server.
*/
public void init (Config config, InvocationManager invmgr)
{
// register our simulator provider
SimulatorProvider sprov = new SimulatorProvider(this);
invmgr.registerProvider(MODULE_NAME, sprov);
}
/**
* Creates a game along with the specified number of simulant players
* and forcibly moves all players into the game room.
*/
public void createGame (
BodyObject source, GameConfig config, String simClass, int playerCount)
{
new CreateGameTask(source, config, simClass, playerCount);
}
public static class CreateGameTask implements CreationObserver
{
public CreateGameTask (
BodyObject source, GameConfig config, String simClass,
int playerCount)
{
// save off game request info
_source = source;
_simClass = simClass;
_playerCount = playerCount;
try {
// create the game manager and begin its initialization
// process. the game manager will take care of notifying
// the players that the game has been created once it has
// been started up (which is done by the place registry
// once the game object creation has completed)
// we needn't hang around and wait for game object
// creation if it's just us
CreationObserver obs = (_playerCount == 1) ? null : this;
_gmgr = (GameManager)
SimulatorServer.plreg.createPlace(config, obs);
// give the game manager the player names
String[] names = new String[_playerCount];
names[0] = _source.username;
for (int ii = 1; ii < _playerCount; ii++) {
names[ii] = "simulant" + ii;
}
_gmgr.setPlayers(names);
} catch (Exception e) {
Log.warning("Unable to create game manager [e=" + e + "].");
Log.logStackTrace(e);
}
}
// documentation inherited
public void placeCreated (PlaceObject place, PlaceManager pmgr)
{
Log.info("Simulator provider notified of place creation " +
"[place=" + place + "].");
// cast the place to the game object for the game we're creating
_gobj = (GameObject)place;
// create a subscriber to await creation of each simulant body
// object
Subscriber sub = new Subscriber() {
public void objectAvailable (DObject object)
{
Log.info("Simulant body object is available.");
// set up the simulant's body object
BodyObject bobj = (BodyObject)object;
bobj.username = "simulant" + _sims.size();
// hold onto it for later game creation
_sims.add(bobj);
// create the game if we've received all body objects
if (_sims.size() == (_playerCount - 1)) {
createSimulants();
}
}
public void requestFailed (
int oid, ObjectAccessException cause)
{
Log.warning("Unable to create simulant object " +
"[class=" + _simClass +
", error=" + cause + "].");
}
};
// fire off simulant body object creation requests
Class simobjClass = SimulatorServer.clmgr.getClientObjectClass();
for (int ii = 1; ii < _playerCount; ii++) {
SimulatorServer.omgr.createObject(simobjClass, sub);
}
}
/**
* Called when all simulant body objects are present and the
* simulants are ready to be created.
*/
protected void createSimulants ()
{
// finish setting up the simulants
for (int ii = 1; ii < _playerCount; ii++) {
// create the simulant object
Simulant sim;
try {
sim = (Simulant)Class.forName(_simClass).newInstance();
} catch (Exception e) {
Log.warning("Can't create simulant " +
"[class=" + _simClass + "].");
return;
}
// give the simulant its body
BodyObject bobj = (BodyObject)_sims.get(ii - 1);
sim.setBodyObject(bobj);
// give the simulant a chance to engage in place antics
sim.willEnterPlace(_gobj);
// move the simulant into the game room since they have no
// location director to move them automagically
try {
LocationProvider.moveTo(bobj, _gobj.getOid());
} catch (Exception e) {
Log.warning("Failed to move simulant into room " +
"[e=" + e + "].");
return;
}
}
}
/** The simulant body objects. */
protected ArrayList _sims = new ArrayList();
/** The game object for the game being created. */
protected GameObject _gobj;
/** The game manager for the game being created. */
protected GameManager _gmgr;
/** The number of players in the game. */
protected int _playerCount;
/** The simulant class instantiated on game creation. */
protected String _simClass;
/** The body object of the player requesting the game creation. */
protected BodyObject _source;
}
}
@@ -0,0 +1,43 @@
//
// $Id: SimulatorProvider.java,v 1.1 2001/12/19 09:32:02 shaper Exp $
package com.threerings.micasa.simulator.server;
import com.threerings.presents.server.InvocationProvider;
import com.threerings.crowd.data.BodyObject;
import com.threerings.parlor.game.GameConfig;
import com.threerings.micasa.Log;
/**
* The simulator provider handles game creation requests on the server
* side, passing them off to the {@link SimulatorManager}.
*/
public class SimulatorProvider
extends InvocationProvider
{
/**
* Constructs a simulator provider.
*/
public SimulatorProvider (SimulatorManager simmgr)
{
_simmgr = simmgr;
}
/**
* Processes a request from the client to create a new game.
*/
public void handleCreateGameRequest (
BodyObject source, int invid, GameConfig config,
String simClass, int playerCount)
{
Log.info("handleCreateGameRequest [source=" + source +
", config=" + config + ", simClass=" + simClass +
", playerCount=" + playerCount + "].");
_simmgr.createGame(source, config, simClass, playerCount);
}
/** The simulator manager. */
protected SimulatorManager _simmgr;
}
@@ -0,0 +1,50 @@
//
// $Id: SimulatorServer.java,v 1.1 2001/12/19 09:32:02 shaper Exp $
package com.threerings.micasa.simulator.server;
import com.threerings.crowd.server.CrowdServer;
import com.threerings.parlor.server.ParlorManager;
import com.threerings.micasa.Log;
/**
* This class is the main entry point and general organizer of everything
* that goes on in the Simulator game server process.
*/
public class SimulatorServer extends CrowdServer
{
/** The parlor manager in operation on this server. */
public static ParlorManager parmgr = new ParlorManager();
/** The simulator manager in operation on this server. */
public static SimulatorManager simmgr = new SimulatorManager();
/**
* Initializes all of the server services and prepares for operation.
*/
public void init ()
throws Exception
{
// do the base server initialization
super.init();
// initialize our managers
parmgr.init(config, invmgr);
simmgr.init(config, invmgr);
Log.info("Simulator server initialized.");
}
public static void main (String[] args)
{
SimulatorServer server = new SimulatorServer();
try {
server.init();
server.run();
} catch (Exception e) {
Log.warning("Unable to initialize server.");
Log.logStackTrace(e);
}
}
}
@@ -0,0 +1,26 @@
//
// $Id: SimulatorContext.java,v 1.1 2001/12/19 09:32:02 shaper Exp $
package com.threerings.micasa.simulator.util;
import com.threerings.parlor.util.ParlorContext;
import com.threerings.micasa.simulator.client.SimulatorFrame;
import com.threerings.micasa.simulator.data.SimulatorInfo;
/**
* The simulator context encapsulates the contexts of all of the services
* that are used by the simulator client so that we can pass around one
* single context implementation that provides all of the necessary
* components to all of the services in use.
*/
public interface SimulatorContext
extends ParlorContext
{
/** Returns a reference to the primary user interface frame. */
public SimulatorFrame getFrame ();
/** Returns a reference to the simulator info describing the game and
* other details of the simulation. */
public SimulatorInfo getSimulatorInfo ();
}