com.threerings.puzzle.drop.client.DropControllerDelegate Maven / Gradle / Ivy
//
// $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.puzzle.drop.client;
import java.awt.Component;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import com.samskivert.util.IntListUtil;
import com.threerings.crowd.client.PlaceController;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.util.CrowdContext;
import com.threerings.media.FrameParticipant;
import com.threerings.media.animation.Animation;
import com.threerings.media.animation.AnimationAdapter;
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;
import com.threerings.puzzle.util.PuzzleContext;
import static com.threerings.puzzle.Log.log;
/**
* 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 PlaceController#addDelegate}.
*
* 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.
*
*
Derived classes must implement {@link #getPieceVelocity} and {@link #evolveBoard}.
*
*
Block-dropping puzzles will likely want to override {@link #createNextBlock},
* {@link #blockDidLand}, and {@link #getPieceDropLogic}.
*
*
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
_usedrop = logic.useBlockDropping();
_userise = logic.useBoardRising();
if (_userise) {
// prepare for board rising
_risevel = getRiseVelocity();
_risedist = getRiseDistance();
}
}
@Override
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 = getPieceDropper(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.
*/
@Override
protected void startAction ()
{
super.startAction();
// log.info("Starting drop action");
// 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();
}
@Override
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.
*/
@Override
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);
}
@Override
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);
}
@Override
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;
}
@Override
public void setBoard (Board board)
{
super.setBoard(board);
// update the casted board reference
_dboard = (DropBoard)board;
// and update our self-summary
updateSelfSummary();
}
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;
}
int pidx = _ctrl.getPlayerIndex();
if (pidx != -1 && !_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 null
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.
*
*
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.
*/
public void updateSelfSummary ()
{
int pidx = _ctrl.getPlayerIndex();
if (pidx != -1 && _puzobj != null && _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 super.animationDidFinish()
.
*/
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", rows, "cols", 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("bounce", "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 drop logic 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;
}
/**
* Returns the piece dropper used to drop any pieces that need dropping in the board.
*/
protected PieceDropper getPieceDropper (PieceDropLogic logic)
{
return new PieceDropper(logic);
}
// 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", 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 super.boardWillRise()
.
*/
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 super.boardDidRise()
.
*/
protected void boardDidRise ()
{
// nothing for now
}
/** The puzzle 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;
/** 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() {
@Override
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;
}