windows下實現win32俄羅斯方塊練手,程式設計的幾點心得
阿新 • • 發佈:2019-01-04
程式設計珠璣2閱讀筆記:
在來說這個俄羅斯方塊,其實主要是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程式設計要學的東西還比較多,下面給出一個簡單的知識點: