// // $Id: GameController.java,v 1.22 2003/08/16 00:28:43 mdb Exp $ package com.threerings.parlor.game; import java.awt.event.ActionEvent; import com.samskivert.swing.Controller; import com.threerings.presents.dobj.AttributeChangeListener; import com.threerings.presents.dobj.AttributeChangedEvent; import com.threerings.presents.dobj.MessageEvent; import com.threerings.crowd.client.PlaceController; import com.threerings.crowd.client.PlaceControllerDelegate; import com.threerings.crowd.data.PlaceConfig; import com.threerings.crowd.data.PlaceObject; import com.threerings.crowd.util.CrowdContext; import com.threerings.parlor.Log; 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. */ 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. * * @param ctx the client context. * @param config the configuration of the game we are intended to * control. */ 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 _ctx = (ParlorContext)ctx; _config = (GameConfig)config; super.init(ctx, config); } /** * 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) { super.willEnterPlace(plobj); // obtain a casted reference _gobj = (GameObject)plobj; // 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 Log.info("Entering game " + _gobj.which() + "."); _ctx.getClient().getInvoker().invokeLater(new Runnable() { public void run () { // finally let the game manager know that we're ready to roll Log.info("Reporting ready " + _gobj.which() + "."); _gobj.gameService.playerReady(_ctx.getClient()); } }); } /** * Removes our listener registration from the game object and cleans * house. */ public void didLeavePlace (PlaceObject plobj) { super.didLeavePlace(plobj); // unlisten to the game object _gobj.removeListener(this); _gobj = null; } /** * Returns whether the game is over. */ public boolean isGameOver () { boolean gameOver = ((_gobj != null) ? (_gobj.state != GameObject.IN_PLAY) : true); 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. */ public void setGameOver (boolean gameOver) { _gameOver = gameOver; } /** * 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 () { // let derived classes do their thing gameWillReset(); // end the game until we receive a new board setGameOver(true); } /** * Returns the unique round identifier for the current round. */ public int getRoundId () { return (_gobj == null) ? -1 : _gobj.roundId; } /** * 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) { return super.handleAction(action); } // documentation inherited public void attributeChanged (AttributeChangedEvent event) { // deal with game state changes if (event.getName().equals(GameObject.STATE)) { switch (event.getIntValue()) { case GameObject.IN_PLAY: gameDidStart(); break; case GameObject.GAME_OVER: gameDidEnd(); break; case GameObject.CANCELLED: gameWasCancelled(); break; default: Log.warning("Game transitioned to unknown state " + "[gobj=" + _gobj + ", state=" + event.getIntValue() + "]."); break; } } } /** * 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 () { // clear out our game over flag setGameOver(false); // let our delegates do their business applyToDelegates(new DelegateOp() { public void apply (PlaceControllerDelegate delegate) { ((GameControllerDelegate)delegate).gameDidStart(); } }); } /** * 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 () { // let our delegates do their business applyToDelegates(new DelegateOp() { public void apply (PlaceControllerDelegate delegate) { ((GameControllerDelegate)delegate).gameDidEnd(); } }); } /** * Called when the game was cancelled for some reason. */ protected void gameWasCancelled () { // let our delegates do their business applyToDelegates(new DelegateOp() { public void apply (PlaceControllerDelegate delegate) { ((GameControllerDelegate)delegate).gameWasCancelled(); } }); } /** * 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 () { // let our delegates do their business applyToDelegates(new DelegateOp() { public void apply (PlaceControllerDelegate delegate) { ((GameControllerDelegate)delegate).gameWillReset(); } }); } /** * Used to determine if this game is a party game. */ protected boolean isPartyGame () { return ((_config instanceof PartyGameConfig) && ((PartyGameConfig)_config).isPartyGame()); } /** A reference to the active parlor context. */ protected ParlorContext _ctx; /** Our game configuration information. */ protected GameConfig _config; /** 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. */ protected boolean _gameOver; }