// // $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}. * *
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
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 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.
*/
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
* 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=" + 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
* 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 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;
}