C项目实践--俄罗斯方块(2)
在VS中新建win32 Application Proj,選擇Empty ,完成TetrisWin項目創建。新建tetris.c和tetris.h兩個文件,打開tetris.h文件。
首先要包括的是可能要用到的頭文件,那在這里要用到是什么頭文件呢? 本系統是開發一個游戲,那么游戲的話就需要有和用戶進行交互的游戲界面,那就需要繪圖操作,那么就會用到windows的繪圖函數庫,所以第一步就是要包括這個windows頭文件,但是要注意我們現在是在頭文件tetris.h中來包含這個頭文件,這里就需要注意使用#ifndef 宏來進行包含處理,因為tetris.h最終會被包含在實現文件中去,但是不確定實現文件的頭部是否也會包含這個windows庫頭文件,如果包括了的話我們就沒有必要再包括了,否則就會引發重定義錯誤,所以在頭文件中要包括什么庫的頭文件時,最好用#ifndef宏先判斷是否已包含了該頭文件,只有在沒有包括的情況下再去包含該頭文件,此時才不會報重定義的錯誤。具體實現如下:
//header infor #ifndef WIN_H_H #define WIN_H_H #include <Windows.h> #endif因為游戲中只有7種游戲方塊,所以可以聲明為一個枚舉類型,同時也有利于在游戲中為了方便的直到當前方塊形狀和下一個方塊形狀。具體實現如下:
//self_definition enum typedef enum tetris_shape{ ZShape = 0, SShape, LineShape, TShape, SquareShape, LShape, MirroredLShape }shape;接下來根據要實現的功能模塊聲明相應的處理函數。具體實現如下:
//function declaraction int maxX(); //取得當前方塊的最大x坐標 int minX(); //取得當前方塊的最小x坐標 void turn_left(); //將當前方塊逆時針旋轉90度 void turn_right(); //將當前方塊順時針旋轉90度 int out_of_table();//檢查當前方塊是否超出桌面的范圍 void transform(); //旋轉當前方塊 int leftable(); //判斷當前方塊能否左移 int rightable(); //判斷當前方塊能否右移 int downable(); //判斷當前方塊能否下移 void move_left(); //向左移動當前方塊 void move_right(); //向右移動當前方塊 ? //operation function int add_to_table();//將當前方塊固定到桌面上,若返回0,表示游戲結束 void remove_full();//刪除桌面上填滿的行 ? //control function void new_game(); //創建一個新游戲 void run_game(); //運行游戲 void next_shape(); //將下一個方塊設為當前方塊,并設置下一個方塊 int random(int seed);//取得一個隨機數,例如random(7)將返回一個0-6之間的隨機數 ? //paint function void paint(); //將內存位圖輸出到窗口上 void draw_table(); //繪制游戲桌面 ? //other functions void key_down(WPARAM wParam); //處理鍵盤按下事件 void resize(); //改變窗口大小時調用的函數 void intialize();//初始化 void finalize(); //結束時,釋放資源 ? //callback function //回調函數,用來處理windows消息 LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);上面基本上把需要用到的處理函數都已聲明完畢,接下來打開tetris.c文件來實現相應的功能,首先需要包含一些頭文件,1.系統中需要用到sprintf()這樣函數來格式化輸出一些字符到相應的變量中,所以需要包含stdio.h文件,同時需要使用當前時間作為rand()種子來獲取隨機數,所以需要包含time.h文件,當然還需要包含tetris.h文件,具體實現如下:
//Header Info #include <time.h> #include <stdio.h> #include "tetris.h"接下來需要定義一些常量,包括游戲開始結束時的提示信息以及相應的顏色值等,具體定義如下:
//constant definition #define APP_NAME "TETRIS" #define APP_TITLE "Tetris Game" #define GAMEOVER "GAME OVER" ? #define SHAPE_COUNT 7 //形狀的個數 #define BLOCK_COUNT 4 //每個形狀由幾個小表格構成 #define MAX_SPEED 5 //速度級別 #define COLUMS 20 //游戲桌面表格的列數 #define ROWS 30 //游戲桌面表格的行數 ? //7種顏色 #define RED RGB(255,0,0) #define YELLOW RGB(255,255,0) #define GRAY RGB(128,128,128) #define BLACK RGB(0,0,0) #define WHITE RGB(255,255,255) #define STONE RGB(192,192,192) ? #define CHARS_IN_LINE 14 //提示信息的一行有多少個字符 #define SCORE "SCORE %4d" //得分格式化接下來定義一些全局變量:
//global variables definition char score_char[CHARS_IN_LINE] = {0}; //聲明一個接收得分情況的字符數組 char* press_enter = "Press Enter Key..."; char* help[] = //幫助提示信息 { "Press space or up key to transform shape.", "Press left or right key to move shape.", "Press down key to speed up.", "Press enter key to pause game.", "Enjoy it. :-)", 0 };接下來把游戲狀態定義為一個枚舉類型,便于對其進行操作,具體實現如下:
//enum the state of game enum game_state { game_start, game_run, game_pause, game_over }state = game_start;之前我們把游戲的7種方塊定義成了一個枚舉類型,那么我們會通過這些枚舉變量的相應的值來判斷目前是哪種方塊,同時我們要為這些方塊涂上不同的顏色,所以我們最好也把這些顏色定義為一個數組,這樣到時候要為某個方塊涂上對應顏色的時候也只需要根據當前方塊的枚舉變量值做為數組的下標值來確定相應的顏色方案,所以下面來定義顏色數組:
//color of Rectangle COLORREF shape_color[] = { RGB(255,0,0), RGB(0,255,0), RGB(0,0,255), RGB(255,255,0), RGB(0,255,255), RGB(255,0,255), RGB(255,255,255) };接下來定義表示7種方形相對坐標的三維數組,具體實現如下:
//SEVEN SHAPE OF RECTANGLE int shape_coordinate[SHAPE_COUNT][BLOCK_COUNT][2] = { {{0,-1},{0,0},{-1,0},{-1,1}}, {{0,-1},{0,0},{1,0},{1,1}}, {{0,-1},{0,0},{0,1},{0,2}}, {{-1,0},{0,0},{1,0},{0,1}}, {{0,0},{1,0},{0,1},{1,1}}, {{-1,-1},{0,-1},{0,0},{0,1}}, {{1,-1},{0,-1},{0,0},{0,1}} };接下來還需要定義一些輔助變量如記錄得分的變量score,如當前方塊的最左邊的坐標位置等以及相應的繪圖變量,具體定義和說明如下:
int score = 0; //得分 ? //shape next = 0; //shape current = 0; shape next = ZShape; //下一個方塊 shape current = ZShape; //當前方塊 ? int current_coordinate[4][2] = {0}; //當前方塊的每一部分的坐標,初始化為0 int table[ROWS][COLUMS] = {0}; //游戲桌面,初始化為全0,表示桌面上還沒有方塊 int shapex = 0; //當前方塊的x坐標 int shapey = 0; //當前方塊的y坐標 int speed = 0; //方塊下移的速度 clock_t start = 0; //每一幀的開始時間 clock_t finish = 0; //每一幀的結束時間 ? //windows paint function HWND gameWND; //window窗口句柄 HBITMAP memBM; //內存位圖 HBITMAP memBMOld; //內存原始位圖 HDC memDC; //內存DC RECT clientRC; //客戶區矩形區域 HBRUSH blackBrush; //黑色畫筆 HBRUSH stoneBrush; //深灰色畫筆 HBRUSH shapeBrush[SHAPE_COUNT]; //方塊畫筆,7種方塊,每種一個 HPEN grayPen; //灰色畫筆 HFONT bigFont; //大字體,用來顯示游戲名稱及"GAME OVER" HFONT smallFont; //小字體用來顯示幫助信息等目前,該聲明的變量都已經聲明完畢, 接下來就開始根據相應的功能實現其處理函數
1.取最大坐標
函數名稱:maxX
函數功能:取得當前方塊的最大x坐標。具體實現如下:
//main functions //對比當前方塊的BLOCK_COUNT個小塊 //選擇它們最大的x坐標 int maxX() { int i =0; int x = current_coordinate[i][0]; int m = x; for(i = 1; i< BLOCK_COUNT; i++) { x = current_coordinate[i][0]; if(m < x) { m = x; } } return m; }2.取最小坐標
函數名稱:minX
函數功能:取得當前方塊的最小x坐標。具體實現如下:
int minX() { int i = 0; int x = current_coordinate[i][0]; int m = x; for(i = 1;i<BLOCK_COUNT;i++) { x = current_coordinate[i][0]; if(m > x) { m = x; } } return m; }3.逆時針旋轉方塊
函數名稱:turn_left
函數功能:將當前方塊逆時針旋轉90度。具體實現如下:
//逆時針旋轉90度 //旋轉公式 x' = y; y' = -x void turn_left() { int i = 0; int x, y; for(i = 0; i < 4; i++) { x = current_coordinate[i][0]; y = current_coordinate[i][1]; current_coordinate[i][0] = y; current_coordinate[i][1] = -x; } }4.順時針旋轉方塊
函數名稱:turn_right
函數功能:將當前方塊順時針旋轉90度。旋轉公式:x' = –y , y' = x; 具體實現如下:
//順時針旋轉 void turn_right() { int i = 0; int x, y; for(i = 0; i< 4; i++) { x = current_coordinate[i][0]; y = current_coordinate[i][1]; current_coordinate[i][0] = -y; current_coordinate[i][1] = x; } }5.檢測方塊是否越界
函數名稱:out_of_table
函數功能:檢查當前方塊是否超出桌面范圍。具體實現如下:
//檢測是否越界 int out_of_table() { int i = 0; int x , y; for(i = 0; i < 4; i++) { //shapex,shapey的值在生成一個新的方塊時設定,它們是方塊生成時最左邊的初始邊界坐標點值 //current_coordinate[i][]值是相對應shapex,shapey的偏移值 //所以直接檢測shapex + current_coordiante[][]值就是其目前在游戲桌面上的坐標值 x = shapex + current_coordinate[i][0]; y = shapey + current_coordinate[i][1]; //如果x值為負值,則表示游戲方塊已經超出了游戲桌面的最左邊的表示邊界 //如果x值大于COLUMS-1表示超出了游戲桌面的最右邊表示邊界 //如果y值大于游戲桌面最下面的表示邊界,則表示超出了游戲邊界 if(x < 0 || x > (COLUMS-1)|| y > (ROWS - 1)) { return 1; } //table[ROWS][COLUMS]表示為table[y][x]本身是初始化為全0的,如果由值不是0 表示 //當前方形已經運行到某個方形上面了,則也表示越界 if(table[y][x]) { return 1; } } //方形沒有越界則返回0,否則返回1 return 0; }6.旋轉方塊
函數名稱:transform
函數功能:旋轉當前方塊。具體實現如下:
//旋轉當前方塊 void transform() { //如果是田字形的方塊則不需要旋轉變化 if(current == SquareShape) { return ; } //默認順時針旋轉 turn_right(); //如果順時針旋轉出現越界情況則 //進行逆時針旋轉 if(out_of_table()) { turn_left(); } }7.判斷方塊能否向左移動
函數名稱:leftable
函數功能:判斷當前方塊能否向左移動,能移動則返回1,否則返回0.具體實現如下:
//判斷能否向左移動 int leftable() { int i = 0; int x , y; for(i = 0; i < 4; i++) { //shapex,shapey 是初始化生成方形時最左邊的x,y坐標值 //current_coordinate是方形的相對偏移位置 //x,y為目前方形中某個方塊的實際坐標值 x = shapex + current_coordinate[i][0]; y = shapey + current_coordinate[i][1]; //如果發生越界則返回0,否則返回1 //x <= 0 判斷x是否越過左邊界 //判斷table[y][x-1] == 1,表示以當前坐標值x,y所在點為基準 //向左探測一小方塊,看這個小方塊是否被已有的方形占用了 //如果占用了則它的值為1,否則為0,如果為1,則表示目前方形已經 //落到了另一個已經存在的方形上了,發生了重疊,那這也是越界了 if(x <= 0 || table[y][x-1] == 1) { return 0; } } return 1; }8.判斷方塊能否向右移動
函數名稱:rightable
函數功能:判斷當前方塊能否向右移動,能移動則返回1,否則返回0.具體實現如下:
//判斷能否向右移動 int rightable() { int i = 0; int x, y ; for(i = 0; i < 4; i++) { x = shapex + current_coordinate[i][0]; y = shapey + current_coordinate[i][1]; //x>=(COLUMS -1)判斷是否越過右邊界 //判斷table[y][x+1] 表示以當前坐標值x,y所在點為基準 //向右探測一小方塊,看這個小方塊是否被已有的方形占用了 //如果占用了則它的值為1,否則為0,如果為1,則表示目前方形已經 //落到了另一個已經存在的方形上了,發生了重疊,那這也是越界了 if(x >= (COLUMS -1) || table[y][x+1] == 1) { return 0; } } return 1; }9.判斷方塊能否向下移動
函數名稱:downable
函數功能:判斷當前方塊能否向下移動,能移動則返回1,否則返回0.具體實現如下:
int downable() { int i = 0; int x , y; for(i = 0; i<4;i++) { x = shapex + current_coordinate[i][0]; y = shapey + current_coordinate[i][1]; if(y >= (ROWS -1) || table[y+1][x] == 1) { return 0; } } return 1; }10.向左或右移動當前方塊
函數名稱:move_left /move_right
函數功能:向左/右移動當前方塊。具體實現如下:
void move_left() { if(leftable()) { //shapex是方塊最左邊的坐標位置點x的值,--表示 //當前方形整體左移一個單位 shapex--; } } ? void move_right() { if(rightable()) { //shapex是方塊最左邊的坐標位置點x的值,++表示 //當前方形整體右移一個單位 shapex++; } }11.向下移動當前方塊
函數名稱:move_down
函數功能:向下移動當前方塊。具體實現如下:
void move_down() { //如果可以向下移動,則 //shapey++,表示當前方形整體下移一個單位 if(downable()) { shapey++; }else{ //如果不能往下移動則繼續判斷 //如果是可以添加到當前游戲桌面上,則自己添加 if(add_to_table()) { //添加完畢之后調用remove_full()函數來檢測是否 //有滿行的,如果有則清除掉 //然后繼續生成下一個方形,將其作為當前方形 remove_full(); next_shape(); }else{ //如果既不能往下移動,也不能把他添加到游戲桌面上 //則表示越界,那就結束游戲 state = game_over; } } }12.將當前方塊固定到桌面上
函數名稱:add_to_table
函數功能:將當前方塊固定到桌面上,若返回0,則表示游戲結束。具體實現如下:
13.刪除填滿的行
函數名稱:remove_full
函數功能:刪除桌面上填滿的行。具體實現如下:
//刪除桌面上填滿的行 void remove_full() { int c = 0; int i,j; //首先定位到最底下那一行 for(i = ROWS -1; i>0;i--) { c = 0; //然后將當前行的所有值相加 for(j = 0; j < COLUMS;j++) { c += table[i][j]; } //如果所有值相加的結果等于列數目,則表示當前行是滿行 if(c == COLUMS) { //void *memmove( void* dest, void* src,count ); //memmove是從src所指內存區域復制count個字節到dest所指內存區域 //memmove有個特性,如果目標區域dest和源區域src有重疊的話,memmove //能夠保證源串在被覆蓋之前將重疊區域的字節拷貝到目標區域中 //這里我們正是利用了memmove的這個特性完成滿行的刪除工具和刪除后其上面的未滿行 //向下移動的工作 memmove(table[1],table[0],sizeof(int)*COLUMS*i); //將table[0]清空 memset(table[0],0,sizeof(int)*COLUMS); score++;//分數加1 speed = (score /100)%MAX_SPEED; //變速 i++; } else if(c == 0) { break; } } }刪除滿行中利用了<windows.h>中的庫函數memmove保證疊加區域復制正確的特性,例如
#define ROWS 5
#define COLUMS 2
int table[ROWS][COLUMS] = {
{0,0},
{1,1},
{0,1},
{1,1},
{1,0}
};
利用memmove(table[1],table[0],sizeof(int)*COLUMS*i)它完成的功能是,是從table[0]地址處開始復制COLUMS*i個int字節個數據將其放到以table[1]地址開始的地方,具體操作如圖:
memset(table[0],0,sizeof(int)*COLUMS); 即把table[0]那一行清空,因為它是最上面那一行,所以只要有滿行被刪除,那么最上面那一行永遠都應該是空的。
14.創建新游戲
函數名稱:new_game
函數功能:創建一個新游戲。具體實現如下:
//創建新游戲 void new_game() { //將桌面表格全部置零,清空桌面上殘余的方形 memset(table,0,sizeof(int)*COLUMS*ROWS); start = clock(); //初始化時鐘 next = (shape)random(SHAPE_COUNT); //初始化下一個方形 score = 0; //得分初始化為0 speed = 0; //速度初始化為0 }15.運行游戲
函數名稱:run_game
函數功能:運行游戲。具體實現如下:
//運行游戲 void run_game() { finish = clock(); if((finish - start) > (MAX_SPEED-speed)*100)//設定方形跳動的時間間隔 { move_down(); //向下移動 start = clock();//重新記錄下一次跳動的開始時間 InvalidateRect(gameWND,NULL,TRUE);//刷新整個客戶區,重新跳動之后的圖形 } }16.操作當前方塊
函數名稱:next_shape
函數功能:將下一個方形設為當前的方塊,并隨機生成下一個方塊。具體實現如下:
void next_shape() { current = next; //將下一個方形設置為當前方形 memcpy(current_coordinate,shape_coordinate[next],sizeof(int)*BLOCK_COUNT*2); //shapex的值為方塊放在游戲桌面正中央位置時其最左邊的x值 //shapey值為0 shapex = (COLUMS - ((maxX()-minX())))/2; shapey = 0; //隨機生成下一個方塊 next = (shape)random(SHAPE_COUNT); }17.取隨機數
函數名稱:random
函數功能:取得一個隨機數,例如,random(7)將返回一個0-6之間的隨機數。具體實現如下:
int random(int seed) { if( 0 == seed) { return 0; } srand((unsigned)time(NULL));//以當前時間作為srand的seed //srand()函數就是給rand()提供種子seed。如果srand()中的形參每次都是同一個值 //那么每次運行rand()產生的隨機數也是一樣的, 所以為了rand()每次產生不一樣的隨機數 //通常把當前時間作為srand()的參數,這樣就可以得到不一樣的srand()參數,從而rand()也 //就產生不一樣的種子 return (rand() % seed); }18.繪圖
函數名稱:paint
函數功能:將內存位圖輸出到窗口上。具體實現如下:
//paint func void paint() { PAINTSTRUCT ps; HDC hdc; draw_table(); /* *BeginPaint函數為指定窗口做繪圖工作前的相關準備工作,將繪圖有關的信息填充到 *一個PAINTSTRUCT結構中。原型如下: * HDC BeginPaint( * HWND hwnd, //[輸入]被重繪的窗口句柄 * LPPAINTSTRUCT lpPaint //[輸出]指向一個用來接收繪圖信息的PAINTSTRUCT結構 * ) * 返回值: 如果函數成功,則返回值是指定窗口的"顯示設備描述表"句柄。 * 否則返回值為NULL,表明沒有得到顯示設備的內容。 * [注] * BeginPaint函數自動設置顯示設備內容的剪切區域,而排除任何更新區域外的區域。該 * 更新區域可以通過 InvalidateRect 或 InvalidateRgn 函數來設置,也可以是系統在改 * 變大小,移動,創建,滾動后設置的。如果更新區域被標記為可擦除的,BeginPaint將發 * 生一個WM_ERASEBKGND消息給窗口。如果窗口類有一個自己的背景刷,那么BeginPaint將 * 使用這個刷子來擦除更新區域的背景。 * BeginPaint與EndPaint只能配對使用,不能單獨使用,BeginPaint返回一個用來繪圖的客 * 戶區的顯示設備內容的HANDLE, 而EndPaint則終止繪畫請求,并釋放設備內容。 * 需要注意的是BeginPaint和 EndPaint只能在響應WM_PAINT消息時使用,而且只能調用一次 * * 如果被繪畫的客戶區中有一個caret(caret:插入符。是窗口客戶區中的一個閃爍的線,塊, * 或位圖。插入符通常表示文本或圖形將被插入的地方。即一閃一閃的光標),BeginPaint將 * 自動隱藏該符號,而保證它不被擦除。 */ hdc = BeginPaint(gameWND,&ps); //BOOL BitBlt(HDC hdcDest, int nXDest,int nYDest, int nWidth,int nHeight, HDC hdcSrc // ,int nXSrc, int nYSrc,DWORD dwRop); 如果dwRop 是SRCCOPY 則表示將源hdcSrc //內存位圖原樣拷貝到目標hdcDest處。 BitBlt(hdc,clientRC.left,clientRC.top,clientRC.right,clientRC.bottom,memDC,0,0,SRCCOPY); EndPaint(gameWND,&ps); }19.繪制游戲桌面
函數名稱:draw_table
函數功能:繪制游戲桌面
處理流程:首先用黑色矩形填充桌面背景區,接著判斷游戲的狀態,如果是開始狀態,用黃色字顯示游戲開始畫面,如果是結束狀態,用紅色字顯示 GAME OVER ; 如果是游戲運行狀態,則依次繪制游戲桌面,當前方塊,下一個方塊,得分和游戲幫助等。具體實現如下:
//繪制游戲桌面 void draw_table() { HBRUSH hBrushOld; HPEN hPenOld; HFONT hFontOld; RECT rc; int x0,y0,w; int x,y,i,j; char* str; ? w = clientRC.bottom /(ROWS + 2); //設置一個方塊的寬度 x0 = y0 = w; //用黑色矩形填充桌面背景區 FillRect(memDC,&clientRC,blackBrush); if(state == game_start || state == game_over) { // 繪制游戲開始或結束的標題界面 memcpy(&rc,&clientRC,sizeof(RECT)); rc.bottom = rc.bottom / 2; hFontOld = (HFONT)SelectObject(memDC,bigFont); SetBkColor(memDC,BLACK); //如果游戲是開始狀態,則用黃色字顯示游戲開始界面 if(state == game_start) { str = APP_TITLE; SetTextColor(memDC,YELLOW); }else{ //如果游戲是結束狀態,則用紅色字顯示 GAME OVER str = GAMEOVER; SetTextColor(memDC,RED); } ? DrawText(memDC,str,strlen(str),&rc,DT_SINGLELINE | DT_CENTER| DT_BOTTOM); SelectObject(memDC,hFontOld); ? //提示信息 hFontOld = (HFONT)SelectObject(memDC,smallFont); rc.top = rc.bottom; rc.bottom = rc.bottom*2; if(state == game_over) { SetTextColor(memDC,YELLOW); sprintf(score_char,SCORE,score); DrawText(memDC,score_char,strlen(score_char),&rc,DT_SINGLELINE|DT_CENTER|DT_TOP); } ? SetTextColor(memDC,STONE); DrawText(memDC,press_enter,strlen(press_enter),&rc,DT_SINGLELINE|DT_CENTER|DT_VCENTER); SelectObject(memDC,hFontOld); ? return; } ? //畫桌面上殘留的方塊 hBrushOld = (HBRUSH)SelectObject(memDC,stoneBrush); for(i = 0; i < ROWS; i++) { for(j = 0; j < COLUMS; j++) { if(table[i][j] == 1) { x = x0 + j*w; y = y0 + i*w; Rectangle(memDC,x,y,x+w+1,y+w+1); } } } ? SelectObject(memDC,hBrushOld); ? //畫當前的方塊 hBrushOld = (HBRUSH)SelectObject(memDC,shapeBrush[current]); for(i = 0; i < 4; i++) { //取得當前方形中某一單元在桌面上的實際坐標值 x = x0 + (current_coordinate[i][0] + shapex)*w; y = y0 + (current_coordinate[i][1] + shapey)*w; if(x < x0 || y < y0) { continue; } //繪制實心矩形 Rectangle(memDC,x,y,x+w+1,y+w+1); } SelectObject(memDC,hBrushOld); //畫桌面上的表格線條 hPenOld = (HPEN)SelectObject(memDC,grayPen); for(i = 0; i<= ROWS; i++) { /*MoveToEx(memDC,x0+i*w,y0,NULL);*/ MoveToEx(memDC,x0,y0+i*w,NULL); LineTo(memDC,x0+COLUMS*w,y0+i*w); } for(i = 0; i <= COLUMS; i++) { MoveToEx(memDC,x0+i*w,y0,NULL); LineTo(memDC,x0+i*w,y0+ROWS*w); } SelectObject(memDC,hPenOld); ? //畫玩家得分 x0= x0+COLUMS*w +3*w;//設置得分字樣在桌面上的偏移位置 y0=y0+w; hFontOld = (HFONT)SelectObject(memDC,smallFont);//選擇字體 SetTextColor(memDC,YELLOW);//設置字體顏色 sprintf(score_char,SCORE,score); /*sprintf(score_char,SCORE,shapex);*/ /*sprintf(score_char,SCORE,table[29][0]);*/ TextOut(memDC,x0,y0,score_char,strlen(score_char));//繪制得分 //畫下一個方塊 y0 += w; SetTextColor(memDC,STONE); TextOut(memDC,x0,y0,"NEXT",4); x0 = x0 + w; y0 += 2*w; hBrushOld = (HBRUSH)SelectObject(memDC,shapeBrush[next]); for(i = 0; i < 4; i++) { x = x0 + shape_coordinate[next][i][0]*w; y = y0 + shape_coordinate[next][i][1]*w; Rectangle(memDC,x,y,x+w+1,y+w+1); } ? SelectObject(memDC,hBrushOld); //打印幫助信息 x0 = (COLUMS + 2)*w; y0 += 4*w; SetTextColor(memDC,GRAY); i = 0; while(help[i]) { TextOut(memDC,x0,y0,help[i],strlen(help[i])); y0 += w; i++; } SelectObject(memDC,hFontOld); }20.處理按鍵
函數名稱:key_down
函數功能:處理鍵盤按下事件。
處理流程:1.如果游戲狀態不是運行狀態,按回車鍵是進行暫停/開始游戲的切換鍵。2.游戲運行狀態,按向上鍵或空格鍵旋轉當前方塊,按向左鍵左移當前方塊,按向右鍵右移當前方塊,按向下鍵下移當前方塊。按回車鍵,來回切換暫停/開始游戲。具體實現如下:
//按鍵事件處理 void key_down(WPARAM wParam) { //如果游戲狀態處理非運行狀態,按下回車鍵,則開始游戲 if(state != game_run) { if(wParam == VK_RETURN) { switch(state) { case game_start: next_shape(); state = game_run; break; case game_pause: state = game_run; break; case game_over: new_game(); next_shape(); state = game_run; break; } } } else //在游戲運行狀態下,響應鍵盤事件 { switch(wParam) { case VK_SPACE: //空格鍵/向上鍵則旋轉當前方塊 case VK_UP: transform(); break; case VK_LEFT: //左向鍵左移當前方塊 move_left(); break; case VK_RIGHT: //右向鍵右移當前方塊 move_right(); break; case VK_DOWN: //下向鍵下移當前方塊 move_down(); break; case VK_RETURN://回車鍵暫停當前游戲 state = game_pause; break; } } //重繪客戶區 InvalidateRect(gameWND,NULL,TRUE); }21.改變窗口大小
函數名稱:resize
函數功能:改變窗口大小時調用的函數。具體實現如下:
void resize() { HDC hdc; LOGFONT lf; hdc = GetDC(gameWND); GetClientRect(gameWND,&clientRC); SelectObject(memDC,memBMOld); DeleteObject(memBM); //重新創建合適的內存位圖 memBM = CreateCompatibleBitmap(hdc,clientRC.right,clientRC.bottom); memBMOld = (HBITMAP)SelectObject(memDC,memBM); //重新創建合適的大字體和小字體 DeleteObject(bigFont); memset(&lf,0,sizeof(LOGFONT)); lf.lfWidth = (clientRC.right - clientRC.left) / CHARS_IN_LINE; lf.lfHeight = (clientRC.bottom - clientRC.top) /4; lf.lfItalic = 1; lf.lfWeight = FW_BOLD; bigFont = CreateFontIndirect(&lf); ? DeleteObject(smallFont); lf.lfHeight = clientRC.bottom / (ROWS + 2); lf.lfWidth = lf.lfHeight / 2; lf.lfItalic = 0; lf.lfWeight = FW_NORMAL; smallFont = CreateFontIndirect(&lf); ? ReleaseDC(gameWND, hdc); }22.處理消息
函數名稱:WndProc
函數功能:回調函數,用來處理Windows消息。具體實現如下:
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_SIZE: //響應改變窗口大小的消息 resize(); return 0; case WM_ERASEBKGND: //響應重繪背景的消息 return 0; case WM_PAINT: //相應繪制消息 paint(); return 0; case WM_KEYDOWN: //相應按鍵消息 key_down(wParam); return 0; case WM_DESTROY: //響應銷毀窗口的消息 PostQuitMessage(0); return 0; } //其它消息用Windows默認的消息處理函數處理 return DefWindowProc(hwnd, message,wParam,lParam); }23.初始化
函數名稱:initialize
函數功能:初始化內存位圖,畫筆,字體等資源。具體實現如下:
void initialize() { LOGFONT lf; HDC hdc; int i; ? hdc = GetDC(gameWND); GetClientRect(gameWND,&clientRC); //取得窗口客戶區大小 memDC = CreateCompatibleDC(hdc); //創建內存DC //創建內存位圖 memBM = CreateCompatibleBitmap(hdc, clientRC.right,clientRC.bottom); //將內存位圖選入到內存DC中 memBMOld = (HBITMAP)SelectObject(memDC,memBM); //創建黑色畫筆和深灰色畫筆 blackBrush = CreateSolidBrush(BLACK); stoneBrush = CreateSolidBrush(STONE); //創建每個方塊所對應顏色的畫筆 for(i = 0; i < SHAPE_COUNT;i++) { shapeBrush[i] = CreateSolidBrush(shape_color[i]); } grayPen = CreatePen(PS_SOLID,1,GRAY);//創建灰色畫筆 memset(&lf,0,sizeof(LOGFONT)); //創建大字體 lf.lfWidth = (clientRC.right - clientRC.left) / CHARS_IN_LINE; lf.lfHeight = (clientRC.bottom - clientRC.top) /4; lf.lfItalic = 1; lf.lfWeight = FW_BOLD; bigFont = CreateFontIndirect(&lf); //創建小字體 lf.lfHeight = clientRC.bottom / (ROWS + 2); lf.lfWidth = lf.lfHeight / 2; lf.lfItalic = 0; lf.lfWeight = FW_NORMAL; smallFont = CreateFontIndirect(&lf); ReleaseDC(gameWND,hdc); }24.釋放資源
函數名稱:finalize
函數功能:游戲結束時調用該函數釋放initialize中創建的資源。具體實現如下:
void finalize() { int i = 0; DeleteObject(blackBrush); DeleteObject(stoneBrush); for(i = 0; i < SHAPE_COUNT; i++) { DeleteObject(shapeBrush[i]); } ? DeleteObject(grayPen); DeleteObject(bigFont); DeleteObject(smallFont); SelectObject(memDC,memBMOld); DeleteObject(memBM); DeleteDC(memDC); }25.系統入口函數
函數名稱:WinMain
函數功能:Windows程序入口,類似于DOS程序的main函數。具體實現如下:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance,PSTR szCmdLine,int iCmdShow) { MSG msg; //聲明一個消息結構體變量 WNDCLASS wndclass; //聲明一個窗口類變量 ? //初始化窗口信息 wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL,IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = APP_NAME; //注冊窗口類 RegisterClass(&wndclass); //創建窗口 gameWND = CreateWindow(APP_NAME, APP_TITLE, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL,NULL, hInstance,NULL); //初始化游戲基本資源 initialize(); //顯示窗口 ShowWindow(gameWND, iCmdShow); UpdateWindow(gameWND); //刷新窗口 //創建游戲 new_game(); for(;;)//進入消息循環 { if(state == game_run) { run_game(); } ? if(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)) { if(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); }else{ break; } } } ? finalize(); ? return msg.wParam; } 至此游戲的基本功能已經實現。5.系統操作過程
F5游戲運行后,首先進入歡迎主界面,如圖:
在歡迎主界面中按任意鍵進入游戲,游戲界面如圖:
游戲結束界面如圖:
6.總結與Bug記錄
Bug.1
//畫桌面上的表格線條
??? hPenOld = (HPEN)SelectObject(memDC,grayPen);
??? for(i = 0; i<= ROWS; i++)
??? {
???? ?? /*MoveToEx(memDC,x0+i*w,y0,NULL);*/
??????? MoveToEx(memDC,x0,y0+i*w,NULL);
??????? LineTo(memDC,x0+COLUMS*w,y0+i*w);
??? }
Bug.2
顯示出了SCORE字樣但是沒有顯示得分情況,
將#define SCORE "SCORE? %4"改成:#define SCORE "SCORE? %4d"
Bug.3
方塊旋轉有問題
void turn_right()
{
??? int i = 0;
??? int x, y;
??? for(i = 0; i< 4; i++)
??? {
??????? x = current_coordinate[i][0];
??????? y = current_coordinate[i][1];
?? ???? current_coordinate[i][0] = -y; //沒有寫
??????? current_coordinate[i][1] = x;
??? }
}
轉載于:https://www.cnblogs.com/AI-Algorithms/p/3428487.html
總結
以上是生活随笔為你收集整理的C项目实践--俄罗斯方块(2)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux to extract con
- 下一篇: 问题 RadioButtonList+T