com.threerings.parlor.game.client.GameController Maven / Gradle / Ivy
The newest version!
//
// $Id$
//
// Vilya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/vilya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.parlor.game.client;
import java.awt.event.ActionEvent;
import com.threerings.presents.dobj.AttributeChangeListener;
import com.threerings.presents.dobj.AttributeChangedEvent;
import com.threerings.crowd.client.PlaceController;
import com.threerings.crowd.client.PlaceControllerDelegate;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.util.CrowdContext;
import com.threerings.parlor.game.data.GameCodes;
import com.threerings.parlor.game.data.GameConfig;
import com.threerings.parlor.game.data.GameObject;
import com.threerings.parlor.util.ParlorContext;
import static com.threerings.parlor.Log.log;
/**
* 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 adjustments 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.
*/
@Override
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.
*/
@Override
public void willEnterPlace (PlaceObject plobj)
{
super.willEnterPlace(plobj);
// obtain a casted reference
_gobj = (GameObject)plobj;
// if this place object is not our current location we'll need to add it as an auxiliary
// chat source
BodyObject bobj = (BodyObject)_ctx.getClient().getClientObject();
if (bobj.location == null || bobj.location.placeOid != plobj.getOid()) {
_ctx.getChatDirector().addAuxiliarySource(_gobj, GameCodes.GAME_CHAT_TYPE);
}
// 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() + ".");
if (_gobj.getPlayerIndex(bobj.getVisibleName()) != -1) {
_ctx.getClient().getRunQueue().postRunnable(new Runnable() {
public void run () {
// finally let the game manager know that we're ready to roll
playerReady();
}
});
}
}
/**
* Removes our listener registration from the game object and cleans house.
*/
@Override
public void didLeavePlace (PlaceObject plobj)
{
super.didLeavePlace(plobj);
_ctx.getChatDirector().removeAuxiliarySource(_gobj);
// unlisten to the game object
_gobj.removeListener(this);
_gobj = null;
}
/**
* Convenience method to determine the type of game.
*/
public int getMatchType ()
{
return _config.getMatchType();
}
/**
* Returns whether the game is over.
*/
public boolean isGameOver ()
{
boolean gameOver = (_gobj == null) || (_gobj.state != GameObject.IN_PLAY);
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 session identifier for the current gameplay session.
*/
public int getSessionId ()
{
return (_gobj == null) ? -1 : _gobj.sessionId;
}
/**
* Handles basic game controller action events. Derived classes should be sure to call
* super.handleAction
for events they don't specifically handle.
*/
@Override
public boolean handleAction (ActionEvent action)
{
return super.handleAction(action);
}
/**
* A way for controllers to display a game-related system message.
*/
public void systemMessage (String bundle, String msg)
{
_ctx.getChatDirector().displayInfo(bundle, msg, GameCodes.GAME_CHAT_TYPE);
}
// documentation inherited
public void attributeChanged (AttributeChangedEvent event)
{
// deal with game state changes
if (event.getName().equals(GameObject.STATE)) {
int newState = event.getIntValue();
if (!stateDidChange(newState)) {
log.warning("Game transitioned to unknown state", "gobj", _gobj, "state", newState);
}
}
}
/**
* Derived classes can override this method if they add additional game states and should
* handle transitions to those states, returning true to indicate they were handled and calling
* super for the normal game states.
*/
protected boolean stateDidChange (int state)
{
switch (state) {
case GameObject.PRE_GAME:
return true;
case GameObject.IN_PLAY:
gameDidStart();
return true;
case GameObject.GAME_OVER:
gameDidEnd();
return true;
case GameObject.CANCELLED:
gameWasCancelled();
return true;
}
return false;
}
/**
* Called after we've entered the game and everything has initialized to notify the server that
* we, as a player, are ready to play.
*/
protected void playerReady ()
{
log.info("Reporting ready " + _gobj.which() + ".");
_gobj.manager.invoke("playerReady");
}
/**
* 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 ()
{
if (_gobj == null) {
log.info("Received gameDidStart() after leaving game room.");
return;
}
// clear out our game over flag
setGameOver(false);
// let our delegates do their business
applyToDelegates(new DelegateOp(GameControllerDelegate.class) {
@Override
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(GameControllerDelegate.class) {
@Override
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(GameControllerDelegate.class) {
@Override
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(GameControllerDelegate.class) {
@Override
public void apply (PlaceControllerDelegate delegate) {
((GameControllerDelegate)delegate).gameWillReset();
}
});
}
/** 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;
}