mirror of https://github.com/buggins/dlangui.git
Tetris example: fall down after rows are filled
This commit is contained in:
parent
a91138cb56
commit
a29fdb0fa1
|
@ -139,6 +139,8 @@ enum TetrisAction : int {
|
||||||
MoveRight,
|
MoveRight,
|
||||||
RotateCCW,
|
RotateCCW,
|
||||||
FastDown,
|
FastDown,
|
||||||
|
Pause,
|
||||||
|
LevelUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum : int {
|
enum : int {
|
||||||
|
@ -210,7 +212,7 @@ struct Cup {
|
||||||
private int _cols;
|
private int _cols;
|
||||||
private int _rows;
|
private int _rows;
|
||||||
private bool[] _destroyedFullRows;
|
private bool[] _destroyedFullRows;
|
||||||
private bool[] _fallingCell;
|
private int[] _cellGroups;
|
||||||
|
|
||||||
private FigurePosition _currentFigure;
|
private FigurePosition _currentFigure;
|
||||||
/// current figure index, orientation, position
|
/// current figure index, orientation, position
|
||||||
|
@ -234,7 +236,7 @@ struct Cup {
|
||||||
_rows = rows;
|
_rows = rows;
|
||||||
_cup = new int[_cols * _rows];
|
_cup = new int[_cols * _rows];
|
||||||
_destroyedFullRows = new bool[_rows];
|
_destroyedFullRows = new bool[_rows];
|
||||||
_fallingCell = new bool[_cols * _rows];
|
_cellGroups = new int[_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) {
|
||||||
|
@ -378,6 +380,113 @@ struct Cup {
|
||||||
return isPositionFree();
|
return isPositionFree();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// get cell group / falling cell value
|
||||||
|
private int cellGroup(int col, int row) {
|
||||||
|
if (col < 0 || row < 0 || col >= _cols || row >= _rows)
|
||||||
|
return 0;
|
||||||
|
return _cellGroups[col + row * _cols];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set cell group / falling cells value
|
||||||
|
private void setCellGroup(int value, int col, int row) {
|
||||||
|
_cellGroups[col + row * _cols] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// recursive fill occupied area of cells with group id
|
||||||
|
private void fillCellGroup(int x, int y, int value) {
|
||||||
|
if (x < 0 || y < 0 || x >= _cols || y >= _rows)
|
||||||
|
return;
|
||||||
|
if (this[x, y] != EMPTY && cellGroup(x, y) == 0) {
|
||||||
|
setCellGroup(value, x, y);
|
||||||
|
fillCellGroup(x + 1, y, value);
|
||||||
|
fillCellGroup(x - 1, y, value);
|
||||||
|
fillCellGroup(x, y + 1, value);
|
||||||
|
fillCellGroup(x, y - 1, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 1 == next cell below is occupied, 2 == one empty cell
|
||||||
|
private int distanceToOccupiedCellBelow(int col, int row) {
|
||||||
|
for (int y = row - 1; y >= -1; y--) {
|
||||||
|
if (this[col, y] != EMPTY)
|
||||||
|
return row - y;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// mark cells in _cellGroups[] matrix which can fall down (value > 0 is distance to fall)
|
||||||
|
bool markFallingCells() {
|
||||||
|
_cellGroups = new int[_cols * _rows];
|
||||||
|
int groupId = 1;
|
||||||
|
for (int y = 0; y < _rows; y++) {
|
||||||
|
for (int x = 0; x < _cols; x++) {
|
||||||
|
if (this[x, y] != EMPTY && cellGroup(x, y) == 0) {
|
||||||
|
fillCellGroup(x, y, groupId);
|
||||||
|
groupId++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check space below each group - can it fall down?
|
||||||
|
int[] spaceBelowGroup = new int[groupId];
|
||||||
|
for (int y = 0; y < _rows; y++) {
|
||||||
|
for (int x = 0; x < _cols; x++) {
|
||||||
|
int group = cellGroup(x, y);
|
||||||
|
if (group > 0) {
|
||||||
|
if (y == 0)
|
||||||
|
spaceBelowGroup[group] = 1;
|
||||||
|
else if (this[x, y - 1] != EMPTY && cellGroup(x, y - 1) != group)
|
||||||
|
spaceBelowGroup[group] = 1;
|
||||||
|
else if (this[x, y - 1] == EMPTY) {
|
||||||
|
int dist = distanceToOccupiedCellBelow(x, y);
|
||||||
|
if (spaceBelowGroup[group] == 0 || spaceBelowGroup[group] > dist)
|
||||||
|
spaceBelowGroup[group] = dist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// replace group IDs with distance to fall (0 == cell cannot fall)
|
||||||
|
for (int y = 0; y < _rows; y++) {
|
||||||
|
for (int x = 0; x < _cols; x++) {
|
||||||
|
int group = cellGroup(x, y);
|
||||||
|
if (group > 0) {
|
||||||
|
// distance to fall
|
||||||
|
setCellGroup(spaceBelowGroup[group] - 1, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool canFall = false;
|
||||||
|
for (int i = 1; i < groupId; i++)
|
||||||
|
if (spaceBelowGroup[i] > 1)
|
||||||
|
canFall = true;
|
||||||
|
return canFall;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// moves all falling cells one cell down
|
||||||
|
/// returns true if there are more cells to fall
|
||||||
|
bool moveFallingCells() {
|
||||||
|
bool res = false;
|
||||||
|
for (int y = 0; y < _rows - 1; y++) {
|
||||||
|
for (int x = 0; x < _cols; x++) {
|
||||||
|
int dist = cellGroup(x, y + 1);
|
||||||
|
if (dist > 0) {
|
||||||
|
// move cell down, decreasing distance
|
||||||
|
setCellGroup(dist - 1, x, y);
|
||||||
|
this[x, y] = this[x, y + 1];
|
||||||
|
setCellGroup(0, x, y + 1);
|
||||||
|
this[x, y + 1] = EMPTY;
|
||||||
|
if (dist > 1)
|
||||||
|
res = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// return true if cell is currently falling
|
||||||
|
bool isCellFalling(int col, int row) {
|
||||||
|
return cellGroup(col, row) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cup widget
|
/// Cup widget
|
||||||
|
@ -555,11 +664,20 @@ class CupWidget : Widget {
|
||||||
break;
|
break;
|
||||||
case CupState.DestroyingRows:
|
case CupState.DestroyingRows:
|
||||||
_cup.destroyFullRows();
|
_cup.destroyFullRows();
|
||||||
|
if (_cup.markFallingCells()) {
|
||||||
setCupState(CupState.FallingRows);
|
setCupState(CupState.FallingRows);
|
||||||
|
} else {
|
||||||
|
nextFigure();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case CupState.FallingRows:
|
case CupState.FallingRows:
|
||||||
// TODO: fall more?
|
if (_cup.moveFallingCells()) {
|
||||||
|
// more cells to fall
|
||||||
|
setCupState(CupState.FallingRows);
|
||||||
|
} else {
|
||||||
|
// no more cells to fall, next figure
|
||||||
nextFigure();
|
nextFigure();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -594,10 +712,12 @@ class CupWidget : Widget {
|
||||||
/// Handle keys
|
/// Handle keys
|
||||||
override bool onKeyEvent(KeyEvent event) {
|
override bool onKeyEvent(KeyEvent event) {
|
||||||
if (event.action == KeyAction.KeyDown && _state == CupState.GameOver) {
|
if (event.action == KeyAction.KeyDown && _state == CupState.GameOver) {
|
||||||
|
// restart game
|
||||||
newGame();
|
newGame();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (event.action == KeyAction.KeyDown && _state == CupState.NewFigure) {
|
if (event.action == KeyAction.KeyDown && _state == CupState.NewFigure) {
|
||||||
|
// stop new figure fade in if key is pressed
|
||||||
onAnimationFinished();
|
onAnimationFinished();
|
||||||
}
|
}
|
||||||
if (event.keyCode == KeyCode.DOWN) {
|
if (event.keyCode == KeyCode.DOWN) {
|
||||||
|
@ -614,9 +734,13 @@ class CupWidget : Widget {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// draw cup cell
|
/// draw cup cell
|
||||||
protected void drawCell(DrawBuf buf, Rect cellRc, uint color) {
|
protected void drawCell(DrawBuf buf, Rect cellRc, uint color, int offset = 0) {
|
||||||
|
cellRc.top += offset;
|
||||||
|
cellRc.bottom += offset;
|
||||||
|
|
||||||
cellRc.right--;
|
cellRc.right--;
|
||||||
cellRc.bottom--;
|
cellRc.bottom--;
|
||||||
|
|
||||||
int w = cellRc.width / 6;
|
int w = cellRc.width / 6;
|
||||||
buf.drawFrame(cellRc, color, Rect(w,w,w,w));
|
buf.drawFrame(cellRc, color, Rect(w,w,w,w));
|
||||||
cellRc.shrink(w, w);
|
cellRc.shrink(w, w);
|
||||||
|
@ -656,6 +780,11 @@ class CupWidget : Widget {
|
||||||
buf.fillRect(Rect(cupRc.right + dw, cupRc.top, cupRc.right + dw + fw, cupRc.bottom + dw), fcl);
|
buf.fillRect(Rect(cupRc.right + dw, cupRc.top, cupRc.right + dw + fw, cupRc.bottom + dw), fcl);
|
||||||
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);
|
||||||
|
|
||||||
|
int fallingCellOffset = 0;
|
||||||
|
if (_state == CupState.FallingRows) {
|
||||||
|
fallingCellOffset = _animation.getProgress(topLeft.height);
|
||||||
|
}
|
||||||
|
|
||||||
for (int row = 0; row < _rows; row++) {
|
for (int row = 0; row < _rows; row++) {
|
||||||
uint cellAlpha = 0;
|
uint cellAlpha = 0;
|
||||||
if (_state == CupState.DestroyingRows && _cup.isRowFull(row))
|
if (_state == CupState.DestroyingRows && _cup.isRowFull(row))
|
||||||
|
@ -670,7 +799,8 @@ class CupWidget : Widget {
|
||||||
|
|
||||||
if (value != EMPTY) {
|
if (value != EMPTY) {
|
||||||
uint cl = addAlpha(_figureColors[value - 1], cellAlpha);
|
uint cl = addAlpha(_figureColors[value - 1], cellAlpha);
|
||||||
drawCell(buf, cellRc, cl);
|
int offset = fallingCellOffset > 0 && _cup.isCellFalling(col, row) ? fallingCellOffset : 0;
|
||||||
|
drawCell(buf, cellRc, cl, offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -722,6 +852,14 @@ class CupWidget : Widget {
|
||||||
case TetrisAction.FastDown:
|
case TetrisAction.FastDown:
|
||||||
handleFastDown(true);
|
handleFastDown(true);
|
||||||
return true;
|
return true;
|
||||||
|
case TetrisAction.Pause:
|
||||||
|
// TODO: implement pause
|
||||||
|
return true;
|
||||||
|
case TetrisAction.LevelUp:
|
||||||
|
if (_level < 10)
|
||||||
|
_level++;
|
||||||
|
// TODO: update state
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
if (parent) // by default, pass to parent widget
|
if (parent) // by default, pass to parent widget
|
||||||
return parent.handleAction(a);
|
return parent.handleAction(a);
|
||||||
|
@ -729,6 +867,7 @@ class CupWidget : Widget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// start new game
|
||||||
void newGame() {
|
void newGame() {
|
||||||
setLevel(1);
|
setLevel(1);
|
||||||
init(_cols, _rows);
|
init(_cols, _rows);
|
||||||
|
@ -753,10 +892,12 @@ class CupWidget : Widget {
|
||||||
focusable = true;
|
focusable = true;
|
||||||
|
|
||||||
acceleratorMap.add( [
|
acceleratorMap.add( [
|
||||||
new Action(TetrisAction.MoveLeft, KeyCode.LEFT, 0),
|
(new Action(TetrisAction.MoveLeft, KeyCode.LEFT)).addAccelerator(KeyCode.KEY_A),
|
||||||
new Action(TetrisAction.MoveRight, KeyCode.RIGHT, 0),
|
(new Action(TetrisAction.MoveRight, KeyCode.RIGHT)).addAccelerator(KeyCode.KEY_D),
|
||||||
new Action(TetrisAction.RotateCCW, KeyCode.UP, 0),
|
(new Action(TetrisAction.RotateCCW, KeyCode.UP)).addAccelerator(KeyCode.KEY_W),
|
||||||
new Action(TetrisAction.FastDown, KeyCode.SPACE, 0),
|
(new Action(TetrisAction.FastDown, KeyCode.SPACE)).addAccelerator(KeyCode.KEY_S),
|
||||||
|
(new Action(TetrisAction.Pause, KeyCode.ESCAPE)).addAccelerator(KeyCode.PAUSE),
|
||||||
|
(new Action(TetrisAction.LevelUp, KeyCode.ADD)).addAccelerator(KeyCode.INS),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -820,13 +961,6 @@ class GameWidget : HorizontalLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum : int {
|
|
||||||
ACTION_FILE_OPEN = 5500,
|
|
||||||
ACTION_FILE_SAVE,
|
|
||||||
ACTION_FILE_CLOSE,
|
|
||||||
ACTION_FILE_EXIT,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// entry point for dlangui based application
|
/// entry point for dlangui based application
|
||||||
extern (C) int UIAppMain(string[] args) {
|
extern (C) int UIAppMain(string[] args) {
|
||||||
|
|
||||||
|
|
|
@ -146,6 +146,11 @@ class Action {
|
||||||
return null;
|
return null;
|
||||||
return _accelerators[0].label;
|
return _accelerators[0].label;
|
||||||
}
|
}
|
||||||
|
/// adds one more accelerator
|
||||||
|
Action addAccelerator(uint keyCode, uint keyFlags = 0) {
|
||||||
|
_accelerators ~= Accelerator(keyCode, keyFlags);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
/// returns true if accelerator matches provided key code and flags
|
/// returns true if accelerator matches provided key code and flags
|
||||||
bool checkAccelerator(uint keyCode, uint keyFlags) {
|
bool checkAccelerator(uint keyCode, uint keyFlags) {
|
||||||
foreach(a; _accelerators)
|
foreach(a; _accelerators)
|
||||||
|
|
Loading…
Reference in New Issue