1. 程式人生 > >windows下實現win32俄羅斯方塊練手,程式設計的幾點心得

windows下實現win32俄羅斯方塊練手,程式設計的幾點心得

程式設計珠璣2閱讀筆記:

1.使用c語言效能監視器,完成對程式碼的調優工作 2.關聯陣列:  拓撲排序演算法,可以用於當存在遮擋的時候決定三維場景的繪製順序。 3.小型演算法中的測試與除錯工具 腳手架程式:《人月神話》一個軟體產品中應該有一半的程式碼都是腳手架。 類似,小型的程式碼庫 4.自描述資料 每個程式設計師都知道破解神祕資料的挫折與艱辛。 5.劈開戈爾迪之結 什麼是使用者的真正需求: 一個運籌學者接到任務,設計末座大樓的電梯排程策略,使乘客等待的時間最短,在走訪了這座大樓之後,他認識到僱主真正想要解決的問題是,儘量減少乘客的不適( 乘客不喜歡等電梯)。他這樣解決問題:在每部電梯附近裝上幾面鏡子。乘客在等電梯時候,可以自我欣賞一下,對電梯速度的抱怨大幅減少了。他發現了使用者的真正需求
7.粗略估算 程式設計師3大美德:對數值敏感,實驗的慾望,良好的數學功底

在來說這個俄羅斯方塊,其實主要是2個大的部分:

1.介面繪製(遊戲區,資訊區,重新整理重繪工作)

遊戲區方塊的繪製,其實都是陣列來記錄


2.遊戲邏輯(上下左右,變形)

其實就是對陣列的旋轉


主要程式碼,才六百行:

// Russian_cube.cpp : 定義應用程式的入口點。
//
//
//
//
#include "stdafx.h"
#include "Russian_cube.h"

#define MAX_LOADSTRING 100
//Tetris
#define BOUND_SIZE 10
#define TETRIS_SIZE 30
#define GAME_X 10
#define GAME_Y 20
#define INFO_X 6
#define INFO_Y GAME_Y

//定時器
#define  MY_TIMEER 1
#define  DEFAULT_INTERVAL 500 //預設每0.5秒下降一格

//定義俄羅斯方塊的形狀
BOOL g_astTetris[][4][4] = 
{
	{{1,1,0,1},{0,0,0,0},{0,0,0,0},{0,0,0,0}},
	{{1,1,0,0},{0,0,1,1},{0,0,0,0},{0,0,0,0}},
	{{1,1,0,0},{1,1,0,0},{0,0,0,0},{0,0,0,0}},
	{{0,1,1,0},{1,1,0,0},{0,0,0,0},{0,0,0,0}},
	{{0,1,0,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}},
	{{1,1,1,1},{0,0,0,0},{0,0,0,0},{0,0,0,0}}
};
#define  TETRIS_CNT (sizeof(g_astTetris)/sizeof(g_astTetris[0]))


//當前方塊的形狀
BOOL g_CurTetris[4][4];
BOOL g_NextTetris[4][4];
BOOL g_stGame[GAME_X][GAME_Y];//記錄已經落下來的方塊

//記錄方塊左上角的座標
UINT TetrisX;
UINT TetrixY;
UINT g_uiInterval;
UINT g_uiScore;

UINT g_uiMySeed = 0xffff;

// 全域性變數:
HINSTANCE hInst;								// 當前例項
TCHAR szTitle[MAX_LOADSTRING];					// 標題欄文字
TCHAR szWindowClass[MAX_LOADSTRING];			// 主視窗類名

// 此程式碼模組中包含的函式的前向宣告:
ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK	About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

 	// TODO: 在此放置程式碼。
	MSG msg;
	HACCEL hAccelTable;

	// 初始化全域性字串
	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_RUSSIAN_CUBE, szWindowClass, MAX_LOADSTRING);
	MyRegisterClass(hInstance);

	// 執行應用程式初始化:
	if (!InitInstance (hInstance, nCmdShow))
	{
		return FALSE;
	}

	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_RUSSIAN_CUBE));

	// 主訊息迴圈:
	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return (int) msg.wParam;
}



//
//  函式: MyRegisterClass()
//
//  目的: 註冊視窗類。
//
//  註釋:
//
//    僅當希望
//    此程式碼與新增到 Windows 95 中的“RegisterClassEx”
//    函式之前的 Win32 系統相容時,才需要此函式及其用法。呼叫此函式十分重要,
//    這樣應用程式就可以獲得關聯的
//    “格式正確的”小圖示。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_RUSSIAN_CUBE));
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= MAKEINTRESOURCE(IDC_RUSSIAN_CUBE);
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	return RegisterClassEx(&wcex);
}

