日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

用C语言实现俄罗斯方块游戏

發布時間:2023/12/10 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 用C语言实现俄罗斯方块游戏 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、工程聲明

這篇文章包括搭建游戲背景、方塊的建模、按鍵響應、方塊的隨機生成和下一塊方塊的生成等,以此記錄一次C語言的練習。本次工程涉及到圖形化,可以使用EasyX庫來畫出簡單的圖形,這個庫的下載和使用均在百度可查詢到。

本工程使用的軟件為Visual Studio 2019,新建C++空項目編寫代碼。文件的工程結構如下圖所示,一共有三個文件,一個頭文件和兩個C++文件。之所以采用cpp后綴,是因為EasyX庫只可以在C++文件使用,但是工程代碼都是C語言編寫的。

基本概念:

  • 方格:指的是游戲區域的小格子,大小為20*20;
  • 方塊:指的是俄羅斯方塊,由4*4共16個小格子構成,大小為80*80;
  • 游戲區域:背景為網格線為游戲區域,大小為300*500,存在25*15=375個方格;
  • 圖形區域:圖形區域包括游戲區域和文字顯示部分,大小為500*500;
  • 方塊的坐標點:定義方塊左下角的坐標點為整個方塊的坐標點;
  • 方塊的行序號和列序號:數格子的數量,第一個格子的行序號為0,列序號為0;序號與坐標點對應的關系為:行序號*20=坐標點的y,列序號*20=坐標點的x;

二、游戲背景的搭建

//game.c void context(void) {initgraph(500, 500); // 初始化圖形模式setorigin(0, 500); //重新定義原點setaspectratio(1, -1); //將Y上半部分設定為正半軸setlinestyle(PS_SOLID); //設定畫線樣式為虛線setlinecolor(WHITE); //線條顏色為白色for (size_t i = 0; i <= 25; i++) //畫出游戲區域網格線,寬300,高500line(0, 0 + 20 * i, 300, 0 + 20 * i); //橫線for (size_t i = 0; i <= 15; i++)line(0 + 20 * i, 0, 0 + 20 * i, 500); //豎線for (size_t i = 0; i <= 4; i++) //畫出下一個方塊網格線,寬80,高80line(370, 300 + 20 * i, 450, 300+20 * i); //橫線for (size_t i = 0; i <= 4; i++)line(370 + 20 * i, 300, 370 + 20 * i, 380); //豎線setaspectratio(1, 1);settextstyle(25, 0, _T("Consolas"));outtextxy(310, -430, _T("SCORE:0 points"));outtextxy(310, -410, _T("下一個方塊:"));outtextxy(310, -270, _T("w 轉換方向"));outtextxy(310, -240, _T("s 加快下降速度"));outtextxy(310, -210, _T("a 左移"));outtextxy(310, -180, _T("d 右移"));outtextxy(310, -150, _T("h 重啟"));setaspectratio(1, -1); }
  • 首先對圖形區域進行了初始的設定,設定線的樣式為白色虛線;
  • 用兩個循環畫出游戲區域的橫線和豎線,共25行15列,大小為300*500;
  • 再用兩個循環畫出下一塊區域的網格線,共4行4列,大小為80*80;
  • 畫出提示文本,主要寫著分數和操作提示等;
  • 效果如下圖所示。
  • 三、方塊的建模

    ?

    ?

    ?

    研究俄羅斯方塊的各種形狀,會發現他們都在在4*4的方格當中的,考慮到C語言的short數據類型剛好有2個字節,16bit的長度。所以我們可以用short數據類型來表示我們的方塊。比如下面的俄羅斯方塊可以表示為,1000 1000 1000 1000,轉化為十六進制就是8888H。

    ?

    考慮到每個俄羅斯方塊有不同的方向,不同的方向對應不同的姿態,所以我新建一個二維數組來表示所有的俄羅斯方塊。一共有7鐘俄羅斯方塊,每種有4個方向的變化,有一些變化還是原來的樣子。在game.h頭文件,聲明展示俄羅斯方塊和位置結構體,以方塊左下角那個點的坐標為整個方塊的坐標點。

    ?

    //main.c unsigned short Diamond[7][4] = {{0x000f,0x8888,0x000f,0x8888},{0x008E,0x0c88,0x00E2,0x044c},{0x002E,0x088c,0x00E8,0x0c44},{0x00CC,0x00CC,0x00CC,0x00CC},{0x006C,0x08c4,0x006c,0x08c4},{0x004E,0x08c8,0x00E4,0x04c4},{0x00C6,0x04c8,0x00c6,0x04c8} }; //game.h //方塊的坐標 typedef struct LOCATE {int x;int y; } Location;//俄羅斯方塊展示函數 void DisplayDiamond(unsigned short diamond, Location Loca, int cur_color);

    俄羅斯方塊展示函數用到的主要是EasyX里面的fillrectangle函數,可以畫有邊框的填充矩形。

    ?

    下面是俄羅斯方塊展示函數具體的代碼,判斷short數據中每一位是否是1,是的話就填充一個小格子,不是就跳過,循環操作,進行四行四列共16次判斷。擦除用的也是這個函數,只不過顏色換為了背景色(黑色),就變相達到了擦除效果。

    int color[15][35] = { 0 }; //定義網格屬性//俄羅斯方塊展示函數 void DisplayDiamond(unsigned short diamond, Location Loca, int draw_color) {int num_x, num_y;int k = 0;setfillcolor(draw_color); //設定填充顏色 for (int i = 3; i >= 0; i--) //四行填充{for (int j = 0; j < 4; j++) //四列填充{if (diamond & (0x8000 >> k))//判斷格子是否存在1,20是格子邊長{fillrectangle(Loca.x + 20 * j, Loca.y + 20 * i, Loca.x + 20 * (j + 1), Loca.y + 20 * (i + 1));num_x = Loca.x / 20 + j;num_y = Loca.y / 20 + i;color[num_x][num_y] = draw_color;} k++;}} }

    ?

  • 設定填充的顏色,這是EasyX的內置函數,直接引用就行;

  • 雙重循環,進行四行、四列填充;

  • 在函數內部進行判斷,對short數據類型的diamond(俄羅斯方塊)的每一位進行判斷,為1則進行填充,為0則跳過;

  • color是一個二維數組,用來記錄格子對應的顏色,共25行15列,剛好對應格子的數量。該變量是一個全局變量,所以game.c文件的函數都可以引用或賦值該變量。

  • 展示一下全部俄羅斯方塊,驗證一下前面建模是否正確,由下圖可以看到,形狀是正確的,符合我們的設想。

  • ?

    ?

    ?

    四、按鍵響應

    ?

    移動和翻轉需要鍵盤來進行選擇,所以這涉及到與鍵盤的交互作用,所以需要包含頭文件conio.h。conio.h不是C標準庫中的頭文件,是vc下的一個頭文件。conio是Console Input/Output(控制臺輸入輸出)的簡寫,其中定義了通過控制臺進行數據輸入和數據輸出的函數,主要是一些用戶通過按鍵盤產生的對應操作,比如getch()函數等等。

    1.實現方塊的自由下落

    //main.c while (MoveEnable(CurDiamond, CurLocation, EnableDown) == 0){DisplayDiamond(CurDiamond, CurLocation, BLACK); //先擦除CurLocation.y -= 20;DisplayDiamond(CurDiamond, CurLocation, cur_color); //再更新Sleep(speed);if (_kbhit()) //如果敲擊了鍵盤,就會返回1{userHit = _getch(); //獲取敲擊鍵盤字符Keyboard(userHit, &row, &column, &CurLocation, &speed);CurDiamond = Diamond[row][column];}}
    • 用MoveEnable函數進行判斷是否能下降,能下降就進入循環,不能下降就跳出循環。MoveEnable函數是自己定義的一個移動判斷函數,后續會進行講解;
    • 循環內部第一步先擦除,即把原來有顏色的俄羅斯方塊,填充為黑色,相當于擦除效果;
    • 把坐標點的y減去20,然后重新畫出該方塊,進行一定的延時,實現下降效果;
    • 接下來進行鍵盤的響應,_kbhit是EasyX的內部函數,主要用于檢查是否有按鍵輸入;
    • 將獲得的按鍵字符傳入Keyboard字符處理函數,獲得相應的效果。
    //game.h #define EnableDown 1 #define EnableLeft 2 #define EnableRight 3//game.c bool MoveEnable(unsigned short diamond, Location CurLocation, int direction) {bool stop=0;int num_x,num_y;switch (direction){case EnableDown: //判斷是否可以下移{for (int i = 0; i < GetWidth(diamond); i++) //對每一列進行判斷{for (int j = 0; j < GetHigh(diamond); j++) //對該列每一行進行判斷{num_x = CurLocation.x / 20 + i ; //小方格的列序號num_y = CurLocation.y / 20 + j ; //小方格的行序號if( diamond & (0x0001<<(3-i+4*j)) ) //如果該小方格是為1,即方塊該列最底部{ if ((color[num_x][num_y - 1] != BLACK) || (CurLocation.y == 0)) //如果下面那個小方格也是非黑色,就要停止{stop = 1;}break; //找到底部小格子即可跳出循環,進行下一列的判斷} }if (stop == 1) break; //既然已經要停止了,就不用去判斷下一列}break;}}return stop; }

    ?

    以上是字符處理函數,里面僅有判斷是否可以下移部分代碼,是否可以左移/右移代碼類似,這里就不進行展示了。

    swith語句選擇究竟是判斷下降、左移、右移;下降判斷,其實就是找出每一列最下面那個有色小格子到底在第幾行,找到之后就對其下面那個格子進行判斷,如果下面那個格子也是有色小格子,就說明不能下降了;如果下面那個格子是黑色小格子,那就可以下降;接下來要找第二列的底部小格子,因為有可能第一列沒被堵住,第二列被堵住了。當其中一列下面存在有色格子,就可以把stop變量置1,然后跳出循環,不用判斷后面的列了。函數返回bool數據類型,stop這個變量。如果是1,代表存在阻礙,不能再移動了。如果是0,代表不存在阻礙,可以繼續移動。以上便是函數的邏輯。

    實現方塊的改變方向和速度改變

    //game.c //響應鍵盤函數 void Keyboard(char userHit, int *row, int *column, Location *CurLocation, int *speed) {unsigned short Diamond[7][4] = {{0x000f,0x8888,0x000f,0x8888},{0x008E,0x0c88,0x00E2,0x044c},{0x002E,0x088c,0x00E8,0x0c44},{0x00CC,0x00CC,0x00CC,0x00CC},{0x006C,0x08c4,0x006c,0x08c4},{0x004E,0x08c8,0x00E4,0x04c4},{0x00C6,0x04c8,0x00c6,0x04c8} };unsigned short CurDiamond;switch(userHit){case 'w': //改變方向{CurDiamond = Diamond[*row][*column];DisplayDiamond(CurDiamond, *CurLocation, BLACK); //先擦除if ((*column) < 3)(*column)++;else(*column) = 0;CurDiamond = Diamond[*row][*column]; DisplayDiamond(CurDiamond, *CurLocation, cur_color); //再更新break;}case 's': //增加下降速度{if ((*speed) > 50 ){(*speed) -= 50;} break;}case 'h': //重新開始{for (size_t i =0; i < 25; i++) //25行{for (size_t j = 0; j < 15; j++) //15列{color[j][i] = BLACK; //每一個格子填充黑色setfillcolor(color[j][i]);fillrectangle(20 * j, 20 * i, 20 * (j + 1), 20 * (i + 1));}}break;}} }

    以上是響應鍵盤函數。首先對傳入的字符進行判斷,看到底應該進行左移、右移、加快下降速度、改變方向、重啟操作。因為我們裝有俄羅斯方塊的模型是7*4的二維數組,對應是7種俄羅斯方塊的四種方向。所以改變方向其實就是改變Dianmond的列號,我們只要在0-4循環就可以達到這種效果。因為要把行和列都保存下來,傳出去函數外部,所以這里用的是指針。

    速度是通過影響中間的間隔時間來改變的,只要間隔時間短,下降速度就快。所以每次按下s,speed變量減少50,速度就會變快。同樣的,speed也要傳出去函數外部,用的也是指針。按下‘h’是重新開始,就是清空已經下落的俄羅斯方塊,所以只要把游戲區域的所有格子填充黑色就可以達到效果,同時要改變格子的屬性color。

    左右移動和方塊的下落是同樣的原理,只不過改變的是x,每次x改變20,就是一格子的長度。先進行擦除,后重新更新方塊,就可以達到預期效果。

    五、方塊的隨機生成和下一塊方塊的生成

    //main.cunsigned short CurDiamond; //下一個方塊unsigned short NewDiamond; //下一個方塊context(); //畫出游戲背景row = 0;column = 0;CurDiamond = Diamond[row][column];cur_color = BLUE;while(1){ RandomDiamond(&new_row, &new_column, &CurLocation, &speed); //隨機生成方塊NewDiamond = Diamond[new_row][new_column];DisplayDiamond(NewDiamond, { 370,300 }, new_color); //畫出下一塊方塊while (MoveEnable(CurDiamond, CurLocation, EnableDown) == 0){//自由下落+按鍵響應代碼}row = new_row;column = new_column;cur_color = new_color;CurDiamond = Diamond[row][column];DisplayDiamond(NewDiamond, { 370,300 }, BLACK); //先擦除FullJudge();}

    方塊的隨機生成用的是自定義的RandomDiamond函數,生成方塊在Diamond數組的行和列,將值賦給NewDiamond,然后在下一塊方塊區域畫出來。while部分進行的curDiamond方塊的自由下落和按鍵響應,當不能下落之后,就會跳出while循環。將上面隨機生成的新行號和列號賦給現在的row和column,生成的顏色也賦給cur_color。然后就可以擦除下一塊區域的方塊,等待新的生成。

    在最后,會進行滿行判斷,滿行則消失,這個后面再講。

    //game.c void RandomDiamond(int* row, int* column, Location* CurLocation, int* speed) {static int num = 0;int color_Diamond[7] = { BLUE, GREEN, CYAN, RED, MAGENTA, BROWN, YELLOW };*row = rand() % 7; //生成0到6隨機數*column = rand() % 4; //生成0到3隨機數 //*row = 0;*CurLocation = { 140,500 };*speed = 200;if (num == 6) num = 0;new_color = color_Diamond[num++]; }

    以上是RandomDiamond俄羅斯方塊隨機生成函數,一開始會生成隨機的row和column,這個就是相當于隨機的俄羅斯方塊形狀。同時也會生成顏色,顏色用的是數組內部循環,于是就生成了五顏六色的俄羅斯方塊。

    六、滿行消除和更新

    //game.c void FullJudge() {int color_num=0;for (size_t i = 0; i < 25; i++) //25行{for (size_t j = 0; j < 15; j++) //15列if (color[j][i] != BLACK) color_num++; if (color_num == 15) //遇到滿行{setfillcolor(BLACK); //設定填充黑色for (size_t j = 0; j < 15; j++) //填充該行15列,即消除該行 { fillrectangle(j*20, i*20 ,(j+1)*20, (i+1)*20);color[j][i] = BLACK;}Updata(i);//更新i--; //重新判斷該行}color_num = 0;} }//消除滿行后,上面往后降,重新判斷該行 void Updata(int row) {//從滿行的第row行開始for (size_t i = row; i < 25; i++){for (size_t j = 0; j < 15; j++) //15列{color[j][i] = color[j][i + 1]; //每一個格子等于上面格子的顏色setfillcolor(color[j][i]);fillrectangle( 20 * j, 20 * i, 20 * (j + 1), 20 * (i + 1));} }static int game_point = 0;game_point++; //更新分數setaspectratio(1, 1);TCHAR s1[5];settextstyle(25, 0, _T("Consolas"));swprintf_s(s1, _T("%d"), game_point);outtextxy(380, -426, s1);setaspectratio(1, -1); }

    滿行判斷就是一行行來判斷,首先判斷第一行,對每個格子的color進行判斷,如果統計格子顏色為非黑色(即存在方塊)的數量為列數量15,這就是遇到了滿行情況。判斷是滿行情況,那就對該行所有格子填充黑色,就相當于消除。

    消除之后,要把上面的格子落下來,相當于整體往下移了一格,這就是Updata函數的任務,其中還得更新一下分數,一行一分。整體下移之后要重新對該行進行判斷,也許說不定下落的那一行又是滿行。之后循環判斷完25行,任務就完成了。

    七、判斷真實寬度和真實高度

    //game.c //計算方塊的真實高度 int GetHigh(unsigned short diamond) {int a=0;for (size_t i = 0; i < 4; i++){if ( (diamond & (0x000f << 4 * i)) > 0)a++;}return a; }//計算方塊的真實寬度 int GetWidth(unsigned short diamond) {int a = 0;for (size_t i = 0; i < 4; i++){if ((diamond & (0x8888 >> i)) > 0)a++;}return a; }

    從以下俄羅斯方塊能看出,雖然每個俄羅斯方塊都占據了4*4小格子的空間大小,但是實際上俄羅斯方塊的真實高度和寬度并不一定是4。在進行左右移動的時候,就會需要判斷俄羅斯方塊的真實高度和寬度,也就是寬度為1,高度為4。判斷的邏輯就是和0x000f、0x8888進行與邏輯運算,然后是否大于0。如果等于0,說明這一行/列都是0,那就是沒被占用的空間。

    以上就是俄羅斯方塊代碼的大部分內容,并不是全部代碼,有一些只講了邏輯,大家可以自由發揮。

    八、運行效果

    ?

    總結

    以上是生活随笔為你收集整理的用C语言实现俄罗斯方块游戏的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。