Files
narya/src/java/com/threerings/parlor/game/GameManager.java
T
Michael Bayne e54a4d41f4 The great invocation services rethink of 2002! Rearchitected the remote
method invocation services and converted everything to the new style.
Could this be my biggest checkin ever?


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1642 542714f4-19e9-0310-aa3c-eee0fc999fb1
2002-08-14 19:08:01 +00:00

527 lines
16 KiB
Java

//
// $Id: GameManager.java,v 1.38 2002/08/14 19:07:53 mdb Exp $
package com.threerings.parlor.game;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.AttributeChangeListener;
import com.threerings.presents.dobj.AttributeChangedEvent;
import com.threerings.presents.dobj.MessageEvent;
import com.threerings.crowd.chat.SpeakProvider;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.server.PlaceManager;
import com.threerings.crowd.server.CrowdServer;
import com.threerings.crowd.server.CrowdServer;
import com.threerings.crowd.server.PlaceManagerDelegate;
import com.threerings.parlor.Log;
import com.threerings.parlor.data.ParlorCodes;
import com.threerings.parlor.server.ParlorSender;
/**
* 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.
*
* <p> 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, GameProvider, AttributeChangeListener
{
/**
* Provides the game manager with a list of the usernames of all
* players in the game. This happens before startup and before the
* game object has been created.
*
* @param players the usernames of all of the players in this game or
* a zero-length array if the game has no specific set of players.
*/
public void setPlayers (String[] players)
{
// keep this around for now, we'll need it later
_players = players;
// instantiate a player oid array which we'll fill in later
_playerOids = new int[players.length];
}
/**
* Sets the specified player as an AI with the specified skill. 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.
* @param skill the skill level, from 0 to 100 inclusive.
*/
public void setAI (final int pidx, final byte skill)
{
if (_AIs == null) {
// create and initialize the AI skill level array
_AIs = new byte[_players.length];
Arrays.fill(_AIs, (byte)-1);
// set up a delegate op for AI ticking
_tickAIOp = new TickAIDelegateOp();
}
// save off the AI's skill level
_AIs[pidx] = skill;
// let the delegates know that the player's been made an AI
applyToDelegates(new DelegateOp() {
public void apply (PlaceManagerDelegate delegate) {
((GameManagerDelegate)delegate).setAI(pidx, skill);
}
});
}
/**
* Returns the name of the player with the specified index.
*/
public String getPlayerName (int index)
{
return _players[index];
}
/**
* Returns the user object oid of the player with the specified index.
*/
public int getPlayerOid (int index)
{
return _playerOids[index];
}
/**
* Returns the number of players in the game.
*/
public int getPlayerCount ()
{
return _players.length;
}
/**
* Returns whether the player at the specified player index is an AI.
*/
public boolean isAI (int pidx)
{
return (_AIs != null && _AIs[pidx] != -1);
}
/**
* Returns whether the game is over.
*/
public boolean isGameOver ()
{
return (_gameobj.state == GameObject.GAME_OVER);
}
/**
* Returns the unique round identifier for the current round.
*/
public int getRoundId ()
{
return _gameobj.roundId;
}
/**
* Sends a system message to the players in the game room.
*/
public void systemMessage (String msgbundle, String msg)
{
systemMessage(msgbundle, msg, false);
}
/**
* 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.
*/
public void systemMessage (
String msgbundle, String msg, boolean waitForStart)
{
if (waitForStart &&
((_gameobj == null) ||
(_gameobj.state == GameObject.AWAITING_PLAYERS))) {
// queue up the message.
if (_startmsgs == null) {
_startmsgs = new ArrayList();
}
_startmsgs.add(msgbundle);
_startmsgs.add(msg);
return;
}
// otherwise, just deliver the message
SpeakProvider.sendSystemSpeak(_gameobj, msgbundle, msg);
}
// documentation inherited
protected Class getPlaceObjectClass ()
{
return GameObject.class;
}
// documentation inherited
protected void didStartup ()
{
super.didStartup();
// obtain a casted reference to our game object
_gameobj = (GameObject)_plobj;
// stick the players into the game object
_gameobj.setPlayers(_players);
// create and fill in our game service object
GameMarshaller service = (GameMarshaller)
_invmgr.registerDispatcher(new GameDispatcher(this), false);
_gameobj.setService(service);
// let the players of this game know that we're ready to roll (if
// we have a specific set of players)
if (_players != null) {
int gameOid = _gameobj.getOid();
for (int i = 0; i < _players.length; i++) {
BodyObject bobj = CrowdServer.lookupBody(_players[i]);
if (bobj == null) {
Log.warning("Unable to deliver game ready to " +
"non-existent player " +
"[gameOid=" + gameOid +
", player=" + _players[i] + "].");
continue;
}
// deliver a game ready notification to the player
ParlorSender.gameIsReady(bobj, gameOid);
}
}
}
// documentation inherited
protected void bodyLeft (int bodyOid)
{
super.bodyLeft(bodyOid);
// deal with disappearing players
}
/**
* 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. " +
"[gameOid=" + _gameobj.getOid() + "].");
// cancel the game if it wasn't over.
if (_gameobj.state != GameObject.GAME_OVER) {
_gameobj.setState(GameObject.CANCELLED);
}
// shut down the place (which will destroy the game object and
// clean up after everything)
shutdown();
}
/**
* 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 ()
{
// start up the game (if we haven't already)
if (_gameobj.state == GameObject.AWAITING_PLAYERS) {
startGame();
}
}
/**
* 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.
*/
public void startGame ()
{
// complain if we're already started
if (_gameobj.state == GameObject.IN_PLAY) {
Log.warning("Requested to start an already in-play game " +
"[game=" + _gameobj + "].");
return;
}
// let the derived class do its pre-start stuff
gameWillStart();
// transition the game to started
_gameobj.setState(GameObject.IN_PLAY);
// do post-start processing
gameDidStart();
}
/**
* 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.
*/
protected void gameWillStart ()
{
// let our delegates do their business
applyToDelegates(new DelegateOp() {
public void apply (PlaceManagerDelegate delegate) {
((GameManagerDelegate)delegate).gameWillStart();
}
});
}
/**
* 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 <code>IN_PLAY</code> is necessary).
*/
protected void gameDidStart ()
{
// let our delegates do their business
applyToDelegates(new DelegateOp() {
public void apply (PlaceManagerDelegate delegate) {
((GameManagerDelegate)delegate).gameDidStart();
}
});
// inform the players of any pending messages.
if (_startmsgs != null) {
for (Iterator iter = _startmsgs.iterator(); iter.hasNext(); ) {
systemMessage((String) iter.next(), // bundle
(String) iter.next()); // message
}
_startmsgs = null;
}
// and register ourselves to receive AI ticks
if (_AIs != null) {
AIGameTicker.registerAIGame(this);
}
}
/**
* Called by the AIGameTicker if we're registered as an AI game.
*/
protected void tickAIs ()
{
for (int ii=0; ii < _AIs.length; ii++) {
byte level = _AIs[ii];
if (level != -1) {
tickAI(ii, level);
}
}
}
/**
* Called by tickAIs to tick each AI in the game.
*/
protected void tickAI (int pidx, byte level)
{
_tickAIOp.setAI(pidx, level);
applyToDelegates(_tickAIOp);
}
/**
* 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 <code>GAME_OVER</code> state.
*/
public void endGame ()
{
// figure out who won...
// transition to the game over state
_gameobj.setState(GameObject.GAME_OVER);
// 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
}
/**
* Called after the game has transitioned to the
* <code>GAME_OVER</code> state. Derived classes should override this
* to perform any post-game activities.
*/
protected void gameDidEnd ()
{
// remove ourselves from the AIticker, if applicable
if (_AIs != null) {
AIGameTicker.unregisterAIGame(this);
}
// let our delegates do their business
applyToDelegates(new DelegateOp() {
public void apply (PlaceManagerDelegate delegate) {
((GameManagerDelegate)delegate).gameDidEnd();
}
});
// calculate ratings and all that...
}
/**
* 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} and {@link #gameDidReset}.
* The standard game start processing ({@link #gameWillStart} and
* {@link #gameDidStart}) will also be called (in between the calls to
* will and did reset). 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 resets.
*/
public void resetGame ()
{
// let the derived class do its pre-reset stuff
gameWillReset();
// increment the round identifier
_gameobj.setRoundId(_gameobj.roundId + 1);
// do the standard game start processing
gameWillStart();
_gameobj.setState(GameObject.IN_PLAY);
gameDidStart();
// let the derived class do its post-reset stuff
gameDidReset();
}
/**
* 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 ()
{
// let our delegates do their business
applyToDelegates(new DelegateOp() {
public void apply (PlaceManagerDelegate delegate) {
((GameManagerDelegate)delegate).gameWillReset();
}
});
}
/**
* Called after the game has been reset. Derived classes can override
* this to put whatever wheels they might need into motion now that
* the game is reset.
*/
protected void gameDidReset ()
{
// let our delegates do their business
applyToDelegates(new DelegateOp() {
public void apply (PlaceManagerDelegate delegate) {
((GameManagerDelegate)delegate).gameDidReset();
}
});
}
// documentation inherited from interface
public void playerReady (ClientObject caller)
{
BodyObject plobj = (BodyObject)caller;
// make a note of this player's oid
int pidx = _gameobj.getPlayerIndex(plobj.username);
if (pidx == -1) {
Log.warning("Received playerReady() from non-player? " +
"[caller=" + caller + "].");
return;
}
_playerOids[pidx] = plobj.getOid();
// and check to see if we're all set
boolean allSet = true;
for (int ii = 0; ii < _players.length; ii++) {
if ((_playerOids[ii] == 0) &&
((_AIs == null) || (_AIs[ii] == -1))) {
allSet = false;
}
}
// if everyone is now ready to go, make a note of it
if (allSet) {
playersAllHere();
}
}
// documentation inherited
public void attributeChanged (AttributeChangedEvent event)
{
if (event.getName().equals(GameObject.STATE)) {
switch (event.getIntValue()) {
case GameObject.CANCELLED:
// fall through to GAME_OVER case
case GameObject.GAME_OVER:
// now we do our end of game processing
gameDidEnd();
break;
}
}
}
/**
* A helper operation to distribute AI ticks to our delegates.
*/
protected class TickAIDelegateOp implements DelegateOp
{
public void apply (PlaceManagerDelegate delegate) {
((GameManagerDelegate) delegate).tickAI(_pidx, _level);
}
public void setAI (int pidx, byte level)
{
_pidx = pidx;
_level = level;
}
protected int _pidx;
protected byte _level;
}
/** A reference to our game object. */
protected GameObject _gameobj;
/** The usernames of the players of this game. */
protected String[] _players;
/** The oids of our player and AI body objects. */
protected int[] _playerOids;
/** If AIs are present, contains their skill levels, or -1 at human
* player indexes. */
protected byte[] _AIs;
/** 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;
}