Tetris example: refactoring

This commit is contained in:
Vadim Lopatin 2014-12-25 15:18:23 +03:00
parent 939f85cd93
commit 75cb2325cb
1 changed files with 168 additions and 110 deletions

View File

@ -141,22 +141,127 @@ enum TetrisAction : int {
FastDown,
}
enum : int {
WALL = -1,
EMPTY = 0,
FIGURE1,
FIGURE2,
FIGURE3,
FIGURE4,
FIGURE5,
FIGURE6,
}
enum : int {
ORIENTATION0,
ORIENTATION90,
ORIENTATION180,
ORIENTATION270
}
const uint[6] _figureColors = [0xFF0000, 0xA0A000, 0xA000A0, 0x0000FF, 0x800000, 0x408000];
/// Figure type, orientation and position container
struct FigurePosition {
int index;
int orientation;
int x;
int y;
this(int index, int orientation, int x, int y) {
this.index = index;
this.orientation = orientation;
this.x = x;
this.y = y;
}
/// return rotated position CCW for angle=1, CW for angle=-1
FigurePosition rotate(int angle) {
int newOrientation = (orientation + 4 + angle) & 3;
return FigurePosition(index, newOrientation, x, y);
}
/// return moved position
FigurePosition move(int dx, int dy = 0) {
return FigurePosition(index, orientation, x + dx, y + dy);
}
/// return shape for figure orientation
@property FigureShape shape() const {
return FIGURES[index - 1].shapes[orientation];
}
/// return color for figure
@property uint color() const {
return _figureColors[index - 1];
}
}
/**
Cup content
Coordinates are relative to bottom left corner.
*/
struct Cup {
private int[] _cup;
private int _cols;
private int _rows;
/// returns number of columns
@property int cols() {
return _cols;
}
/// returns number of columns
@property int rows() {
return _rows;
}
/// inits empty cup of specified size
void init(int cols, int rows) {
_cols = cols;
_rows = rows;
_cup = new int[_cols * _rows];
for (int i = 0; i < _cup.length; i++)
_cup[i] = EMPTY;
}
/// returns cell content at specified position
int opIndex(int col, int row) {
if (col < 0 || row < 0 || col >= _cols || row >= _rows)
return WALL;
return _cup[row * _cols + col];
}
/// set cell value
void opIndexAssign(int value, int col, int row) {
if (col < 0 || row < 0 || col >= _cols || row >= _rows)
return; // ignore modification of cells outside cup
_cup[row * _cols + col] = value;
}
/// put figure at specified position
void putFigure(FigurePosition pos) {
FigureShape shape = pos.shape;
foreach(cell; shape.cells) {
this[pos.x + cell.dx, pos.y + cell.dy] = pos.index;
}
}
/// check if all cells where figire is located are free
bool isPositionFree(in FigurePosition pos) {
FigureShape shape = pos.shape;
foreach(cell; shape.cells) {
int value = this[pos.x + cell.dx, pos.y + cell.dy];
if (value != 0) // occupied
return false;
}
return true;
}
}
/// Cup widget
class CupWidget : Widget {
/// cup columns count
int _cols;
/// cup rows count
int _rows;
int[] _cup;
/// current figure id
int _currentFigure;
/// current figure base point col
int _currentFigureX;
/// current figure base point row
int _currentFigureY;
/// current figure base point row
int _currentFigureOrientation;
/// cup data
Cup _cup;
/// current figure index, orientation, position
FigurePosition _currentFigure;
/// next figure id
int _nextFigure;
FigurePosition _nextFigure;
/// Level 1..10
int _level;
/// Single cell movement duration for current level, in 1/10000000 of seconds
@ -165,6 +270,7 @@ class CupWidget : Widget {
bool _fastDownFlag;
AnimationHelper _animation;
private PopupWidget _gameOverPopup;
/// Cup States
enum CupState : int {
@ -189,25 +295,6 @@ class CupWidget : Widget {
static const int RESERVED_ROWS = 5; // reserved for next figure
enum : int {
WALL = -1,
EMPTY = 0,
FIGURE1,
FIGURE2,
FIGURE3,
FIGURE4,
FIGURE5,
FIGURE6,
}
enum : int {
ORIENTATION0,
ORIENTATION90,
ORIENTATION180,
ORIENTATION270
}
static const uint[6] _figureColors = [0xFF0000, 0xA0A000, 0xA000A0, 0x0000FF, 0x800000, 0x408000];
/// set difficulty level 1..10
@ -256,41 +343,38 @@ class CupWidget : Widget {
return true;
}
bool rotate(int delta) {
int newOrientation = (_currentFigureOrientation + 4 + delta) & 3;
if (isPositionFree(_currentFigure, newOrientation, _currentFigureX, _currentFigureY)) {
bool rotate(int angle) {
FigurePosition newpos = _currentFigure.rotate(angle);
if (_cup.isPositionFree(newpos)) {
if (_state == CupState.FallingFigure) {
// special handling for fall animation
if (!isPositionFree(_currentFigure, newOrientation, _currentFigureX, _currentFigureY - 1)) {
if (!_cup.isPositionFree(newpos.move(0, -1))) {
if (isPositionFreeBelow())
return false;
}
}
_currentFigureOrientation = newOrientation;
_currentFigure = newpos;
return true;
} else if (isPositionFree(_currentFigure, newOrientation, _currentFigureX, _currentFigureY - 1)) {
_currentFigureOrientation = newOrientation;
_currentFigureY = _currentFigureY - 1;
} else if (_cup.isPositionFree(newpos.move(0, -1))) {
_currentFigure = newpos.move(0, -1);
return true;
}
return false;
}
bool move(int deltaX) {
int newx = _currentFigureX + deltaX;
if (isPositionFree(_currentFigure, _currentFigureOrientation, newx, _currentFigureY)) {
if (_state == CupState.FallingFigure && !isPositionFree(_currentFigure, _currentFigureOrientation, newx, _currentFigureY - 1)) {
FigurePosition newpos = _currentFigure.move(deltaX);
if (_cup.isPositionFree(newpos)) {
if (_state == CupState.FallingFigure && !_cup.isPositionFree(newpos.move(0, -1))) {
if (isPositionFreeBelow())
return false;
}
_currentFigureX = newx;
_currentFigure = newpos;
return true;
}
return false;
}
private PopupWidget _gameOverPopup;
protected void onAnimationFinished() {
switch (_state) {
case CupState.NewFigure:
@ -300,14 +384,14 @@ class CupWidget : Widget {
break;
case CupState.FallingFigure:
if (isPositionFreeBelow()) {
_currentFigureY--;
_currentFigure.y--;
if (_fastDownFlag)
setCupState(CupState.FallingFigure, 10);
else
setCupState(CupState.HangingFigure, 75);
} else {
// At bottom of cup
putFigure(_currentFigure, _currentFigureOrientation, _currentFigureX, _currentFigureY);
_cup.putFigure(_currentFigure);
_fastDownFlag = false;
if (!dropNextFigure()) {
// Game Over
@ -339,37 +423,26 @@ class CupWidget : Widget {
}
void genNextFigure() {
_nextFigure = uniform(FIGURE1, FIGURE6 + 1);
_nextFigure.index = uniform(FIGURE1, FIGURE6 + 1);
_nextFigure.orientation = ORIENTATION0;
_nextFigure.x = _cols / 2;
_nextFigure.y = _rows - _nextFigure.shape.extent + 1;
}
bool dropNextFigure() {
if (_nextFigure == 0)
if (_nextFigure.index == 0)
genNextFigure();
_currentFigure = _nextFigure;
_currentFigureOrientation = ORIENTATION0;
_currentFigureX = _cols / 2;
_currentFigureY = _rows - 1 - FIGURES[_currentFigure - 1].shapes[_currentFigureOrientation].y0;
_currentFigure.x = _cols / 2;
_currentFigure.y = _rows - 1 - _currentFigure.shape.y0;
setCupState(CupState.NewFigure, 100, 255);
return isPositionFree();
}
void init(int cols, int rows) {
_cup.init(cols, rows);
_cols = cols;
_rows = rows;
_cup = new int[_cols * _rows];
for (int i = 0; i < _cup.length; i++)
_cup[i] = EMPTY;
}
protected int cell(int col, int row) {
if (col < 0 || row < 0 || col >= _cols || row >= _rows)
return WALL;
return _cup[row * _cols + col];
}
protected void setCell(int col, int row, int value) {
_cup[row * _cols + col] = value;
invalidate();
}
protected Rect cellRect(Rect rc, int col, int row) {
@ -383,50 +456,12 @@ class CupWidget : Widget {
return Rect(x0, y0, x0 + dd, y0 + dd);
}
protected void drawCell(DrawBuf buf, Rect cellRc, uint color) {
cellRc.right--;
cellRc.bottom--;
int w = cellRc.width / 6;
buf.drawFrame(cellRc, color, Rect(w,w,w,w));
cellRc.shrink(w, w);
color = addAlpha(color, 0xC0);
buf.fillRect(cellRc, color);
}
protected void drawFigure(DrawBuf buf, Rect rc, int figureIndex, int orientation, int x, int y, int dy, uint alpha = 0) {
uint color = addAlpha(_figureColors[figureIndex - 1], alpha);
FigureShape shape = FIGURES[figureIndex - 1].shapes[orientation];
for (int i = 0; i < 4; i++) {
Rect cellRc = cellRect(rc, x + shape.cells[i].dx, y + shape.cells[i].dy);
cellRc.top += dy;
cellRc.bottom += dy;
drawCell(buf, cellRc, color);
}
}
protected bool isPositionFree() {
return isPositionFree(_currentFigure, _currentFigureOrientation, _currentFigureX, _currentFigureY);
return _cup.isPositionFree(_currentFigure);
}
protected bool isPositionFreeBelow() {
return isPositionFree(_currentFigure, _currentFigureOrientation, _currentFigureX, _currentFigureY - 1);
}
protected bool isPositionFree(int figureIndex, int orientation, int x, int y) {
FigureShape shape = FIGURES[figureIndex - 1].shapes[orientation];
for (int i = 0; i < 4; i++) {
int value = cell(x + shape.cells[i].dx, y + shape.cells[i].dy);
if (value != 0) // occupied
return false;
}
return true;
}
protected void putFigure(int figureIndex, int orientation, int x, int y) {
FigureShape shape = FIGURES[figureIndex - 1].shapes[orientation];
for (int i = 0; i < 4; i++) {
setCell(x + shape.cells[i].dx, y + shape.cells[i].dy, figureIndex);
}
return _cup.isPositionFree(_currentFigure.move(0, -1));
}
/// Handle keys
@ -448,6 +483,29 @@ class CupWidget : Widget {
return super.onKeyEvent(event);
}
/// draw cup cell
protected void drawCell(DrawBuf buf, Rect cellRc, uint color) {
cellRc.right--;
cellRc.bottom--;
int w = cellRc.width / 6;
buf.drawFrame(cellRc, color, Rect(w,w,w,w));
cellRc.shrink(w, w);
color = addAlpha(color, 0xC0);
buf.fillRect(cellRc, color);
}
/// draw figure
protected void drawFigure(DrawBuf buf, Rect rc, FigurePosition figure, int dy, uint alpha = 0) {
uint color = addAlpha(_figureColors[figure.index - 1], alpha);
FigureShape shape = figure.shape;
foreach(cell; shape.cells) {
Rect cellRc = cellRect(rc, figure.x + cell.dx, figure.y + cell.dy);
cellRc.top += dy;
cellRc.bottom += dy;
drawCell(buf, cellRc, color);
}
}
/// Draw widget at its position to buffer
override void onDraw(DrawBuf buf) {
super.onDraw(buf);
@ -471,7 +529,7 @@ class CupWidget : Widget {
for (int row = 0; row < _rows; row++) {
for (int col = 0; col < _cols; col++) {
int value = cell(col, row);
int value = _cup[col, row];
Rect cellRc = cellRect(rc, col, row);
Point middle = cellRc.middle;
@ -490,19 +548,19 @@ class CupWidget : Widget {
if (_state == CupState.FallingFigure && isPositionFreeBelow()) {
dy = _animation.getProgress(topLeft.height);
}
drawFigure(buf, rc, _currentFigure, _currentFigureOrientation, _currentFigureX, _currentFigureY, dy, 0);
drawFigure(buf, rc, _currentFigure, dy, 0);
}
if (_nextFigure != 0) {
auto shape = FIGURES[_nextFigure - 1].shapes[0];
if (_nextFigure.index != 0) {
//auto shape = _nextFigure.shape;
uint nextFigureAlpha = 0;
if (_state == CupState.NewFigure) {
nextFigureAlpha = _animation.progress;
drawFigure(buf, rc, _currentFigure, _currentFigureOrientation, _currentFigureX, _currentFigureY, 0, 255 - nextFigureAlpha);
drawFigure(buf, rc, _currentFigure, 0, 255 - nextFigureAlpha);
}
if (_state != CupState.GameOver) {
drawFigure(buf, rc, _nextFigure, ORIENTATION0, _cols / 2, _rows - shape.extent + 1, 0, blendAlpha(0xA0, nextFigureAlpha));
drawFigure(buf, rc, _nextFigure, 0, blendAlpha(0xA0, nextFigureAlpha));
}
}