1. 程式人生 > >C++ Windows程式設計實現貪吃蛇(可用Dev C++實現)

C++ Windows程式設計實現貪吃蛇(可用Dev C++實現)

最近學習Windows程式設計,試著自己做了個簡單的貪吃蛇遊戲。不到300行程式碼,把Windows訊息機制,繪製方法,多執行緒等知識都用上了,適合初學者入門。效果圖如下:


完整程式碼附註釋:

#include <windows.h>
#include <stdio.h>
#include <vector>
#include<cstdlib>
#include<ctime>

#define SIZE 20    //指定遊戲地圖大小為20*20
#define WIDTH 15    //指定每個格子的大小
#define FPS 10    //每秒重新整理多少次
#define SPEED 3    //每重新整理多少次蛇移動一格

HWND m_hwnd;//視窗控制代碼,唯一標示遊戲視窗
int g_nWidth = WIDTH*SIZE+10, g_nHeight = WIDTH*SIZE+33;//指定視窗大小
int map[SIZE][SIZE];//地圖陣列,值為0表示空,1表示蛇身,2表示蘋果
std::vector<int> x,y;//用於儲存蛇身體每一個部位的位置
int ax,ay;//蘋果的位置
int px,py;//最後一次蛇經過的位置,用於吃過蘋果後加長蛇身
int dir;//表明當前方向
unsigned long long tk;//遊戲ticket數,即已經過多少邏輯幀
bool lock;//操作鎖,防止一次重新整理操作兩次

void init(){//初始化引數
	srand((unsigned)time(NULL));
	tk = 0;
	dir = 2;
	x.push_back(0);
	y.push_back(0);
	px = py = 0;
	ax = -1;
	memset(map,0,sizeof(map));
	map[0][0] = 1;
	lock = false;
}

void gameover(){//輸了時呼叫
	MessageBox(m_hwnd,"你輸了","遊戲結束",MB_OK);//彈窗
	x.clear();//重新初始化
	y.clear();
	init();
}

void youwin(){//贏了時呼叫
	MessageBox(m_hwnd,"你贏了","遊戲結束",MB_OK);
	x.clear();
	y.clear();
	init();
}

void move(int d){//呼叫次函式以移動
	if(d == 0&&x[0]>0){//當移動方向為上並且移動合法時,儲存並去掉蛇尾,加一格蛇頭
		x.insert(x.begin(),x[0]-1);
		px = x[x.size()-1];
		x.erase(x.begin()+x.size()-1);
		y.insert(y.begin(),y[0]);
		py = y[y.size()-1];
		y.erase(y.begin()+y.size()-1);
	}
	else if(d == 0&&x[0]<=0)gameover();//當移動不合法時遊戲結束
	else if(d == 2&&x[0]<SIZE-1){
		x.insert(x.begin(),x[0]+1);
		px = x[x.size()-1];
		x.erase(x.begin()+x.size()-1);
		y.insert(y.begin(),y[0]);
		py = y[y.size()-1];
		y.erase(y.begin()+y.size()-1);
	}
	else if(d == 2&&x[0]>=SIZE-1)gameover();
	else if(d == 1&&y[0]>0){
		x.insert(x.begin(),x[0]);
		px = x[x.size()-1];
		x.erase(x.begin()+x.size()-1);
		y.insert(y.begin(),y[0]-1);
		py = y[y.size()-1];
		y.erase(y.begin()+y.size()-1);
	}
	else if(d == 1&&y[0]<=0)gameover();
	else if(d == 3&&y[0]<SIZE-1){
		x.insert(x.begin(),x[0]);
		px = x[x.size()-1];
		x.erase(x.begin()+x.size()-1);
		y.insert(y.begin(),y[0]+1);
		py = y[y.size()-1];
		y.erase(y.begin()+y.size()-1);
	}
	else if(d == 3&&y[0]>=SIZE-1)gameover();
}

void update(){//遊戲更新主邏輯,每幀呼叫此函式
	if(tk%SPEED==0){//每隔SPEED間隔幀移動一次
		move(dir);
		lock = false;
	}
	if(x[0]==ax&&y[0]==ay){//如果吃到了蘋果
		x.push_back(px);//加蛇尾
		y.push_back(py);
		ax = -1;//去掉蘋果
	}
	if(x.size()>=SIZE*SIZE){//如果蛇的長度大於等於地圖大小,遊戲結束
		youwin();
		return;
	}
	memset(map,0,sizeof(map));//重新整理地圖
	for(int i = 0; i < x.size(); i++){
		if(map[x[i]][y[i]]==0)map[x[i]][y[i]] = 1;//如果沒有蛇身阻擋則放置蛇身
		else{//否則遊戲結束
			gameover();
			return;
		}
	}
	if(ax==-1){//蘋果被吃,重新整理蘋果
		ax = rand()%SIZE;
		ay = rand()%SIZE;
		while(map[ax][ay]==1){
			ax = rand()%SIZE;
			ay = rand()%SIZE;
		}
	}
	map[ax][ay] = 2;
	tk++;
}

