windows GDI 控制台俄罗斯方块

蛮喜欢控制台小游戏,所以继上篇控制台贪吃蛇[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/

游戏效果截图:


猜你喜欢

转载自blog.csdn.net/atp1992/article/details/80960199