windows GDI 編寫控制檯貪喫蛇

剛學windows GDI編程,對用代碼實現繪圖興奮不已,於是便先參考網上的一份代碼寫了個簡單的貪喫蛇練手。

程序是以消息處理回調函數爲核心,響應定時時間和按鍵消息,定時時間到則繪製蛇的移動已經死亡判斷,而按鍵消息響應裏進行蛇的移動操作以及遊戲控制。

先看主函數:

//------------ 以下初始化窗口類----------------
int WINAPI WinMain(HINSTANCE	hInstance,HINSTANCE hPrevInst,LPSTR lpszCmdLine,int	nCmdShow)
{
	MSG Msg ;
	WNDCLASS wndclass ;
	char lpszClassName[] = "Snake";			//窗口類名
	char lpszTitle[]= "Snake";			//窗口標題名

	//窗口類的定義
	wndclass.style = 0;						//窗口類型爲缺省類型
	wndclass.lpfnWndProc = WndProc ;		//窗口處理函數爲WndProc
	wndclass.cbClsExtra	= 0 ;				//窗口類無擴展
	wndclass.cbWndExtra	= 0 ;				//窗口實例無擴展
	wndclass.hInstance = hInstance ;		//當前實例句柄
	wndclass.hIcon = LoadIcon( NULL, "ICON_SNAKE") ;
											//窗口的最小化圖標爲缺省圖標
	wndclass.hCursor = LoadCursor( NULL,IDC_ARROW); //窗口採用箭頭光標
	wndclass.hbrBackground =(HBRUSH) GetStockObject(BLACK_BRUSH); //窗口背景爲白色
	wndclass.lpszMenuName = NULL ;			//窗口中無菜單
	wndclass.lpszClassName = lpszClassName; 			//窗口類名爲"窗口示例"

//--------------- 以下進行窗口類的註冊 -----------------
	if( !RegisterClass( &wndclass))			//如果註冊失敗則發出警告聲音
	{
		//MessageBeep(0) ;
		return FALSE ;
	}

	//創建窗口
	hwnd=CreateWindow(
			lpszClassName,		//窗口類名
			lpszTitle,				//窗口實例的標題名
			WS_OVERLAPPEDWINDOW,	//窗口的風格
			CW_USEDEFAULT,
			CW_USEDEFAULT,			//窗口左上角座標爲缺省值
			600,
			500,			//窗口的高和寬爲缺省值
			NULL,					//此窗口無父窗口
			NULL,					//此窗口無主菜單
			hInstance,				//創建此窗口的應用程序的當前句柄
			NULL) ;					//不使用該值

	ShowWindow( hwnd, nCmdShow); 			//顯示窗口
	UpdateWindow(hwnd); 					//繪製用戶區

	while( GetMessage(&Msg, NULL, 0, 0))	//消息循環
	{
		TranslateMessage( &Msg) ;
		DispatchMessage( &Msg) ;
	}
	return Msg.wParam;						//消息循環結束即程序終止時將信息返回系統
}

 

主要是新建、初始化、註冊窗口類並創建、顯示窗口,然後進入消息循環。

 

