Revamped drop puzzle framework to use sprites for pieces instead of

rendering the board from the board state and doing all manner of jockeying
to prevent that from wigging out when the board state changes before the
display is ready to change.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3108 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2004-08-29 06:50:47 +00:00
parent 174d0ac3ad
commit 97086356fc
6 changed files with 407 additions and 388 deletions
@@ -1,5 +1,5 @@
//
// $Id: DropBoardView.java,v 1.4 2004/08/27 02:20:29 mdb Exp $
// $Id: DropBoardView.java,v 1.5 2004/08/29 06:50:47 mdb Exp $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
@@ -30,8 +30,13 @@ import java.awt.Rectangle;
import java.util.Iterator;
import com.threerings.media.image.Mirage;
import com.threerings.media.sprite.ImageSprite;
import com.threerings.media.sprite.PathAdapter;
import com.threerings.media.sprite.Sprite;
import com.threerings.media.util.LinePath;
import com.threerings.media.util.Path;
import com.threerings.puzzle.Log;
import com.threerings.puzzle.client.PuzzleBoardView;
import com.threerings.puzzle.client.ScoreAnimation;
import com.threerings.puzzle.data.Board;
@@ -128,34 +133,152 @@ public abstract class DropBoardView extends PuzzleBoardView
}
/**
* Dirties the rectangle encompassing the piece segment with the given
* direction and length whose bottom-leftmost corner is at <code>(col,
* row)</code>.
* Creates a new piece sprite and places it directly in it's correct
* position.
*/
public void dirtySegment (int dir, int col, int row, int len)
public void createPiece (int piece, int sx, int sy)
{
int x = _pwid * col, y = (_phei * row) - _roff;
int wid = (dir == VERTICAL) ? _pwid : len * _pwid;
int hei = (dir == VERTICAL) ? _phei * len : _phei;
_remgr.invalidateRegion(x, y, wid, hei);
if (sx < 0 || sy < 0 || sx >= _bwid || sy >= _bhei) {
Log.warning("Requested to create piece in invalid location " +
"[sx=" + sx + ", sy=" + sy + "].");
Thread.dumpStack();
return;
}
createPiece(piece, sx, sy, sx, sy, 0L);
}
/**
* Dirties the rectangle encompassing the specified piece in the
* board.
* Refreshes the piece sprite at the specified location, if no sprite
* exists at the location, one will be created. <em>Note:</em> this
* method assumes the default {@link ImageSprite} is being used to
* display pieces. If {@link #createPieceSprite} is overridden to
* return a non-ImageSprite, this method must also be customized.
*/
public void dirtyPiece (int col, int row)
public void updatePiece (int sx, int sy)
{
_remgr.invalidateRegion(_pwid * col, (_phei * row) - _roff,
_pwid, _phei);
updatePiece(_dboard.getPiece(sx, sy), sx, sy);
}
/**
* Dirties a rectangular region of pieces.
* Updates the piece sprite at the specified location, if no sprite
* exists at the location, one will be created. <em>Note:</em> this
* method assumes the default {@link ImageSprite} is being used to
* display pieces. If {@link #createPieceSprite} is overridden to
* return a non-ImageSprite, this method must also be customized.
*/
public void dirtyPieces (int xx, int yy, int width, int height)
public void updatePiece (int piece, int sx, int sy)
{
if (sx < 0 || sy < 0 || sx >= _bwid || sy >= _bhei) {
Log.warning("Requested to update piece in invalid location " +
"[sx=" + sx + ", sy=" + sy + "].");
Thread.dumpStack();
return;
}
int spos = sy * _bwid + sx;
if (_pieces[spos] != null) {
((ImageSprite)_pieces[spos]).setMirage(getPieceImage(piece));
} else {
createPiece(piece, sx, sy);
}
}
/**
* Creates a new piece sprite and moves it into position on the board.
*/
public void createPiece (int piece, int sx, int sy, int tx, int ty,
long duration)
{
if (tx < 0 || ty < 0 || tx >= _bwid || ty >= _bhei) {
Log.warning("Requested to create and move piece to invalid " +
"location [tx=" + tx + ", ty=" + ty + "].");
Thread.dumpStack();
return;
}
Sprite sprite = createPieceSprite(piece);
addSprite(sprite);
movePiece(sprite, sx, sy, tx, ty, duration);
}
/**
* Instructs the view to move the piece at the specified starting
* position to the specified destination position. There must be a
* sprite at the starting position, if there is a sprite at the
* destination position, it must also be moved immediately following
* this call (as in the case of a swap) to avoid badness.
*
* @return the piece sprite that is being moved.
*/
public Sprite movePiece (int sx, int sy, int tx, int ty, long duration)
{
int spos = sy * _bwid + sx;
Sprite piece = _pieces[spos];
if (piece == null) {
Log.warning("Missing source sprite for drop [sx=" + sx +
", sy=" + sy + ", tx=" + tx + ", ty=" + ty + "].");
return null;
}
_pieces[spos] = null;
movePiece(piece, sx, sy, tx, ty, duration);
return piece;
}
/**
* A helper function for moving pieces into place.
*/
protected void movePiece (Sprite piece, final int sx, final int sy,
final int tx, final int ty, long duration)
{
final Exception where = new Exception();
// if the sprite needn't move, then just position it and be done
Point start = new Point();
getPiecePosition(sx, sy, start);
if (sx == tx && sy == ty) {
int tpos = ty * _bwid + tx;
if (_pieces[tpos] != null) {
Log.warning("Zoiks! Asked to add a piece where we already " +
"have one [sx=" + sx + ", sy=" + sy +
", tx=" + tx + ", ty=" + ty + "].");
Log.logStackTrace(where);
return;
}
_pieces[tpos] = piece;
piece.setLocation(start.x, start.y);
return;
}
// otherwise create a path and do some bits
Point end = new Point();
getPiecePosition(tx, ty, end);
piece.addSpriteObserver(new PathAdapter() {
public void pathCompleted (Sprite sprite, Path path, long when) {
sprite.removeSpriteObserver(this);
int tpos = ty * _bwid + tx;
if (_pieces[tpos] != null) {
Log.warning("Oh god, we're dropping onto another piece " +
"[sx=" + sx + ", sy=" + sy +
", tx=" + tx + ", ty=" + ty + "].");
Log.logStackTrace(where);
return;
}
_pieces[tpos] = sprite;
if (_actionSprites.remove(sprite)) {
maybeFireCleared();
}
pieceArrived(when, sprite, tx, ty);
}
});
_actionSprites.add(piece);
piece.move(new LinePath(start, end, duration));
}
/**
* Called when a piece is finished moving into its requested position.
* Derived classes may wish to take this opportunity to play a sound
* or whatnot.
*/
protected void pieceArrived (long tickStamp, Sprite sprite, int px, int py)
{
_remgr.invalidateRegion(xx*_pwid, yy*_phei, width*_pwid, height*_phei);
}
/**
@@ -192,9 +315,63 @@ public abstract class DropBoardView extends PuzzleBoardView
}
}
// remove all of this board's piece sprites
int pcount = (_pieces == null) ? 0 : _pieces.length;
for (int ii = 0; ii < pcount; ii++) {
if (_pieces[ii] != null) {
removeSprite(_pieces[ii]);
}
}
super.setBoard(board);
_dboard = (DropBoard)board;
// create the pieces for the new board
Point spos = new Point();
int width = _dboard.getWidth(), height = _dboard.getHeight();
_pieces = new Sprite[width * height];
for (int yy = 0; yy < height; yy++) {
for (int xx = 0; xx < width; xx++) {
Sprite piece = createPieceSprite(_dboard.getPiece(xx, yy));
if (piece != null) {
int ppos = yy * width + xx;
getPiecePosition(xx, yy, spos);
piece.setLocation(spos.x, spos.y);
addSprite(piece);
_pieces[ppos] = piece;
}
}
}
}
/**
* Returns the piece sprite at the specified location.
*/
public Sprite getPieceSprite (int xx, int yy)
{
return _pieces[yy * _dboard.getWidth() + xx];
}
/**
* Clears the specified piece from the board.
*/
public void clearPieceSprite (int xx, int yy)
{
int ppos = yy * _dboard.getWidth() + xx;
if (_pieces[ppos] != null) {
removeSprite(_pieces[ppos]);
_pieces[ppos] = null;
}
}
/**
* Clears out a piece from the board along with its piece sprite.
*/
public void clearPiece (int xx, int yy)
{
_dboard.setPiece(xx, yy, PIECE_NONE);
clearPieceSprite(xx, yy);
}
/**
@@ -206,6 +383,19 @@ public abstract class DropBoardView extends PuzzleBoardView
return new DropSprite(this, col, row, pieces, dist);
}
/**
* Dirties the rectangle encompassing the segment with the given
* direction and length whose bottom-leftmost corner is at <code>(col,
* row)</code>.
*/
public void dirtySegment (int dir, int col, int row, int len)
{
int x = _pwid * col, y = (_phei * row) - _roff;
int wid = (dir == VERTICAL) ? _pwid : len * _pwid;
int hei = (dir == VERTICAL) ? _phei * len : _phei;
_remgr.invalidateRegion(x, y, wid, hei);
}
/**
* Creates and returns an animation which makes use of a label sprite
* that is assigned a path that floats it a short distance up the
@@ -294,6 +484,20 @@ public abstract class DropBoardView extends PuzzleBoardView
return anim;
}
/**
* Creates the sprite that is used to display the specified piece. If
* the piece represents no piece, this method should return null.
*/
protected Sprite createPieceSprite (int piece)
{
if (piece == PIECE_NONE) {
return null;
}
ImageSprite sprite = new ImageSprite(getPieceImage(piece));
sprite.setRenderOrder(-1);
return sprite;
}
/**
* Populates <code>pos</code> with the most appropriate screen
* coordinates to center a rectangle of the given width and height (in
@@ -371,6 +575,9 @@ public abstract class DropBoardView extends PuzzleBoardView
/** The drop board. */
protected DropBoard _dboard;
/** A sprite for every piece displayed in the drop board. */
protected Sprite[] _pieces;
/** The piece dimensions in pixels. */
protected int _pwid, _phei;
@@ -1,5 +1,5 @@
//
// $Id: DropControllerDelegate.java,v 1.5 2004/08/27 02:20:29 mdb Exp $
// $Id: DropControllerDelegate.java,v 1.6 2004/08/29 06:50:47 mdb Exp $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
@@ -51,7 +51,6 @@ 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.DropPieceProvider;
import com.threerings.puzzle.drop.util.PieceDropLogic;
import com.threerings.puzzle.drop.util.PieceDropper.PieceDropInfo;
import com.threerings.puzzle.drop.util.PieceDropper;
@@ -149,15 +148,6 @@ public abstract class DropControllerDelegate extends PuzzleControllerDelegate
}
}
/**
* Get the DropPieceProvider for this puzzle. This is currently
* only needed if you are using the alwaysfilled property of dropboards.
*/
protected DropPieceProvider getDropPieceProvider ()
{
return null;
}
/**
* Returns the speed with which the next board row should rise into
* place, in pixels per millisecond.
@@ -217,14 +207,14 @@ public abstract class DropControllerDelegate extends PuzzleControllerDelegate
}
// evolve the board to kick-start the game into action
tryEvolveBoard();
unstabilizeBoard();
}
// documentation inherited
protected boolean canClearAction ()
{
// Log.info("Drop can clear " + _evolving);
return !_evolving && super.canClearAction();
// Log.info("Drop can clear " + _stable);
return _stable && super.canClearAction();
}
/**
@@ -534,21 +524,21 @@ public abstract class DropControllerDelegate extends PuzzleControllerDelegate
}
// keep dropping the drop block
sprite.drop();
return;
}
if (sprite.getDistance() > 0) {
sprite.drop();
} else {
if (sprite.getDistance() > 0) {
sprite.drop();
// remove the sprite
_dview.removeSprite(sprite);
} else {
// remove the sprite
_dview.removeSprite(sprite);
// apply the pieces to the board
applyDropSprite(sprite, col, row);
// apply the pieces to the board
applyDropSprite(sprite, col, row);
// perform any new destruction and falling
tryEvolveBoard();
}
// perform any new destruction and falling
unstabilizeBoard();
}
}
@@ -562,62 +552,13 @@ public abstract class DropControllerDelegate extends PuzzleControllerDelegate
// set the pieces in the board
int[] pieces = sprite.getPieces();
_dboard.setSegment(VERTICAL, col, row, pieces);
// dirty the updated board pieces
_dview.dirtySegment(VERTICAL, col, row, pieces.length);
}
/**
* Calls {@link #tryEvolveBoard(boolean)} with debugging deactivated.
*/
protected void tryEvolveBoard ()
{
tryEvolveBoard(false);
}
/**
* Attempts to evolve the board. This involves first calling {@link
* #canEvolveBoard} and only calling {@link #evolveBoard} if the
* former returned true. If the board is fully stabilized, {@link
* #boardDidStabilize} will be called to reinstate the puzzle action.
*/
protected void tryEvolveBoard (boolean debug)
{
// if we can't evolve the board because things are going on, we
// bail out immediately
if (!canEvolveBoard()) {
if (debug) {
Log.info("Can't evolve board " +
"[acount=" + _dview.getActionCount() + "].");
}
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
_evolving = evolveBoard();
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) {
if (_ctrl.hasAction()) {
// this will trigger further puzzle activity
if (debug) {
Log.info("Board did stabilize");
}
boardDidStabilize();
} else {
if (debug) {
Log.info("Maybe clearing action.");
}
// this will ensure that if we have been postponing action
// due to board evolution, that it will now be cleared
maybeClearAction();
// 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);
}
}
}
@@ -629,7 +570,7 @@ public abstract class DropControllerDelegate extends PuzzleControllerDelegate
*/
protected boolean canEvolveBoard ()
{
return (_dview.getActionCount() == 0);
return (!_ctrl.isWaiting() && _dview.getActionCount() == 0);
}
/**
@@ -647,6 +588,16 @@ public abstract class DropControllerDelegate extends PuzzleControllerDelegate
*/
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
@@ -683,7 +634,7 @@ public abstract class DropControllerDelegate extends PuzzleControllerDelegate
*/
protected void animationDidFinish (Animation anim)
{
tryEvolveBoard();
unstabilizeBoard();
}
/**
@@ -766,7 +717,7 @@ public abstract class DropControllerDelegate extends PuzzleControllerDelegate
if (!error) {
// stuff the piece into the board
_dboard.setPiece(col, row, pieces[ii]);
_dview.dirtyPiece(col, row);
_dview.createPiece(pieces[ii], col, row);
}
}
@@ -827,7 +778,7 @@ public abstract class DropControllerDelegate extends PuzzleControllerDelegate
// forcibly land the block if we bounce twice at the same row
if (_bounceStamp == 0 && _bounceRow == bounceRow) {
if (checkBlockLanded("double-bounced", true, true)) {
tryEvolveBoard();
unstabilizeBoard();
}
return;
}
@@ -878,7 +829,7 @@ public abstract class DropControllerDelegate extends PuzzleControllerDelegate
// make sure we weren't cancelled for some reason
if (_bounceStamp != 0) {
if (checkBlockLanded("bounced", true, true)) {
tryEvolveBoard();
unstabilizeBoard();
} else if (_blocksprite != null) {
// take the block sprite out of bouncing mode
@@ -897,31 +848,20 @@ public abstract class DropControllerDelegate extends PuzzleControllerDelegate
*/
protected boolean dropPieces ()
{
// get a list of the piece columns to be dropped
List drops = _dropper.getDroppedPieces(_dboard, getDropPieceProvider());
int size = drops.size();
if (size == 0) {
return false;
}
// drop each column
for (int ii = 0; ii < size; ii++) {
PieceDropInfo pdi = (PieceDropInfo)drops.get(ii);
// Log.info("Dropping column segment [pdi=" + pdi + "].");
// clear the dropping pieces from the board
_dboard.setSegment(
VERTICAL, pdi.col, pdi.row, pdi.pieces.length, PIECE_NONE);
// create a piece sprite animating the pieces falling
DropSprite sprite = _dview.createPieces(
pdi.col, pdi.row, pdi.pieces, pdi.dist);
sprite.setVelocity(1.5f * getPieceVelocity(true));
sprite.addSpriteObserver(_dropMovedHandler);
_dview.addActionSprite(sprite);
}
return true;
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);
}
/**
@@ -948,6 +888,45 @@ public abstract class DropControllerDelegate extends PuzzleControllerDelegate
((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;
if (_ctrl.hasAction()) {
// this will trigger further puzzle activity
if (debug) {
Log.info("Board did stabilize");
}
boardDidStabilize();
} else {
if (debug) {
Log.info("Maybe clearing action.");
}
// this will ensure that if we have been postponing action
// due to board evolution, that it will now be cleared
maybeClearAction();
}
}
}
// documentation inherited
@@ -989,7 +968,7 @@ public abstract class DropControllerDelegate extends PuzzleControllerDelegate
// we're un-paused, so we should try evolving the board to start
// things up again
tryEvolveBoard(true);
unstabilizeBoard();
}
/**
@@ -1107,7 +1086,7 @@ public abstract class DropControllerDelegate extends PuzzleControllerDelegate
if (canRise) {
// evolve the board
tryEvolveBoard();
unstabilizeBoard();
} else {
Log.debug("Sticking fork in it [risers=" +
@@ -1170,15 +1149,15 @@ public abstract class DropControllerDelegate extends PuzzleControllerDelegate
/** The drop board. */
protected DropBoard _dboard;
/** Whether or not we are in the middle of board evolution. */
protected boolean _evolving;
/** 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;
@@ -1241,6 +1220,16 @@ public abstract class DropControllerDelegate extends PuzzleControllerDelegate
}
};
/** 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;
@@ -1,5 +1,5 @@
//
// $Id: DropBoard.java,v 1.7 2004/08/27 02:20:30 mdb Exp $
// $Id: DropBoard.java,v 1.8 2004/08/29 06:50:47 mdb Exp $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
@@ -145,6 +145,15 @@ public class DropBoard extends Board
}
}
/**
* For boards that are always filled, this method is called to obtain
* pieces to fill the board.
*/
public int getNextPiece ()
{
return PIECE_NONE;
}
/**
* Returns the distance the piece at the given column and row can drop
* until it hits a non-empty piece (defined as {@link #PIECE_NONE}).
@@ -1,5 +1,5 @@
//
// $Id: DropManagerDelegate.java,v 1.4 2004/08/27 02:20:30 mdb Exp $
// $Id: DropManagerDelegate.java,v 1.5 2004/08/29 06:50:47 mdb Exp $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
@@ -129,7 +129,7 @@ public abstract class DropManagerDelegate extends PuzzleManagerDelegate
*/
protected boolean dropPieces (DropBoard board)
{
return (_dropper.dropPieces(board, null).size() > 0);
return (_dropper.dropPieces(board, null) > 0);
}
/**
@@ -1,41 +0,0 @@
//
// $Id: DropPieceProvider.java,v 1.2 2004/08/27 02:20:31 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.util;
/**
* Does something extraordinary.
*/
public interface DropPieceProvider
{
/**
* Get the next piece to add to the drop board.
*/
public int getNextPiece ()
throws OutOfPiecesException;
/**
* I lost my touch.
*/
public static class OutOfPiecesException extends Exception
{
}
}
@@ -1,5 +1,5 @@
//
// $Id: PieceDropper.java,v 1.3 2004/08/27 02:20:31 mdb Exp $
// $Id: PieceDropper.java,v 1.4 2004/08/29 06:50:47 mdb Exp $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
@@ -22,6 +22,7 @@
package com.threerings.puzzle.drop.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.samskivert.util.StringUtil;
@@ -71,6 +72,15 @@ public class PieceDropper
}
}
/**
* Called to inform a drop observer that a piece has been dropped.
*/
public static interface DropObserver
{
/** Indicates that the specified piece was dropped. */
public void pieceDropped (int piece, int sx, int sy, int dx, int dy);
}
/**
* Constructs a piece dropper that uses the supplied piece drop logic
* to specialise itself for a particular puzzle.
@@ -81,83 +91,63 @@ public class PieceDropper
}
/**
* Destructively modifies the supplied board to contain pieces dropped
* in the first of potentially multiple drop positions and returns a
* list of {@link PieceDropInfo} objects detailing all column segments
* to be dropped. Note that a single list is used internally to store
* the drop info objects, and so callers that care to do anything
* long-term with the drop info should create their own copy of the
* list or somesuch.
* Effects any drops possible on the supplied board (modifying the
* board in the progress) and notifying the supplied drop observer of
* those drops.
*
* @param DropPieceProvider if the board should always be filled
* (as specified by the PieceDropLogic) this will provide
* information on the newly filled in pieces.
* @return the number of pieces dropped.
*/
public List dropPieces (DropBoard board, DropPieceProvider provider)
public int dropPieces (DropBoard board, DropObserver drobs)
{
int bhei = board.getHeight(), bwid = board.getWidth();
_drops.clear();
int dropped = 0, bhei = board.getHeight(), bwid = board.getWidth();
for (int yy = bhei - 1; yy >= 0; yy--) {
for (int xx = 0; xx < bwid; xx++) {
// find all drops in this column
getColumnDrops(board, _drops, xx, yy);
dropped += dropPieces(board, xx, yy, drobs);
}
}
// if the board wants pieces to be dropped in to fill the gaps, do
// that now
if (_logic.boardAlwaysFilled()) {
addFillingDrops(board, provider, _drops);
for (int xx = 0; xx < bwid; xx++) {
int dist = board.getDropDistance(xx, -1);
for (int ii = 0; ii < dist; ii++) {
int yy = (-1 - ii);
int piece = board.getNextPiece();
if (piece != PIECE_NONE) {
drop(board, piece, xx, yy, yy + dist, drobs);
dropped++;
}
}
}
}
return _drops;
return dropped;
}
/**
* Analyzes but does not modify the supplied board and returns a list
* of {@link PieceDropInfo} objects detailing all column segments to
* be dropped. Note that a single list is used internally to store
* the drop info objects, and so callers that care to do anything
* long-term with the drop info should create their own copy of the
* list or somesuch.
*
* @param DropPieceProvider if the board should always be filled
* (as specified by the PieceDropLogic) this will provide
* information on the newly filled in pieces.
* Computes and effects the drop for the specified piece and any
* associated attached pieces. The supplied observer is notified of
* all drops.
*/
public List getDroppedPieces (DropBoard board, DropPieceProvider provider)
{
// grab a snapshot of the board within which we're dropping
// pieces to avoid modifying the source board directly while we're
// figuring out what to drop where
if (_board == null) {
_board = (DropBoard)board.clone();
} else {
board.copyInto(_board);
}
return dropPieces(_board, provider);
}
/**
* Populates the <code>drops</code> list with {@link PieceDropInfo}
* objects detailing the column segments to be dropped per empty space
* below. Destructively modifies the given board to reflect the final
* positions of the column segments once dropped.
*/
protected void getColumnDrops (DropBoard board, List drops, int x, int y)
protected int dropPieces (
DropBoard board, int xx, int yy, DropObserver drobs)
{
// skip empty or fixed pieces
int piece = board.getPiece(x, y);
int piece = board.getPiece(xx, yy);
if (!_logic.isDroppablePiece(piece)) {
return;
return 0;
}
int dropped = 0;
if (_logic.isConstrainedPiece(piece)) {
// find out where this constrained block starts and ends
int start = _logic.getConstrainedEdge(board, x, y, LEFT);
int end = _logic.getConstrainedEdge(board, x, y, RIGHT);
int start = _logic.getConstrainedEdge(board, xx, yy, LEFT);
int end = _logic.getConstrainedEdge(board, xx, yy, RIGHT);
int bwid = board.getWidth();
if (start < 0 || end >= bwid) {
Log.warning("Board reported bogus constrained edge " +
"[x=" + x + ", y=" + y +
"[x=" + xx + ", y=" + yy +
", start=" + start + ", end=" + end + "].");
board.dump();
start = Math.max(start, 0);
@@ -167,182 +157,47 @@ public class PieceDropper
// get the smallest drop distance across all of the block columns
int dist = board.getHeight() - 1;
for (int xpos = start; xpos <= end; xpos++) {
dist = Math.min(dist, board.getDropDistance(xpos, y));
dist = Math.min(dist, board.getDropDistance(xpos, yy));
}
if (dist == 0) {
return;
return 0;
}
// scoot along the bottom edge of the block dropping each column
// scoot along the bottom edge of the block, noting the drop
// for each column
for (int xpos = start; xpos <= end; xpos++) {
addDropInfo(board, drops, true, xpos, y, dist);
piece = board.getPiece(xpos, yy);
drop(board, piece, xpos, yy, yy + dist, drobs);
dropped++;
}
} else {
// get the distance to drop the pieces
int dist = board.getDropDistance(x, y);
int dist = board.getDropDistance(xx, yy);
if (dist == 0) {
return;
return 0;
}
// add the column segment to the list of drops
addDropInfo(board, drops, false, x, y, dist);
drop(board, piece, xx, yy, yy + dist, drobs);
dropped++;
}
return dropped;
}
/**
* Adds a {@link PieceDropInfo} object to the drop list detailing
* dropping of the column segment at the specified location.
*
* @param board the working board.
* @param allowConst whether to allow dropping constrained pieces in
* the specified column segment.
* @param x the column x-coordinate.
* @param y the column segment bottom y-coordinate.
* @param dist the distance to drop the pieces.
*/
protected void addDropInfo (DropBoard board, List drops,
boolean allowConst, int x, int y, int dist)
/** Helpy helper function. */
protected final void drop (DropBoard board, int piece,
int xx, int yy, int ty, DropObserver drobs)
{
// sanity check our column
if (x < 0 || x >= board.getWidth()) {
Log.warning("Requested to add bogus drop info [board=" + board +
", drops=" + StringUtil.toString(drops) +
", allowConst=" + allowConst +
", x=" + x + ", y=" + y + ", dist=" + dist + "].");
Thread.dumpStack();
// don't try to clear things out if we're filling in from off-board
if (yy >= 0) {
board.setPiece(xx, yy, PIECE_NONE);
}
// traverse up the column looking for an empty or block piece
// that will terminate this column segment
int height = getDropHeight(board, allowConst, x, y, dist);
// create the piece drop info object
PieceDropInfo pdi = new PieceDropInfo(x, y, dist);
// copy in the relevant pieces
pdi.pieces = new int[height];
int idx = 0;
for (int yy = y; yy > (y - height); yy--) {
pdi.pieces[idx++] = board.getPiece(x, yy);
}
// update the working copy of the board with the eventual
// piece locations
dropPieces(board, pdi);
// add the column segment to the pot
drops.add(pdi);
}
/**
* If we want to keep the board filled at all times,
* this method will be called to create drop objects for the
* newly-filled in pieces.
*/
protected void addFillingDrops (
DropBoard board, DropPieceProvider provider, List drops)
{
boolean out = false;
// drop in new pieces for empty spaces at the top
for (int xx=0, bwid=board.getWidth(); xx < bwid; xx++) {
// get the distance to drop the pieces
int dist = board.getDropDistance(xx, -1);
if (dist != 0) {
PieceDropInfo pdi = new PieceDropInfo(xx, -1, dist);
pdi.pieces = new int[dist];
for (int ii=0; ii < dist; ii++) {
try {
pdi.pieces[ii] = provider.getNextPiece();
} catch (DropPieceProvider.OutOfPiecesException oop) {
// well, how far did we get?
int[] something = new int[ii];
System.arraycopy(pdi.pieces, 0, something, 0, ii);
pdi.pieces = something;
out = true;
break;
}
}
dropPieces(board, pdi);
drops.add(pdi);
// if we're outta pieces, bail.
if (out) {
return;
}
}
board.setPiece(xx, ty, piece);
if (drobs != null) {
drobs.pieceDropped(piece, xx, yy, xx, ty);
}
}
/**
* Returns the height of the piece segment to be dropped at the
* given coordinates.
*/
protected int getDropHeight (
DropBoard board, boolean allowConst, int x, int y, int dist)
{
int height = 0;
for (int yy = y; yy >= 0; yy--) {
int curpiece = board.getPiece(x, yy);
if (!_logic.isClimbablePiece(allowConst, curpiece, true)) {
return height;
}
height++;
if (!_logic.isClimbablePiece(allowConst, curpiece, false)) {
return height;
}
}
return height;
}
/**
* Updates the given board to reflect the eventual destination of the
* given pieces.
*/
protected void dropPieces (DropBoard board, PieceDropInfo pdi)
{
// clear out the original piece positions
if (!board.setSegment(
VERTICAL, pdi.col, pdi.row, pdi.pieces.length, PIECE_NONE)) {
Log.warning("Bogosity encountered when clearing pieces for drop " +
"[bwid=" + board.getWidth() +
", bhei=" + board.getHeight() + ", pdi=" + pdi + "].");
}
// place the pieces in their destination positions
applyPieces(board, pdi);
}
/**
* Applies the pieces in the given piece drop info object to the given
* board at their eventual destination positions.
*/
protected void applyPieces (DropBoard board, PieceDropInfo pdi)
{
int start = pdi.row, end = (pdi.row - pdi.pieces.length);
int idx = 0;
boolean error = false;
for (int yy = (start + pdi.dist); yy > (end + pdi.dist); yy--) {
error = !board.setPiece(pdi.col, yy, pdi.pieces[idx++]) || error;
}
if (error) {
Log.warning("Bogosity encountered while applying dropped " +
"pieces to board [bwid=" + board.getWidth() +
", bhei=" + board.getHeight() + ", pdi=" + pdi + "].");
}
}
/** The piece drop logic used to allow puzzle-specific piece dropping
* hooks. */
/** Allows puzzle-specific customizations. */
protected PieceDropLogic _logic;
/** The list of piece drop info objects detailing the piece drops
* resulting from the last call to {@link #getDroppedPieces}. */
protected ArrayList _drops = new ArrayList();
/** The board on which pieces are being dropped. */
protected DropBoard _board;
}