Subversion Repositories AndroidProjects

Rev

Rev 685 | Rev 690 | 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.Bauzoid.graphics.sprite.Sprite;
import com.gebauz.Bauzoid.math.MathUtil;
import com.gebauz.PonPonChun.game.GameLogic;
import com.gebauz.PonPonChun.game.GameSounds;
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 TYPE_JOKER       = 8;
        public static final int TYPE_CLOCK       = 9;
        public static final int TYPE_CHERRY      = 10;
        public static final int TYPE_BANANA      = 11;
        public static final int TYPE_APPLE       = 12;
        public static final int TYPE_GRAPES      = 13;
        public static final int NUM_TYPES        = 14;
       
        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 between each block when exploding during clearing. */
        public static final float INCREMENTAL_CLEAR_TIME = 0.25f;
       
        /** Time for a chance for a chain. */
        public static final float CHAINABLE_TIME = 0.25f;
       
        /** Squash and stretch time. */
        public static final float SQUASH_TIME = 0.2f;
       
        /** Time per frame in animation. */
        public static final float ANIM_PHASE_TIME = 0.1f;
       
        /** Time for continuous animation. */
        public static final float ANIM_TIME_CONTINOUS = 0.5f;
       
        /** Anim frame indices for color blocks. */
        public static final int ANIM_INDICES_BLOCK[] = { 0, 1, 2, 1, 0 };
       
        /** Anim frame indices for bombs. */
        public static final int ANIM_INDICES_BOMB[] = { 1, 2, 1, 0, 1 };
       
        /** Anim frame indices for clock, fruit. */
        public static final int ANIM_INDICES_CONTINUOUS[] = { 1, 2, 1, 0 };
       
        /** Anim frame indices for crystalized blocks. */
        public static final int ANIM_INDICES_CRYSTALIZED[] = { 0, 1, 2, 3, 2, 1, 0 };
       

        /** Period for glossy shine. */
        public static final float GLOSSY_SHINE_PERIOD = 3.5f;
       
        /** Period for bombs. */
        public static final float BOMB_ANIM_PERIOD = 0.9f;
       
        /** Total time for no move indicator. */
        public static final float NO_MOVE_INDICATOR_TIME = 1.0f;
       
        /** Time to crystalize. */
        public static final float CRYSTALIZATION_TIME = 0.25f;
       
        // Embedded Types===================================================================================
        public 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;
       
        /** Store the total clear time for a group of cleared blocks. */
        private float mTotalClearTime = 0.0f;
       
        /** Total time allotted for the state (currently only used for clearing). */
        private float mTotalStateTime = 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;
       
        /** Extra timer for squash and stretch. */
        private float mSquashTimer = 0.0f;
       
        /** Timer for the animation. */
        private float mAnimTimer = 0.0f;
       
        /** Current animation index. */
        private int mAnimPhase = 0;
       
        /** Time to display no move indicator. */
        private float mNoMoveTimer = 0.0f;
       
        /** Block is crystalized and can only be destroyed by a bomb. */
        private boolean mIsCrystalized = false;
        private float mCrystalizationTimer = 0.0f;
       
        private int mCrystalAnimPhase = 0;
       
        // Methods==========================================================================================
       
        public Block(PlayField parent, int type)
        {
                mPlayField = parent;
                mType = type;
               
                /*if ((getType() == TYPE_CLOCK) ||
                                 (getType() == TYPE_CHERRY) ||
                                 (getType() == TYPE_BANANA) ||
                                 (getType() == TYPE_APPLE) ||
                                 (getType() == TYPE_GRAPES))
                {
                        mAnimTimer = mPlayField.getGameLogic().getGame().getTime() % ANIM_PHASE_TIME_CONTINOUS;                
                }*/

        }      
       
        public void update(float deltaTime)
        {
                if (isCrystalized())
                {
                        if (mCrystalizationTimer > 0.0f)
                        {
                                mCrystalizationTimer -= deltaTime;
                                if (mCrystalizationTimer < 0.0f)
                                {
                                        mCrystalizationTimer = 0.0f;
                                }
                        }
                }
               
                if (mNoMoveTimer > 0.0f)
                {
                        mNoMoveTimer -= deltaTime;
                        if (mNoMoveTimer <= 0.0f)
                                mNoMoveTimer = 0.0f;
                }
               
                if (mAnimTimer > 0.0f)
                {
                        mAnimTimer -= deltaTime;
                        if (mAnimTimer < 0.0f)
                        {
                                mAnimTimer = 0.0f;
                        }
                }
               
                // update animation
                int num = ANIM_INDICES_BLOCK.length;    // default color block case
                float period = GLOSSY_SHINE_PERIOD;
                if ((getType() == TYPE_BOMB))
                {
                        period = BOMB_ANIM_PERIOD;
                        num = ANIM_INDICES_BOMB.length;
                }
                else if ((getType() == TYPE_CLOCK) ||
                                 (getType() == TYPE_CHERRY) ||
                                 (getType() == TYPE_BANANA) ||
                                 (getType() == TYPE_APPLE) ||
                                 (getType() == TYPE_GRAPES))
                {
                        // continous animation
                        //period = CONTINUOUS_ANIM_PERIOD;
                        //num = ANIM_INDICES_CONTINUOUS.length;
                       
                        float t = mPlayField.getGameLogic().getGame().getTime() % ANIM_TIME_CONTINOUS;
                        t /= ANIM_TIME_CONTINOUS;
                        mAnimPhase = (int)(t * (ANIM_INDICES_CONTINUOUS.length));
                        return;
                }
               
                // calculate animation phase
                float s = 1.0f - MathUtil.clamp(mAnimTimer / (ANIM_PHASE_TIME * num), 0, 1);
                mAnimPhase = MathUtil.clamp((int)(s * num), 0, num-1);
               
                //float s2 = 1.0f - MathUtil.clamp(mAnimTimer / (ANIM_PHASE_TIME * ANIM_INDICES_CRYSTALIZED.length), 0, 1);
                mCrystalAnimPhase = MathUtil.clamp((int)(s * ANIM_INDICES_CRYSTALIZED.length), 0, ANIM_INDICES_CRYSTALIZED.length-1);
               
                // synchronize to global timer
                float tPrev = mPlayField.getGameLogic().getGame().getTime() % period;
                float tPost = (mPlayField.getGameLogic().getGame().getTime() + deltaTime) % period;
               
                // If the latter time is earlier, then we had a carry-over
                if (tPost < tPrev)
                {
                        // trigger animation
                        mAnimTimer = ANIM_PHASE_TIME * num;
                }
        }

        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;
                }              
               
                if (mSquashTimer > 0.0f)
                {
                        mSquashTimer -= deltaTime;
                        if (mSquashTimer < 0.0f)
                                mSquashTimer = 0.0f;
                }
        }
       
        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;
                                mSquashTimer = SQUASH_TIME;
                               
                                mPlayField.playFallingSound();
                        }
                        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;
                        mTotalClearTime -= deltaTime;
                        if (mStateTimer < 0.0f)
                        {
                                mStateTimer = 0.0f;

                                // spawn particles

                                // if bomb, remove all surrounding blocks and spawn explosion
                                if (getType() == TYPE_BOMB)
                                {
                                        mPlayField.getBombParticles().spawnExplosion(getCenterX(), getCenterY(), BombParticles.BIG_SIZE, BombParticles.EXPLO_LIFE_TIME_BIG);
                                       
                                        mPlayField.getGameLogic().getSounds().playSound(GameSounds.SFX_EXPLOSION);
                                       
                                        // get quicker to actual destruction
                                        mTotalClearTime = 0.15f;
                                }
                                else
                                        mPlayField.getBlockBreakParticles().spawnParticles(getCenterX(), getCenterY());        
                               
                                // add score
                               
                                if (isFruitType())
                                {
                                        // spawn fruit type animation and add fruit score
                                       
                                        mPlayField.getGameLogic().getSounds().playSound(GameSounds.SFX_PICKUP_FRUIT);
                                        mPlayField.getGameLogic().getFruitParticles().spawnFruit(getCenterX(), getCenterY(), getType());                                       
                                        mPlayField.getGameLogic().addScore(GameLogic.getFruitScore(getType()));
                                        // TODO: add fruit meter for mascot instead of score!
                                }
                                else if (getType() == TYPE_CLOCK)
                                {
                                        mPlayField.getGameLogic().getHeadsUpDisplay().startFreezeTimeDisplay(GameLogic.TIME_FREEZE_AMOUNT);
                                }
                                else
                                        mPlayField.getGameLogic().addScore(GameLogic.BLOCK_CLEAR_SCORE);
                               
                                // play sound
                                mPlayField.getGameLogic().getSounds().playSound(GameSounds.SFX_CLEAR_BREAK);                           
                        }
                }
                else
                {
                        // after personal clear time
                        mTotalClearTime -= deltaTime;
                       
                        if (mTotalClearTime < 0.0f)
                        {
                                mTotalClearTime = 0.0f;
                               
                                // clear all around if bomb
                                if (getType() == TYPE_BOMB)
                                {
                                        for (int ix = 0; ix < 3; ix++)
                                        {
                                                for (int iy = 0; iy < 3; iy++)
                                                {
                                                        int dx = cellX - 1 + ix;
                                                        int dy = cellY - 1 + iy;
                                                       
                                                        if ((dx == cellX) && (dy == cellY))
                                                                continue;
                                                        if ((dx < 0) || (dx >= PlayField.NUM_CELLS_X))
                                                                continue;
                                                        if ((dy < 0) || (dy >= PlayField.NUM_CELLS_Y))
                                                                continue;
                                                       
                                                        Cell cell = mPlayField.getCell(dx, dy);
                                                        if (!cell.isEmpty())
                                                        {
                                                                //cell.containedBlock.switchState(State.CLEARED);
                                                                cell.containedBlock.switchState(State.CLEARING, 0.1f);
                                                                cell.containedBlock.setTotalClearTime(0.2f);
                                                                mPlayField.getBombParticles().spawnExplosion(cell.containedBlock.getCenterX(), cell.containedBlock.getCenterY(), BombParticles.SMALL_SIZE, BombParticles.EXPLO_LIFE_TIME_SMALL);
                                                        }
                                                }
                                        }
                                }

                                // remove block
                                switchState(State.CLEARED);
                        }
                       
                }
        }
       
        /** Switch the current state. */
        public void switchState(State newState)
        {
                switchState(newState, 0.0f);
        }
       
        /** Switch the current state. */
        public void switchState(State newState, float initialStateTimerValue)
        {
                mStateTimer = initialStateTimerValue;
                mTotalStateTime = initialStateTimerValue;
                mCurrentState = newState;
        }
       
        /** Clearing has two values - total clear time and a personal clear time (stored in mStateTimer) per block. */
        public void setTotalClearTime(float totalClearTime)
        {
                mTotalClearTime = totalClearTime;
        }

        public void render(boolean selected)
        {
                // swapping blocks are rendered by PlayFieldLogic
                if (mCurrentState == State.SWAPPING)
                        return;
               
                // update animation
                int index = getAnimIndex();

               
                // apply local atttributes
                int spriteIndex = getSpriteIndex();
                AtlasSpriteInstance sprite = mPlayField.getSprites().getBlockSprite(spriteIndex + index * BlockSprites.NUM_BLOCK_SPRITES);
                if (sprite == null)
                        return;
               
                float scale = 1.0f;
               
                if (selected)
                        scale = MAX_DRAG_BLOCK_SIZE/BLOCK_SIZE;
               
                // for Rumbling
                float offsetX = 0.0f;
                float offsetY = 0.0f;
               
                if (mPlayField.isGameOver() && !mPlayField.getGameLogic().getGameOverScreen().isShowingMenu())
                {
                       
                        offsetX = mPlayField.getGameLogic().getGame().getRandomFloat(-PlayField.CELL_SIZE/32, PlayField.CELL_SIZE/32);
                        offsetY = mPlayField.getGameLogic().getGame().getRandomFloat(-PlayField.CELL_SIZE/32, PlayField.CELL_SIZE/32);
                }
               
                sprite.x = getCenterX() + offsetX;
                sprite.y = getCenterY() - mPlayField.getAdvanceOffsetY() + offsetY;
                sprite.w = BLOCK_SIZE * scale;
                sprite.h = BLOCK_SIZE * scale;
                sprite.pivotX = sprite.w/2;
                sprite.pivotY = sprite.h/2;
               
                // modify for squashing if in squashing state
                if ((mCurrentState == State.NORMAL) && (mSquashTimer > 0.0f) && (!isCrystalized()))
                {
                        float f = 1 - mSquashTimer / SQUASH_TIME;
                        float squashFactor = f*f;
                        sprite.w *= 1.2f - 0.2f * squashFactor;                
                        sprite.h *= 0.8f + 0.2f * squashFactor;
                        sprite.pivotX = sprite.w/2;
                        sprite.pivotY = sprite.h;
                        sprite.y += Block.BLOCK_SIZE/2;
                }
               
                if (mCurrentState == State.CLEARING)
                {
                        //sprite.alpha = mStateTimer / mTotalStateTime;
                        sprite.alpha = 1.0f;
                        if (mStateTimer < (0.1f))
                                sprite.alpha = 0.0f;                                   
                }
                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 = (mTotalStateTime - mStateTimer);
                        if (s < 0.2f)
                        {
                                // fade in
                                breakSprite.alpha = s / 0.2f;
                        }
                        else if (mStateTimer <= 0.0f)
                        {
                                breakSprite.alpha = 0.0f;
                        }
                        else
                        {
                                breakSprite.alpha = 1.0f;                              
                        }
                       
                        //breakSprite.alpha *= 0.5f;
/*                      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;
                        //Gdx.app.log("BLA", "t:" + mPlayField.getGameLogic().getGame().getCurrentTimeMs());
                        float animate = ((mPlayField.getGameLogic().getGame().getTime()) * 10.0f) % 1.0f;
                        int n = 0;
                        if (animate < 0.33f)
                                n = 1;
                        else if (animate > 0.66f)
                                n = 2;
                        breakSprite.render(n);
                }
               
                if (isCrystalized())
                {
                        //float animate = ((mPlayField.getGameLogic().getGame().getTime()) * 0.1f) % 1.0f;
                        //int n = MathUtil.clamp((int)(animate * 4), 0, 3);
                       
                        int n = ANIM_INDICES_CRYSTALIZED[mCrystalAnimPhase];
                       
                        AtlasSpriteInstance s = mPlayField.getSprites().getCrystalizedFrame(n);
                       
                        s.x = getCenterX();
                        s.y = getCenterY() - mPlayField.getAdvanceOffsetY();;
                        s.w = BLOCK_SIZE*2;
                        s.h = BLOCK_SIZE*2;
                        s.pivotX = s.w/2;
                        s.pivotY = s.h/2;
                       
                        s.alpha = 1-(mCrystalizationTimer / CRYSTALIZATION_TIME);
                       
                        s.render();                    
                }
               
                if (mNoMoveTimer > 0.0f)
                {
                        float a = mNoMoveTimer / NO_MOVE_INDICATOR_TIME;
                        Sprite noMoveSprite = mPlayField.getSprites().getNoMove();
                        noMoveSprite.x = getCenterX();
                        noMoveSprite.y = getCenterY() - mPlayField.getAdvanceOffsetY();;
                        noMoveSprite.alpha = a;
                        noMoveSprite.render();                 
                }
        }
       
        public void renderSwapping(float offsetX, float scale)
        {
                if (mCurrentState != State.SWAPPING)
                        return;
                       
                // apply local atttributes
               
                // update animation
                int index = getAnimIndex();
               
                int spriteIndex = getSpriteIndex();
                AtlasSpriteInstance sprite = mPlayField.getSprites().getBlockSprite(spriteIndex + index * BlockSprites.NUM_BLOCK_SPRITES);
                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();
        }
       
        public int getAnimIndex()
        {
                if ((getType() == TYPE_BOMB))
                {
                        return ANIM_INDICES_BOMB[mAnimPhase];
                }
                else if ((getType() == TYPE_CLOCK) ||
                                 (getType() == TYPE_CHERRY) ||
                                 (getType() == TYPE_BANANA) ||
                                 (getType() == TYPE_APPLE) ||
                                 (getType() == TYPE_GRAPES))
                {
                        return ANIM_INDICES_CONTINUOUS[mAnimPhase];
                }
               
                return ANIM_INDICES_BLOCK[mAnimPhase];
        }
       
        /** Calculate sprite index based on type. */
        public int getSpriteIndex()
        {              
                return mType;
        }
       
        public boolean isImmovableBlockType()
        {
                return (isCrystalized() || isImmovableBlockType(mType));
        }
       
        public boolean isSpecialBlockType()
        {
                return isSpecialBlockType(mType);
        }
       
        public boolean isJokerBlockType()
        {
                return isJokerBlockType(mType);
        }
       
        public boolean isColorBlockType()
        {
                return isColorBlockType(mType);
        }
       
        public boolean isFruitType()
        {
                return isFruitType(mType);
        }
       
        /** Check if the type is not movable. */
        public static boolean isImmovableBlockType(int type)
        {
                if ((type == TYPE_BOMB) ||
                        (type == TYPE_CRYSTAL) ||
                        (type == TYPE_CLOCK) ||
                        (type == TYPE_CHERRY) ||
                        (type == TYPE_BANANA) ||
                        (type == TYPE_APPLE) ||
                        (type == TYPE_GRAPES))
                                return true;
                       
                return false;
        }
       
        /** Check if the type is compatible with a matching color set (only one allowed per clearing row). */
        public static boolean isSpecialBlockType(int type)
        {
                if ((type == TYPE_BOMB) ||
                        //(type == TYPE_JOKER) ||
                        (type == TYPE_CRYSTAL) ||
                        (type == TYPE_CLOCK) ||
                        (type == TYPE_CHERRY) ||
                        (type == TYPE_BANANA) ||
                        (type == TYPE_APPLE) ||
                        (type == TYPE_GRAPES))
                                return true;
                               
                return false;
        }
       
        /** Check if the type is a joker color block that counts as any color. */
        public static boolean isJokerBlockType(int type)
        {
                if (type == TYPE_JOKER)
                        return true;
                return false;
        }
       
        /** Check if the type is one of the 6 color block types. */
        public static boolean isColorBlockType(int type)
        {
                if ((type == TYPE_BLACK) ||
                        (type == TYPE_WHITE) ||
                        (type == TYPE_RED) ||
                        (type == TYPE_GREEN) ||
                        (type == TYPE_BLUE) ||
                        (type == TYPE_YELLOW) ||
                        (type == TYPE_JOKER))
                        return true;
                return false;
        }
       
        /** Check if the type is one of the 4 fruit types. */
        public static boolean isFruitType(int type)
        {
                if ((type == TYPE_CHERRY) ||
                        (type == TYPE_BANANA) ||
                        (type == TYPE_APPLE) ||
                        (type == TYPE_GRAPES))
                        return true;
                return false;
        }
       
        public void showNoMoveIndicator()
        {
                mNoMoveTimer = NO_MOVE_INDICATOR_TIME;
        }
       
       
        public void changeToCrystal()
        {
                mIsCrystalized = true;
                mCrystalizationTimer = CRYSTALIZATION_TIME;
        }
       
        // Getters/Setters==================================================================================
       
        /** Get the cell the block is in. */
        public final Cell getCell()
        {
                return mPlayField.getCell(cellX, cellY);
        }
       
        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;
        }
       
        public boolean isCrystalized()
        {
                return mIsCrystalized;
        }
       
}