移步消息處理回調函數:

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 
{
	HDC hdc;
	HFONT hF_black;	//定義兩種字體句柄
	PAINTSTRUCT ps;
	TEXTMETRIC tm;			//定義一個TEXTMETRIC結構,用以記錄字體信息
	char lpsz_0[]="操作規則:";
	char lpsz_1[]="輸入'f'開始遊戲,輸入'q'停止遊戲,輸入'p'或空格鍵暫停遊戲";
	char lpsz_2[]="上--W 下--S 左--A 右--D, 或者使用方向鍵, 遊戲中不區分大小寫";
	char lpsz_3[]="規則:";
	char lpsz_4[]="1. 剛開始向右運動;";
	char lpsz_5[]="2. 每次只能向左轉或向右轉;";
	char lpsz_6[]="3. 每喫到4個物品就會出現一個“紅心";
	char lpsz_7[]="4. 每喫完5個進入下一關, 每個基礎分值+1, 分值加倍;";
	char lpsz_8[]="5. 碰到邊界或者與自身相交或者蛇佈滿整個整個畫面則遊戲結束!";
	int X=0,Y=MOVESTARTY+MOVEHEITH;
	//	SIZE size;					//定義一個SIZE類型的結構
	//GetTextExtentPoint32(hdc,lpsz_2,strlen(lpsz_2),&size);	//獲取字符串的寬度
      switch (message) 
      { 
		case WM_PAINT:
			hdc=BeginPaint(hwnd,&ps);
			hF_black=CreateFont		//創建自定義字體
				(
					15,				//字體的高度
					0,				//由系統根據高寬比選取字體最佳寬度值
					0,				//文本的傾斜度爲0,表示水平
					0,				//字體的傾斜度爲0
					FW_NORMAL,		//字體的粗度,FW_HEAVY爲最粗
					0,				//非斜體字
					0,				//無下劃線
					0,				//無刪除線
					GB2312_CHARSET,	//表示所用的字符集爲ANSI_CHARSET
					OUT_DEFAULT_PRECIS,	//輸出精度爲缺省精度
					CLIP_DEFAULT_PRECIS,	//剪裁精度爲缺省精度
					DEFAULT_QUALITY,		//輸出質量爲缺省值
					DEFAULT_PITCH|FF_DONTCARE,//字間距和字體系列使用缺省值
					"粗體字"				//字體名稱
					);

			SetTextColor(hdc,RGB(0,255,0));	//設置文本顏色爲綠色
			SetBkColor(hdc,RGB(0,0,0));
			SelectObject(hdc,hF_black);		//將自定義字體選入設備環境
			GetTextMetrics(hdc,&tm);		//獲取字體的信息,並寫入tm結構中

			TextOut(hdc,X,Y,lpsz_0,strlen(lpsz_0));	//使用當前字體輸出文本

			Y=Y+tm.tmHeight+tm.tmExternalLeading;
			TextOut(hdc,X,Y,lpsz_1,strlen(lpsz_1));	

			Y=Y+tm.tmHeight+tm.tmExternalLeading;//計算換行時下一行文本的輸出座標
			TextOut(hdc,X,Y,lpsz_2,strlen(lpsz_2));//使用當前字體輸出文本

			//換行繼續輸出文本,計算新行的起始Y座標位置
			Y=Y+tm.tmHeight+tm.tmExternalLeading;	
			TextOut(hdc,X,Y,lpsz_3,strlen(lpsz_3));

			Y=Y+tm.tmHeight+tm.tmExternalLeading;	
			TextOut(hdc,X,Y,lpsz_4,strlen(lpsz_4));

			Y=Y+tm.tmHeight+tm.tmExternalLeading;	
			TextOut(hdc,X,Y,lpsz_5,strlen(lpsz_5)); 	//輸出文本
			//在該行繼續輸出文本
			
			Y=Y+tm.tmHeight+tm.tmExternalLeading;	
			TextOut(hdc,X,Y,lpsz_6,strlen(lpsz_6));//以當前字體輸出文本

			Y=Y+tm.tmHeight+tm.tmExternalLeading;	
			TextOut(hdc,X,Y,lpsz_7,strlen(lpsz_7));//以當前字體輸出文本

			Y=Y+tm.tmHeight+tm.tmExternalLeading;	
			TextOut(hdc,X,Y,lpsz_8,strlen(lpsz_8));//以當前字體輸出文本
			
			EndPaint(hwnd,&ps);
			DeleteObject(hF_black);		//刪除自定義字體句柄
			break;
        case WM_TIMER: 
			if (wParam==ID_TIMER)
			{
				moveDirect(*sSnk);
			}
			else if(wParam==ID_TIMEOVER){
				f_start=0;
				KillTimer(hwnd,ID_TIMEOVER);
				delete sSnk;
			}
			break;
		case WM_CHAR:
			switch (wParam)
			{
			case 'f':
			case 'F':
				if(!f_start){
					f_start=1;
					sSnk=new Snake;
					SetTimer(hwnd,ID_TIMER,(*sSnk).speed,NULL);
				}
				break;
			case 'p':
			case 'P':
				if(!f_start)break;
				f_pause=f_pause==0?1:0;
				if(f_pause){
					KillTimer(hwnd,ID_TIMER);
				}
				else{
					SetTimer(hwnd,ID_TIMER,(*sSnk).speed,NULL);
				}
				break;
			case 'w':
			case 'W':
				if(!f_start||f_pause)break;
				sSnk->direct=0;
				moveDirect(*sSnk);
				break;
			case 'd':
			case 'D':
				if(!f_start||f_pause)break;
				sSnk->direct=1;
				moveDirect(*sSnk);
				break;
			case 's':
			case 'S':
				if(!f_start||f_pause)break;
				sSnk->direct=2;
				moveDirect(*sSnk);
				break;
			case 'a':
			case 'A':
				if(!f_start||f_pause)break;
				sSnk->direct=3;
				moveDirect(*sSnk);
				break;
			case 'q':
			case 'Q':
				PostQuitMessage(0);
				break;
			case ' ':
				if(!f_start)break;
				f_pause=f_pause==0?1:0;
				if(f_pause){
					KillTimer(hwnd,ID_TIMER);
				}
				else{
					SetTimer(hwnd,ID_TIMER,(*sSnk).speed,NULL);
				}
			break;
			default:
				break;
			}
        case WM_KEYDOWN: 
			switch (wParam)
			{
			case VK_UP:
				if(!f_start||f_pause)break;
				sSnk->direct=0;
				moveDirect(*sSnk);
				break;
			case VK_RIGHT:
				if(!f_start||f_pause)break;
				sSnk->direct=1;
				moveDirect(*sSnk);
				break;
			case VK_DOWN:
				if(!f_start||f_pause)break;
				sSnk->direct=2;
				moveDirect(*sSnk);
				break;
			case VK_LEFT:
				if(!f_start||f_pause)break;
				sSnk->direct=3;
				moveDirect(*sSnk);
				break;
			
			default:
				break;
			}
			break;//這個地方很容易忽略,導致一有其他消息程序就EXIT;
		case WM_DESTROY:
			PostQuitMessage(0);					//調用PostQuitMessage發出WM_QUIT消息
			break;
		default:								//缺省時採用系統消息缺省處理函數
			return  DefWindowProc(hwnd,message,wParam,lParam);
		}
		return (0);
}

 

其主要對窗口重繪、定時、按鍵消息三大類消息進行處理。

 

重繪消息處理是爲了能在窗體被遮擋等情況下進行重新繪製。

定時消息裏對貪喫蛇進行移動和遊戲邏輯判斷,比如是否觸及邊界game over或者喫到東西加分或者進入下一關。

按鍵消息分爲遊戲控制鍵,如f鍵開始遊戲,p鍵暫停,和玩家操控鍵,如上下左右方向鍵和W\S\D\A方向鍵。

對蛇的移動和移動後的狀態判斷處理打包成一個函數:moveDirect。

下面看下Snake的類:

class Snake  
{
public:
	typedef struct SnakeNode
	{
		int x;
		int y;
	}SnakeNode;

	typedef struct SnakeFood
	{
		int x;
		int y;
		int special;
	}SnakeFood;

	SnakeNode data[100];
	int curr;  //
	int score, tot, per, level; //score表示最終的得分, tot表示總共喫的物品, per表示現在每個物品的分數
	int direct;			//方向0,2 UP/DOWN 3,4 LEFT/RIGHT,順時針方向
	int speed;
	int nGameState;

	SnakeFood food;
	
	Snake();			//初始化
	virtual ~Snake();

	void MoveSnake();	//前進一步
	void DrawSnake();	//畫出蛇的形狀
	void UnDrawSnake();	//重新畫矩形進行覆蓋
	void DrawFood();	//畫食物

	void TurnSnake();	//蛇頭蛇尾交換
	bool RandomFood();	//隨機生成一個食物,若成功則返回TRUE,否則即蛇佔滿整個畫面則返回FALSE
	void EatFood();		//當前如果可喫,則進行相應的操作:累計等分,隨機生成食物,增大長度
	bool GameOver();	//蛇撞死了 或者 通關??:幾乎不可能啊~~
};

蛇的身體用鏈表表示,每個節點的結構體爲SnakeNode,包含座標,不過這裏的座標是指遊戲地圖中的座標,並不是完全對應的像素,遊戲地圖中的每個座標對應一個方塊。其餘的成員函數和成員變量代碼中有註釋,這裏不再複述。

遊戲截圖如下:

代碼下載:https://download.csdn.net/download/atp1992/9897413

代碼等更多信息也可以參見原博客:http://www.straka.cn/blog/snake-game-by-windows-gdi/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章