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