diff --git a/src/java/com/threerings/puzzle/drop/client/DropBoardView.java b/src/java/com/threerings/puzzle/drop/client/DropBoardView.java index 51a531c2a..dbcca13b8 100644 --- a/src/java/com/threerings/puzzle/drop/client/DropBoardView.java +++ b/src/java/com/threerings/puzzle/drop/client/DropBoardView.java @@ -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 (col, - * row). + * 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. Note: 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. Note: 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 (col, + * row). + */ + 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 pos 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; diff --git a/src/java/com/threerings/puzzle/drop/client/DropControllerDelegate.java b/src/java/com/threerings/puzzle/drop/client/DropControllerDelegate.java index 6100ac019..560845daf 100644 --- a/src/java/com/threerings/puzzle/drop/client/DropControllerDelegate.java +++ b/src/java/com/threerings/puzzle/drop/client/DropControllerDelegate.java @@ -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; diff --git a/src/java/com/threerings/puzzle/drop/data/DropBoard.java b/src/java/com/threerings/puzzle/drop/data/DropBoard.java index 0a87af285..f01d7af47 100644 --- a/src/java/com/threerings/puzzle/drop/data/DropBoard.java +++ b/src/java/com/threerings/puzzle/drop/data/DropBoard.java @@ -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}). diff --git a/src/java/com/threerings/puzzle/drop/server/DropManagerDelegate.java b/src/java/com/threerings/puzzle/drop/server/DropManagerDelegate.java index 062fc97e1..22cc5a1fe 100644 --- a/src/java/com/threerings/puzzle/drop/server/DropManagerDelegate.java +++ b/src/java/com/threerings/puzzle/drop/server/DropManagerDelegate.java @@ -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); } /** diff --git a/src/java/com/threerings/puzzle/drop/util/DropPieceProvider.java b/src/java/com/threerings/puzzle/drop/util/DropPieceProvider.java deleted file mode 100644 index e44c58a7e..000000000 --- a/src/java/com/threerings/puzzle/drop/util/DropPieceProvider.java +++ /dev/null @@ -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 - { - } -} diff --git a/src/java/com/threerings/puzzle/drop/util/PieceDropper.java b/src/java/com/threerings/puzzle/drop/util/PieceDropper.java index ee654a08e..08a55a105 100644 --- a/src/java/com/threerings/puzzle/drop/util/PieceDropper.java +++ b/src/java/com/threerings/puzzle/drop/util/PieceDropper.java @@ -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 drops 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; }