//
//   函式: InitInstance(HINSTANCE, int)
//
//   目的: 儲存例項控制代碼並建立主視窗
//
//   註釋:
//
//        在此函式中,我們在全域性變數中儲存例項控制代碼並
//        建立和顯示主程式視窗。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // 將例項控制代碼儲存在全域性變數中

   hWnd = CreateWindow(szWindowClass, szTitle,  WS_MINIMIZEBOX | WS_SYSMENU,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

int GetRandNum(int iMin,int iMax)
{
	//取隨機數
	srand(GetTickCount() + g_uiMySeed-- );

	return iMin + rand()%(iMax -iMin);
}

VOID DrawBackGround(HDC hdc)
{
	int x, y;
	HPEN hPen = (HPEN)GetStockObject(NULL_PEN);
	HBRUSH hBrush = (HBRUSH)GetStockObject(GRAY_BRUSH);
    //(HBRUSH)CreateSolidBrush()

	
	HBRUSH hBrush_luo = (HBRUSH)GetStockObject(BLACK_BRUSH);

	Rectangle(hdc,BOUND_SIZE,BOUND_SIZE,BOUND_SIZE + GAME_X*TETRIS_SIZE
		,BOUND_SIZE + GAME_Y * TETRIS_SIZE);

	SelectObject(hdc,hPen);
	
	for (x = 0; x < GAME_X; x++)
	{
		for (y = 0 ;y < GAME_Y; y++)
		{
			if (g_stGame[x][y])
			{
				SelectObject(hdc,hBrush_luo);
			}
			else
			{
				
				SelectObject(hdc,hBrush);
			}
			Rectangle(hdc,BOUND_SIZE + x*TETRIS_SIZE,
				BOUND_SIZE + y * TETRIS_SIZE,
				BOUND_SIZE + (x + 1)*TETRIS_SIZE,
				BOUND_SIZE + (y + 1) * TETRIS_SIZE);
		}
	}
}
//資訊區 的繪製
VOID DrawInfo(HDC hdc)
{
	int x,y;
	int nStartX,nStartY;
	RECT rect;
	TCHAR szBuf[100];//得分的字串

	HPEN hPen = (HPEN)GetStockObject(BLACK_PEN);
	HBRUSH hBrush = (HBRUSH)GetStockObject(NULL_BRUSH);

	HBRUSH hBrush_have = (HBRUSH)GetStockObject(GRAY_BRUSH);
	SelectObject(hdc,hPen);
	SelectObject(hdc,hBrush);

	Rectangle(hdc,BOUND_SIZE*2 + GAME_X * TETRIS_SIZE,
		BOUND_SIZE,BOUND_SIZE *2 + (GAME_X + INFO_X)*TETRIS_SIZE,
		BOUND_SIZE + INFO_Y * TETRIS_SIZE);

	for (x = 0; x < 4; x++)
	{
		for (y = 0 ;y < 4 ;y++)
		{
			nStartX = BOUND_SIZE *2 + GAME_X*TETRIS_SIZE + (y +1)*TETRIS_SIZE;
			nStartY = BOUND_SIZE + (x +1)*TETRIS_SIZE;
			if (g_NextTetris[x][y])
			{
				SelectObject(hdc,hBrush);
			}
			else
			{
				SelectObject(hdc,hBrush_have);
			}
			Rectangle(hdc,nStartX,nStartY,nStartX+TETRIS_SIZE,nStartY+TETRIS_SIZE);
		}
	}

	nStartX = BOUND_SIZE *2 + GAME_X*TETRIS_SIZE;
	nStartY = BOUND_SIZE ;

	rect.left = nStartX + TETRIS_SIZE;
	rect.right = nStartX + TETRIS_SIZE * (INFO_X -1);
	rect.top = nStartY + TETRIS_SIZE *6;
	rect.bottom = nStartY + TETRIS_SIZE *7;

	wsprintf(szBuf,L"Score: %d",g_uiScore = 0);

	 DrawText(hdc,szBuf,wcslen(szBuf),&rect,DT_CENTER);

}

//繪製區方塊,起始座標和需要繪製的方塊形狀
VOID DrawTetris(HDC hdc, int nStartX,int nStartY,BOOL bTetris[4][4])
{
	int i,j;
	HPEN hPen = (HPEN)GetStockObject(BLACK_PEN);
	HBRUSH hBrush = (HBRUSH)GetStockObject(WHITE_BRUSH);
	SelectObject(hdc,hPen);
	SelectObject(hdc,hBrush);

	for (i = 0;i < 4; i++)
	{
		for (j = 0;j < 4;j++)
		{
			//j 是x方向的座標偏移
			if (bTetris[i][j])
			{
				Rectangle(hdc,BOUND_SIZE +(nStartX + j) * TETRIS_SIZE,
					BOUND_SIZE + (nStartY + i)* TETRIS_SIZE,
					BOUND_SIZE +(nStartX + j + 1) * TETRIS_SIZE,
					BOUND_SIZE + (nStartY + i + 1)* TETRIS_SIZE);
			}
		}
	}
}

//旋轉方塊, 並且靠左上角
VOID RotateTetris(BOOL bTetris[4][4])
{
	BOOL bNewTetris[4][4] = {};//初始化置零
	int x, y;
	int xPos,yPos;
	BOOL bFlag;//靠近左上角

	//從上往下,從左往右,順時針旋轉
	//靠上
	for (x = 0,xPos = 0 ;x < 4 ; x++)
	{
		bFlag = FALSE;
		for (y = 0 ;y < 4 ;y++)
		{
			bNewTetris[xPos][y] = bTetris[3 - y][x];
			//逆時針旋轉
			//bNewTetris[x][y] = bTetris[y][3 - x];
			if (bNewTetris[xPos][y])
			{
				bFlag = TRUE;//這一行有資料
			}
		}
		if (bFlag)
		{
			xPos++;
		}
	}


	memset(bTetris,0,sizeof(bNewTetris));
	//靠左
	for (y = 0, yPos = 0;y < 4 ;y++)
	{
		bFlag = FALSE;
		for (x = 0;x < 4; x++)
		{
			bTetris[x][yPos] = bNewTetris[x][y];
			if (bTetris[x][yPos])
			{
				bFlag = TRUE;
			}
		}
		if (bFlag)
		{
			yPos++;
		}
	}
	//memcpy(bTetris,bNewTetris,sizeof(bNewTetris));
	return;
}
BOOL CheckTetris(int nStartX,int nStartY,BOOL bTetris[4][4],BOOL bGame[GAME_X][GAME_Y])
{
	int x,y;
	if (nStartX < 0)
	{//碰到左牆
		return FALSE;
	}

	for (x = 0;x < 4;x++)
	{
		for (y = 0;y < 4;y++)
		{
			if (bTetris[x][y])
			{
				//碰右牆
				if (nStartX +y >=GAME_X)
				{
					return FALSE;
				}
				//碰下牆
				if (nStartY + x >=GAME_Y)
				{
					return FALSE;
				}
				//碰到已有的方塊
				if (bGame[nStartX +y][nStartY + x])
				{
					return FALSE;
				}
			}
		}
	}
	return TRUE;
}

//落地的方塊合併,並且滿足消除一行
VOID RefreshTetris(int nStartX,int nStartY,BOOL bTetris[4][4], BOOL bGame[GAME_X][GAME_Y])
{
	BOOL bFlag = FALSE;
	int x,y;
	int newX,newY;//主要用來記錄
	int iFulllie = 0; //校區滿行的格子記錄行數,用於積分

	for (x = 0; x < 4;x ++)
	{
		for (y = 0 ;y < 4; y++)
		{
			if (bTetris[x][y])
			{
				bGame[nStartX + y][nStartY +x] = TRUE;
			}
		}
	}

	for (y = GAME_Y,newY = GAME_Y; y >= 0; y--)
	{
		bFlag= FALSE;
		for (x = 0;x < GAME_X;x++)
		{
			bGame[x][newY] = bGame[x][y];
			if (!bGame[x][y])//這一行不滿格
			{
				bFlag = TRUE;
			}
		}
		if (bFlag)
		{
			newY--;
		}
		else
		{
			//滿格的話,用上一行替換這一行
			iFulllie++;
		}
	}

	if (iFulllie)
	{
		g_uiScore -= iFulllie *1;
	}
	//合併以後生成新的方塊,並重新整理位置
	memcpy(g_CurTetris,g_NextTetris,sizeof(g_CurTetris));
	memcpy(g_NextTetris,g_astTetris[ GetRandNum(0,TETRIS_CNT)],sizeof(g_NextTetris));

	TetrisX = (GAME_X - 4)/2;
	TetrixY = 0;
}

//初始化遊戲,就是方塊最先初始化的位置
VOID InitGame()
{
	int iTmp;

	TetrisX = (GAME_X - 4 )/2 ; //居中
	TetrixY = 0;

	g_uiScore = 0;
	g_uiInterval = DEFAULT_INTERVAL;

	iTmp = GetRandNum(0,TETRIS_CNT);
	memcpy(g_CurTetris,g_astTetris[iTmp],sizeof(g_CurTetris));

	iTmp = GetRandNum(0,TETRIS_CNT);
	memcpy(g_NextTetris,g_astTetris[iTmp],sizeof(g_NextTetris));

	memset(g_stGame,0,sizeof(g_stGame));
}
//
//  函式: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的: 處理主視窗的訊息。
//
//  WM_COMMAND	- 處理應用程式選單
//  WM_PAINT	- 繪製主視窗
//  WM_DESTROY	- 傳送退出訊息並返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;

	int nWinX,nWinY,nClientX,nClientY;
	RECT rect;
	BOOL bTmpTetris[4][4] = {};

	switch (message)
	{
	case WM_CREATE:
		//獲取視窗大小
		GetWindowRect(hWnd,&rect);
		nWinX = rect.right - rect.left;
		nWinY = rect.bottom - rect.top;
		//獲取客戶區大小
		GetClientRect(hWnd,&rect);
		nClientX = rect.right - rect.left;
		nClientY = rect.bottom - rect.top;

		MoveWindow(hWnd,0,0,3 * BOUND_SIZE + (GAME_X + INFO_X)* TETRIS_SIZE +
			(nWinX  - nClientX),
			2 * BOUND_SIZE + GAME_Y*TETRIS_SIZE + (nWinY - nClientY),true);
		InitGame();
		SetTimer(hWnd,MY_TIMEER,g_uiInterval,NULL);
		break;
	case WM_TIMER:
		//定時器中方塊下降
		if (CheckTetris(TetrisX,TetrixY + 1,g_CurTetris,g_stGame))
		{
			TetrixY++;
		}
		else
		{
			if (TetrixY == 0)
			{
				MessageBox(NULL,L"不行了",L"shit!",MB_OK);
				KillTimer(hWnd,MY_TIMEER);
			}
			RefreshTetris(TetrisX,TetrixY,g_CurTetris,g_stGame);
		}
		InvalidateRect(hWnd,NULL,TRUE);

		break;
	case WM_LBUTTONDOWN:
		RotateTetris(g_CurTetris);
		InvalidateRect(hWnd,NULL,TRUE);
		break;
	case WM_KEYDOWN:
		switch(wParam)
		{
		case VK_LEFT://左方向鍵
			if (CheckTetris(TetrisX -1,TetrixY,g_CurTetris,g_stGame))
			{//判斷一下當前方塊沒有靠牆就
				TetrisX--;
				InvalidateRect(hWnd,NULL,TRUE);
			}
			else
			{
				MessageBeep(0);
			}
			break;
		case  VK_RIGHT:
			if (CheckTetris(TetrisX +1,TetrixY,g_CurTetris,g_stGame))
			{//判斷一下當前方塊沒有靠牆就
				TetrisX++;
				InvalidateRect(hWnd,NULL,TRUE);
			}
			else
			{
				MessageBeep(0);
			}
			break;
		case VK_UP://變形,但是要判斷變形成功或者失敗
			memcpy(bTmpTetris,g_CurTetris,sizeof(bTmpTetris));
			RotateTetris((bTmpTetris));
			if (CheckTetris(TetrisX,TetrixY,bTmpTetris,g_stGame))
			{
				//成功後,再把旋轉後的copy回來
				memcpy(g_CurTetris,bTmpTetris,sizeof(bTmpTetris));
				InvalidateRect(hWnd,NULL,TRUE);
			}
			break;
		case VK_DOWN:
			while (CheckTetris(TetrisX,TetrixY + 1,g_CurTetris,g_stGame))
			{
				TetrixY++;
				
			}
			RefreshTetris(TetrisX,TetrixY,g_CurTetris,g_stGame);
			InvalidateRect(hWnd,NULL,TRUE);
			break;
		default:
			break;
		}
		
		break;
	case WM_COMMAND:
		wmId    = LOWORD(wParam);
		wmEvent = HIWORD(wParam);
		// 分析選單選擇:
		switch (wmId)
		{
		case IDM_ABOUT:
			DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
			break;
		case IDM_EXIT:
			DestroyWindow(hWnd);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		// TODO: 在此新增任意繪圖程式碼...
		DrawBackGround(hdc);
		DrawInfo(hdc);
		DrawTetris(hdc,TetrisX,TetrixY,g_CurTetris);
		EndPaint(hWnd, &ps);
		break;
	case WM_DESTROY:
		KillTimer(hWnd,MY_TIMEER);
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

// “關於”框的訊息處理程式。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
		return (INT_PTR)TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		{
			EndDialog(hDlg, LOWORD(wParam));
			return (INT_PTR)TRUE;
		}
		break;
	}
	return (INT_PTR)FALSE;
}

程式碼參考:

主要是聽了這個課程,這個公開課做點小專案,貪吃蛇,網路啊什麼的,都是程式碼挺好的,作為一個熟悉其他領域的小專案非常適合上手:

windows下的win32程式設計要學的東西還比較多,下面給出一個簡單的知識點: