使用C++一步步實(shí)現(xiàn)俄羅斯方塊后續(xù)
一、實(shí)驗(yàn)簡(jiǎn)介
1.1 實(shí)驗(yàn)內(nèi)容
本節(jié)實(shí)驗(yàn)我們將實(shí)現(xiàn)俄羅斯方塊主要函數(shù)的設(shè)計(jì),完成基本功能并運(yùn)行。
1.2 實(shí)驗(yàn)知識(shí)點(diǎn)
窗口的繪制
方塊類的設(shè)計(jì)
旋轉(zhuǎn)算法
移動(dòng)、消除函數(shù)
1.3 實(shí)驗(yàn)環(huán)境
xface 終端
g++ 編譯器
ncurses 庫(kù)
1.4 編譯程序
編譯命令要加上 -l 選項(xiàng)引入 ncurses 庫(kù):
g++ main.c -l ncurses
1.5 運(yùn)行程序
./a.out
1.6 運(yùn)行結(jié)果
二、實(shí)驗(yàn)步驟
2.1 頭文件
首先包含頭文件以及定義一個(gè)交換函數(shù)和隨機(jī)數(shù)函數(shù),后面用到(交換函數(shù)用來做方塊的旋轉(zhuǎn),隨機(jī)數(shù)用來設(shè)置方塊的形狀)
#include <iostream> #include <sys/time.h> #include <sys/types.h> #include <stdlib.h> #include <ncurses.h> #include <unistd.h> /* 交換a和b */ void swap(int &a, int &b){ int t=a; a = b; b = t; } /* 得到一個(gè)(min,max)區(qū)間的隨機(jī)整數(shù) int getrand(int min, int max) { return(min+rand()%(max-min+1)); }
2.2 定義類
由于程序內(nèi)容相對(duì)簡(jiǎn)單,這里只定義了一個(gè) Piece 類
class Piece { public: int score; //得分 int shape; //表示當(dāng)前方塊的形狀 int next_shape; //表示下一個(gè)方塊的形狀 int head_x; //當(dāng)前方塊首個(gè)box的位置,標(biāo)記位置 int head_y; int size_h; //當(dāng)前方塊的size int size_w; int next_size_h; //下一個(gè)方塊的size int next_size_w; int box_shape[4][4]; //當(dāng)前方塊的shpe數(shù)組 4x4 int next_box_shape[4][4]; //下一個(gè)方塊的shpe數(shù)組 4x4 int box_map[30][45]; //用來標(biāo)記游戲框內(nèi)的每個(gè)box bool game_over; //游戲結(jié)束的標(biāo)志 public: void initial(); //初始化函數(shù) void set_shape(int &cshape, int box_shape[][4],int &size_w, int & size_h); //設(shè)置方塊形狀 void score_next(); //顯示下一個(gè)方塊的形狀以及分?jǐn)?shù) void judge(); //判斷是否層滿 void move(); //移動(dòng)函數(shù) 通過 ← → ↓ 控制 void rotate(); //旋轉(zhuǎn)函數(shù) bool isaggin(); //判斷下一次行動(dòng)是否會(huì)越界或者重合 bool exsqr(int row); //判斷當(dāng)前行是否空 };
2.3 設(shè)置方塊形狀
這里通過 case 語(yǔ)句定義了7種方塊的形狀,在每次下一個(gè)方塊掉落之前都要調(diào)用以設(shè)置好它的形狀以及初始位置
void Piece::set_shape(int &cshape, int shape[][4],int &size_w,int &size_h) { /*首先將用來表示的4x4數(shù)組初始化為0*/ int i,j; for(i=0;i<4;i++) for(j=0;j<4;j++) shape[i][j]=0; /*設(shè)置7種初始形狀并設(shè)置它們的size*/ switch(cshape) { case 0: size_h=1; size_w=4; shape[0][0]=1; shape[0][1]=1; shape[0][2]=1; shape[0][3]=1; break; case 1: size_h=2; size_w=3; shape[0][0]=1; shape[1][0]=1; shape[1][1]=1; shape[1][2]=1; break; case 2: size_h=2; size_w=3; shape[0][2]=1; shape[1][0]=1; shape[1][1]=1; shape[1][2]=1; break; case 3: size_h=2; size_w=3; shape[0][1]=1; shape[0][2]=1; shape[1][0]=1; shape[1][1]=1; break; case 4: size_h=2; size_w=3; shape[0][0]=1; shape[0][1]=1; shape[1][1]=1; shape[1][2]=1; break; case 5: size_h=2; size_w=2; shape[0][0]=1; shape[0][1]=1; shape[1][0]=1; shape[1][1]=1; break; case 6: size_h=2; size_w=3; shape[0][1]=1; shape[1][0]=1; shape[1][1]=1; shape[1][2]=1; break; } //設(shè)置完形狀以后初始化方塊的起始位置 head_x=game_win_width/2; head_y=1; //如果剛初始化就重合了,游戲結(jié)束~ if(isaggin()) /* GAME OVER ! */ game_over=true; }
2.4 旋轉(zhuǎn)函數(shù)
這里用了一個(gè)比較簡(jiǎn)單的算法對(duì)方塊進(jìn)行旋轉(zhuǎn),類似于矩陣的旋轉(zhuǎn),先將 shape 數(shù)組進(jìn)行斜對(duì)角線對(duì)稱化,再進(jìn)行左右對(duì)稱,便完成了旋轉(zhuǎn),需要注意的是要判斷旋轉(zhuǎn)后方塊是否出界或重合,如果是,則取消本次旋轉(zhuǎn)。
void Piece::rotate() { int temp[4][4]={0}; //臨時(shí)變量 int temp_piece[4][4]={0}; //備份用的數(shù)組 int i,j,tmp_size_h,tmp_size_w; tmp_size_w=size_w; tmp_size_h=size_h; for(int i=0; i<4;i++) for(int j=0;j<4;j++) temp_piece[i][j]=box_shape[i][j]; //備份一下當(dāng)前的方塊,如果旋轉(zhuǎn)失敗則返回到當(dāng)前的形狀 for(i=0;i<4;i++) for(j=0;j<4;j++) temp[j][i]=box_shape[i][j]; //斜對(duì)角線對(duì)稱 i=size_h; size_h=size_w; size_w=i; for(i=0;i<size_h;i++) for(j=0;j<size_w;j++) box_shape[i][size_w-1-j]=temp[i][j]; //左右對(duì)稱 /*如果旋轉(zhuǎn)以后重合,則返回到備份的數(shù)組形狀*/ if(isaggin()){ for(int i=0; i<4;i++) for(int j=0;j<4;j++) box_shape[i][j]=temp_piece[i][j]; size_w=tmp_size_w; //記得size也要變回原來的size size_h=tmp_size_h; } /*如果旋轉(zhuǎn)成功,那么在屏幕上進(jìn)行顯示*/ else{ for(int i=0; i<4;i++) for(int j=0;j<4;j++){ if(temp_piece[i][j]==1){ mvwaddch(game_win,head_y+i,head_x+j,' '); //移動(dòng)到game_win窗口的某個(gè)坐標(biāo)處打印字符 wrefresh(game_win); } } for(int i=0; i<size_h;i++) for(int j=0;j<size_w;j++){ if(this->box_shape[i][j]==1){ mvwaddch(game_win,head_y+i,head_x+j,'#'); wrefresh(game_win); } } } }
2.5 移動(dòng)函數(shù)
如果玩家沒有按下任何按鍵,方塊需要慢速下落,所以我們不能夠因?yàn)榈却存I輸入而阻塞在 getch() ,這里用到了 select() 來取消阻塞。
/* 這里只是截取了程序的一部分,具體實(shí)現(xiàn)請(qǐng)參考源碼 */ struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec= 500000; if (select(1, &set, NULL, NULL, &timeout) == 0)
timeout 就是我們最多等待按鍵的時(shí)間,這里設(shè)置了 500000us,超過這個(gè)時(shí)間就不再等待 getch() 的輸入,直接進(jìn)行下一步。
如果在 timeout 時(shí)間內(nèi)檢測(cè)到按鍵,則下面的 if 語(yǔ)句為真,得到輸入的 key 值,通過判斷不同的 key 值進(jìn)行向左、右、下、旋轉(zhuǎn)等操作。
if (FD_ISSET(0, &set))
while ((key = getch()) == -1) ;
向左、右、下移動(dòng)的函數(shù)處理方式基本相同,這里只拿向下移動(dòng)的函數(shù)進(jìn)行說明
/* 這里只是截取了程序的一部分,具體實(shí)現(xiàn)請(qǐng)參考源碼 */ /* 如果輸入的按鍵是 ↓ */ if(key==KEY_DOWN){ head_y++; //方塊的y坐標(biāo)+1 if(isaggin()){ //如果重合或出界,則取消這次移動(dòng) head_y--; /*既然停下來了,那么把地圖上對(duì)應(yīng)的box設(shè)置為已被占用,用1表示,0表示未被占用 for(int i=0;i<size_h;i++) for(int j=0;j<size_w;j++) if(box_shape[i][j]==1) box_map[head_y+i][head_x+j]=1; score_next(); //顯示分?jǐn)?shù)以及提示下一個(gè)方塊 } /*如果能夠向下移動(dòng),那么取消當(dāng)前方塊的顯示,向下移動(dòng)一行進(jìn)行顯示,這里注意for循環(huán)的行要從下往上 else{ for(int i=size_h-1; i>=0;i--) for(int j=0;j<size_w;j++){ if(this->box_shape[i][j]==1){ mvwaddch(game_win,head_y-1+i,head_x+j,' '); mvwaddch(game_win,head_y+i,head_x+j,'#'); } } wrefresh(game_win); }
2.6 重復(fù)函數(shù)
每次移動(dòng)或旋轉(zhuǎn)之后要進(jìn)行判斷的函數(shù),函數(shù)返回真則不能行動(dòng),返回假則可以進(jìn)行下一步。
bool Piece::isaggin(){ for(int i=0;i<size_h;i++) for(int j=0;j<size_w;j++){ if(box_shape[i][j]==1){ if(head_y+i > game_win_height-2) //下面出界 return true; if(head_x+j > game_win_width-2 || head_x+i-1<0) //左右出界 return true; if(box_map[head_y+i][head_x+j]==1) //與已占用的box重合 return true ; } } return false; }
2.7 層滿函數(shù)
最后一個(gè)很重要的功能是對(duì)方塊已滿的行進(jìn)行消除,每當(dāng)一個(gè)方塊向下移動(dòng)停止后都需要進(jìn)行判斷。
void Piece::judge(){ int i,j; int line=0; //用來記錄層滿的行數(shù) bool full; for(i=1;i<game_win_height-1;i++){ //除去邊界 full=true; for(j=1;j<game_win_width-1;j++){ if(box_map[i][j]==0) //存在未被占用的box full=false; //說明本層未滿 } if(full){ //如果該層滿 line++; //行滿+1 score+=50; //加分~ for(j=1;j<game_win_width-1;j++) box_map[i][j]=0; //把該層清空(標(biāo)記為未被占用) } } /*上面判斷完后 看line的值,如果非 0 說明有層已滿需要進(jìn)行消除*/ if(line!=0){ for(i=game_win_height-2;i>=2;i--){ int s=i; if(exsqr(i)==0){ while(s>1 && exsqr(--s)==0); //查找存在方塊的行,將其下移 for(j=1;j<game_win_width-1;j++){ box_map[i][j]=box_map[s][j]; //上層下移 box_map[s][j]=0; //上層清空 } } } /*清空和移動(dòng)標(biāo)記完成以后就要屏幕刷新了,重新打印game_win*/ for(int i=1;i<game_win_height-1;i++) for(int j=1;j<game_win_width-1;j++){ if(box_map[i][j]==1){ mvwaddch(game_win,i,j,'#'); wrefresh(game_win); } else{ mvwaddch(game_win,i,j,' '); wrefresh(game_win); } } } }
三、實(shí)驗(yàn)總結(jié)
到這里幾個(gè)關(guān)鍵函數(shù)的介紹也就完成了,搞明白這些函數(shù)的功能并實(shí)現(xiàn),再參考源碼補(bǔ)全其他函數(shù)以及main函數(shù)就可以運(yùn)行啦!當(dāng)然俄羅斯方塊的實(shí)現(xiàn)方法還有很多,每個(gè)人的思路和方法可能會(huì)不一樣,或許你寫出來的俄羅斯方塊更簡(jiǎn)潔、更流暢! Enjoy it !:)
相關(guān)文章
C語(yǔ)言中sizeof()與strlen()的區(qū)別詳解
這篇文章主要給大家介紹了關(guān)于C語(yǔ)言中sizeof()與strlen()區(qū)別的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12C++數(shù)據(jù)結(jié)構(gòu)之文件壓縮(哈夫曼樹)實(shí)例詳解
這篇文章主要介紹了C++數(shù)據(jù)結(jié)構(gòu)之文件壓縮(哈夫曼樹)實(shí)例詳解的相關(guān)資料,利用哈夫曼編碼的方式對(duì)文件進(jìn)行壓縮,并且對(duì)壓縮文件可以解壓,需要的朋友可以參考下2017-07-07基于C++實(shí)現(xiàn)高精度計(jì)時(shí)器
chrono是C++ 11中的時(shí)間庫(kù),它提供了跨平臺(tái)的高精度時(shí)鐘解決方案,精確到納秒級(jí),本文主要為大家詳細(xì)介紹了如何使用chrono實(shí)現(xiàn)高精度計(jì)時(shí)器,感興趣的可以了解下2024-02-02深入解析C語(yǔ)言中的內(nèi)存分配相關(guān)問題
這篇文章主要深入地介紹了C語(yǔ)言中的內(nèi)存分配,C語(yǔ)言編程中的內(nèi)存泄漏問題一直以來都是C編程中的一大棘手問題,本文從malloc和指針等方面對(duì)C內(nèi)存進(jìn)行了深層次講解,強(qiáng)烈推薦!需要的朋友可以參考下2015-08-08C語(yǔ)言進(jìn)階之內(nèi)存操作函數(shù)詳解
這篇文章主要為大家學(xué)習(xí)介紹了C語(yǔ)言中內(nèi)存操作函數(shù)(memcpy、memmove和memcmp)的使用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-07-07一文帶你認(rèn)識(shí)C語(yǔ)言的聯(lián)合體和枚舉
聯(lián)合體(Union)是一種特殊的數(shù)據(jù)結(jié)構(gòu),允許在同一內(nèi)存地址上存儲(chǔ)不同類型的數(shù)據(jù),這篇文章主要給大家介紹了關(guān)于C語(yǔ)言聯(lián)合體和枚舉的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-10-10