控制台俄罗斯方块
Brief: Another console game programming by windows GDI — Russian Brick. It’s a well known game that any of us know the rules. It’s a little complex to program than snake game but still not difficult. Both the games have much in common. The main process procedure is also in messages callback function.
蛮喜欢控制台小游戏,所以继上篇控制台贪吃蛇[http://www.straka.cn/blog/snake-game-by-windows-gdi/]之后又写了个控制台俄罗斯方块。
方法类似,仍然是在消息循环处理函数中完成主要功能。不过俄罗斯方块比贪吃蛇稍微复杂一点在方块的绘制、变形和满行消除机制上。
首先里面用到了几个全局变量,
1 2 3 4 |
HWND hwnd;//handle of the console, used to draw int ifStart = 0; //flag indicate whether the game has started int ifPause = 0; //flag of the game pausing Brick * brik; //the instance of the brick class |
然后这里用一个三维数组代表俄罗斯方块的形状。
因为俄罗斯方块每种都是四块组成,总共7种,所以数组是7*4*2维,最后的2维代表坐标,当然是相对于方块位置的坐标偏移量。
1 2 3 4 5 6 7 8 9 10 |
//the seven type brick's locate int typeBK[7][4][2] = { { { 0, 0 }, { 0, 1 }, { 1, 0 }, { 1, 1 } }, { { -1, 0 }, { 0, 0 }, { 1, 0 }, { 2, 0 } }, { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 0, 1 } }, { { -1, 0 }, { 0, 0 }, { 0, 1 }, { 1, 1 } }, { { -1, 1 }, { 0, 0 }, { 0, 1 }, { 1, 0 } }, { { -2, 0 }, { -1, 0 }, { 0, 0 }, { 0, 1 } }, { { -1, 0 }, { 0, 0 }, { 1, 0 }, { 0, 1 } } }; |
由于方块形状的不规则,因而为方便判断方块出界,另用一个7*4二维数组代表方块在四个上的上下界。
1 2 3 4 5 6 7 8 9 10 |
//the min/max x/y of the brick, for the convience of judging the edge of the brick int typeBKxy[7][4] = { { 0, 1, 0, 1 }, { -1, 2, 0, 0 }, { 0, 2, 0, 1 }, { -1, 1, 0, 1 }, { -1, 1, 0, 1 }, { -2, 0, 0, 1 }, { -1, 1, 0, 1 } }; |
再看下类定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
#define AREASTARTX 200 //game area offset pixels on x-axis #define AREASTARTY 0 #define AREAWIDTH 10 //game area width by grid #define AREAHEITH 15 #define UNITWIDTH 20 //grid width by pixels #define BRICKSTARTX 40 //offset pixels on x-axis of area displaying the next brick #define BRICKSTARTY 90 #define BRICKWIDTH 4 //grid width of next brick display area #define BRICKHEITH 4 #define BKUWIDTH 15 class Brick{ public: //mark whether a grid of the game area has been filled by a brick int gameArea[AREAHEITH][AREAWIDTH]; int score;//score of the game now int speed;//speed of the game, it increases when levels up struct BK{ int unit[4][2]; int minX,maxX; int minY, maxY; int locaX, locaY; }activeBK,nextBK; //the current brick in the window, and the next waited one int nextType; //the type of the next brick int nextDirect; //the coming direction of the next brick Brick(); virtual ~Brick(); //move the brick to one of the three direction, except upper bool moveBK(int direct); //rotate the brick anticlockwise by the degree of "deg" multiple by 90, //when parameter "ifJudge" is true, it will call judgeBK() to judge the brick position bool rotateBK(int deg,int ifJudge); //generate the first random brick void firstrandBK(); //generate a next random brick void randBK(); //judge whether the brick has go out of the boundary, or hit the other bricks //returns true if there is no problem bool judgeBK(); //if the brick has move to the end, just update the game area, which set the grid filled by the brick to 1 void updateArea(); //draw the active brick void drawBK(); //draw the next brick void drawNextBk(); //draw the game area void drawArea(); //count how many lines have been filled by bricks bool judgeScore(); //remove the line full of bricks void removeRow(int row); //return true is game over bool gameOver(); //fill nextBK struct by the type of it void formNext(); } |
类中注释很多不一一解释,看下主要的消息回调:
WM_PAINT消息重绘的只是非游戏区域,即游戏说明部分
WM_TIMER定时器消息处理方块的定时移动:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
case WM_TIMER: if (wParam == ID_TIMER) { KillTimer(hWnd, ID_TIMER); if (!brik->moveBK(1)){ brik->updateArea(); brik->judgeScore(); brik->randBK(); } brik->drawArea(); brik->drawBK(); if (brik->gameOver()){ ifStart = 0; delete brik; } else{ SetTimer(hWnd, ID_TIMER, (*brik).speed, NULL); } } else if (wParam == ID_TIMEOVER){ ifStart = 0; KillTimer(hWnd, ID_TIMEOVER); delete brik; } break; |
字符键按键消息处理游戏控制逻辑,方向键同W\S\A\D的响应:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
case WM_CHAR: switch (wParam) { case 'f': case 'F': if (!ifStart){ ifStart = 1; brik = new Brick; brik->drawArea(); brik->drawBK(); SetTimer(hwnd, ID_TIMER, (*brik).speed, NULL); } break; case 'p': case 'P': if (!ifStart)break; ifPause = ifPause == 0 ? 1 : 0; if (ifPause){ KillTimer(hwnd, ID_TIMER); } else{ SetTimer(hwnd, ID_TIMER, (*brik).speed, NULL); } RECT ret; GetWindowRect(hwnd, &ret); InvalidateRect(hwnd, &ret, true); UpdateWindow(hwnd); //SendMessage(hwnd, WM_PAINT, NULL, NULL); break; case 'w': case 'W': if (!ifStart || ifPause)break; brik->rotateBK(1,1); brik->drawArea(); brik->drawBK(); break; case 'd': case 'D': if (!ifStart || ifPause)break; brik->moveBK(0); brik->drawArea(); brik->drawBK(); break; case 's': case 'S': KillTimer(hWnd, ID_TIMER); if (!ifStart || ifPause)break; if (!brik->moveBK(1)){ brik->updateArea(); brik->judgeScore(); brik->randBK(); } brik->drawArea(); brik->drawBK(); if (brik->gameOver()){ ifStart = 0; delete brik; } else{ SetTimer(hWnd, ID_TIMER, (*brik).speed, NULL); } break; case 'a': case 'A': if (!ifStart || ifPause)break; brik->moveBK(2); brik->drawArea(); brik->drawBK(); break; case 'q': case 'Q': PostQuitMessage(0); break; case ' ': if (!ifStart)break; ifPause = ifPause == 0 ? 1 : 0; if (ifPause){ KillTimer(hwnd, ID_TIMER); } else{ SetTimer(hwnd, ID_TIMER, (*brik).speed, NULL); } break; case 'y': case 'Y': if (!ifStart || ifPause)break; brik->rotateBK(1,1); brik->drawArea(); brik->drawBK(); break; case 'u': case 'U': if (!ifStart || ifPause)break; brik->rotateBK(-1,1); brik->drawArea(); brik->drawBK(); break; default: break; } |
具体的类成员函数实现参考源码:
https://github.com/atp798/BlogStraka
游戏效果截图: