Subversion Repositories AndroidProjects

Rev

Rev 461 | Rev 549 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

package com.gebauz.PonPonChun.game.entities;

import com.gebauz.Bauzoid.graphics.sprite.AtlasSprite;
import com.gebauz.Bauzoid.graphics.sprite.AtlasSpriteInstance;
import com.gebauz.PonPonChun.game.entities.PlayField.Cell;

/** Represents a single Block.
 * Each Block has a certain type and pixel position in the PlayField. The cell raster position can be calculated
 * from this using convenience methods in PlayField.
 *
 * Every Block also belongs to exactly a PlayField to which they are spawned. The PlayField keeps the Blocks
 * in a list, and each Block also references the parent PlayField.
 *
 * A Block can occupy more than one cell of the PlayField (for instance, while falling downwards). Hence
 * Cell occupancy information is stored in the Cell data structures of the PlayField - each Cell can
 * only have one Block at a time.
 *
 * @author chiu
 *
 */

public class Block
{
        // Constants========================================================================================
       
        public static final int TYPE_EMPTY       = -1;
        public static final int TYPE_BLACK       = 0;
        public static final int TYPE_WHITE       = 1;
        public static final int TYPE_RED         = 2;
        public static final int TYPE_GREEN       = 3;
        public static final int TYPE_BLUE        = 4;
        public static final int TYPE_YELLOW      = 5;
        public static final int TYPE_BOMB        = 6;
        public static final int TYPE_CRYSTAL = 7;
        public static final int NUM_TYPES        = 8;
       
        public static final int BLOCK_SIZE = 52;
        public static float MAX_DRAG_BLOCK_SIZE = BLOCK_SIZE + 8;
        public static final float BREAKBLOCK_SIZE = 56;
       
        /** Time in which a Block falls by a single Cell. */
        public static final float FALLING_TIME = 0.05f;
       
        /** Time for the clearing animation. */
        public static final float CLEAR_TIME = 1.0f;
       
        /** Time for a chance for a chain. */
        public static final float CHAINABLE_TIME = 0.25f;
       
        // Embedded Types===================================================================================
        enum State
        {
                NORMAL,         // Normal, resting.
                FALLING,        // Block is falling.
                SWAPPING,       // Block is being swapped.
                CLEARING,       // Block is being cleared.
                CLEARED,        // Block is already cleared, ready to be removed from list.
        }
       
        // Members==========================================================================================
       
        private PlayField mPlayField = null;
        private int mType = TYPE_EMPTY;
       
        /** Primary cell occupied by this block. Note that falling blocks might partially occupy the slot above too. */
        public int cellX = 0;
        public int cellY = 0;  
       
        /** Current state of the block. */
        private State mCurrentState = State.NORMAL;
       
        /** Countdown Timer for current state (if timed). */
        private float mStateTimer = 0.0f;
       
        /** When the chain countdown timer should start counting down, set to true. */
        private boolean mDoChainCountdown = false;
       
        /** If this block is part of a chainable group, this value is > 0.0f. */
        private float mChainableTimer = 0.0f;
       
        // Methods==========================================================================================
       
        public Block(PlayField parent, int type)
        {
                mPlayField = parent;
                mType = type;
        }      
       
        public void update(float deltaTime)
        {
                // general state-independent update
        }

        public void updateStates(float deltaTime)
        {
                switch (mCurrentState)
                {
                case NORMAL:
                        updateNormal(deltaTime);
                        break;
                case FALLING:
                        // handled by PlayFieldLogic
                        //updateFalling(deltaTime);
                        break;
                case SWAPPING:
                        updateSwapping(deltaTime);
                        break;
                case CLEARING:
                        updateClearing(deltaTime);
                        break;
                case CLEARED:
                        break;
                }
        }
       
        public void updateNormal(float deltaTime)
        {
                // count down chainable time
                if ((mDoChainCountdown) && (mChainableTimer > 0.0f))
                {
                        mChainableTimer -= deltaTime;
                       
                        if (mChainableTimer < 0.0f)
                                mDoChainCountdown = false;
                }              
        }
       
        public void updateFalling(float deltaTime)
        {
                if (mStateTimer > 0.0f)
                {
                        // move downwards
                        mStateTimer -= deltaTime;
                }
               
               
                if (mStateTimer <= 0.0f)
                {
                        // check cell below if empty
                        Cell cell = getContainingCell();
                        Cell below = cell.below;
                        if ((below == null) || (!below.isEmpty()))
                        {
                                // if cell below is falling, do nothing and wait for next frame                        
                                if ((below != null) && (!below.isEmpty()) && (below.containedBlock.getState() == State.FALLING))
                                {
                                        return;
                                }
                                // No Cell below, or Cell below is not empty, so stop falling state
                                mDoChainCountdown = true;
                                switchState(State.NORMAL);
                                mStateTimer = 0.0f;
                        }
                        else
                        {
                                // Empty Cell below so move to next Cell and reset state timer
                                cell.containedBlock = null;
                                below.containedBlock = this;
                                cellY++;
                                mStateTimer += FALLING_TIME;
                        }
                }
        }
       
        public void updateSwapping(float deltaTime)
        {
                // while swapping
        }
       
        public void updateClearing(float deltaTime)
        {
                if (mStateTimer > 0.0f)
                {
                        mStateTimer -= deltaTime;
                        if (mStateTimer < 0.0f)
                        {
                                mStateTimer = 0.0f;
                                // remove block
                                switchState(State.CLEARED);
                        }
                }
        }
       
        /** Switch the current state. */
        public void switchState(State newState)
        {
                if (newState == State.CLEARING)
                {
                        mStateTimer = CLEAR_TIME;
                }
                mCurrentState = newState;
        }

        public void render(boolean selected)
        {
                // swapping blocks are rendered by PlayFieldLogic
                if (mCurrentState == State.SWAPPING)
                        return;
               
                // apply local atttributes
                AtlasSpriteInstance sprite = mPlayField.getSprites().getBlockSprite(mType);
                if (sprite == null)
                        return;
               
                float scale = 1.0f;
               
                if (selected)
                        scale = MAX_DRAG_BLOCK_SIZE/BLOCK_SIZE;
               
                sprite.x = getCenterX();
                sprite.y = getCenterY() - mPlayField.getAdvanceOffsetY();
                sprite.w = BLOCK_SIZE * scale;
                sprite.h = BLOCK_SIZE * scale;
                sprite.pivotX = sprite.w/2;
                sprite.pivotY = sprite.h/2;
               
                if (mCurrentState == State.CLEARING)
                        sprite.alpha = mStateTimer / CLEAR_TIME;
                else
                        sprite.alpha = 1.0f;
               
                sprite.render();
               
                // on clearing, overlay the breaking block
                if (getState() == State.CLEARING)
                {
                        AtlasSprite breakSprite = mPlayField.getSprites().getBreakSprite();
                       
                        breakSprite.x = getCenterX();
                        breakSprite.y = getCenterY() - mPlayField.getAdvanceOffsetY();;
                        breakSprite.w = BREAKBLOCK_SIZE;
                        breakSprite.h = BREAKBLOCK_SIZE;
                        breakSprite.pivotX = breakSprite.w/2;
                        breakSprite.pivotY = breakSprite.h/2;
                       
                        float s = (mStateTimer / CLEAR_TIME);
                        if (s > 0.8f)
                                breakSprite.alpha = (1.0f-s) / 0.2f;
                        else if (s < 0.2f)
                                breakSprite.alpha = (s) / 0.2f;
                        else
                                breakSprite.alpha = 1.0f;
                       
                        s = (s*10.0f) % 1.0f;
                        int n = 0;
                        if (s < 0.33f)
                                n = 1;
                        else if (s > 0.66f)
                                n = 2;
                        breakSprite.render(n);
                }
        }
       
        public void renderSwapping(float offsetX, float scale)
        {
                if (mCurrentState != State.SWAPPING)
                        return;
                       
                // apply local atttributes
                AtlasSpriteInstance sprite = mPlayField.getSprites().getBlockSprite(mType);
                if (sprite == null)
                        return;
               
                sprite.x = getCenterX() + offsetX;
                sprite.y = getCenterY() - mPlayField.getAdvanceOffsetY();;
                sprite.w = BLOCK_SIZE * scale;
                sprite.h = BLOCK_SIZE * scale;
                sprite.pivotX = sprite.w/2;
                sprite.pivotY = sprite.h/2;
                sprite.alpha = 1.0f;
                       
                sprite.render();
        }
       
        // Getters/Setters==================================================================================
       
        public final int getType()
        {
                return mType;
        }

        /** Get the current state. */
        public final State getState()
        {
                return mCurrentState;
        }
       
        /** Get the center X position in pixels. */
        public final float getCenterX()
        {
                return (cellX * PlayField.CELL_SIZE);
        }
       
        /** Get the center Y position in pixels. */
        public final float getCenterY()
        {
                float offset = 0.0f;
               
                // account for falling
                if (mCurrentState == State.FALLING)
                {
                        offset = (mStateTimer * (float)PlayField.CELL_SIZE) / (float)FALLING_TIME;
                }
               
                return (cellY * PlayField.CELL_SIZE) - offset;
        }
       
        /** Get the Cell that contains this Block. */
        public final Cell getContainingCell()
        {
                return mPlayField.getCell(cellX, cellY);
        }

        /** Returns true when still chainable. */
        public final boolean isChainable()
        {
                return (mChainableTimer > 0.0f);
        }
       
        /** Set chainable. */
        public void setChainable()
        {
                // this value is set to true once the transition from FALLING->NORMAL occurs
                mDoChainCountdown = false;
                mChainableTimer = CHAINABLE_TIME;
        }
       
}