Behold Vilya, Ring of Air and repository for our game and virtual worldly

extensions to the distributed environment provided by Narya.


git-svn-id: svn+ssh://src.earth.threerings.net/vilya/trunk@1 c613c5cb-e716-0410-b11b-feb51c14d237
This commit is contained in:
Michael Bayne
2006-06-23 17:58:11 +00:00
commit a4df87e52f
317 changed files with 45818 additions and 0 deletions
@@ -0,0 +1,273 @@
//
// $Id: DropBlockSprite.java 4191 2006-06-13 22:42:20Z ray $
//
// 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.Rectangle;
/**
* The drop block sprite represents a block of multiple pieces that can be
* rotated to any of the four cardinal compass directions. As such, it
* may span multiple columns or rows depending on its orientation. The
* block has a "central" piece around which it rotates, with the other
* pieces referred to as "external" pieces.
*/
public class DropBlockSprite extends DropSprite
{
/**
* Constructs a drop block sprite and starts it dropping.
*
* @param view the board view upon which this sprite will be displayed.
* @param col the column of the central piece.
* @param row the row of the central piece.
* @param orient the orientation of the sprite.
* @param pieces the pieces displayed by the sprite.
*/
public DropBlockSprite (
DropBoardView view, int col, int row, int orient, int[] pieces)
{
super(view, col, row, pieces, 0);
_orient = orient;
}
/**
* Constructs a drop block sprite and starts it dropping.
*
* @param view the board view upon which this sprite will be displayed.
* @param col the column of the central piece.
* @param row the row of the central piece.
* @param orient the orientation of the sprite.
* @param pieces the pieces displayed by the sprite.
* @param renderOrder the rendering order of the sprite.
*/
public DropBlockSprite (
DropBoardView view, int col, int row, int orient, int[] pieces,
int renderOrder)
{
super(view, col, row, pieces, 0, renderOrder);
_orient = orient;
}
// documentation inherited
protected void init ()
{
super.init();
setOrientation(_orient);
}
/**
* Returns an array of the row numbers containing the block pieces.
* The first index is the row of the central piece. The array is
* cached and re-used internally and so the caller should make their
* own copy if they care to modify it.
*/
public int[] getRows ()
{
return _rows;
}
/**
* Returns an array of the column numbers containing the block pieces.
* The first index is the column of the central piece. The array is
* cached and re-used internally and so the caller should make their
* own copy if they care to modify it.
*/
public int[] getColumns ()
{
return _cols;
}
/**
* Returns the bounds of the block in board piece coordinates. The
* bounds rectangle is cached and re-used internally and so the caller
* should make their own copy if they care to modify it.
*/
public Rectangle getBoardBounds ()
{
return _dbounds;
}
/**
* Returns the row the external piece is located in.
*/
public int getExternalRow ()
{
return _erow;
}
/**
* Returns the column the external piece is located in.
*/
public int getExternalColumn ()
{
return _ecol;
}
// documentation inherited
public void setColumn (int col)
{
super.setColumn(col);
updateDropInfo();
}
// documentation inherited
public void setRow (int row)
{
super.setRow(row);
updateDropInfo();
}
// documentation inherited
public void setBoardLocation (int row, int col)
{
super.setBoardLocation(row, col);
updateDropInfo();
}
/**
* Updates the sprite image offset to reflect the direction in which
* the external piece is hanging.
*/
public void setOrientation (int orient)
{
super.setOrientation(orient);
int edx = 0, edy = 0;
if (orient == NORTH) {
edy = -1;
} else if (orient == WEST) {
edx = -1;
}
// update the sprite image offset
setRowOffset(edy);
setColumnOffset(edx);
// update the external piece position and drop block bounds
updateDropInfo();
}
// documentation inherited
public void toString (StringBuilder buf)
{
super.toString(buf);
buf.append(", erow=").append(_erow);
buf.append(", ecol=").append(_ecol);
}
/**
* Can this sprite pop-up a row on a forgiving rotation?
*/
public boolean canPopup ()
{
return (_popups > 0);
}
/**
* Called if we pop up to decrement the remaining popups we have.
*/
public void didPopup ()
{
_popups--;
}
/**
* Re-calculates the external piece position and bounds of the drop
* block.
*/
protected void updateDropInfo ()
{
// update the external piece location
_erow = calculateExternalRow();
_ecol = calculateExternalColumn();
// update the piece row and column arrays
_rows[0] = _row;
_rows[1] = _erow;
_cols[0] = _col;
_cols[1] = _ecol;
// calculate the drop block board bounds
int maxrow = Math.max(_row, _erow);
int mincol = Math.min(_col, _ecol);
int bpwid, bphei;
if (_orient == NORTH || _orient == SOUTH) {
bpwid = 1;
bphei = 2;
} else {
bpwid = 2;
bphei = 1;
}
// create the bounds rectangle if necessary
_dbounds.setBounds(mincol, maxrow, bpwid, bphei);
}
/**
* Returns the row the external piece is located in based on the
* current central piece location and sprite orientation.
*/
protected int calculateExternalRow ()
{
if (_orient == NORTH) {
return (_row - 1);
} else if (_orient == SOUTH) {
return (_row + 1);
} else {
return _row;
}
}
/**
* Returns the column the external piece is located in based on the
* current central piece location and sprite orientation.
*/
protected int calculateExternalColumn ()
{
if (_orient == WEST) {
return (_col - 1);
} else if (_orient == EAST) {
return (_col + 1);
} else {
return _col;
}
}
/** How many times this sprite can be popped-up a row in a forgiving
* rotation. */
protected byte _popups = 2;
/** The drop block bounds in board coordinates. */
protected Rectangle _dbounds = new Rectangle();
/** The drop block piece rows. */
protected int[] _rows = new int[2];
/** The drop block piece columns. */
protected int[] _cols = new int[2];
/** The external piece location in board coordinates. */
protected int _ecol, _erow;
}
@@ -0,0 +1,585 @@
//
// $Id: DropBoardView.java 3527 2005-04-28 23:17:29Z ray $
//
// 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.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
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.parlor.media.ScoreAnimation;
import com.threerings.puzzle.Log;
import com.threerings.puzzle.client.PuzzleBoardView;
import com.threerings.puzzle.data.Board;
import com.threerings.puzzle.data.PuzzleConfig;
import com.threerings.puzzle.util.PuzzleContext;
import com.threerings.puzzle.drop.data.DropBoard;
import com.threerings.puzzle.drop.data.DropConfig;
import com.threerings.puzzle.drop.data.DropPieceCodes;
/**
* The drop board view displays a drop puzzle game in progress for a
* single player.
*/
public abstract class DropBoardView extends PuzzleBoardView
implements DropPieceCodes
{
/** The color used to render normal scoring text. */
public static final Color SCORE_COLOR = Color.white;
/** The color used to render chain reward scoring text. */
public static final Color CHAIN_COLOR = Color.yellow;
/**
* Constructs a drop board view.
*/
public DropBoardView (PuzzleContext ctx, int pwid, int phei)
{
super(ctx);
// save off piece dimensions
_pwid = pwid;
_phei = phei;
// determine distance to float score animations
_scoreDist = 2 * _phei;
}
/**
* Initializes the board with the board dimensions.
*/
public void init (PuzzleConfig config)
{
DropConfig dconfig = (DropConfig)config;
// save off the board dimensions in pieces
_bwid = dconfig.getBoardWidth();
_bhei = dconfig.getBoardHeight();
super.init(config);
}
/**
* Returns the width in pixels of a single board piece.
*/
public int getPieceWidth ()
{
return _pwid;
}
/**
* Returns the height in pixels of a single board piece.
*/
public int getPieceHeight ()
{
return _phei;
}
/**
* Called by the {@link DropSprite} to populate <code>pos</code> with
* the screen coordinates in pixels at which a piece at <code>(col,
* row)</code> in the board should be drawn. Derived classes may wish
* to override this method to allow specialised positioning of
* sprites.
*/
public void getPiecePosition (int col, int row, Point pos)
{
pos.setLocation(col * _pwid, (row * _phei) - _roff);
}
/**
* Called by the {@link DropSprite} to get the dimensions of the area
* that will be occupied by rendering a piece segment of the given
* orientation and length whose bottom-leftmost corner is at
* <code>(col, row)</code>.
*/
public Dimension getPieceSegmentSize (int col, int row, int orient, int len)
{
if (orient == NORTH || orient == SOUTH) {
return new Dimension(_pwid, len * _phei);
} else {
return new Dimension(len * _pwid, _phei);
}
}
/**
* Creates a new piece sprite and places it directly in it's correct
* position.
*/
public void createPiece (int piece, int sx, int sy)
{
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);
}
/**
* 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 updatePiece (int sx, int sy)
{
updatePiece(_dboard.getPiece(sx, sy), sx, sy);
}
/**
* 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 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, sx, sy, NORTH));
} 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, sx, sy);
if (sprite != null) {
// position the piece properly to start
Point start = new Point();
getPiecePosition(sx, sy, start);
sprite.setLocation(start.x, start.y);
// now add it to the view
addSprite(sprite);
// and potentially move it into place
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;
}
// note that this piece is moving toward its destination
final int tpos = ty * _bwid + tx;
_moving[tpos] = true;
// then 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);
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)
{
_moving[py * _bwid + px] = false;
}
/**
* Returns true if the piece that is supposed to be at the specified
* coordinates is not yet there but is actually en route to that
* location (due to a call to {@link #movePiece}).
*/
protected boolean isMoving (int px, int py)
{
return _moving[py * _bwid + px];
}
/**
* Returns the image used to display the given piece at coordinates
* <code>(0, 0)</code> with an orientation of {@link #NORTH}. This
* serves as a convenience routine for those puzzles that don't bother
* rendering their pieces differently when placed at different board
* coordinates or in different orientations.
*/
public Mirage getPieceImage (int piece)
{
return getPieceImage(piece, 0, 0, NORTH);
}
/**
* Returns the image used to display the given piece at the specified
* column and row with the given orientation.
*/
public abstract Mirage getPieceImage (
int piece, int col, int row, int orient);
// documentation inherited
public void setBoard (Board board)
{
// when a new board arrives, we want to remove all drop sprites
// so that they don't modify the new board with their old ideas
for (Iterator iter = _actionSprites.iterator(); iter.hasNext(); ) {
Sprite s = (Sprite) iter.next();
if (s instanceof DropSprite) {
// remove it from _sprites safely
iter.remove();
// but then use the standard removal method
removeSprite(s);
}
}
// 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), 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;
}
}
}
// we use this to track when pieces are animating toward their new
// position and are not yet in place
_moving = new boolean[width * height];
}
/**
* 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);
}
/**
* Creates a new drop sprite used to animate the given pieces falling
* in the specified column.
*/
public DropSprite createPieces (int col, int row, int[] pieces, int dist)
{
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 showing the specified score
* floating up the view, with the label initially centered within the
* view.
*
* @param score the score text to display.
* @param color the color of the text.
* @param font the font.
*/
public ScoreAnimation createScoreAnimation (
String score, Color color, Font font)
{
return createScoreAnimation(
score, color, font, 0, _bhei - 1, _bwid, _bhei);
}
/**
* Creates and returns an animation showing the specified score
* floating up the view.
*
* @param score the score text to display.
* @param color the color of the text.
* @param font the font to use.
* @param x the left coordinate in board coordinates of the rectangle
* within which the score is to be centered.
* @param y the bottom coordinate in board coordinates of the
* rectangle within which the score is to be centered.
* @param width the width in board coordinates of the rectangle within
* which the score is to be centered.
* @param height the height in board coordinates of the rectangle
* within which the score is to be centered.
*/
public ScoreAnimation createScoreAnimation (String score, Color color,
Font font, int x, int y,
int width, int height)
{
// create the score animation
ScoreAnimation anim =
createScoreAnimation(score, color, font, x, y);
// position the label within the specified rectangle
Dimension lsize = anim.getLabel().getSize();
Point pos = new Point();
centerRectInBoardRect(
x, y, width, height, lsize.width, lsize.height, pos);
anim.setLocation(pos.x, pos.y);
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, int px, int py)
{
if (piece == PIECE_NONE) {
return null;
}
ImageSprite sprite = new ImageSprite(
getPieceImage(piece, px, py, NORTH));
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
* pixels) within the specified rectangle (in board coordinates).
*
* @param bx the bounding rectangle's left board coordinate.
* @param by the bounding rectangle's bottom board coordinate.
* @param bwid the bounding rectangle's width in board coordinates.
* @param bhei the bounding rectangle's height in board coordinates.
* @param rwid the width of the rectangle to position in pixels.
* @param rhei the height of the rectangle to position in pixels.
* @param pos the point to populate with the rectangle's final
* position.
*/
protected void centerRectInBoardRect (
int bx, int by, int bwid, int bhei, int rwid, int rhei, Point pos)
{
getPiecePosition(bx, by + 1, pos);
pos.x += (((bwid * _pwid) - rwid) / 2);
pos.y -= ((((bhei * _phei) - rhei) / 2) + rhei);
// constrain to fit wholly within the board bounds
pos.x = Math.max(Math.min(pos.x, _bounds.width - rwid), 0);
}
/**
* Rotates the given drop block sprite to the specified orientation,
* updating the image as necessary. Derived classes that make use of
* block dropping functionality should override this method to do the
* right thing.
*/
public void rotateDropBlock (DropBlockSprite sprite, int orient)
{
// nothing for now
}
// documentation inherited
public void paintBetween (Graphics2D gfx, Rectangle dirtyRect)
{
gfx.translate(0, -_roff);
renderBoard(gfx, dirtyRect);
renderRisingPieces(gfx, dirtyRect);
gfx.translate(0, _roff);
}
// documentation inherited
public Dimension getPreferredSize ()
{
int wid = _bwid * _pwid;
int hei = _bhei * _phei;
return new Dimension(wid, hei);
}
/**
* Renders the row of rising pieces to the given graphics context.
* Sub-classes that make use of board rising functionality should
* override this method to draw the rising piece row.
*/
protected void renderRisingPieces (Graphics2D gfx, Rectangle dirtyRect)
{
// nothing for now
}
/**
* Sets the board rising offset to the given y-position.
*/
protected void setRiseOffset (int y)
{
if (y != _roff) {
_roff = y;
_remgr.invalidateRegion(_bounds);
}
}
/** The drop board. */
protected DropBoard _dboard;
/** A sprite for every piece displayed in the drop board. */
protected Sprite[] _pieces;
/** Indicates whether a piece is en route to its destination. */
protected boolean[] _moving;
/** The piece dimensions in pixels. */
protected int _pwid, _phei;
/** The board rising offset. */
protected int _roff;
/** The board dimensions in pieces. */
protected int _bwid, _bhei;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
//
// $Id: DropPanel.java 4191 2006-06-13 22:42:20Z ray $
//
// 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 com.threerings.puzzle.data.BoardSummary;
/**
* Puzzles using the drop services need implement this interface to
* display drop puzzle related information.
*/
public interface DropPanel
{
/**
* Sets the next block to be displayed.
*/
public void setNextBlock (int[] pieces);
/**
* Updates the board summary display for the given player.
*/
public void setSummary (int pidx, BoardSummary summary);
}
@@ -0,0 +1,584 @@
//
// $Id: DropSprite.java 4191 2006-06-13 22:42:20Z ray $
//
// 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.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import com.samskivert.util.ObserverList;
import com.threerings.util.DirectionUtil;
import com.threerings.media.image.Mirage;
import com.threerings.media.sprite.Sprite;
import com.threerings.puzzle.Log;
/**
* The drop sprite is a sprite that displays one or more pieces falling
* toward the bottom of the board.
*/
public class DropSprite extends Sprite
{
/**
* Constructs a drop sprite and starts it dropping.
*
* @param view the board view upon which this sprite will be displayed.
* @param col the column of the sprite.
* @param row the row of the bottom-most piece.
* @param pieces the pieces displayed by the sprite.
* @param dist the distance the sprite is to drop in rows.
*/
public DropSprite (
DropBoardView view, int col, int row, int[] pieces, int dist)
{
this(view, col, row, pieces, dist, -1);
}
/**
* Constructs a drop sprite and starts it dropping.
*
* @param view the board view upon which this sprite will be displayed.
* @param col the column of the sprite.
* @param row the row of the bottom-most piece.
* @param pieces the pieces displayed by the sprite.
* @param dist the distance the sprite is to drop in rows.
* @param renderOrder the render order.
*/
public DropSprite (
DropBoardView view, int col, int row, int[] pieces, int dist,
int renderOrder)
{
_view = view;
_col = col;
_row = row;
_pieces = pieces;
_dist = (dist == 0) ? 1 : dist;
_orient = NORTH;
_unit = _view.getPieceHeight();
setRenderOrder(renderOrder);
}
// documentation inherited
protected void init ()
{
super.init();
// size the bounds to fit our pieces
updateBounds();
// set up the piece location
setBoardLocation(_row, _col);
// calculate vertical render offset based on the number of pieces
setRowOffset(-(_pieces.length - 1));
}
/**
* Returns the remaining number of columns to drop.
*/
public int getDistance ()
{
return _dist;
}
/**
* Returns the column the piece is located in.
*/
public int getColumn ()
{
return _col;
}
/**
* Returns the row the piece is located in.
*/
public int getRow ()
{
return _row;
}
/**
* Returns the pieces the sprite is displaying.
*/
public int[] getPieces ()
{
return _pieces;
}
/**
* Returns the velocity of this sprite.
*/
public float getVelocity ()
{
return _vel;
}
/**
* Sets the row and column the piece is located in.
*/
public void setBoardLocation (int row, int col)
{
_row = row;
_col = col;
updatePosition();
}
/**
* Sets the column the piece is located in.
*/
public void setColumn (int col)
{
_col = col;
updatePosition();
}
/**
* Set the row the piece is located in.
*/
public void setRow (int row)
{
_row = row;
updatePosition();
}
/**
* Sets the column offset of the sprite image.
*/
public void setColumnOffset (int count)
{
_offx = count;
updateRenderOffset();
updateRenderOrigin();
}
/**
* Sets the row offset of the sprite image.
*/
public void setRowOffset (int count)
{
_offy = count;
updateRenderOffset();
updateRenderOrigin();
}
/**
* Sets the pieces the sprite is displaying.
*/
public void setPieces (int[] pieces)
{
_pieces = pieces;
}
/**
* Sets the velocity of this sprite. The time at which the current
* row was entered is modified so that the sprite position will remain
* the same when calculated using the new velocity since the piece
* sprite may have its velocity modified in the middle of a row
* traversal.
*/
public void setVelocity (float velocity)
{
// bail if we've already got the requested velocity
if (_vel == velocity) {
return;
}
if (_rowstamp > 0) {
// get our current distance along the row
long now = _view.getTimeStamp();
float pctdone = getPercentDone(now);
// revise the current row entry time to account for the new velocity
float travpix = pctdone * _unit;
long msecs = (long)(travpix / velocity);
_rowstamp = now - msecs;
}
// update the velocity
_vel = velocity;
}
/**
* Starts the piece dropping toward the next row.
*/
public void drop ()
{
// Log.info("Dropping piece [piece=" + this + "].");
// drop one row by default
if (_dist <= 0) {
_dist = 1;
}
if (_stopstamp > 0) {
// we're dropping from a stand-still
long delta = _view.getTimeStamp() - _stopstamp;
_rowstamp += delta;
_stopstamp = 0;
} else {
// we're continuing a previous drop, so make use of any
// previously existing time
_rowstamp = _endstamp;
}
}
/**
* Returns true if this drop sprite is dropping, false if it has been
* {@link #stop}ped or has not yet been {@link #drop}ped.
*/
public boolean isDropping ()
{
return (_stopstamp == 0) && (_rowstamp != 0);
}
/**
* Stops the piece from dropping.
*/
public void stop ()
{
if (_stopstamp == 0) {
_stopstamp = _view.getTimeStamp();
// Log.info("Stopped piece [piece=" + this + "].");
}
}
/**
* Puts the drop sprite into (or takes it out of) bouncing
* mode. Bouncing mode is used to put the sprite into limbo after it
* lands but before we commit the landing, giving the user a last
* moment change move or rotate the piece. While the sprite is
* "bouncing" it will be rendered one pixel below it's at rest state.
*/
public void setBouncing (boolean bouncing)
{
if (_bouncing = bouncing) {
// if we've activated bouncing, shift the sprite slightly to
// illustrate its new state
shiftForBounce();
// to prevent funny business in the event that we were a long
// ways past the end of the row when we landed, we warp the
// sprite back to the exact point of landing for the purposes
// of the bounce and any subsequent antics
_endstamp = _rowstamp = _view.getTimeStamp();
// Log.info("Adjusted rowstap due to bounce " +
// "[time=" + _endstamp + "].");
}
}
/**
* Returns true if this sprite is bouncing.
*/
public boolean isBouncing ()
{
return _bouncing;
}
/**
* Updates the sprite's location to illustrate that it is currently in
* the "bouncing" state.
*/
protected void shiftForBounce ()
{
setLocation(_ox, _srcPos.y+1);
}
// documentation inherited
public boolean inside (Shape shape)
{
return shape.contains(_bounds);
}
/**
* Returns a value between <code>0.0</code> and <code>1.0</code>
* representing how far the piece has moved toward the next row
* as of the given time stamp.
*/
public float getPercentDone (long timestamp)
{
// if we've never been ticked and so haven't yet initialized our
// row start timestamp, just let the caller know that we've not
// traversed our row at all
if (_rowstamp == 0) {
return 0.0f;
}
long msecs = Math.max(0, timestamp - _rowstamp);
float travpix = msecs * _vel;
float pctdone = (travpix / _unit);
// Log.info("getPercentDone [timestamp=" + timestamp +
// ", rowstamp=" + _rowstamp + ", msecs=" + msecs +
// ", travpix=" + travpix + ", pctdone=" + pctdone +
// ", vel=" + _vel + "].");
return pctdone;
}
// documentation inherited
public void paint (Graphics2D gfx)
{
// get the column and row increment based on the sprite's orientation
int oidx = _orient/2;
int incx = ORIENT_DX[oidx];
int incy = ORIENT_DY[oidx];
// determine offset from the start of each actual row and column
int dx = _ox - _srcPos.x, dy = _oy - _srcPos.y;
int pcol = _col, prow = _row;
for (int ii = 0; ii < _pieces.length; ii++) {
// ask the board for the render position of this piece
_view.getPiecePosition(pcol, prow, _renderPos);
// draw the piece image
paintPieceImage(gfx, ii, pcol, prow, _orient,
_renderPos.x + dx, _renderPos.y + dy);
// increment the target column and row
pcol += incx;
prow += incy;
}
}
/**
* Paints the specified piece with the supplied parameters.
*/
protected void paintPieceImage (Graphics2D gfx, int pieceidx,
int col, int row, int orient, int x, int y)
{
Mirage image = _view.getPieceImage(_pieces[pieceidx], col, row, orient);
image.paint(gfx, x, y);
}
// documentation inherited
public void tick (long timestamp)
{
super.tick(timestamp);
// initialize our rowstamp if we haven't done so already
if (_rowstamp == 0) {
_rowstamp = timestamp;
}
// if we're bouncing or paused, do nothing here
if (_bouncing || _stopstamp > 0) {
return;
}
PieceMovedOp pmop = null;
// figure out how far along the current board coordinate we should be
float pctdone = getPercentDone(timestamp);
if (pctdone >= 1.0f) {
// note that we've reached the next row
advancePosition();
// update remaining drop distance
_dist--;
// calculate any remaining time to be used
long used = (long)(_unit / _vel);
_endstamp = _rowstamp + used;
_rowstamp = _endstamp;
// update our percent done because we've moved down a row
pctdone -= 1.0;
// inform observers that we've reached our destination
pmop = new PieceMovedOp(this, timestamp, _col, _row);
}
// constrain the sprite's position to the destination row
pctdone = Math.min(pctdone, 1.0f);
// calculate the latest sprite position
int nx = _srcPos.x + (int)((_destPos.x - _srcPos.x) * pctdone);
int ny = _srcPos.y + (int)((_destPos.y - _srcPos.y) * pctdone);
// Log.info("Drop sprite tick [dist=" + _dist + ", pctdone=" + pctdone +
// ", row=" + _row + ", col=" + _col +
// ", nx=" + nx + ", ny=" + ny + "].");
// only update the sprite's location if it actually moved
if (_ox != nx || _oy != ny) {
setLocation(nx, ny);
}
// lastly notify our observers if we made it to the next row
if (pmop != null) {
_observers.apply(pmop);
}
}
/**
* Called when the sprite has finished traversing its current row to
* advance its board coordinates to the next row.
*/
protected void advancePosition ()
{
setRow(_row + 1);
// Log.info("Moved to row " + _row);
}
// documentation inherited
public void fastForward (long timeDelta)
{
if (_rowstamp > 0) {
_rowstamp += timeDelta;
}
}
// documentation inherited
public void toString (StringBuilder buf)
{
super.toString(buf);
buf.append(", orient=").append(DirectionUtil.toShortString(_orient));
buf.append(", row=").append(_row);
buf.append(", col=").append(_col);
buf.append(", offx=").append(_offx);
buf.append(", offy=").append(_offy);
buf.append(", dist=").append(_dist);
}
/**
* Updates internal pixel coordinates used when the piece is moving.
*/
protected void updatePosition ()
{
_view.getPiecePosition(_col, _row, _srcPos);
_view.getPiecePosition(_col, _row+1, _destPos);
setLocation(_srcPos.x, _srcPos.y);
}
// documentation inherited
public void setOrientation (int orient)
{
invalidate();
super.setOrientation(orient);
updateBounds();
invalidate();
}
/**
* Updates the bounds for this sprite based on the sprite display
* dimensions in the view.
*/
protected void updateBounds ()
{
Dimension size = _view.getPieceSegmentSize(
_col, _row, _orient, _pieces.length);
_bounds.width = size.width;
_bounds.height = size.height;
}
/**
* Adjusts our render origin such that our location is not in the
* upper left of the sprite's rendered image but is in fact offset by
* some number of rows and columns.
*/
protected void updateRenderOffset ()
{
_oxoff = -(_view.getPieceWidth() * _offx);
_oyoff = -(_view.getPieceHeight() * _offy);
}
/** Used to dispatch {@link DropSpriteObserver#pieceMoved}. */
protected static class PieceMovedOp implements ObserverList.ObserverOp
{
public PieceMovedOp (DropSprite sprite, long when, int col, int row)
{
_sprite = sprite;
_when = when;
_col = col;
_row = row;
}
public boolean apply (Object observer)
{
if (observer instanceof DropSpriteObserver) {
((DropSpriteObserver)observer).pieceMoved(
_sprite, _when, _col, _row);
}
return true;
}
protected DropSprite _sprite;
protected long _when;
protected int _col, _row;
}
/** The default piece velocity. */
protected static final float DEFAULT_VELOCITY = 30f/1000f;
/** The time at which we started the current row. */
protected long _rowstamp;
/** The time at which we reached the end of the previous row. */
protected long _endstamp;
/** The time at which we were stopped en route to our next row. */
protected long _stopstamp;
/** The board view upon which this sprite is displayed. */
protected DropBoardView _view;
/** The unit distance the sprite moves to reach the next row. */
protected int _unit;
/** The screen coordinates of the top-left of the row currently
* occupied by the sprite. */
protected Point _srcPos = new Point();
/** The screen coordinates of the top-left of the row toward which the
* sprite is falling. */
protected Point _destPos = new Point();
/** The piece render position; used as working data when determining
* where to render each piece in the sprite. */
protected Point _renderPos = new Point();
/** The number of rows remaining to drop. */
protected int _dist;
/** The piece velocity. */
protected float _vel = DEFAULT_VELOCITY;
/** The offsets in columns or rows at which the piece is rendered. */
protected int _offx, _offy;
/** The current piece location in the board. */
protected int _row, _col;
/** The pieces this sprite is displaying. */
protected int[] _pieces;
/** Indicates that the drop sprite is bouncing; see {@link
* #setBouncing}. */
protected boolean _bouncing;
// used to compute the column and row increment while rendering the
// sprite's pieces based on its orientation
// W N E S
protected static final int[] ORIENT_DX = { -1, 0, 1, 0 };
protected static final int[] ORIENT_DY = { 0, -1, 0, 1 };
}
@@ -0,0 +1,34 @@
//
// $Id: DropSpriteObserver.java 4191 2006-06-13 22:42:20Z ray $
//
// 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;
/**
* Provides notifications for drop puzzle specific stuff.
*/
public interface DropSpriteObserver
{
/**
* Called when the drop sprite has moved completely to the specified
* board coordinates.
*/
public void pieceMoved (DropSprite sprite, long when, int col, int row);
}
@@ -0,0 +1,108 @@
//
// $Id: NextBlockView.java 4191 2006-06-13 22:42:20Z ray $
//
// 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.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JComponent;
import com.threerings.media.image.Mirage;
import com.threerings.util.DirectionCodes;
/**
* The next block view displays an image representing the next drop block
* to appear in the game.
*/
public class NextBlockView extends JComponent
implements DirectionCodes
{
/**
* Constructs a next block view.
*/
public NextBlockView (DropBoardView view, int pwid, int phei, int orient)
{
// save things off
_view = view;
_pwid = pwid;
_phei = phei;
_orient = orient;
// configure the component
setOpaque(false);
}
/**
* Sets the pieces displayed by the view.
*/
public void setPieces (int[] pieces)
{
_pieces = pieces;
repaint();
}
// documentation inherited
public void paintComponent (Graphics g)
{
super.paintComponent(g);
// draw the pieces
Graphics2D gfx = (Graphics2D)g;
if (_pieces != null) {
Dimension size = getSize();
int xpos = (_orient == VERTICAL) ? 0 : (size.width - _pwid);
int ypos = (_orient == VERTICAL) ? (size.height - _phei) : 0;
for (int ii = 0; ii < _pieces.length; ii++) {
Mirage image = _view.getPieceImage(_pieces[ii]);
image.paint(gfx, xpos, ypos);
if (_orient == VERTICAL) {
ypos -= _phei;
} else {
xpos -= _pwid;
}
}
}
}
// documentation inherited
public Dimension getPreferredSize ()
{
int wid = (_orient == VERTICAL) ? _pwid : (2 * _pwid);
int hei = (_orient == VERTICAL) ? (2 * _phei) : _phei;
return new Dimension(wid, hei);
}
/** The drop board view from which we obtain piece images. */
protected DropBoardView _view;
/** The pieces displayed by this view. */
protected int[] _pieces;
/** The piece dimensions in pixels. */
protected int _pwid, _phei;
/** The view orientation; one of {@link #HORIZONTAL} or {@link
* #VERTICAL}. */
protected int _orient;
}
@@ -0,0 +1,111 @@
//
// $Id: PieceGroupAnimation.java 4191 2006-06-13 22:42:20Z ray $
//
// 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.Graphics2D;
import java.awt.Rectangle;
import com.threerings.media.animation.Animation;
import com.threerings.media.sprite.ImageSprite;
import com.threerings.media.sprite.PathObserver;
import com.threerings.media.sprite.Sprite;
import com.threerings.media.util.Path;
import com.threerings.puzzle.drop.data.DropBoard;
import com.threerings.puzzle.drop.data.DropPieceCodes;
/**
* Animates all the pieces on a puzzle board doing some sort of global
* effect like all flying into place or out into the ether.
*/
public abstract class PieceGroupAnimation extends Animation
implements PathObserver
{
/**
* Creates a piece group animation which must be initialized with a
* subsequent call to {@link #init}.
*/
public PieceGroupAnimation (DropBoardView view, DropBoard board)
{
super(new Rectangle(0, 0, 0, 0)); // we don't render ourselves
_view = view;
_board = board;
}
// documentation inherited
public void tick (long tickStamp)
{
// nothing doing
}
// documentation inherited
public void paint (Graphics2D gfx)
{
// nothing doing
}
// documentation inherited from interface
public void pathCancelled (Sprite sprite, Path path)
{
_finished = (--_penders == 0);
}
// documentation inherited from interface
public void pathCompleted (Sprite sprite, Path path, long when)
{
_finished = (--_penders == 0);
}
// documentation inherited
protected void willStart (long tickStamp)
{
super.willStart(tickStamp);
// create an image sprite for every piece on the board and set
// them on their paths
int width = _board.getWidth(), height = _board.getHeight();
_sprites = new Sprite[width * height];
for (int yy = 0; yy < height; yy++) {
for (int xx = 0; xx < width; xx++) {
int spos = yy*width+xx;
_sprites[spos] = _view.getPieceSprite(xx, yy);
if (_sprites[spos] != null) {
configureSprite(_sprites[spos], xx, yy);
_sprites[spos].addSpriteObserver(this);
_penders++;
}
}
}
}
/**
* An animation must override this method to configure each sprite
* with a path, potentially a render order, and whatever other
* configurations are needed.
*/
protected abstract void configureSprite (Sprite sprite, int xx, int yy);
protected DropBoardView _view;
protected DropBoard _board;
protected Sprite[] _sprites;
protected int _penders;
}