蛮喜欢控制台小游戏,所以继上篇控制台贪吃蛇[http://www.straka.cn/blog/snake-game-by-windows-gdi/]之后又写了个控制台俄罗斯方块。
方法类似,仍然是在消息循环处理函数中完成主要功能。不过俄罗斯方块比贪吃蛇稍微复杂一点在方块的绘制、变形和满行消除机制上。
首先里面用到了几个全局变量,
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维代表坐标,当然是相对于方块位置的坐标偏移量。
//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二维数组代表方块在四个上的上下界。
//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 }
};
再看下类定义:
#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定时器消息处理方块的定时移动:
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的响应:
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;
}
具体的类成员函数实现参考源码。
原博客链接:
http://www.straka.cn/blog/console-russian-brick-wingdi/
游戏效果截图: