Files
narya/src/java/com/threerings/puzzle/drop/client/DropControllerDelegate.java
T
Michael Bayne 2aafbbfe7f We need always to call boardDidStabilize() before clearing the action
because that will do things like queue up score events and whatnot that
cannot be ignored. It will also queue up the next drop sprite, etc. but
that's fine as we can safely end the action in such circumstances.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3165 542714f4-19e9-0310-aa3c-eee0fc999fb1
2004-10-22 21:13:08 +00:00

1216 lines
40 KiB
Java

//
// $Id: DropControllerDelegate.java,v 1.8 2004/10/22 21:13:08 mdb Exp $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.puzzle.drop.client;
import java.awt.Component;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.util.List;
import com.samskivert.util.IntListUtil;
import com.threerings.media.FrameParticipant;
import com.threerings.media.animation.Animation;
import com.threerings.media.animation.AnimationAdapter;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.util.CrowdContext;
import com.threerings.puzzle.util.PuzzleContext;
import com.threerings.puzzle.Log;
import com.threerings.puzzle.client.PuzzleController;
import com.threerings.puzzle.client.PuzzleControllerDelegate;
import com.threerings.puzzle.client.PuzzlePanel;
import com.threerings.puzzle.data.Board;
import com.threerings.puzzle.data.BoardSummary;
import com.threerings.puzzle.drop.data.DropBoard;
import com.threerings.puzzle.drop.data.DropCodes;
import com.threerings.puzzle.drop.data.DropConfig;
import com.threerings.puzzle.drop.data.DropLogic;
import com.threerings.puzzle.drop.data.DropPieceCodes;
import com.threerings.puzzle.drop.util.PieceDropLogic;
import com.threerings.puzzle.drop.util.PieceDropper.PieceDropInfo;
import com.threerings.puzzle.drop.util.PieceDropper;
/**
* Games that wish to make use of the drop puzzle services will need to
* create an extension of this delegate class, customizing it for their
* particular game and then adding it via {@link
* PuzzleController#addDelegate}.
*
* <p> It handles logical actions for a puzzle game that generally
* consists of a two-dimensional board containing pieces, with new pieces
* either falling into the board as a "drop block", or rising into the
* bottom of the board in new piece rows.
*
* <p> Derived classes must implement {@link #getPieceVelocity} and {@link
* #evolveBoard}.
*
* <p> Block-dropping puzzles will likely want to override {@link
* #createNextBlock}, {@link #blockDidLand}, and {@link
* #getPieceDropLogic}.
*
* <p> Board-rising puzzles will likely want to override {@link
* #getRiseVelocity}, {@link #getRiseDistance}, {@link
* #getPieceDropLogic}, and {@link #boardDidRise}.
*/
public abstract class DropControllerDelegate extends PuzzleControllerDelegate
implements DropCodes, DropPieceCodes, FrameParticipant
{
/** The action command for moving the block to the left. */
public static final String MOVE_BLOCK_LEFT = "move_block_left";
/** The action command for moving the block to the right. */
public static final String MOVE_BLOCK_RIGHT = "move_block_right";
/** The action command for rotating the block counter-clockwise. */
public static final String ROTATE_BLOCK_CCW = "rotate_block_ccw";
/** The action command for rotating the block clockwise. */
public static final String ROTATE_BLOCK_CW = "rotate_block_cw";
/** The action command for starting to dropping the block. */
public static final String START_DROP_BLOCK = "start_drop_block";
/** The action command for ending dropping the block. */
public static final String END_DROP_BLOCK = "end_drop_block";
/** The action command for raising the next rising row. */
public static final String RAISE_ROW = "raise_row";
/**
* Creates a delegate with the specified drop game logic and
* controller.
*/
public DropControllerDelegate (PuzzleController ctrl, DropLogic logic)
{
super(ctrl);
// keep this for later
_ctrl = ctrl;
// obtain the drop logic parameters
DropLogic dlogic = (DropLogic)logic;
_usedrop = dlogic.useBlockDropping();
_userise = dlogic.useBoardRising();
if (_userise) {
// prepare for board rising
_risevel = getRiseVelocity();
_risedist = getRiseDistance();
}
}
// documentation inherited
public void init (CrowdContext ctx, PlaceConfig config)
{
super.init(ctx, config);
// save things off
PuzzlePanel panel = (PuzzlePanel)_ctrl.getPlaceView();
_ctx = (PuzzleContext)ctx;
_dview = (DropBoardView)panel.getBoardView();
_dpanel = (DropPanel)panel;
_dboard = (DropBoard)_ctrl.getBoard();
// obtain the board dimensions
DropConfig dconfig = (DropConfig)config;
_bwid = dconfig.getBoardWidth();
_bhei = dconfig.getBoardHeight();
// create the piece dropper if appropriate
PieceDropLogic pdl = getPieceDropLogic();
if (pdl != null) {
_dropper = new PieceDropper(pdl);
}
}
/**
* Returns the speed with which the next board row should rise into
* place, in pixels per millisecond.
*/
protected float getRiseVelocity ()
{
return DEFAULT_RISE_VELOCITY;
}
/**
* Returns the distance in pixels that each board row will traverse
* when rising into place.
*/
protected int getRiseDistance ()
{
return DEFAULT_RISE_DISTANCE;
}
/**
* Starts up the action; tries evolving the board to get things going.
*/
protected void startAction ()
{
super.startAction();
// Log.info("Starting drop action");
// save off the player index
_pidx = _ctrl.getPlayerIndex();
// add ourselves as a frame participant
_ctx.getFrameManager().registerFrameParticipant(this);
// if (_userise) {
// // make sure the board has its next row of pieces
// advanceRisingPieces();
// // we'll set up the risestamp on the first rise tick
// }
// if we've a drop block left over from our previous action, set
// it on its merry way once more
if (_blocksprite != null) {
long delta = _dview.getTimeStamp() - _blockStamp;
Log.info("Restarting drop sprite [delta=" + delta + "].");
_blocksprite.fastForward(delta);
_blockStamp = 0L;
_dview.addSprite(_blocksprite);
// if we cleared the action while the drop sprite was
// bouncing, we need to land the block to get things going
// again
if (_blocksprite.isBouncing()) {
Log.info("Ended on a bounce, landing the block and " +
"starting things up.");
checkBlockLanded("bounced", true, true);
}
}
// evolve the board to kick-start the game into action
unstabilizeBoard();
}
// documentation inherited
protected boolean canClearAction ()
{
if (!_stable) {
Log.info("Rejecting canClear() request because not stable.");
}
return _stable && super.canClearAction();
}
/**
* Clears out all of the action in the board; removes any drop block
* sprites, any pieces rising in the board, and resets the animation
* timestamps.
*/
protected void clearAction ()
{
super.clearAction();
// Log.info("Clearing drop action.");
// do away with the bounce interval
_bounceStamp = 0;
_bounceRow = Integer.MIN_VALUE;
// kill any active drop block
if (_blocksprite != null) {
_dview.removeSprite(_blocksprite);
_blockStamp = _dview.getTimeStamp();
}
// reset intermediate rising timestamps
_rpstamp = 0;
_zipstamp = 0;
_fastDrop = false;
// remove ourselves as a frame participant
_ctx.getFrameManager().removeFrameParticipant(this);
}
// documentation inherited
public void gameDidEnd ()
{
super.gameDidEnd();
// clear out the drop block sprite
_blocksprite = null;
// reset ourselves back to pre-game conditions
_risestamp = 0;
// _dview.setRisingPieces(null);
}
// documentation inherited
public boolean handleAction (ActionEvent action)
{
// handle any block-related movement actions
if (handleBlockAction(action)) {
return true;
}
String cmd = action.getActionCommand();
if (cmd.equals(START_DROP_BLOCK)) {
handleDropBlock(true);
} else if (cmd.equals(END_DROP_BLOCK)) {
handleDropBlock(false);
} else {
return super.handleAction(action);
}
return true;
}
// documentation inherited
public void setBoard (Board board)
{
super.setBoard(board);
// update the casted board reference
_dboard = (DropBoard)board;
}
// documentation inherited
protected boolean handleBlockAction (ActionEvent action)
{
String cmd = action.getActionCommand();
boolean handled = false;
if (cmd.equals(MOVE_BLOCK_LEFT)) {
handleMoveBlock(LEFT);
handled = true;
} else if (cmd.equals(MOVE_BLOCK_RIGHT)) {
handleMoveBlock(RIGHT);
handled = true;
} else if (cmd.equals(ROTATE_BLOCK_CCW)) {
handleRotateBlock(CCW);
handled = true;
} else if (cmd.equals(ROTATE_BLOCK_CW)) {
handleRotateBlock(CW);
handled = true;
}
if (handled && _blocksprite != null) {
// land the block if it's been placed on something solid as a
// result of one of the above actions
String source = "fiddled [cmd=" + cmd + "]";
if (checkBlockLanded(source, false, false)) {
startBounceTimer(source);
}
}
return handled;
}
/**
* Handles block moved events.
*/
protected void handleMoveBlock (int dir)
{
if (_blocksprite == null) {
return;
}
// gather information regarding the attempted move
Rectangle bb = _blocksprite.getBoardBounds();
int row = _blocksprite.getRow(), col = _blocksprite.getColumn();
int dx = (dir == LEFT) ? -1 : 1;
// if the sprite has made it to the bottom of the board then we
// don't want to allow it to "virtually" fall any further because
// of the bounce interval
float pctdone = (row >= (_bhei - 1)) ? 0 :
_blocksprite.getPercentDone(_dview.getTimeStamp());
// get the drop block position resulting from the move
Point pos = _dboard.getForgivingMove(
bb.x, bb.y, bb.width, bb.height, dx, 0, pctdone);
if (pos != null) {
int frow = row + (pos.y - bb.y);
int fcol = col + (pos.x - bb.x);
// Log.info("Valid move [row=" + frow + ", col=" + col + "].");
_blocksprite.setBoardLocation(frow, fcol);
}
}
/**
* Handles block rotation events.
*/
protected void handleRotateBlock (int dir)
{
if (_blocksprite == null) {
return;
}
// gather information regarding the attempted rotation
int[] rows = _blocksprite.getRows();
int[] cols = _blocksprite.getColumns();
// if the sprite has made it to the bottom of the board then we
// don't want to allow it to "virtually" fall any further because
// of the bounce interval
float pctdone = (rows[0] >= (_bhei - 1)) ? 0 :
_blocksprite.getPercentDone(_dview.getTimeStamp());
// get the drop block position resulting from the rotation
int[] info = _dboard.getForgivingRotation(
rows, cols, _blocksprite.getOrientation(), dir,
getRotationType(), pctdone, _blocksprite.canPopup());
if (info != null) {
// Log.info("Found valid rotation " +
// "[orient=" + DirectionUtil.toShortString(info[0]) +
// ", col=" + info[1] + ", row=" + info[2] +
// ", blocksprite=" + _blocksprite + "].");
// update the piece image
_dview.rotateDropBlock(_blocksprite, info[0]);
// place the block in its newly rotated location
_blocksprite.setBoardLocation(info[2], info[1]);
if (info[3] != 0) {
_blocksprite.didPopup();
}
// let derived classes do what they will
blockDidRotate(dir);
}
}
/**
* Called when the drop block has rotated in the specified direction
* to allow derived classes to engage in any game-specific antics.
*/
protected void blockDidRotate (int dir)
{
}
/**
* Returns the rotation type used by this drop game. Either {@link
* DropBoard#RADIAL_ROTATION} or {@link DropBoard#INPLACE_ROTATION}.
*/
protected int getRotationType ()
{
return DropBoard.RADIAL_ROTATION;
}
/**
* Handles drop block events.
*/
protected void handleDropBlock (boolean fast)
{
_fastDrop = fast;
// only allow changing the piece velocity if we're not bouncing
if (_blocksprite != null && _bounceStamp == 0) {
// Log.info("Updating drop block velocity [fast=" + fast + "].");
_blocksprite.setVelocity(getPieceVelocity(fast));
}
}
/**
* Returns the drop sprite velocity to assign to a new drop sprite.
*/
protected abstract float getPieceVelocity (boolean fast);
/**
* Handles creation and dropping of the next dropping block.
*/
protected void dropNextBlock ()
{
if (_blocksprite != null || !_ctrl.hasAction()) {
Log.info("Not dropping block [bs=" + (_blocksprite != null) +
", action=" + _ctrl.hasAction() + "].");
return;
}
// determine whether or not the game should be ended because we
// can't drop the next block
if (checkDropEndsGame()) {
return;
}
if (!_ctrl.isGameOver() && _puzobj.isActivePlayer(_pidx)) {
// create the next block
_blocksprite = createNextBlock();
if (_blocksprite != null) {
// reset the drop block fast-drop state
_fastDrop = false;
// configure and add the drop block sprite
_blocksprite.setVelocity(getPieceVelocity(_fastDrop));
_blocksprite.addSpriteObserver(_dropMovedHandler);
_dview.addSprite(_blocksprite);
// update the next block display
_dpanel.setNextBlock(peekNextPieces());
// make sure the block has somewhere to go
if (checkBlockLanded("next-block", false, true)) {
startBounceTimer("next-block");
}
}
// clear out our last bounce row
_bounceRow = Integer.MIN_VALUE;
}
}
/**
* Called by {@link #dropNextBlock} to determine whether the game
* should be ended rather than dropping the next block because the
* board is filled and a new block cannot enter. If true is returned,
* the drop controller assumes that the derived class will have ended
* or reset the game as appropriate and will simply abandon its
* attempt to drop the next block.
*/
protected boolean checkDropEndsGame ()
{
return false;
}
/**
* Called only for block-dropping puzzles when it's time to create the
* next drop block. Returns the drop block sprite if it was
* successfully created, or <code>null</code> if it was not.
*/
protected DropBlockSprite createNextBlock ()
{
// nothing for now
return null;
}
/**
* Take a peek at the next pieces.
*/
protected int[] peekNextPieces ()
{
return null;
}
/**
* Called when a drop sprite posts a piece moved event.
*/
protected void handleDropSpriteMoved (
DropSprite sprite, long when, int col, int row)
{
if (sprite instanceof DropBlockSprite) {
if (checkBlockLanded("piece-moved", false, true)) {
startBounceTimer("piece-moved");
}
// keep dropping the drop block
sprite.drop();
return;
}
if (sprite.getDistance() > 0) {
sprite.drop();
} else {
// remove the sprite
_dview.removeSprite(sprite);
// apply the pieces to the board
applyDropSprite(sprite, col, row);
// perform any new destruction and falling
unstabilizeBoard();
}
}
/**
* Applies the pieces in the given sprite to the specified column and
* row in the board. Called when a drop sprite has finished
* traversing its entire distance.
*/
protected void applyDropSprite (DropSprite sprite, int col, int row)
{
// set the pieces in the board
int[] pieces = sprite.getPieces();
_dboard.setSegment(VERTICAL, col, row, pieces);
// create the updated board pieces
for (int dy = 0; dy < pieces.length; dy++) {
// pieces outside the board are discarded
if (row - dy >= 0) {
// note: vertical segments are applied counting downwards
// from the starting row toward row zero
_dview.createPiece(pieces[dy], col, row - dy);
}
}
}
/**
* Called to determine whether it is safe to evolve the board. The
* default implementation does not allow board evolution if there are
* sprites or animations active on the board.
*/
protected boolean canEvolveBoard ()
{
return (_dview.getActionCount() == 0);
}
/**
* Evolves the board to an unchanging state. If the board is in a
* state where pieces should react with one another to cause changes
* to the board state (such as piece dropping via {@link #dropPieces},
* piece destruction, and/or piece joining), this is where that
* process should be effected.
*
* <p> When no further evolution is possible and the board has
* stabilized this method should return false to indicate that such
* action should be taken. That will result in a follow-up call to
* {@link #boardDidStabilize} (assuming that the action was not
* cleared prior to the final stabilization of the board).
*/
protected abstract boolean evolveBoard ();
/**
* Derived classes should call this method whenever they change some
* board state that will require board evolution to restabilize the
* board.
*/
protected void unstabilizeBoard ()
{
_stable = false;
}
/**
* Called when the board has been fully evolved and is once again
* stable. The default implementation updates the player's local board
* summary and drops the next block into the board, but derived
* classes may wish to perform custom actions if they don't use drop
* blocks or have other requirements.
*/
protected void boardDidStabilize ()
{
updateSelfSummary();
dropNextBlock();
}
/**
* Updates the player's own local board summary to reflect the local
* copy of the player's board which is likely to be more up to date
* than the server-side board from which all other player board
* summaries originate.
*/
protected void updateSelfSummary ()
{
if (_puzobj.summaries != null) {
BoardSummary bsum = _puzobj.summaries[_pidx];
bsum.setBoard(_dboard);
bsum.summarize();
_dpanel.setSummary(_pidx, bsum);
}
}
/**
* Called when an animation finishes doing its business. Derived
* classes may wish to override this method but should be sure to call
* <code>super.animationDidFinish()</code>.
*/
protected void animationDidFinish (Animation anim)
{
unstabilizeBoard();
}
/**
* Checks whether the drop block can continue dropping and lands its
* pieces if not. Returns whether at least one piece of the block has
* landed; note that the other piece may need subsequent dropping.
*
* @param commit if true, the block landing is committed, if false, it
* is only checked, not committed.
* @param atTop whether the block sprite is to be treated as being at
* the top of its current row.
*/
protected boolean checkBlockLanded (
String source, boolean commit, boolean atTop)
{
if (_blocksprite == null) {
return true;
}
// check to see that both pieces can continue dropping
int[] rows = _blocksprite.getRows();
int[] cols = _blocksprite.getColumns();
// TODO: we may need to limit pctdone here to account for landing
// on the bottom of the board.
float pctdone = (atTop) ? 0.0f :
_blocksprite.getPercentDone(_dview.getTimeStamp());
// Log.info("Checking landed [source=" + source +
// ", bounceRow=" + _bounceRow +
// ", rows=" + StringUtil.toString(rows) +
// ", cols=" + StringUtil.toString(cols) +
// ", orient=" + DirectionUtil.toShortString(
// _blocksprite.getOrientation()) +
// ", commit=" + commit + ", pctdone=" + pctdone + "].");
if (_dboard.isValidDrop(rows, cols, pctdone)) {
if (commit) {
Log.info("Not valid drop [source=" + source +
", commit=" + commit + ", atTop=" + atTop +
", pctdone=" + pctdone + "].");
}
return false;
}
// if we're committing the landing, remove the sprite and update
// the board and all that
if (commit) {
// give sub-classes a chance to do any pre-landing business
blockWillLand();
// stamp the pieces into the board
int[] pieces = _blocksprite.getPieces();
boolean error = false;
for (int ii = 0; ii < pieces.length; ii++) {
if (rows[ii] >= 0) {
int col = cols[ii], row = rows[ii];
// sanity-check the block to make sure it's located in
// a valid position, and that we aren't somehow
// overwriting an existing piece
if (col < 0 || col >= _bwid || row >= _bhei) {
Log.warning("Placing drop block piece outside board " +
"bounds!? [x=" + col + ", y=" + row +
", pidx=" + ii +
", blocksprite=" + _blocksprite + "].");
error = true;
} else {
int cpiece = _dboard.getPiece(col, row);
if (cpiece != PIECE_NONE) {
Log.warning("Placing drop block piece onto " +
"occupied board position!? [x=" + col +
", y=" + row + ", pidx=" + ii +
", blocksprite=" + _blocksprite + "].");
error = true;
}
}
if (!error) {
// stuff the piece into the board
_dboard.setPiece(col, row, pieces[ii]);
_dview.createPiece(pieces[ii], col, row);
}
}
if (DEBUG_PUZZLE && error) {
_dboard.dump();
Log.warning("Bailing out in a flaming pyre of glory.");
System.exit(0);
}
}
// remove the drop block sprite
_dview.removeSprite(_blocksprite);
_blocksprite = null;
// give sub-classes a chance to do any post-landing business
blockDidLand();
}
return true;
}
/**
* Called only for block-dropping puzzles when the drop block is about
* to land on something. Derived classes may wish to override this
* method to perform game-specific actions such as queueing up a
* "block placed" progress event.
*/
protected void blockWillLand ()
{
// nothing for now
}
/**
* Called only for block-dropping puzzles when the drop block lands on
* something. Derived classes may wish to override this method to
* perform any game-specific actions.
*/
protected void blockDidLand ()
{
// nothing for now
}
/**
* Called when a block lands. We give the user a smidgen of time to
* continue to fiddle with the block before we actually land it. If
* the block is still landed when the bounce timer expires, we commit
* the landing, otherwise we let the block keep falling.
*/
protected void startBounceTimer (String source)
{
int bounceRow = IntListUtil.getMaxValue(_blocksprite.getRows());
// Log.info("startBounceTimer [source=" + source +
// ", bounceStamp=" + _bounceStamp +
// ", time=" + _dview.getTimeStamp() +
// ", bounceRow=" + _bounceRow +
// ", nbounceRow=" + bounceRow + "].");
// forcibly land the block if we bounce twice at the same row
if (_bounceStamp == 0 && _bounceRow == bounceRow) {
if (checkBlockLanded("double-bounced", true, true)) {
unstabilizeBoard();
}
return;
}
// if the bounce "timer" is already started, the user probably did
// something like rotate the piece while it was bouncing (which is
// why we give them the bounce interval), so we don't reset
if (_bounceStamp == 0) {
// slow the piece down so that it doesn't fly past the
// coordinates at which it's potentially landing; we have to
// do this before we tell the sprite that it's bouncing
// because changing the velocity fiddles with the rowstamp and
// we're going to reset the rowstamp when we tell the sprite
// that it's bouncing
_blocksprite.setVelocity(getPieceVelocity(false));
// set up our bounce interval (it depends on the current piece
// velocity and so must be set at the time we bounce)
_bounceInterval = (int)
((_dview.getPieceHeight() * BOUNCE_FRACTION) /
getPieceVelocity(false));
// Log.info("bounceInterval=" + _bounceInterval +
// ", phei=" + _dview.getPieceHeight() +
// ", vel=" + getPieceVelocity(false));
// make a note of the time we started bouncing
_bounceStamp = _dview.getTimeStamp();
// and the row at which we're bouncing
_bounceRow = bounceRow;
// put the block sprite into bouncing mode
_blocksprite.setBouncing(true);
}
}
/**
* Called when the bounce timer expires. Herein we either commit the
* landing of a block if it is still landed or let it keep falling if
* it is no longer landed.
*/
protected void bounceTimerExpired ()
{
// Log.info("bounceTimerExpired [bounceStamp=" + _bounceStamp +
// ", time=" + _dview.getTimeStamp() +
// ", bounceRow=" + _bounceRow + "].");
// make sure we weren't cancelled for some reason
if (_bounceStamp != 0) {
if (checkBlockLanded("bounced", true, true)) {
unstabilizeBoard();
} else if (_blocksprite != null) {
// take the block sprite out of bouncing mode
_blocksprite.setBouncing(false);
}
_bounceStamp = 0;
}
}
/**
* Drops any pieces that need dropping and returns whether any pieces
* were dropped. Derived classes that would like to drop their pieces
* should include a call to this method in their {@link #evolveBoard}
* implementation, and must also override {@link #getPieceDropLogic}
* to provide their game-specific piece dropper implementation.
*/
protected boolean dropPieces ()
{
PieceDropper.DropObserver drobs = new PieceDropper.DropObserver() {
public void pieceDropped (
int piece, int sx, int sy, int dx, int dy) {
float vel = getPieceVelocity(true) * 1.5f;
long duration = (long)(_dview.getPieceHeight() *
Math.abs(dy-sy) / vel);
if (sy < 0) {
_dview.createPiece(piece, sx, sy, dx, dy, duration);
} else {
_dview.movePiece(sx, sy, dx, dy, duration);
}
}
};
return (_dropper.dropPieces(_dboard, drobs) > 0);
}
/**
* Returns the piece dropper used to drop any pieces that need
* dropping in the board. Derived classes that intend to make use of
* {@link #dropPieces} must implement this method and return a
* reference to their game-specific piece dropper implementation.
*/
protected PieceDropLogic getPieceDropLogic ()
{
return null;
}
// documentation inherited
public void tick (long tickStamp)
{
// if (_userise && (tickStamp >= _risesent + RISE_INTERVAL)) {
// _risesent += RISE_INTERVAL;
// raiseBoard(tickStamp);
// }
// check the bounce timer
if ((_bounceStamp != 0) &&
((tickStamp - _bounceStamp) >= _bounceInterval)) {
bounceTimerExpired();
}
// if we can't evolve the board because it doesn't need evolving
// or things are going on, we stop here
if (_stable || !canEvolveBoard()) {
return;
}
// if we do not evolve the board in any way, let the derived class
// know that the board stabilized so that they can drop in a new
// piece if they like or take whatever other action is appropriate
boolean evolving = evolveBoard();
boolean debug = false;
if (debug) {
Log.info("Evolved board [evolving=" + evolving + "].");
}
// if we're no longer evolving and the action has not ended, go
// ahead and let our derived class know that the board has
// stabilized so that it can drop in the next piece or somesuch
if (!evolving) {
// no evolving again until someone destabilizes the board
_stable = true;
// this will trigger further puzzle activity
if (debug) {
Log.info("Board did stabilize");
}
boardDidStabilize();
// ensure that if we have been postponing action due to board
// evolution, that it will now be cleared
if (!_ctrl.hasAction()) {
if (debug) {
Log.info("Maybe clearing action.");
}
maybeClearAction();
}
}
}
// documentation inherited
public Component getComponent ()
{
return null;
}
// documentation inherited
public boolean needsPaint ()
{
return false;
}
/**
* Sets whether the board rising is paused.
*/
public void setRisingPaused (boolean paused)
{
if (paused && _rpstamp == 0) {
// pause the board
_rpstamp = _dview.getTimeStamp();
} else if (!paused && _rpstamp != 0) {
// un-pause the board
long delta = _dview.getTimeStamp() - _rpstamp;
_risestamp += delta;
if (_zipstamp != 0) {
_zipstamp += delta;
}
_rpstamp = 0;
}
}
/**
* Causes the board to zip quickly to the next row.
*/
public void zipToNextRow ()
{
// don't overwrite an existing zip
if (_zipstamp == 0) {
// if we're paused, inherit the pause time, otherwise use the
// current time
if (_rpstamp != 0) {
_zipstamp = _rpstamp;
} else {
_zipstamp = _dview.getTimeStamp();
}
}
}
/**
* Called periodically on the frame tick. Raises the board row based
* on the time since the current row traversal began.
*/
/*
protected void raiseBoard (long tickStamp)
{
// don't raise if rising is paused or the action is cleared
if (_rpstamp != 0 || !_ctrl.hasAction()) {
return;
}
// initialize the rise stamp the first time we're risen
if (_risestamp == 0) {
_risestamp = tickStamp;
_risesent = _risestamp;
}
// determine how far we've risen
long msecs = tickStamp - _risestamp;
float travpix = msecs * _risevel;
// account for any zipping effect
long zipsecs = 0;
if (_zipstamp > 0) {
zipsecs = tickStamp - _zipstamp;
// make sure we don't zip past the top
float zippix = (zipsecs * _risevel * 15);
if (travpix < _risedist) {
travpix += zippix;
travpix = Math.min(travpix, _risedist);
}
}
float pctdone = travpix / _risedist;
boolean rose = false;
if (pctdone >= 1.0f) {
rose = true;
if (_zipstamp > 0) {
// clear out any zip stamp
_zipstamp = 0;
_risestamp = tickStamp;
pctdone = 1f;
} else {
long used = (long)(_risedist / _risevel);
_risestamp += used;
}
// give sub-classes a chance to do their thing
boardWillRise();
}
// update the board display
int ypos = ((int)(_risedist * pctdone)) % _risedist;
_dview.setRiseOffset(ypos);
if (rose) {
// check to see if this means doom and defeat (even though the
// game might be over, we still want to advance the piece
// packet one last time and do the last rise so that the
// server can tell that we kicked the proverbial bucket)
boolean canRise = checkCanRise();
// apply the rising row pieces to the board
int[] pieces = _dview.getRisingPieces();
_dboard.applyRisingPieces(pieces);
// set up the next row of rising pieces
_dview.setRisingPieces(null);
advanceRisingPieces();
// give sub-classes a chance to do their thing
boardDidRise();
if (canRise) {
// evolve the board
unstabilizeBoard();
} else {
Log.debug("Sticking fork in it [risers=" +
StringUtil.toString(pieces) + ".");
// let the controller know that we're done for
_ctrl.resetGame();
}
}
// Log.info("Board rise [msecs=" + msecs + ", roff=" + ypos +
// ", pctdone=" + pctdone + ", zipsecs=" + zipsecs + "].");
}
*/
/**
* Called to determine whether or not rising a new row into the board
* is legal. The default implementation will return false if the top
* row of the board contains any pieces.
*/
protected boolean checkCanRise ()
{
return !_dboard.rowContainsPieces(0, PIECE_NONE);
}
/**
* Called only for board-rising puzzles before effecting the rising of
* the board by one row. Derived classes may wish to override this
* method to add any desired behaviour, but should be sure to call
* <code>super.boardWillRise()</code>.
*/
protected void boardWillRise ()
{
// nothing for now
}
/**
* Called only for board-rising puzzles when the board has finished
* rising one row. Derived classes may wish to override this method
* to add any desired behaviour, but should be sure to call
* <code>super.boardDidRise()</code>.
*/
protected void boardDidRise ()
{
// nothing for now
}
/** The yohoho context. */
protected PuzzleContext _ctx;
/** Our puzzle controller. */
protected PuzzleController _ctrl;
/** The drop panel. */
protected DropPanel _dpanel;
/** The drop board view. */
protected DropBoardView _dview;
/** The drop board. */
protected DropBoard _dboard;
/** Whether the game is using drop block functionality. */
protected boolean _usedrop;
/** Whether the game is using board rising functionality. */
protected boolean _userise;
/** Whether or not the board is currently stable. */
protected boolean _stable;
/** The board dimensions in pieces. */
protected int _bwid, _bhei;
/** Our player index in the game. */
protected int _pidx;
/** The distance the board row travels in pixels. */
protected int _risedist;
/** The speed with which the board rises in pixels per millisecond. */
protected float _risevel;
/** The drop block sprite associated with the landing block, if any. */
protected DropBlockSprite _blocksprite;
/** The piece dropper used to drop pieces in the board if the puzzle
* chooses to make use of piece dropping functionality. */
protected PieceDropper _dropper;
/** The time at which the board rise was paused. */
protected long _rpstamp;
/** The time at which the last board rise began. */
protected long _risestamp;
/** The time at which we last fired off a board rising event. */
protected long _risesent;
/** The time at which we were requested to start zipping. */
protected long _zipstamp;
/** The duration of the bounce interval. */
protected int _bounceInterval;
/** The time at which we last started bouncing, or 0. */
protected long _bounceStamp;
/** The row at which we last bounced, or {@link Integer#MIN_VALUE}. */
protected int _bounceRow;
/** The timestamp used to keep track of when the drop block was
* removed so that we can fast-forward it when restored. */
protected long _blockStamp;
/** Whether the drop blocks are currently dropping quickly. */
protected boolean _fastDrop;
/** Used to evolve the board following the completion of animations. */
protected AnimationAdapter _evolveObserver = new AnimationAdapter() {
public void animationCompleted (Animation anim, long when) {
animationDidFinish(anim);
}
};
/** Used to listen to drop sprites and react to their move events. */
protected DropSpriteObserver _dropMovedHandler = new DropSpriteObserver() {
public void pieceMoved (
DropSprite sprite, long when, int col, int row) {
handleDropSpriteMoved(sprite, when, col, row);
}
};
/** A piece operation that will update piece sprites as board
* positions are updated. */
protected DropBoard.PieceOperation _updateBoardOp =
new DropBoard.PieceOperation() {
public boolean execute (DropBoard board, int col, int row) {
_dview.updatePiece(col, row);
return true;
}
};
/** The default board row rising velocity. */
protected static final float DEFAULT_RISE_VELOCITY = 100f / 1000f;
/** The default board row rising distance. */
protected static final int DEFAULT_RISE_DISTANCE = 20;
/** The delay in milliseconds between board rising intervals. */
protected static final long RISE_INTERVAL = 50L;
/** Defines the distance of a piece that we allow to bounce before we
* land it. */
protected static final float BOUNCE_FRACTION = 0.125f;
}