/* 此函式用於處理視窗接受的所有訊息 */
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {
	switch(Message) {
		
		/* Upon destruction, tell the main thread to stop */
		case WM_DESTROY: {
			PostQuitMessage(0);
			break;
		}
		
		case WM_KEYDOWN:{//當收到按鍵訊息
			if(!lock){//且操作鎖沒有鎖上,則改變蛇的移動方向並上鎖
				if(wParam == VK_UP&&(dir-0)%2!=0){
					dir = 0;
					lock = true;
				}
				else if(wParam == VK_DOWN&&(dir-2)%2!=0){
					dir = 2;
					lock = true;
				}
				else if(wParam == VK_LEFT&&(dir-1)%2!=0){
					dir = 1;
					lock = true;
				}
				else if(wParam == VK_RIGHT&&(dir-3)%2!=0){
					dir = 3;
					lock = true;
				}
			}
			if(wParam == VK_ESCAPE)PostQuitMessage(0);//如果是ESC鍵則退出
			break;
		}
		
		/* All other messages (a lot of them) are processed using default procedures */
		default:
			return DefWindowProc(hwnd, Message, wParam, lParam);
	}
	return 0;
}

void render()//渲染函式,用於繪製遊戲影象
{  
    HDC hDC = GetDC(m_hwnd);//定義視窗控制代碼的DC
  
    HDC memDC = CreateCompatibleDC(0);//定義相容DC
  
    HBITMAP bmpBack = CreateCompatibleBitmap(hDC,g_nWidth,g_nHeight);//定義點陣圖畫布
    SelectObject(memDC,bmpBack);
  
    HPEN penBack = CreatePen(PS_SOLID,1,RGB(0,0,0));//定義畫筆
    SelectObject(memDC,penBack);
  
    HBRUSH brushBack = CreateSolidBrush(RGB(255,255,255));//定義背景畫刷
    SelectObject(memDC,brushBack);
  
    RECT rcClient;
    GetClientRect(m_hwnd,&rcClient);
    FillRect(memDC,&rcClient,brushBack);//用背景畫刷以矩形填充整個視窗
    HBRUSH brushObj = CreateSolidBrush(RGB(255,0,0));//定義蛇身畫刷
    HBRUSH brushApple = CreateSolidBrush(RGB(0,0,255));//定義蘋果畫刷
    int dw = WIDTH;  
    int rows = SIZE;  
    int cols = SIZE;  
    for (int r=0; r<rows; ++ r){//繪製整個矩陣
        for (int c=0; c<cols; ++c){  
            if (map[r][c]==1){  
                SelectObject(memDC,brushObj);
            }
            else if(map[r][c] == 2){
            	SelectObject(memDC,brushApple);
			}
            else{  
                SelectObject(memDC,brushBack);  
            }  
            Rectangle(memDC,c*dw,r*dw,(c+1)*dw,(r+1)*dw);  
        }  
    }  
  
    DeleteObject(brushObj);//釋放資源
    BitBlt(hDC,0,0,g_nWidth,g_nHeight,memDC,0,0,SRCCOPY);
    DeleteObject(penBack);
    DeleteObject(brushBack);
    DeleteObject(bmpBack); 
    DeleteDC(memDC);
    ReleaseDC(m_hwnd,hDC);
}  

DWORD WINAPI startLoop(LPVOID lpParamter){//開始主迴圈,定義這個函式是為了多執行緒執行,函式名隨意,函式格式一定
    while(1){
		update();
		render();
		Sleep(1000/FPS);
	}
    return 0L;
}

/* windows程式設計的主函式 */
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	WNDCLASSEX wc; /* A properties struct of our window */
	HWND hwnd; /* A 'HANDLE', hence the H, or a pointer to our window */
	MSG msg; /* A temporary location for all messages */

	/* zero out the struct and set the stuff we want to modify */
	memset(&wc,0,sizeof(wc));
	wc.cbSize		 = sizeof(WNDCLASSEX);
	wc.lpfnWndProc	 = WndProc; /* This is where we will send messages to */
	wc.hInstance	 = hInstance;
	wc.hCursor		 = LoadCursor(NULL, IDC_ARROW);
	
	/* White, COLOR_WINDOW is just a #define for a system color, try Ctrl+Clicking it */
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
	wc.lpszClassName = "WindowClass";
	wc.hIcon		 = LoadIcon(NULL, IDI_APPLICATION); /* Load a standard icon */
	wc.hIconSm		 = LoadIcon(NULL, IDI_APPLICATION); /* use the name "A" to use the project icon */

	if(!RegisterClassEx(&wc)) {
		MessageBox(NULL, "Window Registration Failed!","Error!",MB_ICONEXCLAMATION|MB_OK);
		return 0;
	}

	hwnd = CreateWindowEx(WS_EX_CLIENTEDGE,"WindowClass","·½Ïò¼üÒƶ¯£¬ESCÍ˳ö",WS_VISIBLE|WS_EX_STATICEDGE,
		CW_USEDEFAULT, /* x */
		CW_USEDEFAULT, /* y */
		g_nWidth, /* width */
		g_nHeight, /* height */
		NULL,NULL,hInstance,NULL);

	if(hwnd == NULL) {
		MessageBox(NULL, "Window Creation Failed!","Error!",MB_ICONEXCLAMATION|MB_OK);
		return 0;
	}
	
	m_hwnd = hwnd;
	init();//初始化

	HANDLE hThread = CreateThread(NULL, 0, startLoop, NULL, 0, NULL);//建立執行緒
        CloseHandle(hThread);//釋放執行緒控制代碼

	/*
		This is the heart of our program where all input is processed and 
		sent to WndProc. Note that GetMessage blocks code flow until it receives something, so
		this loop will not produce unreasonably high CPU usage
	*/
	while(GetMessage(&msg, NULL, 0, 0) > 0) { /* If no error is received... */
		TranslateMessage(&msg); /* Translate key codes to chars if present */
		DispatchMessage(&msg); /* Send it to WndProc */
	}
	return msg.wParam;
}