All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.threerings.puzzle.drop.client.DropBoardView Maven / Gradle / Ivy

The newest version!
//
// $Id$
//
// Vilya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/vilya/
//
// 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.util.Iterator;

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 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.game.data.GameConfig;
import com.threerings.parlor.media.ScoreAnimation;

import com.threerings.puzzle.client.PuzzleBoardView;
import com.threerings.puzzle.data.Board;
import com.threerings.puzzle.drop.data.DropBoard;
import com.threerings.puzzle.drop.data.DropConfig;
import com.threerings.puzzle.drop.data.DropPieceCodes;
import com.threerings.puzzle.util.PuzzleContext;

import static com.threerings.puzzle.Log.log;

/**
 * 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.
     */
    @Override
    public void init (GameConfig 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 pos with the screen coordinates
     * in pixels at which a piece at (col, row) in the board should be drawn. Derived
     * classes may wish to override this method to allow specialized 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 (col, row).
     */
    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 its correct position.
     */
    public void createPiece (int piece, int sx, int sy)
    {
        if (!inBounds(sx, sy)) {
            log.warning("Requested to create piece in invalid location", "sx", sx, "sy", sy,
                        new Exception());
            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. 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 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. 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 updatePiece (int piece, int sx, int sy)
    {
        if (!inBounds(sx, sy)) {
            log.warning("Requested to update piece in invalid location",
                "sx", sx, "sy", sy, new Exception());
            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 (!inBounds(tx, ty)) {
            log.warning("Requested to create and move piece to invalid location",
                "tx", tx, "ty", ty, new Exception());
            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, where);
            }
            _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() {
            @Override
            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, 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(Sprite,int,int,int,int,long)}).
     */
    protected boolean isMoving (int px, int py)
    {
        return _moving[py * _bwid + px];
    }

    /**
     * Returns the image used to display the given piece at coordinates (0, 0) 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);

    @Override
    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 = 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 (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 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 pos 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
    }

    @Override
    public void paintBetween (Graphics2D gfx, Rectangle dirtyRect)
    {
        gfx.translate(0, -_roff);
        renderBoard(gfx, dirtyRect);
        renderRisingPieces(gfx, dirtyRect);
        gfx.translate(0, _roff);
    }

    @Override
    public Dimension getPreferredSize ()
    {
        int wid = _bwid * _pwid;
        int hei = _bhei * _phei;
        return new Dimension(wid, hei);
    }

    /**
     * Returns true if the specified coordinate is within the bounds of the board, false if it is
     * not.
     */
    public boolean inBounds (int col, int row)
    {
        return (col >= 0 && row >= 0 && col < getWidth() && row < getHeight());
    }

    /**
     * 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;
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy