From a77a20e6e73e88069a6f687563c1fd76c2661d1a Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Fri, 26 Dec 2014 14:20:12 +0300 Subject: [PATCH] Tetris example improvements --- examples/tetris/src/gui.d | 245 +++++++++++++++++++----------------- examples/tetris/src/main.d | 11 +- examples/tetris/src/model.d | 119 +++++++++++------- src/dlangui/all.d | 12 +- src/dlangui/core/events.d | 11 ++ 5 files changed, 222 insertions(+), 176 deletions(-) diff --git a/examples/tetris/src/gui.d b/examples/tetris/src/gui.d index 334ac23e..22f269d7 100644 --- a/examples/tetris/src/gui.d +++ b/examples/tetris/src/gui.d @@ -1,14 +1,29 @@ module gui; -import dlangui.all; -import dlangui.widgets.popup; -import dlangui.graphics.drawbuf; -//import std.stdio; -import std.conv; -import std.utf; 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); +const Action ACTION_MOVE_RIGHT = (new Action(TetrisAction.MoveRight, KeyCode.RIGHT)).addAccelerator(KeyCode.KEY_D); +const Action ACTION_ROTATE = (new Action(TetrisAction.RotateCCW, KeyCode.UP)).addAccelerator(KeyCode.KEY_W); +const Action ACTION_FAST_DOWN = (new Action(TetrisAction.FastDown, KeyCode.SPACE)).addAccelerator(KeyCode.KEY_S); +const Action ACTION_PAUSE = (new Action(TetrisAction.Pause, KeyCode.ESCAPE)).addAccelerator(KeyCode.PAUSE); +const Action ACTION_LEVEL_UP = (new Action(TetrisAction.LevelUp, KeyCode.ADD)).addAccelerator(KeyCode.INS); + +const Action[] CUP_ACTIONS = [ACTION_MOVE_LEFT, ACTION_MOVE_RIGHT, ACTION_ROTATE, ACTION_FAST_DOWN, ACTION_PAUSE, ACTION_LEVEL_UP]; + +/// about dialog Widget createAboutWidget() { LinearLayout res = new VerticalLayout(); @@ -26,16 +41,24 @@ Widget createAboutWidget() return res; } -enum TetrisAction : int { - MoveLeft = 10000, - MoveRight, - RotateCCW, - FastDown, - Pause, - LevelUp, +/// 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 @@ -54,37 +77,21 @@ class CupWidget : Widget { 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; - - /// 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, - } - + /// 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, 3500000, 3000000, 2500000, 2000000]; - static const int RESERVED_ROWS = 5; // reserved for next figure - - /// set difficulty level 1..10 void setLevel(int level) { _level = level; @@ -93,8 +100,10 @@ class CupWidget : Widget { } static const int MIN_FAST_FALLING_INTERVAL = 600000; - static const int ROWS_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) { @@ -114,6 +123,8 @@ class CupWidget : Widget { animationIntervalPercent = 50; break; default: + // no animation for other states + animationIntervalPercent = 0; break; } _state = state; @@ -128,18 +139,14 @@ class CupWidget : Widget { invalidate(); } - /// 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; - } + 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 @@ -167,6 +174,8 @@ class CupWidget : Widget { 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()) { @@ -177,6 +186,8 @@ class CupWidget : Widget { _gameOverPopup = window.showPopup(popupWidget, this); } else { setCupState(CupState.NewFigure); + if (_level < 10 && _totalRowsDestroyed >= NEXT_LEVEL_SCORE[_level]) + setLevel(_level + 1); // level up } } @@ -214,6 +225,8 @@ class CupWidget : Widget { 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; @@ -245,14 +258,23 @@ class CupWidget : Widget { } } - /// 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(); + /// 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; @@ -321,6 +343,31 @@ class CupWidget : Widget { } } + //================================================================================================= + // 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); @@ -387,15 +434,6 @@ class CupWidget : Widget { } } - } - /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). - override void measure(int parentWidth, int parentHeight) { - /// fixed size 350 x 550 - measuredContent(parentWidth, parentHeight, 350, 550); - } - - @property bool falling() { - return _state == CupState.FallingFigure; } /// override to handle specific actions @@ -428,54 +466,30 @@ class CupWidget : Widget { } } - void addScore(int score) { - _score += score; - _status.setScore(_score); + /// 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); } - /// start new game - void newGame() { - setLevel(1); - _score = 0; - init(_cols, _rows); - _cup.dropNextFigure(); - setCupState(CupState.NewFigure); - if (window && _gameOverPopup) { - window.removePopup(_gameOverPopup); - _gameOverPopup = null; - } - _status.setScore(_score); - } - - private StatusWidget _status; this(StatusWidget status) { super("CUP"); this._status = status; - layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT).layoutWeight(3); - setState(State.Default); - //backgroundColor = 0xC0808080; - padding(Rect(20, 20, 20, 20)); - _cols = 11; - _rows = 15; + layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT).layoutWeight(3).setState(State.Default).focusable(true).padding(Rect(20, 20, 20, 20)); + + _cols = 10; + _rows = 18; newGame(); focusable = true; - acceleratorMap.add( [ - (new Action(TetrisAction.MoveLeft, KeyCode.LEFT)).addAccelerator(KeyCode.KEY_A), - (new Action(TetrisAction.MoveRight, KeyCode.RIGHT)).addAccelerator(KeyCode.KEY_D), - (new Action(TetrisAction.RotateCCW, KeyCode.UP)).addAccelerator(KeyCode.KEY_W), - (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), - ]); - + 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) { @@ -483,26 +497,27 @@ class StatusWidget : VerticalLayout { } TextWidget createTextWidget(dstring str, uint color) { TextWidget res = new TextWidget(null, str); - res.layoutWidth(FILL_PARENT).alignment(Align.Center); - res.fontSize(30); - res.textColor(color); + res.layoutWidth(FILL_PARENT).alignment(Align.Center).fontSize(25).textColor(color); return res; } this() { super("CUP_STATUS"); - //backgroundColor = 0xC080FF80; + addChild(new VSpacer()); - addChild(new ImageWidget(null, "tetris_logo_big")); + addChild((new ImageWidget(null, "tetris_logo_big")).layoutWidth(FILL_PARENT).alignment(Align.Center)); 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(new VSpacer()); - layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT).layoutWeight(2); - padding(Rect(20, 20, 20, 20)); + + layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT).layoutWeight(2).padding(Rect(20, 20, 20, 20)); } void setLevel(int level) { @@ -513,17 +528,16 @@ class StatusWidget : VerticalLayout { _score.text = toUTF32(to!string(score)); } - /// 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 350 x 550 - measuredContent(parentWidth, parentHeight, 150, 550); + 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; @@ -539,13 +553,13 @@ class CupPage : HorizontalLayout { /// 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 350 x 550 - measuredContent(parentWidth, parentHeight, 500, 550); + /// fixed size + measuredContent(parentWidth, parentHeight, 550, 550); } } -//FrameLayout -class GameWidget : HorizontalLayout { +// +class GameWidget : FrameLayout { CupPage _cupPage; this() { @@ -555,9 +569,4 @@ class GameWidget : HorizontalLayout { //showChild(_cupPage.id, Visibility.Invisible, true); backgroundImageId = "tx_fabric.tiled"; } - /// 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); - measuredContent(parentWidth, parentHeight, 500, 550); - } } diff --git a/examples/tetris/src/main.d b/examples/tetris/src/main.d index 760473c9..1a0a1ce7 100644 --- a/examples/tetris/src/main.d +++ b/examples/tetris/src/main.d @@ -19,10 +19,9 @@ 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) { @@ -50,14 +49,10 @@ extern (C) int UIAppMain(string[] args) { // load theme from file "theme_default.xml" Platform.instance.uiTheme = "theme_default"; - //drawableCache.get("tx_fabric.tiled"); - // create window - Window window = Platform.instance.createWindow("DLangUI: Tetris game example", null, WindowFlag.Modal); + Window window = Platform.instance.createWindow("DLangUI: Tetris game example"d, null, WindowFlag.Modal); - GameWidget game = new GameWidget(); - - window.mainWidget = game; + window.mainWidget = new GameWidget(); window.windowIcon = drawableCache.getImage("dtetris-logo1"); diff --git a/examples/tetris/src/model.d b/examples/tetris/src/model.d index 72e00468..38a065d9 100644 --- a/examples/tetris/src/model.d +++ b/examples/tetris/src/model.d @@ -2,6 +2,28 @@ 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 @@ -22,8 +44,9 @@ struct FigureShape { int extent; /// upper y coordinate - initial Y offset to place figure to cup int y0; - this(int[2] c1, int[2] c2, int[2] c3, int[2] c4) { - cells[0] = FigureCell(c1); + /// 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); @@ -37,6 +60,7 @@ struct FigureShape { } } +/// Figure data - shapes for 4 orientations struct Figure { FigureShape[4] shapes; // by orientation this(FigureShape[4] v) { @@ -44,71 +68,69 @@ struct Figure { } } -const Figure[6] FIGURES = [ +/// All shapes +const Figure[7] FIGURES = [ + // FIGURE1 =========================================== // ## #### // 00## 00## // ## - Figure([FigureShape([0, 0], [1, 0], [1, 1], [0, -1]), - FigureShape([0, 0], [0, 1], [-1, 1], [1, 0]), - FigureShape([0, 0], [1, 0], [1, 1], [0, -1]), - FigureShape([0, 0], [0, 1], [-1, 1], [1, 0])]), + 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([0, 0], [1, 0], [0, 1], [1, 1]), - FigureShape([0, 0], [0, 1], [1, 1], [-1, 0]), - FigureShape([0, 0], [1, 0], [0, 1], [1, 1]), - FigureShape([0, 0], [0, 1], [1, 1], [-1, 0])]), + 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([0, 0], [1, 0], [-1,0], [-1,-1]), - FigureShape([0, 0], [0, 1], [0,-1], [ 1,-1]), - FigureShape([0, 0], [1, 0], [-1,0], [1, 1]), - FigureShape([0, 0], [0, 1], [-1,1], [0,-1])]), + 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([0, 0], [1, 0], [-1,0], [ 1, 1]), - FigureShape([0, 0], [0, 1], [0,-1], [ 1, 1]), - FigureShape([0, 0], [1, 0], [-1,0], [-1, 1]), - FigureShape([0, 0], [0, 1], [-1,-1], [0, -1])]), + 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([0, 0], [1, 0], [0, 1], [ 1, 1]), - FigureShape([0, 0], [1, 0], [0, 1], [ 1, 1]), - FigureShape([0, 0], [1, 0], [0, 1], [ 1, 1]), - FigureShape([0, 0], [1, 0], [0, 1], [ 1, 1])]), + 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, 0], [0, 1], [0, 2], [ 0,-1]), - FigureShape([0, 0], [1, 0], [2, 0], [-1, 0]), - FigureShape([0, 0], [0, 1], [0, 2], [ 0,-1]), - FigureShape([0, 0], [1, 0], [2, 0], [-1, 0])]), + 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])]), ]; -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]; +/// colors for different figure types +const uint[7] _figureColors = [0xC00000, 0x80A000, 0xA00080, 0x0000C0, 0x800020, 0x408000, 0x204000]; /// Figure type, orientation and position container struct FigurePosition { @@ -289,13 +311,15 @@ struct Cup { return false; } + /// random next figure void genNextFigure() { - _nextFigure.index = uniform(FIGURE1, FIGURE6 + 1); + _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(); @@ -412,6 +436,7 @@ struct Cup { return cellGroup(col, row) > 0; } + /// returns true if next figure is generated @property bool hasNextFigure() { return !_nextFigure.empty; } diff --git a/src/dlangui/all.d b/src/dlangui/all.d index 2a0858e7..4ada06ca 100644 --- a/src/dlangui/all.d +++ b/src/dlangui/all.d @@ -48,7 +48,8 @@ module dlangui.all; public import dlangui.core.logger; public import dlangui.core.types; -public import dlangui.platforms.common.platform; +public import dlangui.core.i18n; +public import dlangui.core.files; public import dlangui.graphics.images; public import dlangui.widgets.widget; public import dlangui.widgets.controls; @@ -61,6 +62,11 @@ public import dlangui.widgets.editors; public import dlangui.widgets.grid; public import dlangui.widgets.tree; public import dlangui.widgets.combobox; +public import dlangui.widgets.popup; public import dlangui.graphics.fonts; -public import dlangui.core.i18n; -public import dlangui.core.files; +public import dlangui.graphics.drawbuf; +public import dlangui.platforms.common.platform; + +// some useful imports from Phobos +public import std.conv : to; +public import std.utf : toUTF32, toUTF8; diff --git a/src/dlangui/core/events.d b/src/dlangui/core/events.d index 0ef1aaf2..9a5aee5e 100644 --- a/src/dlangui/core/events.d +++ b/src/dlangui/core/events.d @@ -140,6 +140,10 @@ class Action { @property Accelerator[] accelerators() { return _accelerators; } + /// returs const array of accelerators + @property const(Accelerator)[] accelerators() const { + return _accelerators; + } /// returns text description for first accelerator of action; null if no accelerators @property dstring acceleratorText() { if (_accelerators.length < 1) @@ -217,6 +221,13 @@ struct ActionMap { _map[acc] = a; } } + /// Add array of actions + void add(const Action[] items) { + foreach(a; items) { + foreach(acc; a.accelerators) + _map[acc] = a.clone; + } + } /// Add action void add(Action a) { foreach(acc; a.accelerators)