improve workspace operations; add upgrade dependencies and refresh project commans; add Tetris project as a sample
|
@ -39,7 +39,7 @@ extern (C) int UIAppMain(string[] args) {
|
|||
// open home screen tab
|
||||
frame.showHomeScreen();
|
||||
// for testing: load workspace at startup
|
||||
frame.openFileOrWorkspace(appendPath(exePath, "../workspaces/sample1/sample1.dlangidews"));
|
||||
//frame.openFileOrWorkspace(appendPath(exePath, "../workspaces/sample1/sample1.dlangidews"));
|
||||
|
||||
// show window
|
||||
window.show();
|
||||
|
|
|
@ -63,9 +63,12 @@ class Builder : BackgroundOperationWatcher {
|
|||
params ~= "clean".dup;
|
||||
} else if (_buildOp == BuildOperation.Run) {
|
||||
params ~= "run".dup;
|
||||
} else if (_buildOp == BuildOperation.Upgrade) {
|
||||
params ~= "upgrade".dup;
|
||||
params ~= "--force-remove".dup;
|
||||
}
|
||||
|
||||
if (_buildOp != BuildOperation.Clean) {
|
||||
if (_buildOp != BuildOperation.Clean && _buildOp != BuildOperation.Upgrade) {
|
||||
switch (_buildConfig) {
|
||||
default:
|
||||
case BuildConfiguration.Debug:
|
||||
|
|
|
@ -22,6 +22,8 @@ enum IDEActions : int {
|
|||
BuildProject,
|
||||
RebuildProject,
|
||||
CleanProject,
|
||||
RefreshProject,
|
||||
UpdateProjectDependencies,
|
||||
SetStartupProject,
|
||||
ProjectSettings,
|
||||
DebugStart,
|
||||
|
@ -52,6 +54,8 @@ const Action ACTION_PROJECT_REBUILD = new Action(IDEActions.RebuildProject, "MEN
|
|||
const Action ACTION_PROJECT_CLEAN = new Action(IDEActions.CleanProject, "MENU_BUILD_PROJECT_CLEAN"c, null);
|
||||
const Action ACTION_PROJECT_SET_STARTUP = new Action(IDEActions.SetStartupProject, "MENU_PROJECT_SET_AS_STARTUP"c, null);
|
||||
const Action ACTION_PROJECT_SETTINGS = new Action(IDEActions.ProjectSettings, "MENU_PROJECT_SETTINGS"c, null);
|
||||
const Action ACTION_PROJECT_REFRESH = new Action(IDEActions.RefreshProject, "MENU_PROJECT_REFRESH"c);
|
||||
const Action ACTION_PROJECT_UPDATE_DEPENDENCIES = new Action(IDEActions.UpdateProjectDependencies, "MENU_PROJECT_UPDATE_DEPENDENCIES"c);
|
||||
const Action ACTION_DEBUG_START = new Action(IDEActions.DebugStart, "MENU_DEBUG_START_DEBUGGING"c, "debug-run"c, KeyCode.F5, 0);
|
||||
const Action ACTION_DEBUG_START_NO_DEBUG = new Action(IDEActions.DebugStartNoDebug, "MENU_DEBUG_START_NO_DEBUGGING"c, null, KeyCode.F5, KeyFlag.Control);
|
||||
const Action ACTION_DEBUG_CONTINUE = new Action(IDEActions.DebugContinue, "MENU_DEBUG_CONTINUE"c);
|
||||
|
|
|
@ -298,7 +298,7 @@ class IDEFrame : AppFrame {
|
|||
editItem.add(new Action(20, "MENU_EDIT_PREFERENCES"));
|
||||
|
||||
MenuItem projectItem = new MenuItem(new Action(21, "MENU_PROJECT"));
|
||||
projectItem.add(ACTION_PROJECT_SET_STARTUP, ACTION_PROJECT_SETTINGS);
|
||||
projectItem.add(ACTION_PROJECT_SET_STARTUP, ACTION_PROJECT_REFRESH, ACTION_PROJECT_UPDATE_DEPENDENCIES, ACTION_PROJECT_SETTINGS);
|
||||
|
||||
MenuItem buildItem = new MenuItem(new Action(22, "MENU_BUILD"));
|
||||
buildItem.add(ACTION_WORKSPACE_BUILD, ACTION_WORKSPACE_REBUILD, ACTION_WORKSPACE_CLEAN,
|
||||
|
@ -416,6 +416,14 @@ class IDEFrame : AppFrame {
|
|||
buildProject(BuildOperation.Run);
|
||||
//setBackgroundOperation(new BackgroundOperationWatcherTest(this));
|
||||
return true;
|
||||
case IDEActions.UpdateProjectDependencies:
|
||||
buildProject(BuildOperation.Upgrade);
|
||||
//setBackgroundOperation(new BackgroundOperationWatcherTest(this));
|
||||
return true;
|
||||
case IDEActions.RefreshProject:
|
||||
refreshWorkspace();
|
||||
//setBackgroundOperation(new BackgroundOperationWatcherTest(this));
|
||||
return true;
|
||||
case IDEActions.WindowCloseAllDocuments:
|
||||
askForUnsavedEdits(delegate() {
|
||||
closeAllDocuments();
|
||||
|
@ -454,15 +462,20 @@ class IDEFrame : AppFrame {
|
|||
return;
|
||||
}
|
||||
} else if (filename.isProjectFile) {
|
||||
_logPanel.clear();
|
||||
_logPanel.logLine("Trying to open project from " ~ filename);
|
||||
Project project = new Project();
|
||||
if (!project.load(filename)) {
|
||||
_logPanel.logLine("Cannot read project file " ~ filename);
|
||||
window.showMessageBox(UIString("Cannot open project"d), UIString("Error occured while opening project"d));
|
||||
return;
|
||||
}
|
||||
_logPanel.logLine("Project file is opened ok");
|
||||
string defWsFile = project.defWorkspaceFile;
|
||||
if (currentWorkspace) {
|
||||
Project existing = currentWorkspace.findProject(project.filename);
|
||||
if (existing) {
|
||||
_logPanel.logLine("This project already exists in current workspace");
|
||||
window.showMessageBox(UIString("Open project"d), UIString("Project is already in workspace"d));
|
||||
return;
|
||||
}
|
||||
|
@ -470,28 +483,43 @@ class IDEFrame : AppFrame {
|
|||
[ACTION_ADD_TO_CURRENT_WORKSPACE, ACTION_CREATE_NEW_WORKSPACE, ACTION_CANCEL], 0, delegate(const Action result) {
|
||||
if (result.id == IDEActions.CreateNewWorkspace) {
|
||||
// new ws
|
||||
Workspace ws = new Workspace();
|
||||
ws.name = project.name;
|
||||
ws.description = project.description;
|
||||
ws.addProject(project);
|
||||
ws.save(defWsFile);
|
||||
setWorkspace(ws);
|
||||
createNewWorkspaceForExistingProject(project);
|
||||
} else if (result.id == IDEActions.AddToCurrentWorkspace) {
|
||||
// add to current
|
||||
currentWorkspace.addProject(project);
|
||||
currentWorkspace.save();
|
||||
_wsPanel.reloadItems();
|
||||
refreshWorkspace();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
// new workspace file
|
||||
createNewWorkspaceForExistingProject(project);
|
||||
}
|
||||
} else {
|
||||
_logPanel.logLine("File is not recognized as DlangIDE project or workspace file");
|
||||
window.showMessageBox(UIString("Invalid workspace file"d), UIString("This file is not a valid workspace or project file"d));
|
||||
}
|
||||
}
|
||||
|
||||
void refreshWorkspace() {
|
||||
_logPanel.logLine("Refreshing workspace");
|
||||
_wsPanel.reloadItems();
|
||||
}
|
||||
|
||||
void createNewWorkspaceForExistingProject(Project project) {
|
||||
string defWsFile = project.defWorkspaceFile;
|
||||
_logPanel.logLine("Creating new workspace " ~ defWsFile);
|
||||
// new ws
|
||||
Workspace ws = new Workspace();
|
||||
ws.name = project.name;
|
||||
ws.description = project.description;
|
||||
ws.addProject(project);
|
||||
ws.save(defWsFile);
|
||||
setWorkspace(ws);
|
||||
_logPanel.logLine("Done");
|
||||
}
|
||||
|
||||
//bool loadWorkspace(string path) {
|
||||
// // testing workspace loader
|
||||
// Workspace ws = new Workspace();
|
||||
|
@ -505,11 +533,17 @@ class IDEFrame : AppFrame {
|
|||
closeAllDocuments();
|
||||
currentWorkspace = ws;
|
||||
_wsPanel.workspace = ws;
|
||||
if (ws.startupProject && ws.startupProject.mainSourceFile) {
|
||||
openSourceFile(ws.startupProject.mainSourceFile.filename);
|
||||
_tabs.setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void buildProject(BuildOperation buildOp) {
|
||||
if (!currentWorkspace || !currentWorkspace.startupProject)
|
||||
if (!currentWorkspace || !currentWorkspace.startupProject) {
|
||||
_logPanel.logLine("No project is opened");
|
||||
return;
|
||||
}
|
||||
Builder op = new Builder(this, currentWorkspace.startupProject, _logPanel, currentWorkspace.buildConfiguration, buildOp, false);
|
||||
setBackgroundOperation(op);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import dlangui.all;
|
|||
import dlangide.workspace.workspace;
|
||||
import dlangide.workspace.project;
|
||||
|
||||
import std.utf;
|
||||
|
||||
class OutputPanel : DockWindow {
|
||||
protected LogWidget _logWidget;
|
||||
|
||||
|
@ -24,6 +26,22 @@ class OutputPanel : DockWindow {
|
|||
_logWidget.appendText(msg);
|
||||
}
|
||||
|
||||
void logLine(string category, dstring msg) {
|
||||
appendText(category, msg ~ "\n");
|
||||
}
|
||||
|
||||
void logLine(dstring msg) {
|
||||
logLine(null, msg);
|
||||
}
|
||||
|
||||
void logLine(string category, string msg) {
|
||||
appendText(category, toUTF32(msg ~ "\n"));
|
||||
}
|
||||
|
||||
void logLine(string msg) {
|
||||
logLine(null, msg);
|
||||
}
|
||||
|
||||
void clear(string category = null) {
|
||||
_logWidget.text = ""d;
|
||||
}
|
||||
|
|
|
@ -209,6 +209,7 @@ class Project : WorkspaceItem {
|
|||
protected Workspace _workspace;
|
||||
protected bool _opened;
|
||||
protected ProjectFolder _items;
|
||||
protected ProjectSourceFile _mainSourceFile;
|
||||
this(string fname = null) {
|
||||
super(fname);
|
||||
_items = new ProjectFolder(fname);
|
||||
|
@ -220,6 +221,7 @@ class Project : WorkspaceItem {
|
|||
return buildNormalizedPath(_dir, path);
|
||||
}
|
||||
|
||||
@property ProjectSourceFile mainSourceFile() { return _mainSourceFile; }
|
||||
@property ProjectFolder items() {
|
||||
return _items;
|
||||
}
|
||||
|
@ -244,6 +246,19 @@ class Project : WorkspaceItem {
|
|||
return folder;
|
||||
}
|
||||
|
||||
void findMainSourceFile() {
|
||||
string n = toUTF8(name);
|
||||
string[] mainnames = ["app.d", "main.d", n ~ ".d"];
|
||||
foreach(sname; mainnames) {
|
||||
_mainSourceFile = findSourceFileItem(buildNormalizedPath(_dir, "src", sname));
|
||||
if (_mainSourceFile)
|
||||
break;
|
||||
_mainSourceFile = findSourceFileItem(buildNormalizedPath(_dir, "source", sname));
|
||||
if (_mainSourceFile)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// tries to find source file in project, returns found project source file item, or null if not found
|
||||
ProjectSourceFile findSourceFileItem(ProjectItem dir, string filename) {
|
||||
for (int i = 0; i < dir.childCount; i++) {
|
||||
|
@ -266,6 +281,7 @@ class Project : WorkspaceItem {
|
|||
}
|
||||
|
||||
override bool load(string fname = null) {
|
||||
_mainSourceFile = null;
|
||||
if (fname.length > 0)
|
||||
filename = fname;
|
||||
if (!exists(filename) || !isFile(filename)) {
|
||||
|
@ -282,6 +298,7 @@ class Project : WorkspaceItem {
|
|||
Log.d(" project description: ", _description);
|
||||
|
||||
_items = findItems();
|
||||
findMainSourceFile();
|
||||
} catch (JSONException e) {
|
||||
Log.e("Cannot parse json", e);
|
||||
return false;
|
||||
|
|
|
@ -12,7 +12,8 @@ enum BuildOperation {
|
|||
Build,
|
||||
Clean,
|
||||
Rebuild,
|
||||
Run
|
||||
Run,
|
||||
Upgrade
|
||||
}
|
||||
|
||||
enum BuildConfiguration {
|
||||
|
|
|
@ -28,6 +28,8 @@ MENU_BUILD_PROJECT_CLEAN=Clean Project
|
|||
MENU_PROJECT=&PROJECT
|
||||
MENU_PROJECT_SET_AS_STARTUP=Set as Startup Project
|
||||
MENU_PROJECT_SETTINGS=Project Settings
|
||||
MENU_PROJECT_REFRESH=Refresh Workspace Items
|
||||
MENU_PROJECT_UPDATE_DEPENDENCIES=Upgrade Dependencies
|
||||
MENU_DEBUG=&DEBUG
|
||||
MENU_DEBUG_START_DEBUGGING=Start Debugging
|
||||
MENU_DEBUG_START_NO_DEBUGGING=Start Without Debugging
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "tetris",
|
||||
"description": "dlangui library example: Tetris game",
|
||||
"homepage": "https://github.com/buggins/dlangui",
|
||||
"license": "Boost",
|
||||
"authors": ["Vadim Lopatin"],
|
||||
|
||||
"targetPath": "bin",
|
||||
"targetType": "executable",
|
||||
"targetName": "tetris",
|
||||
|
||||
"stringImportPaths": ["views", "views/res", "views/res/i18n", "views/res/mdpi"],
|
||||
|
||||
"dependencies": {
|
||||
"dlangui:dlanguilib": "~master"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,599 @@
|
|||
module gui;
|
||||
|
||||
import model;
|
||||
|
||||
import dlangui.all;
|
||||
|
||||
/// game action codes
|
||||
enum TetrisAction : int {
|
||||
MoveLeft = 10000,
|
||||
MoveRight,
|
||||
RotateCCW,
|
||||
FastDown,
|
||||
Pause,
|
||||
LevelUp,
|
||||
}
|
||||
|
||||
const Action ACTION_MOVE_LEFT = (new Action(TetrisAction.MoveLeft, KeyCode.LEFT)).addAccelerator(KeyCode.KEY_A).iconId("arrow-left");
|
||||
const Action ACTION_MOVE_RIGHT = (new Action(TetrisAction.MoveRight, KeyCode.RIGHT)).addAccelerator(KeyCode.KEY_D).iconId("arrow-right");
|
||||
const Action ACTION_ROTATE = (new Action(TetrisAction.RotateCCW, KeyCode.UP)).addAccelerator(KeyCode.KEY_W).iconId("rotate");
|
||||
const Action ACTION_FAST_DOWN = (new Action(TetrisAction.FastDown, KeyCode.SPACE)).addAccelerator(KeyCode.KEY_S).iconId("arrow-down");
|
||||
const Action ACTION_PAUSE = (new Action(TetrisAction.Pause, KeyCode.ESCAPE)).addAccelerator(KeyCode.PAUSE).iconId("pause");
|
||||
const Action ACTION_LEVEL_UP = (new Action(TetrisAction.LevelUp, KeyCode.ADD)).addAccelerator(KeyCode.INS).iconId("levelup");
|
||||
|
||||
const Action[] CUP_ACTIONS = [ACTION_PAUSE, ACTION_ROTATE, ACTION_LEVEL_UP,
|
||||
ACTION_MOVE_LEFT, ACTION_FAST_DOWN, ACTION_MOVE_RIGHT];
|
||||
|
||||
/// about dialog
|
||||
Widget createAboutWidget()
|
||||
{
|
||||
LinearLayout res = new VerticalLayout();
|
||||
res.padding(Rect(10,10,10,10));
|
||||
res.addChild(new TextWidget(null, "DLangUI Tetris demo app"d));
|
||||
res.addChild(new TextWidget(null, "(C) Vadim Lopatin, 2014"d));
|
||||
res.addChild(new TextWidget(null, "http://github.com/buggins/dlangui"d));
|
||||
Button closeButton = new Button("close", "Close"d);
|
||||
closeButton.onClickListener = delegate(Widget src) {
|
||||
Log.i("Closing window");
|
||||
res.window.close();
|
||||
return true;
|
||||
};
|
||||
res.addChild(closeButton);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Cup States
|
||||
enum CupState : int {
|
||||
/// New figure appears
|
||||
NewFigure,
|
||||
/// Game is paused
|
||||
Paused,
|
||||
/// Figure is falling
|
||||
FallingFigure,
|
||||
/// Figure is hanging - pause between falling by one row
|
||||
HangingFigure,
|
||||
/// destroying complete rows
|
||||
DestroyingRows,
|
||||
/// falling after some rows were destroyed
|
||||
FallingRows,
|
||||
/// Game is over
|
||||
GameOver,
|
||||
}
|
||||
|
||||
/// Cup widget
|
||||
class CupWidget : Widget {
|
||||
/// cup columns count
|
||||
int _cols;
|
||||
/// cup rows count
|
||||
int _rows;
|
||||
/// cup data
|
||||
Cup _cup;
|
||||
|
||||
|
||||
/// Level 1..10
|
||||
int _level;
|
||||
/// Score
|
||||
int _score;
|
||||
/// Single cell movement duration for current level, in 1/10000000 of seconds
|
||||
long _movementDuration;
|
||||
/// When true, figure is falling down fast
|
||||
bool _fastDownFlag;
|
||||
/// animation helper for fade and movement in different states
|
||||
AnimationHelper _animation;
|
||||
/// GameOver popup
|
||||
private PopupWidget _gameOverPopup;
|
||||
/// Status widget
|
||||
private StatusWidget _status;
|
||||
/// Current state
|
||||
protected CupState _state;
|
||||
|
||||
protected int _totalRowsDestroyed;
|
||||
|
||||
static const int[10] LEVEL_SPEED = [15000000, 10000000, 7000000, 6000000, 5000000, 4000000, 300000, 2000000, 1500000, 1000000];
|
||||
|
||||
static const int RESERVED_ROWS = 5; // reserved for next figure
|
||||
|
||||
/// set difficulty level 1..10
|
||||
void setLevel(int level) {
|
||||
if (level > 10)
|
||||
return;
|
||||
_level = level;
|
||||
_movementDuration = LEVEL_SPEED[level - 1];
|
||||
_status.setLevel(_level);
|
||||
}
|
||||
|
||||
static const int MIN_FAST_FALLING_INTERVAL = 600000;
|
||||
|
||||
static const int ROWS_FALLING_INTERVAL = 1200000;
|
||||
|
||||
/// change game state, init state animation when necessary
|
||||
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:
|
||||
// no animation for other states
|
||||
animationIntervalPercent = 0;
|
||||
break;
|
||||
}
|
||||
_state = state;
|
||||
if (animationIntervalPercent) {
|
||||
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();
|
||||
}
|
||||
|
||||
void addScore(int score) {
|
||||
_score += score;
|
||||
_status.setScore(_score);
|
||||
}
|
||||
|
||||
/// returns true if figure is in falling - movement state
|
||||
@property bool falling() {
|
||||
return _state == CupState.FallingFigure;
|
||||
}
|
||||
|
||||
/// Turn on / off fast falling down
|
||||
bool handleFastDown(bool fast) {
|
||||
if (fast == true) {
|
||||
if (_fastDownFlag)
|
||||
return false;
|
||||
// handle turn on fast down
|
||||
if (falling) {
|
||||
_fastDownFlag = true;
|
||||
// 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;
|
||||
} else if (_state == CupState.HangingFigure) {
|
||||
_fastDownFlag = true;
|
||||
setCupState(CupState.FallingFigure);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_fastDownFlag = fast;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const int[] NEXT_LEVEL_SCORE = [0, 20, 50, 100, 200, 350, 500, 750, 1000, 1500, 2000];
|
||||
|
||||
/// try start next figure
|
||||
protected void nextFigure() {
|
||||
if (!_cup.dropNextFigure()) {
|
||||
// Game Over
|
||||
setCupState(CupState.GameOver);
|
||||
Widget popupWidget = new TextWidget("popup", "Game Over!"d);
|
||||
popupWidget.padding(Rect(30, 30, 30, 30)).backgroundImageId("popup_background").alpha(0x40).fontWeight(800).fontSize(30);
|
||||
_gameOverPopup = window.showPopup(popupWidget, this);
|
||||
} else {
|
||||
setCupState(CupState.NewFigure);
|
||||
if (_level < 10 && _totalRowsDestroyed >= NEXT_LEVEL_SCORE[_level])
|
||||
setLevel(_level + 1); // level up
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
case CupState.HangingFigure:
|
||||
setCupState(CupState.FallingFigure);
|
||||
break;
|
||||
case CupState.DestroyingRows:
|
||||
int rowsDestroyed = _cup.destroyFullRows();
|
||||
_totalRowsDestroyed += rowsDestroyed;
|
||||
_status.setRowsDestroyed(_totalRowsDestroyed);
|
||||
int scorePerRow = 0;
|
||||
for (int i = 0; i < rowsDestroyed; i++) {
|
||||
scorePerRow += 10;
|
||||
addScore(scorePerRow);
|
||||
}
|
||||
if (_cup.markFallingCells()) {
|
||||
setCupState(CupState.FallingRows);
|
||||
} else {
|
||||
nextFigure();
|
||||
}
|
||||
break;
|
||||
case CupState.FallingRows:
|
||||
if (_cup.moveFallingCells()) {
|
||||
// more cells to fall
|
||||
setCupState(CupState.FallingRows);
|
||||
} else {
|
||||
// no more cells to fall, next figure
|
||||
if (_cup.hasFullRows) {
|
||||
// new full rows were constructed: destroy
|
||||
destroyFullRows();
|
||||
} else {
|
||||
// next figure
|
||||
nextFigure();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// start new game
|
||||
void newGame() {
|
||||
setLevel(1);
|
||||
init(_cols, _rows);
|
||||
_cup.dropNextFigure();
|
||||
setCupState(CupState.NewFigure);
|
||||
if (window && _gameOverPopup) {
|
||||
window.removePopup(_gameOverPopup);
|
||||
_gameOverPopup = null;
|
||||
}
|
||||
_score = 0;
|
||||
_status.setScore(0);
|
||||
_totalRowsDestroyed = 0;
|
||||
_status.setRowsDestroyed(0);
|
||||
}
|
||||
|
||||
/// init cup
|
||||
void init(int cols, int rows) {
|
||||
_cup.init(cols, rows);
|
||||
_cols = cols;
|
||||
_rows = rows;
|
||||
}
|
||||
|
||||
protected Rect cellRect(Rect rc, int col, int row) {
|
||||
int dx = rc.width / _cols;
|
||||
int dy = rc.height / (_rows + RESERVED_ROWS);
|
||||
int dd = dx;
|
||||
if (dd > dy)
|
||||
dd = dy;
|
||||
int x0 = rc.left + (rc.width - dd * _cols) / 2 + dd * col;
|
||||
int y0 = rc.bottom - (rc.height - dd * (_rows + RESERVED_ROWS)) / 2 - dd * row - dd;
|
||||
return Rect(x0, y0, x0 + dd, y0 + dd);
|
||||
}
|
||||
|
||||
/// Handle keys
|
||||
override bool onKeyEvent(KeyEvent event) {
|
||||
if (event.action == KeyAction.KeyDown && _state == CupState.GameOver) {
|
||||
// restart game
|
||||
newGame();
|
||||
return true;
|
||||
}
|
||||
if (event.action == KeyAction.KeyDown && _state == CupState.NewFigure) {
|
||||
// stop new figure fade in if key is pressed
|
||||
onAnimationFinished();
|
||||
}
|
||||
if (event.keyCode == KeyCode.DOWN) {
|
||||
if (event.action == KeyAction.KeyDown) {
|
||||
handleFastDown(true);
|
||||
} else if (event.action == KeyAction.KeyUp) {
|
||||
handleFastDown(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if ((event.action == KeyAction.KeyDown || event.action == KeyAction.KeyUp) && event.keyCode != KeyCode.SPACE)
|
||||
handleFastDown(false); // don't stop fast down on Space key KeyUp
|
||||
return super.onKeyEvent(event);
|
||||
}
|
||||
|
||||
/// draw cup cell
|
||||
protected void drawCell(DrawBuf buf, Rect cellRc, uint color, int offset = 0) {
|
||||
cellRc.top += offset;
|
||||
cellRc.bottom += offset;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
// Overrides of Widget methods
|
||||
|
||||
/// returns true is widget is being animated - need to call animate() and redraw
|
||||
override @property bool animating() {
|
||||
switch (_state) {
|
||||
case CupState.NewFigure:
|
||||
case CupState.FallingFigure:
|
||||
case CupState.HangingFigure:
|
||||
case CupState.DestroyingRows:
|
||||
case CupState.FallingRows:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// animates window; interval is time left from previous draw, in hnsecs (1/10000000 of second)
|
||||
override void animate(long interval) {
|
||||
_animation.animate(interval);
|
||||
if (_animation.finished) {
|
||||
onAnimationFinished();
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw widget at its position to buffer
|
||||
override void onDraw(DrawBuf buf) {
|
||||
super.onDraw(buf);
|
||||
Rect rc = _pos;
|
||||
applyMargins(rc);
|
||||
auto saver = ClipRectSaver(buf, rc, alpha);
|
||||
applyPadding(rc);
|
||||
|
||||
Rect topLeft = cellRect(rc, 0, _rows - 1);
|
||||
Rect bottomRight = cellRect(rc, _cols - 1, 0);
|
||||
Rect cupRc = Rect(topLeft.left, topLeft.top, bottomRight.right, bottomRight.bottom);
|
||||
|
||||
int fw = 7;
|
||||
int dw = 0;
|
||||
uint fcl = 0xA0606090;
|
||||
buf.fillRect(cupRc, 0xC0A0C0B0);
|
||||
buf.fillRect(Rect(cupRc.left - dw - fw, cupRc.top, cupRc.left - dw, 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);
|
||||
|
||||
int fallingCellOffset = 0;
|
||||
if (_state == CupState.FallingRows) {
|
||||
fallingCellOffset = _animation.getProgress(topLeft.height);
|
||||
}
|
||||
|
||||
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++) {
|
||||
|
||||
int value = _cup[col, row];
|
||||
Rect cellRc = cellRect(rc, col, row);
|
||||
|
||||
Point middle = cellRc.middle;
|
||||
buf.fillRect(Rect(middle.x - 1, middle.y - 1, middle.x + 1, middle.y + 1), 0x80404040);
|
||||
|
||||
if (value != EMPTY) {
|
||||
uint cl = addAlpha(_figureColors[value - 1], cellAlpha);
|
||||
int offset = fallingCellOffset > 0 && _cup.isCellFalling(col, row) ? fallingCellOffset : 0;
|
||||
drawCell(buf, cellRc, cl, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw current figure falling
|
||||
if (_state == CupState.FallingFigure || _state == CupState.HangingFigure) {
|
||||
int dy = 0;
|
||||
if (falling && _cup.isPositionFreeBelow())
|
||||
dy = _animation.getProgress(topLeft.height);
|
||||
drawFigure(buf, rc, _cup.currentFigure, dy, 0);
|
||||
}
|
||||
|
||||
// draw next figure
|
||||
if (_cup.hasNextFigure) {
|
||||
//auto shape = _nextFigure.shape;
|
||||
uint nextFigureAlpha = 0;
|
||||
if (_state == CupState.NewFigure) {
|
||||
nextFigureAlpha = _animation.progress;
|
||||
drawFigure(buf, rc, _cup.currentFigure, 0, 255 - nextFigureAlpha);
|
||||
}
|
||||
if (_state != CupState.GameOver) {
|
||||
drawFigure(buf, rc, _cup.nextFigure, 0, blendAlpha(0xA0, nextFigureAlpha));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// override to handle specific actions
|
||||
override bool handleAction(const Action a) {
|
||||
switch (a.id) {
|
||||
case TetrisAction.MoveLeft:
|
||||
_cup.move(-1, 0, falling);
|
||||
return true;
|
||||
case TetrisAction.MoveRight:
|
||||
_cup.move(1, 0, falling);
|
||||
return true;
|
||||
case TetrisAction.RotateCCW:
|
||||
_cup.rotate(1, falling);
|
||||
return true;
|
||||
case TetrisAction.FastDown:
|
||||
handleFastDown(true);
|
||||
return true;
|
||||
case TetrisAction.Pause:
|
||||
// TODO: implement pause
|
||||
return true;
|
||||
case TetrisAction.LevelUp:
|
||||
setLevel(_level + 1);
|
||||
return true;
|
||||
default:
|
||||
if (parent) // by default, pass to parent widget
|
||||
return parent.handleAction(a);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Measure widget according to desired width and height constraints. (Step 1 of two phase layout).
|
||||
override void measure(int parentWidth, int parentHeight) {
|
||||
measuredContent(parentWidth, parentHeight, parentWidth * 3 / 5, parentHeight);
|
||||
}
|
||||
|
||||
this(StatusWidget status) {
|
||||
super("CUP");
|
||||
this._status = status;
|
||||
layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT).layoutWeight(2).setState(State.Default).focusable(true).padding(Rect(20, 20, 20, 20));
|
||||
|
||||
_cols = 10;
|
||||
_rows = 18;
|
||||
newGame();
|
||||
|
||||
focusable = true;
|
||||
|
||||
acceleratorMap.add(CUP_ACTIONS);
|
||||
}
|
||||
}
|
||||
|
||||
/// Panel to show game status
|
||||
class StatusWidget : VerticalLayout {
|
||||
private TextWidget _level;
|
||||
private TextWidget _rowsDestroyed;
|
||||
private TextWidget _score;
|
||||
private CupWidget _cup;
|
||||
void setCup(CupWidget cup) {
|
||||
_cup = cup;
|
||||
}
|
||||
TextWidget createTextWidget(dstring str, uint color) {
|
||||
TextWidget res = new TextWidget(null, str);
|
||||
res.layoutWidth(FILL_PARENT).alignment(Align.Center).fontSize(25).textColor(color);
|
||||
return res;
|
||||
}
|
||||
|
||||
Widget createControls() {
|
||||
TableLayout res = new TableLayout();
|
||||
res.colCount = 3;
|
||||
foreach(const Action a; CUP_ACTIONS) {
|
||||
ImageButton btn = new ImageButton(a);
|
||||
btn.focusable = false;
|
||||
res.addChild(btn);
|
||||
}
|
||||
res.layoutWidth(WRAP_CONTENT).layoutHeight(WRAP_CONTENT).margins(Rect(10, 10, 10, 10)).alignment(Align.Center);
|
||||
return res;
|
||||
}
|
||||
|
||||
this() {
|
||||
super("CUP_STATUS");
|
||||
|
||||
addChild(new VSpacer());
|
||||
|
||||
ImageWidget image = new ImageWidget(null, "tetris_logo_big");
|
||||
image.layoutWidth(FILL_PARENT).alignment(Align.Center).clickable(true);
|
||||
image.onClickListener = delegate(Widget src) {
|
||||
_cup.handleAction(ACTION_PAUSE);
|
||||
// about dialog when clicking on image
|
||||
Window wnd = Platform.instance.createWindow("About...", window, WindowFlag.Modal);
|
||||
wnd.mainWidget = createAboutWidget();
|
||||
wnd.show();
|
||||
return true;
|
||||
};
|
||||
addChild(image);
|
||||
|
||||
addChild(new VSpacer());
|
||||
addChild(createTextWidget("Level:"d, 0x008000));
|
||||
addChild((_level = createTextWidget(""d, 0x008000)));
|
||||
addChild(new VSpacer());
|
||||
addChild(createTextWidget("Rows:"d, 0x202080));
|
||||
addChild((_rowsDestroyed = createTextWidget(""d, 0x202080)));
|
||||
addChild(new VSpacer());
|
||||
addChild(createTextWidget("Score:"d, 0x800000));
|
||||
addChild((_score = createTextWidget(""d, 0x800000)));
|
||||
addChild(new VSpacer());
|
||||
addChild(createControls());
|
||||
addChild(new VSpacer());
|
||||
|
||||
layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT).layoutWeight(2).padding(Rect(10, 10, 10, 10));
|
||||
}
|
||||
|
||||
void setLevel(int level) {
|
||||
_level.text = toUTF32(to!string(level));
|
||||
}
|
||||
|
||||
void setScore(int score) {
|
||||
_score.text = toUTF32(to!string(score));
|
||||
}
|
||||
|
||||
void setRowsDestroyed(int rows) {
|
||||
_rowsDestroyed.text = toUTF32(to!string(rows));
|
||||
}
|
||||
|
||||
override bool handleAction(const Action a) {
|
||||
return _cup.handleAction(a);
|
||||
}
|
||||
}
|
||||
|
||||
/// Cup page: cup widget + status widget
|
||||
class CupPage : HorizontalLayout {
|
||||
CupWidget _cup;
|
||||
StatusWidget _status;
|
||||
this() {
|
||||
super("CUP_PAGE");
|
||||
layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
|
||||
_status = new StatusWidget();
|
||||
_cup = new CupWidget(_status);
|
||||
_status.setCup(_cup);
|
||||
addChild(_cup);
|
||||
addChild(_status);
|
||||
}
|
||||
/// Measure widget according to desired width and height constraints. (Step 1 of two phase layout).
|
||||
override void measure(int parentWidth, int parentHeight) {
|
||||
super.measure(parentWidth, parentHeight);
|
||||
/// fixed size
|
||||
measuredContent(parentWidth, parentHeight, 600, 550);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
class GameWidget : FrameLayout {
|
||||
|
||||
CupPage _cupPage;
|
||||
this() {
|
||||
super("GAME");
|
||||
_cupPage = new CupPage();
|
||||
addChild(_cupPage);
|
||||
//showChild(_cupPage.id, Visibility.Invisible, true);
|
||||
backgroundImageId = "tx_fabric.tiled";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,444 @@
|
|||
module model;
|
||||
|
||||
import std.random : uniform;
|
||||
|
||||
/// Cell codes
|
||||
enum : int {
|
||||
WALL = -1,
|
||||
EMPTY = 0,
|
||||
FIGURE1,
|
||||
FIGURE2,
|
||||
FIGURE3,
|
||||
FIGURE4,
|
||||
FIGURE5,
|
||||
FIGURE6,
|
||||
FIGURE7,
|
||||
}
|
||||
|
||||
/// Orientations
|
||||
enum : int {
|
||||
ORIENTATION0,
|
||||
ORIENTATION90,
|
||||
ORIENTATION180,
|
||||
ORIENTATION270
|
||||
}
|
||||
|
||||
|
||||
/// Cell offset
|
||||
struct FigureCell {
|
||||
// horizontal offset
|
||||
int dx;
|
||||
// vertical offset
|
||||
int dy;
|
||||
this(int[2] v) {
|
||||
dx = v[0];
|
||||
dy = v[1];
|
||||
}
|
||||
}
|
||||
|
||||
/// Single figure shape for some particular orientation - 4 cells
|
||||
struct FigureShape {
|
||||
/// by cell index 0..3
|
||||
FigureCell[4] cells;
|
||||
/// lowest y coordinate - to show next figure above cup
|
||||
int extent;
|
||||
/// upper y coordinate - initial Y offset to place figure to cup
|
||||
int y0;
|
||||
/// Init cells (cell 0 is [0,0])
|
||||
this(int[2] c2, int[2] c3, int[2] c4) {
|
||||
cells[0] = FigureCell([0, 0]);
|
||||
cells[1] = FigureCell(c2);
|
||||
cells[2] = FigureCell(c3);
|
||||
cells[3] = FigureCell(c4);
|
||||
extent = y0 = 0;
|
||||
foreach (cell; cells) {
|
||||
if (extent > cell.dy)
|
||||
extent = cell.dy;
|
||||
if (y0 < cell.dy)
|
||||
y0 = cell.dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Figure data - shapes for 4 orientations
|
||||
struct Figure {
|
||||
FigureShape[4] shapes; // by orientation
|
||||
this(FigureShape[4] v) {
|
||||
shapes = v;
|
||||
}
|
||||
}
|
||||
|
||||
/// All shapes
|
||||
const Figure[7] FIGURES = [
|
||||
// FIGURE1 ===========================================
|
||||
// ## ####
|
||||
// 00## 00##
|
||||
// ##
|
||||
Figure([FigureShape([1, 0], [ 1, 1], [0,-1]),
|
||||
FigureShape([0, 1], [-1, 1], [1, 0]),
|
||||
FigureShape([1, 0], [ 1, 1], [0,-1]),
|
||||
FigureShape([0, 1], [-1, 1], [1, 0])]),
|
||||
// FIGURE2 ===========================================
|
||||
// ## ####
|
||||
// 00## ##00
|
||||
// ##
|
||||
Figure([FigureShape([1, 0], [0, 1], [ 1,-1]),
|
||||
FigureShape([0, 1], [1, 1], [-1, 0]),
|
||||
FigureShape([1, 0], [0, 1], [ 1,-1]),
|
||||
FigureShape([0, 1], [1, 1], [-1, 0])]),
|
||||
// FIGURE3 ===========================================
|
||||
// ## ## ####
|
||||
// ##00## 00 ##00## 00
|
||||
// ## #### ##
|
||||
Figure([FigureShape([1, 0], [-1, 0], [-1,-1]),
|
||||
FigureShape([0, 1], [ 0,-1], [ 1,-1]),
|
||||
FigureShape([1, 0], [-1, 0], [ 1, 1]),
|
||||
FigureShape([0, 1], [-1, 1], [ 0,-1])]),
|
||||
// FIGURE4 ===========================================
|
||||
// #### ## ##
|
||||
// ##00## 00 ##00## 00
|
||||
// ## ## ####
|
||||
Figure([FigureShape([1, 0], [-1, 0], [ 1,-1]),
|
||||
FigureShape([0, 1], [ 0,-1], [ 1, 1]),
|
||||
FigureShape([1, 0], [-1, 0], [-1, 1]),
|
||||
FigureShape([0, 1], [-1,-1], [ 0,-1])]),
|
||||
// FIGURE5 ===========================================
|
||||
// ####
|
||||
// 00##
|
||||
//
|
||||
Figure([FigureShape([1, 0], [0, 1], [ 1, 1]),
|
||||
FigureShape([1, 0], [0, 1], [ 1, 1]),
|
||||
FigureShape([1, 0], [0, 1], [ 1, 1]),
|
||||
FigureShape([1, 0], [0, 1], [ 1, 1])]),
|
||||
// FIGURE6 ===========================================
|
||||
// ##
|
||||
// ##
|
||||
// 00 ##00####
|
||||
// ##
|
||||
Figure([FigureShape([0, 1], [0, 2], [ 0,-1]),
|
||||
FigureShape([1, 0], [2, 0], [-1, 0]),
|
||||
FigureShape([0, 1], [0, 2], [ 0,-1]),
|
||||
FigureShape([1, 0], [2, 0], [-1, 0])]),
|
||||
// FIGURE7 ===========================================
|
||||
// ## ## ##
|
||||
// ##00## 00## ##00## ##00
|
||||
// ## ## ##
|
||||
Figure([FigureShape([1, 0], [-1,0], [ 0,-1]),
|
||||
FigureShape([0, 1], [0,-1], [ 1, 0]),
|
||||
FigureShape([1, 0], [-1,0], [ 0, 1]),
|
||||
FigureShape([0, 1], [0,-1], [-1, 0])]),
|
||||
];
|
||||
|
||||
/// colors for different figure types
|
||||
const uint[7] _figureColors = [0xC00000, 0x80A000, 0xA00080, 0x0000C0, 0x800020, 0x408000, 0x204000];
|
||||
|
||||
/// 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];
|
||||
}
|
||||
/// return true if figure index is not initialized
|
||||
@property empty() const {
|
||||
return index == 0;
|
||||
}
|
||||
/// clears content
|
||||
void reset() {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Cup content
|
||||
|
||||
Coordinates are relative to bottom left corner.
|
||||
*/
|
||||
struct Cup {
|
||||
private int[] _cup;
|
||||
private int _cols;
|
||||
private int _rows;
|
||||
private bool[] _destroyedFullRows;
|
||||
private int[] _cellGroups;
|
||||
|
||||
private FigurePosition _currentFigure;
|
||||
/// current figure index, orientation, position
|
||||
@property ref FigurePosition currentFigure() { return _currentFigure; }
|
||||
|
||||
private FigurePosition _nextFigure;
|
||||
/// next figure
|
||||
@property ref FigurePosition nextFigure() { return _nextFigure; }
|
||||
|
||||
/// 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];
|
||||
_destroyedFullRows = new bool[_rows];
|
||||
_cellGroups = new int[_cols * _rows];
|
||||
}
|
||||
/// 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 current figure into cup at current position and orientation
|
||||
void putFigure() {
|
||||
FigureShape shape = _currentFigure.shape;
|
||||
foreach(cell; shape.cells) {
|
||||
this[_currentFigure.x + cell.dx, _currentFigure.y + cell.dy] = _currentFigure.index;
|
||||
}
|
||||
}
|
||||
|
||||
/// check if all cells where specified figure 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;
|
||||
}
|
||||
/// 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 count of destroyed rows, 0 if no rows destroyed
|
||||
int destroyFullRows() {
|
||||
int res = 0;
|
||||
for (int i = 0; i < _rows; i++) {
|
||||
if (isRowFull(i)) {
|
||||
_destroyedFullRows[i] = true;
|
||||
res++;
|
||||
for (int col = 0; col < _cols; col++)
|
||||
this[col, i] = EMPTY;
|
||||
} else {
|
||||
_destroyedFullRows[i] = false;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/// check if all cells where current figire is located are free
|
||||
bool isPositionFree() {
|
||||
return isPositionFree(_currentFigure);
|
||||
}
|
||||
|
||||
/// check if all cells where current figire is located are free
|
||||
bool isPositionFreeBelow() {
|
||||
return isPositionFree(_currentFigure.move(0, -1));
|
||||
}
|
||||
|
||||
/// try to rotate current figure, returns true if figure rotated
|
||||
bool rotate(int angle, bool falling) {
|
||||
FigurePosition newpos = _currentFigure.rotate(angle);
|
||||
if (isPositionFree(newpos)) {
|
||||
if (falling) {
|
||||
// special handling for fall animation
|
||||
if (!isPositionFree(newpos.move(0, -1))) {
|
||||
if (isPositionFreeBelow())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_currentFigure = newpos;
|
||||
return true;
|
||||
} else if (isPositionFree(newpos.move(0, -1))) {
|
||||
_currentFigure = newpos.move(0, -1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// try to move current figure, returns true if figure rotated
|
||||
bool move(int deltaX, int deltaY, bool falling) {
|
||||
FigurePosition newpos = _currentFigure.move(deltaX, deltaY);
|
||||
if (isPositionFree(newpos)) {
|
||||
if (falling && !isPositionFree(newpos.move(0, -1))) {
|
||||
if (isPositionFreeBelow())
|
||||
return false;
|
||||
}
|
||||
_currentFigure = newpos;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// random next figure
|
||||
void genNextFigure() {
|
||||
_nextFigure.index = uniform(FIGURE1, FIGURE7 + 1);
|
||||
_nextFigure.orientation = ORIENTATION0;
|
||||
_nextFigure.x = _cols / 2;
|
||||
_nextFigure.y = _rows - _nextFigure.shape.extent + 1;
|
||||
}
|
||||
|
||||
/// New figure: put it on top of cup
|
||||
bool dropNextFigure() {
|
||||
if (_nextFigure.empty)
|
||||
genNextFigure();
|
||||
_currentFigure = _nextFigure;
|
||||
_currentFigure.x = _cols / 2;
|
||||
_currentFigure.y = _rows - 1 - _currentFigure.shape.y0;
|
||||
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;
|
||||
}
|
||||
|
||||
/// returns true if next figure is generated
|
||||
@property bool hasNextFigure() {
|
||||
return !_nextFigure.empty;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// Written in the D programming language.
|
||||
|
||||
/**
|
||||
This app is a Tetris demo for DlangUI library.
|
||||
|
||||
Synopsis:
|
||||
|
||||
----
|
||||
dub run dlangui:tetris
|
||||
----
|
||||
|
||||
Copyright: Vadim Lopatin, 2014
|
||||
License: Boost License 1.0
|
||||
Authors: Vadim Lopatin, coolreader.org@gmail.com
|
||||
*/
|
||||
module main;
|
||||
|
||||
import dlangui.all;
|
||||
import model;
|
||||
import gui;
|
||||
|
||||
/// Required for Windows platform: DMD cannot find WinMain if it's in library
|
||||
mixin APP_ENTRY_POINT;
|
||||
|
||||
/// entry point for dlangui based application
|
||||
extern (C) int UIAppMain(string[] args) {
|
||||
|
||||
//auto power2 = delegate(int X) { return X * X; };
|
||||
auto power2 = (int X) => X * X;
|
||||
|
||||
// embed resources listed in views/resources.list into executable
|
||||
embeddedResourceList.addResources(embedResourcesFromList!("resources.list")());
|
||||
|
||||
// select translation file - for english language
|
||||
Platform.instance.uiLanguage = "en";
|
||||
// load theme from file "theme_default.xml"
|
||||
Platform.instance.uiTheme = "theme_default";
|
||||
|
||||
// create window
|
||||
Window window = Platform.instance.createWindow("DLangUI: Tetris game example"d, null, WindowFlag.Modal);
|
||||
|
||||
window.mainWidget = new GameWidget();
|
||||
|
||||
window.windowIcon = drawableCache.getImage("dtetris-logo1");
|
||||
|
||||
window.show();
|
||||
|
||||
// run message loop
|
||||
return Platform.instance.enterMessageLoop();
|
||||
}
|
After Width: | Height: | Size: 843 B |
After Width: | Height: | Size: 834 B |
After Width: | Height: | Size: 810 B |
After Width: | Height: | Size: 971 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 374 B |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 8.8 KiB |
|
@ -0,0 +1,10 @@
|
|||
res/arrow-down.png
|
||||
res/arrow-left.png
|
||||
res/arrow-right.png
|
||||
res/dtetris-logo1.png
|
||||
res/levelup.png
|
||||
res/pause.png
|
||||
res/popup_background.9.png
|
||||
res/rotate.png
|
||||
res/tetris_logo_big.png
|
||||
res/tx_fabric.jpg
|