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

com.threerings.puzzle.client.PuzzleBoardView 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.client;

import java.util.List;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;

import com.google.common.collect.Lists;

import com.samskivert.util.StringUtil;

import com.samskivert.swing.Label;

import com.threerings.media.VirtualMediaPanel;
import com.threerings.media.animation.Animation;
import com.threerings.media.animation.AnimationAdapter;
import com.threerings.media.animation.AnimationArranger;
import com.threerings.media.image.Mirage;
import com.threerings.media.sprite.Sprite;

import com.threerings.parlor.game.data.GameConfig;
import com.threerings.parlor.media.ScoreAnimation;

import com.threerings.puzzle.data.Board;
import com.threerings.puzzle.data.PuzzleCodes;
import com.threerings.puzzle.util.PuzzleContext;

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

/**
 * The puzzle board view displays a view of a puzzle game.
 */
public abstract class PuzzleBoardView extends VirtualMediaPanel
{
    /**
     * Constructs a puzzle board view.
     */
    public PuzzleBoardView (PuzzleContext ctx)
    {
        super(ctx.getFrameManager());

        // keep this for later
        _ctx = ctx;
    }

    /**
     * Initializes the board with the board dimensions.
     */
    public void init (GameConfig config)
    {
        // save off our bounds
        Dimension bounds = getPreferredSize();
        _bounds = new Rectangle(0, 0, bounds.width, bounds.height);
    }

    /**
     * Sets the board to be displayed.
     */
    public void setBoard (Board board)
    {
        _board = board;
    }

    /**
     * Provides the board view with a reference to its controller so that it may communicate
     * directly rather than by posting actions up the interface hierarchy which sometimes fails if
     * the puzzle board view is hidden before we get a chance to post our actions.
     */
    public void setController (PuzzleController pctrl)
    {
        _pctrl = pctrl;
    }

    /**
     * Sets the background image displayed by the board view.
     */
    public void setBackgroundImage (Mirage image)
    {
        _background = image;
    }

    /**
     * Set whether this puzzle is paused or not. If paused, a label will be displayed with the
     * component's font, which may be set with setFont().
     */
    @Override
    public void setPaused (boolean paused)
    {
        if (paused) {
            String pmsg = _pctrl.getPauseString();
            pmsg = _ctx.getMessageManager().getBundle(
                PuzzleCodes.PUZZLE_MESSAGE_BUNDLE).xlate(pmsg);
            // create a label using our component's standard font
            _pauseLabel = new Label(pmsg, Label.BOLD | Label.OUTLINE,
                                    Color.WHITE, Color.BLACK, getFont());
            _pauseLabel.setTargetWidth(_bounds.width);
            _pauseLabel.layout(this);
        } else {
            _pauseLabel = null;
        }
        repaint();
    }

    /**
     * Adds the given animation to the set of animations currently present on the board. The
     * animation will be added to a list of action animations whose count can be queried with
     * {@link #getActionAnimationCount}. The animation will automatically be removed from the
     * action list when it completes.
     */
    public void addActionAnimation (Animation anim)
    {
        super.addAnimation(anim);

        // remember the animation's existence
        _actionAnims.add(anim);

        // and listen for it to finish so that we can clear it out
        anim.addAnimationObserver(_actionAnimObs);
    }

    @Override
    public void abortAnimation (Animation anim)
    {
        super.abortAnimation(anim);

        // always check to see if it was action-y
        animationFinished(anim);
    }

    /**
     * Called when a potential action animation is finished.
     */
    protected void animationFinished (Animation anim)
    {
        if (DEBUG_ACTION) {
            log.info("Animation cleared " + StringUtil.shortClassName(anim) +
                     ":" + _actionAnims.contains(anim));
        }

        // if it WAS an action animation, check for a clear
        if (_actionAnims.remove(anim)) {
            maybeFireCleared();
        }
    }

    /**
     * Adds the given sprite to the set of sprites currently present on the board. The sprite will
     * be added to a list of action sprites whose count can be queried with
     * {@link #getActionSpriteCount}. Callers should be sure to remove the sprite when their work
     * with it is done via {@link #removeSprite}.
     */
    public void addActionSprite (Sprite sprite)
    {
        // add the piece to the sprite manager
        addSprite(sprite);

        // note that this piece is interesting
        _actionSprites.add(sprite);
    }

    @Override
    public void removeSprite (Sprite sprite)
    {
        super.removeSprite(sprite);

        if (DEBUG_ACTION) {
            log.info("Sprite cleared " + StringUtil.shortClassName(sprite) +
                     ":" + _actionSprites.contains(sprite));
        }

        // we just always check to see if it was action-y
        if (_actionSprites.remove(sprite)) {
            maybeFireCleared();
        }
    }

    @Override
    public void clearSprites ()
    {
        super.clearSprites();
        _actionSprites.clear();
    }

    @Override
    public void clearAnimations ()
    {
        super.clearAnimations();
        _actionAnims.clear();
    }

    /**
     * Returns the number of action animations on the board.
     */
    public int getActionAnimationCount ()
    {
        return _actionAnims.size();
    }

    /**
     * Returns the number of action sprites on the board.
     */
    public int getActionSpriteCount ()
    {
        return _actionSprites.size();
    }

    /**
     * Returns the count of action sprites and animations on the board.
     */
    public int getActionCount ()
    {
        return _actionSprites.size() + _actionAnims.size();
    }

