Holy crap Batman! It's the beginnings of refactoring the basic puzzle

stuff into Narya.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@2876 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2003-11-26 01:42:34 +00:00
parent 0601c94e42
commit 54eaddb27d
53 changed files with 9395 additions and 0 deletions
@@ -0,0 +1,46 @@
//
// $Id: DropBoardUtil.java,v 1.1 2003/11/26 01:42:34 mdb Exp $
package com.threerings.puzzle.drop.util;
import com.threerings.util.DirectionCodes;
import com.threerings.puzzle.drop.data.DropBoard;
public class DropBoardUtil
implements DirectionCodes
{
/**
* Returns the orientation resulting from rotating the block in the
* given direction the specified number of times.
*
* @param orient the current orientation.
* @param dir the direction to rotate in; one of <code>CW</code> or
* <code>CCW</code>.
* @param count the number of rotations to perform.
*
* @return the rotated orientation.
*/
public static int getRotatedOrientation (int orient, int dir, int count)
{
for (int ii = 0; ii < (count % 4); ii++) {
orient = getRotatedOrientation(orient, dir);
}
return orient;
}
/**
* Returns the orientation resulting from rotating the block in
* the given direction.
*
* @param orient the current orientation.
* @param dir the direction to rotate in; one of <code>CW</code> or
* <code>CCW</code>.
*
* @return the rotated orientation.
*/
public static int getRotatedOrientation (int orient, int dir)
{
return (orient + ((dir == CW) ? 2 : 6)) % DIRECTION_COUNT;
}
}
@@ -0,0 +1,53 @@
//
// $Id: DropGameUtil.java,v 1.1 2003/11/26 01:42:34 mdb Exp $
package com.threerings.puzzle.drop.util;
import java.awt.event.KeyEvent;
import com.threerings.util.KeyTranslatorImpl;
import com.threerings.puzzle.drop.client.DropControllerDelegate;
import com.threerings.puzzle.util.PuzzleGameUtil;
/**
* Drop puzzle game related utilities.
*/
public class DropGameUtil
{
/**
* Returns a key translator configured with mappings suitable for a
* drop puzzle game.
*/
public static KeyTranslatorImpl getKeyTranslator ()
{
// start with the standard puzzle key mappings
KeyTranslatorImpl xlate = PuzzleGameUtil.getKeyTranslator();
// add all press key mappings
xlate.addPressCommand(KeyEvent.VK_LEFT,
DropControllerDelegate.MOVE_BLOCK_LEFT,
MOVE_RATE, MOVE_DELAY);
xlate.addPressCommand(KeyEvent.VK_RIGHT,
DropControllerDelegate.MOVE_BLOCK_RIGHT,
MOVE_RATE, MOVE_DELAY);
xlate.addPressCommand(KeyEvent.VK_UP,
DropControllerDelegate.ROTATE_BLOCK_CCW, 0);
xlate.addPressCommand(KeyEvent.VK_DOWN,
DropControllerDelegate.ROTATE_BLOCK_CW, 0);
xlate.addPressCommand(KeyEvent.VK_SPACE,
DropControllerDelegate.START_DROP_BLOCK, 0);
// add all release key mappings
xlate.addReleaseCommand(KeyEvent.VK_SPACE,
DropControllerDelegate.END_DROP_BLOCK);
return xlate;
}
/** The move key repeat rate in moves per second. */
protected static final int MOVE_RATE = 7;
/** The delay in milliseconds before the move keys begin to repeat. */
protected static final long MOVE_DELAY = 300L;
}
@@ -0,0 +1,23 @@
//
// $Id: DropPieceProvider.java,v 1.1 2003/11/26 01:42:34 mdb Exp $
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
{
}
}
@@ -0,0 +1,163 @@
//
// $Id: PieceDestroyer.java,v 1.1 2003/11/26 01:42:34 mdb Exp $
package com.threerings.puzzle.drop.util;
import java.util.ArrayList;
import java.util.List;
import com.threerings.puzzle.drop.data.DropBoard;
import com.threerings.puzzle.drop.data.DropBoard.PieceOperation;
import com.threerings.puzzle.drop.data.DropPieceCodes;
import com.threerings.puzzle.drop.data.SegmentInfo;
/**
* Handles destroying contiguous piece segments in a drop board.
*/
public class PieceDestroyer
implements DropPieceCodes
{
/**
* An interface to be implemented by specific puzzles to detail the
* parameters and methodology by which pieces are destroyed in the
* puzzle board.
*/
public interface DestroyLogic
{
/**
* Returns the minimum length of a contiguously piece segment that
* should be destroyed.
*/
public int getMinimumLength ();
/**
* Returns whether piece <code>a</code> is equivalent to piece
* <code>b</code> for the purposes of including it in a contiguous
* piece segment to be destroyed.
*/
public boolean isEquivalent (int a, int b);
}
/**
* Constructs a piece destroyer that destroys pieces as specified by
* the supplied destroy logic.
*/
public PieceDestroyer (DestroyLogic logic)
{
_logic = logic;
}
/**
* Destroys all pieces in the given board that are in contiguous rows
* or columns of pieces, returning a list of {@link SegmentInfo}
* objects detailing the destroyed piece segments. Note that a single
* list is used internally to gather the segment info, and so callers
* that care to modify the list should create their own copy; also,
* the pieces in the segments may overlap, i.e., two segments may
* contain the same piece.
*/
public List destroyPieces (DropBoard board, PieceOperation destroyOp)
{
// find all horizontally-oriented destroyed segments
int bwid = board.getWidth(), bhei = board.getHeight();
_destroyed.clear();
int end = bwid - _logic.getMinimumLength() + 1;
for (int yy = (bhei - 1); yy >= 0; yy--) {
int xx = 0;
while (xx < end) {
xx += findSegment(board, HORIZONTAL, xx, yy);
}
}
// find all vertically-oriented destroyed segments
end = _logic.getMinimumLength() - 2;
for (int xx = 0; xx < bwid; xx++) {
int yy = bhei - 1;
while (yy > end) {
yy -= findSegment(board, VERTICAL, xx, yy);
}
}
// destroy the pieces
int size = _destroyed.size();
for (int ii = 0; ii < size; ii++) {
SegmentInfo si = (SegmentInfo)_destroyed.get(ii);
board.applyOp(si.dir, si.x, si.y, si.len, destroyOp);
}
return _destroyed;
}
/**
* Searches for a contiguously colored piece segment with the
* specified orientation and root coordinates in the supplied board
* and returns the length of the segment traversed.
*/
protected int findSegment (DropBoard board, int dir, int x, int y)
{
_lengthOp.reset();
board.applyOp(dir, x, y, _lengthOp);
int len = _lengthOp.getLength();
if (len >= _logic.getMinimumLength()) {
_destroyed.add(new SegmentInfo(dir, x, y, len));
}
return len;
}
/**
* A piece operation that calculates the length of the contiguous
* piece segment to which it is applied.
*/
protected class SegmentLengthOperation
implements PieceOperation
{
/**
* Resets the operation for application to a new piece segment.
*/
public void reset ()
{
_len = 0;
}
/**
* Returns the length of the contiguous piece segment.
*/
public int getLength ()
{
return _len;
}
// documentation inherited
public boolean execute (DropBoard board, int col, int row)
{
int piece = board.getPiece(col, row);
if (_len == 0) {
_len = 1;
_piece = piece;
return (piece != PIECE_NONE);
} else if (_logic.isEquivalent(piece, _piece)) {
_len++;
return true;
} else {
return false;
}
}
/** The root segment piece. */
protected int _piece;
/** The segment length in pieces. */
protected int _len;
}
/** The puzzle-specific destroy logic with which we do our business. */
protected DestroyLogic _logic;
/** The piece operation used to determine segment length. */
protected SegmentLengthOperation _lengthOp = new SegmentLengthOperation();
/** The list of destroyed piece segments. */
protected ArrayList _destroyed = new ArrayList();
}
@@ -0,0 +1,61 @@
//
// $Id: PieceDropLogic.java,v 1.1 2003/11/26 01:42:34 mdb Exp $
package com.threerings.puzzle.drop.util;
import com.threerings.util.DirectionCodes;
import com.threerings.puzzle.drop.data.DropBoard;
/**
* An interface to be implemented by games that would like to be able to
* drop their pieces during game play.
*/
public interface PieceDropLogic
{
/**
* Should the board always be filled?
*
* @return false for normal behavior.
*/
public boolean boardAlwaysFilled ();
/**
* Returns whether the given piece is potentially droppable.
*/
public boolean isDroppablePiece (int piece);
/**
* Returns whether the given piece has constraints upon it that
* impact its droppability.
*/
public boolean isConstrainedPiece (int piece);
/**
* Returns whether the given piece terminates a column climb when
* determining the height of a piece column to be dropped.
*
* @param allowConst whether to allow dropping constrained pieces
* (though only in the first encountered constrained block.)
* @param piece the piece to consider.
* @param pre whether the climbability check is being performed
* before the height is incremented, or after.
*/
public boolean isClimbablePiece (
boolean allowConst, int piece, boolean pre);
/**
* Returns the x-axis coordinate of the specified edge of the
* given constrained piece.
*
* <p> TODO: This should go away once the sword and sail games
* have standardized on WEST/EAST or BLOCK_LEFT/BLOCK_RIGHT to
* reference block edges.
*
* @param board the board to search.
* @param col the column of the constrained piece.
* @param row the row of the constrained piece.
* @param dir the edge direction to find; one of {@link
* DirectionCodes#LEFT} or {@link DirectionCodes#RIGHT}.
*/
public int getConstrainedEdge (DropBoard board, int col, int row, int dir);
}
@@ -0,0 +1,340 @@
//
// $Id: PieceDropper.java,v 1.1 2003/11/26 01:42:34 mdb Exp $
package com.threerings.puzzle.drop.util;
import java.util.ArrayList;
import java.util.List;
import com.samskivert.util.StringUtil;
import com.threerings.puzzle.Log;
import com.threerings.puzzle.drop.data.DropBoard;
import com.threerings.puzzle.drop.data.DropPieceCodes;
/**
* Handles dropping pieces in a board.
*/
public class PieceDropper
implements DropPieceCodes
{
/**
* A class to hold information detailing the pieces to be dropped
* in a particular column.
*/
public static class PieceDropInfo
{
/** The starting row of the bottom piece being dropped. */
public int row;
/** The column number. */
public int col;
/** The distance to drop the pieces. */
public int dist;
/** The pieces to be dropped. */
public int[] pieces;
/**
* Constructs a piece drop info object.
*/
public PieceDropInfo (int col, int row, int dist)
{
this.col = col;
this.row = row;
this.dist = dist;
}
/** Returns a string representation of this instance. */
public String toString ()
{
return StringUtil.fieldsToString(this);
}
}
/**
* Constructs a piece dropper that uses the supplied piece drop logic
* to specialise itself for a particular puzzle.
*/
public PieceDropper (PieceDropLogic logic)
{
_logic = logic;
}
/**
* 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.
*
* @param DropPieceProvider if the board should always be filled
* (as specified by the PieceDropLogic) this will provide
* information on the newly filled in pieces.
*/
public List dropPieces (DropBoard board, DropPieceProvider provider)
{
int bhei = board.getHeight(), bwid = board.getWidth();
_drops.clear();
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);
}
}
if (_logic.boardAlwaysFilled()) {
addFillingDrops(board, provider, _drops);
}
return _drops;
}
/**
* 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.
*/
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)
{
// skip empty or fixed pieces
int piece = board.getPiece(x, y);
if (!_logic.isDroppablePiece(piece)) {
return;
}
if (_logic.isConstrainedPiece(piece)) {
// get the distance to drop the block
int dist = getBlockDropDistance(board, x, y);
if (dist == 0) {
return;
}
// scoot along the bottom edge of the block dropping each column
int bwid = board.getWidth();
int start = _logic.getConstrainedEdge(board, x, y, LEFT);
int end = _logic.getConstrainedEdge(board, x, y, RIGHT);
for (int xpos = start; xpos <= end; xpos++) {
addDropInfo(board, drops, true, xpos, y, dist);
}
} else {
// get the distance to drop the pieces
int dist = board.getDropDistance(x, y);
if (dist == 0) {
return;
}
// add the column segment to the list of drops
addDropInfo(board, drops, false, x, y, dist);
}
}
/**
* 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)
{
// 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();
}
// 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;
}
}
}
}
/**
* 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);
}
/**
* Returns the number of spaces the block whose bottom edge
* contains the specified coordinate can be dropped based on the
* contents of the given board. The droppable distance for a
* block is determined by finding the minimum drop distance across
* all columns the block spans.
*/
protected int getBlockDropDistance (DropBoard board, int x, int y)
{
// get the smallest drop distance across all of the block columns
int bwid = board.getWidth();
int start = Math.max(_logic.getConstrainedEdge(board, x, y, LEFT), 0);
int end = Math.min(
_logic.getConstrainedEdge(board, x, y, RIGHT), bwid - 1);
int dist = board.getHeight() - 1;
for (int xpos = start; xpos <= end; xpos++) {
dist = Math.min(dist, board.getDropDistance(xpos, y));
}
return dist;
}
/**
* 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. */
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;
}