Tetris example: refactoring & improvements; destroying of full rows

This commit is contained in:
Vadim Lopatin 2014-12-26 09:46:10 +03:00
parent f1cbf075c5
commit a91138cb56
1 changed files with 136 additions and 23 deletions

View File

@ -209,6 +209,8 @@ struct Cup {
private int[] _cup; private int[] _cup;
private int _cols; private int _cols;
private int _rows; private int _rows;
private bool[] _destroyedFullRows;
private bool[] _fallingCell;
private FigurePosition _currentFigure; private FigurePosition _currentFigure;
/// current figure index, orientation, position /// current figure index, orientation, position
@ -231,8 +233,8 @@ struct Cup {
_cols = cols; _cols = cols;
_rows = rows; _rows = rows;
_cup = new int[_cols * _rows]; _cup = new int[_cols * _rows];
for (int i = 0; i < _cup.length; i++) _destroyedFullRows = new bool[_rows];
_cup[i] = EMPTY; _fallingCell = new bool[_cols * _rows];
} }
/// returns cell content at specified position /// returns cell content at specified position
int opIndex(int col, int row) { int opIndex(int col, int row) {
@ -264,6 +266,57 @@ struct Cup {
} }
return true; return true;
} }
/// returns true if specified row is full
bool isRowFull(int row) {
for (int i = 0; i < _cols; i++)
if (this[i, row] == EMPTY)
return false;
return true;
}
/// returns true if at least one row is full
@property bool hasFullRows() {
for (int i = 0; i < _rows; i++)
if (isRowFull(i))
return true;
return false;
}
/// destroy all full rows, saving flags for destroyed rows; returns true if any rows were destroyed
bool destroyFullRows() {
bool res = false;
for (int i = 0; i < _rows; i++) {
if (isRowFull(i)) {
_destroyedFullRows[i] = true;
res = true;
for (int col = 0; col < _cols; col++)
this[col, i] = EMPTY;
} else {
_destroyedFullRows[i] = false;
}
}
return true;
}
@property int lowestDestroyedRow() {
for (int i = 0; i < _rows; i++) {
if (_destroyedFullRows[i]) {
return i;
}
}
return -1;
}
bool fallAfterDestroy() {
int bottomRow = lowestDestroyedRow;
if (bottomRow < 0)
return false;
for (int i = bottomRow; i < _rows; i++) {
_destroyedFullRows[i] = i < _rows - 1 ? _destroyedFullRows[i + 1] : false;
for (int col = 0; col < _cols; col++) {
this[col, i] = i < _rows - 1 ? this[col, i + 1] : EMPTY;
}
}
return true;
}
/// check if all cells where current figire is located are free /// check if all cells where current figire is located are free
bool isPositionFree() { bool isPositionFree() {
@ -359,6 +412,8 @@ class CupWidget : Widget {
HangingFigure, HangingFigure,
/// destroying complete rows /// destroying complete rows
DestroyingRows, DestroyingRows,
/// falling after some rows were destroyed
FallingRows,
/// Game is over /// Game is over
GameOver, GameOver,
} }
@ -378,10 +433,39 @@ class CupWidget : Widget {
_movementDuration = LEVEL_SPEED[level - 1]; _movementDuration = LEVEL_SPEED[level - 1];
} }
void setCupState(CupState state, int animationIntervalPercent = 100, int maxProgress = 10000) { static const int MIN_FAST_FALLING_INTERVAL = 600000;
static const int ROWS_FALLING_INTERVAL = 600000;
void setCupState(CupState state) {
int animationIntervalPercent = 100;
switch (state) {
case CupState.FallingFigure:
animationIntervalPercent = _fastDownFlag ? 10 : 25;
break;
case CupState.HangingFigure:
animationIntervalPercent = 75;
break;
case CupState.NewFigure:
animationIntervalPercent = 100;
break;
case CupState.FallingRows:
animationIntervalPercent = 25;
break;
case CupState.DestroyingRows:
animationIntervalPercent = 50;
break;
default:
break;
}
_state = state; _state = state;
if (animationIntervalPercent) if (animationIntervalPercent) {
_animation.start(_movementDuration * animationIntervalPercent / 100, maxProgress); long interval = _movementDuration * animationIntervalPercent / 100;
if (_fastDownFlag && falling && interval > MIN_FAST_FALLING_INTERVAL)
interval = MIN_FAST_FALLING_INTERVAL;
if (_state == CupState.FallingRows)
interval = ROWS_FALLING_INTERVAL;
_animation.start(interval, 255);
}
invalidate(); invalidate();
} }
@ -392,6 +476,7 @@ class CupWidget : Widget {
case CupState.FallingFigure: case CupState.FallingFigure:
case CupState.HangingFigure: case CupState.HangingFigure:
case CupState.DestroyingRows: case CupState.DestroyingRows:
case CupState.FallingRows:
return true; return true;
default: default:
return false; return false;
@ -401,14 +486,19 @@ class CupWidget : Widget {
/// Turn on / off fast falling down /// Turn on / off fast falling down
bool handleFastDown(bool fast) { bool handleFastDown(bool fast) {
if (fast == true) { if (fast == true) {
if (_fastDownFlag)
return false;
// handle turn on fast down // handle turn on fast down
if (_state == CupState.FallingFigure) { if (falling) {
_fastDownFlag = true; _fastDownFlag = true;
_animation.interval = _movementDuration * 10 / 100; // increase speed // if already falling, just increase speed
_animation.interval = _movementDuration * 10 / 100;
if (_animation.interval > MIN_FAST_FALLING_INTERVAL)
_animation.interval = MIN_FAST_FALLING_INTERVAL;
return true; return true;
} else if (_state == CupState.HangingFigure) { } else if (_state == CupState.HangingFigure) {
_fastDownFlag = true; _fastDownFlag = true;
setCupState(CupState.FallingFigure, 10); setCupState(CupState.FallingFigure);
return true; return true;
} else { } else {
return false; return false;
@ -418,24 +508,8 @@ class CupWidget : Widget {
return true; return true;
} }
protected void onAnimationFinished() { /// try start next figure
switch (_state) { protected void nextFigure() {
case CupState.NewFigure:
_fastDownFlag = false;
_cup.genNextFigure();
setCupState(CupState.HangingFigure, 75);
break;
case CupState.FallingFigure:
if (_cup.isPositionFreeBelow()) {
_cup.move(0, -1, false);
if (_fastDownFlag)
setCupState(CupState.FallingFigure, 10);
else
setCupState(CupState.HangingFigure, 75);
} else {
// At bottom of cup
_cup.putFigure();
_fastDownFlag = false;
if (!_cup.dropNextFigure()) { if (!_cup.dropNextFigure()) {
// Game Over // Game Over
setCupState(CupState.GameOver); setCupState(CupState.GameOver);
@ -443,16 +517,49 @@ class CupWidget : Widget {
popupWidget.padding(Rect(30, 30, 30, 30)).backgroundImageId("popup_background").alpha(0x40).fontWeight(800).fontSize(30); popupWidget.padding(Rect(30, 30, 30, 30)).backgroundImageId("popup_background").alpha(0x40).fontWeight(800).fontSize(30);
_gameOverPopup = window.showPopup(popupWidget, this); _gameOverPopup = window.showPopup(popupWidget, this);
} else { } else {
setCupState(CupState.NewFigure, 100, 255); setCupState(CupState.NewFigure);
}
}
protected void destroyFullRows() {
setCupState(CupState.DestroyingRows);
}
protected void onAnimationFinished() {
switch (_state) {
case CupState.NewFigure:
_fastDownFlag = false;
_cup.genNextFigure();
setCupState(CupState.HangingFigure);
break;
case CupState.FallingFigure:
if (_cup.isPositionFreeBelow()) {
_cup.move(0, -1, false);
if (_fastDownFlag)
setCupState(CupState.FallingFigure);
else
setCupState(CupState.HangingFigure);
} else {
// At bottom of cup
_cup.putFigure();
_fastDownFlag = false;
if (_cup.hasFullRows) {
destroyFullRows();
} else {
nextFigure();
} }
} }
break; break;
case CupState.HangingFigure: case CupState.HangingFigure:
setCupState(CupState.FallingFigure, 25); setCupState(CupState.FallingFigure);
break; break;
case CupState.DestroyingRows: case CupState.DestroyingRows:
// TODO _cup.destroyFullRows();
_fastDownFlag = false; setCupState(CupState.FallingRows);
break;
case CupState.FallingRows:
// TODO: fall more?
nextFigure();
break; break;
default: default:
break; break;
@ -490,6 +597,9 @@ class CupWidget : Widget {
newGame(); newGame();
return true; return true;
} }
if (event.action == KeyAction.KeyDown && _state == CupState.NewFigure) {
onAnimationFinished();
}
if (event.keyCode == KeyCode.DOWN) { if (event.keyCode == KeyCode.DOWN) {
if (event.action == KeyAction.KeyDown) { if (event.action == KeyAction.KeyDown) {
handleFastDown(true); handleFastDown(true);
@ -547,6 +657,9 @@ class CupWidget : Widget {
buf.fillRect(Rect(cupRc.left - dw - fw, cupRc.bottom + dw, cupRc.right + dw + fw, cupRc.bottom + dw + fw), fcl); buf.fillRect(Rect(cupRc.left - dw - fw, cupRc.bottom + dw, cupRc.right + dw + fw, cupRc.bottom + dw + fw), fcl);
for (int row = 0; row < _rows; row++) { for (int row = 0; row < _rows; row++) {
uint cellAlpha = 0;
if (_state == CupState.DestroyingRows && _cup.isRowFull(row))
cellAlpha = _animation.progress;
for (int col = 0; col < _cols; col++) { for (int col = 0; col < _cols; col++) {
int value = _cup[col, row]; int value = _cup[col, row];
@ -556,7 +669,7 @@ class CupWidget : Widget {
buf.fillRect(Rect(middle.x - 1, middle.y - 1, middle.x + 1, middle.y + 1), 0x80404040); buf.fillRect(Rect(middle.x - 1, middle.y - 1, middle.x + 1, middle.y + 1), 0x80404040);
if (value != EMPTY) { if (value != EMPTY) {
uint cl = _figureColors[value - 1]; uint cl = addAlpha(_figureColors[value - 1], cellAlpha);
drawCell(buf, cellRc, cl); drawCell(buf, cellRc, cl);
} }
} }
@ -620,7 +733,7 @@ class CupWidget : Widget {
setLevel(1); setLevel(1);
init(_cols, _rows); init(_cols, _rows);
_cup.dropNextFigure(); _cup.dropNextFigure();
setCupState(CupState.NewFigure, 100, 255); setCupState(CupState.NewFigure);
if (window && _gameOverPopup) { if (window && _gameOverPopup) {
window.removePopup(_gameOverPopup); window.removePopup(_gameOverPopup);
_gameOverPopup = null; _gameOverPopup = null;