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