// // $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; }