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