    /**
     * Dumps to the logs, a list of interesting sprites and animations
     * currently active on the puzzle board.
     */
    public void dumpActors ()
    {
        StringUtil.Formatter fmt = new StringUtil.Formatter() {
            @Override
            public String toString (Object obj) {
                return StringUtil.shortClassName(obj);
            }
        };
        log.info("Board contents",
            "board", StringUtil.shortClassName(this),
            "sprites", StringUtil.listToString(_actionSprites, fmt),
            "anims", StringUtil.listToString(_actionAnims, fmt));

    }

    /**
     * Creates and returns an animation displaying the given string with the specified parameters,
     * floating it a short distance up the view.
     *
     * @param score the score text to display.
     * @param color the color of the text.
     * @param font the font with which to create the score animation.
     * @param x the x-position at which the score is to be placed.
     * @param y the y-position at which the score is to be placed.
     */
    public ScoreAnimation createScoreAnimation (String score, Color color, Font font, int x, int y)
    {
        return createScoreAnimation(
            ScoreAnimation.createLabel(score, color, font, (Component)this), x, y);
    }

    /**
     * Creates a score animation, allowing derived classes to use custom animations that are
     * customized following a call to {@link #createScoreAnimation(String,Color,Font,int,int)}.
     */
    protected ScoreAnimation createScoreAnimation (Label label, int x, int y)
    {
        return new ScoreAnimation(label, x, y);
    }

    /**
     * Positions the supplied animation so as to avoid any active animations previously registered
     * with this method, and adds the animation to the list of animations to be avoided by any
     * future avoid animations.
     */
    public void trackAvoidAnimation (Animation anim)
    {
        // lazy init the arranger
        if (_avoidArranger == null) {
            _avoidArranger = new AnimationArranger();
        }
        _avoidArranger.positionAvoidAnimation(anim, _vbounds);
    }

    @Override
    public void paintBehind (Graphics2D gfx, Rectangle dirty)
    {
        super.paintBehind(gfx, dirty);

        // render the background
        renderBackground(gfx, dirty);
    }

    /**
     * Fills the background of the board with the background color.
     */
    protected void renderBackground (Graphics2D gfx, Rectangle dirty)
    {
        gfx.setColor(getBackground());
        gfx.fill(dirty);
    }

    @Override
    public void paintBetween (Graphics2D gfx, Rectangle dirty)
    {
        super.paintBetween(gfx, dirty);
//         PerformanceMonitor.tick(this, "paint");
        renderBoard(gfx, dirty);
    }

    @Override
    protected void paintInFront (Graphics2D gfx, Rectangle dirty)
    {
        super.paintInFront(gfx, dirty);

        // if the action is paused, indicate as much
        if (_pauseLabel != null) {
            Dimension d = _pauseLabel.getSize();
            _pauseLabel.render(gfx,
                _vbounds.x + (_vbounds.width - d.width) / 2,
                _vbounds.y + (_vbounds.height - d.height) / 2);
        }
    }

    /**
     * Fires a {@link #ACTION_CLEARED} command if we have no remaining interesting sprites or
     * animations.
     */
    protected void maybeFireCleared ()
    {
        if (DEBUG_ACTION) {
            log.info("Maybe firing cleared " + getActionCount() + ":" + isShowing());
        }
        if (getActionCount() == 0) {
            // we're probably in the middle of a tick() in an animationDidFinish() call and we want
            // everyone to finish processing their business before we go clearing the action, so we
            // queue this up to be run after the tick is complete
            _ctx.getClient().getRunQueue().postRunnable(new Runnable() {
                public void run () {
                    _pctrl.boardActionCleared();
                }
            });
        }
    }

    /**
     * Renders the board contents to the given graphics context. Sub-classes should implement this
     * method to draw all of their game-specific business.
     */
    protected abstract void renderBoard (Graphics2D gfx, Rectangle dirty);

    /** Our client context. */
    protected PuzzleContext _ctx;

    /** Our puzzle controller. */
    protected PuzzleController _pctrl;

    /** The board data to be displayed. */
    protected Board _board;

    /** The board's bounding rectangle. */
    protected Rectangle _bounds;

    /** The action animations on the board. */
    protected List _actionAnims = Lists.newArrayList();

    /** The action sprites on the board. */
    protected List _actionSprites = Lists.newArrayList();

    /** Prevents certain animations from overlapping others. */
    protected AnimationArranger _avoidArranger;

    /** Our background image. */
    protected Mirage _background;

    /** A label to show when the puzzle is paused. */
    protected Label _pauseLabel;

    /** The distance in pixels that score animations float. */
    protected int _scoreDist = DEFAULT_SCORE_DISTANCE;

    /** Listens to our action animations and clears them when they're done. */
    protected AnimationAdapter _actionAnimObs = new AnimationAdapter() {
        @Override
        public void animationCompleted (Animation anim, long when) {
            animationFinished(anim);
        }
    };

    /** Temporary action debugging. */
    protected static boolean DEBUG_ACTION = false;

    // action state constants
    protected static final int ACTION_GOING = 0;
    protected static final int CLEAR_PENDING = 1;
    protected static final int ACTION_CLEARED = 2;

    /** The default vertical distance to float score animations. */
    protected static final int DEFAULT_SCORE_DISTANCE = 30;
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy