【C语言】百行代码实现—俄罗斯方块
文章目錄
- 自述
- 整體框架和流程
- 開始游戲頁面設(shè)計
- 游戲設(shè)計流程介紹
- 注意
- 可執(zhí)行源碼-全部
自述
這個代碼是19年的末尾寫的,最近就想著想把這個用博客分享出來,一方面是為了鞏固自己的知識,另一方面也希望同學(xué)們能夠因為這篇文章有所收獲,所以也在原來代碼的基礎(chǔ)上添加了蠻多注釋的(感覺就和重新寫了一遍俄羅斯方塊似的😂) ,如果對于這篇文章有任何問題都可以提出來,也希望這篇文章能夠幫助到大家,謝謝大家。
整體框架和流程
開始游戲頁面設(shè)計
歡迎界面
游戲開始界面
這些大家應(yīng)該都可以知道這個頁面的布局了吧,接下來我們來了解對于這些布局我們應(yīng)該要利用哪些函數(shù)來設(shè)置
初始化畫布
如果我們像要進(jìn)行圖形界面的編程和繪畫的話就一定要去初始化畫布
設(shè)置窗口標(biāo)題:利用window接口
//設(shè)置窗口標(biāo)題:利用window接口HWND hwnd=GetHWnd();//hwnd變量來接收,句柄SetWindowText(hwnd,_T("俄羅斯方塊 宋同學(xué)2019.12.18"));對于顯示中間的文字
//設(shè)置文本的字體樣式setfont(40,0,_T("微軟雅黑"));//高度40個像素寬度自己適應(yīng)setcolor(WHITE);outtextxy(205,200,_T("俄羅斯方塊"));//利用坐標(biāo)setfont(22,0,_T("楷體"));outtextxy(175,300,_T("編程從俄羅斯方塊開始!"));Sleep(3000);//休眠3秒鐘那樣轉(zhuǎn)到游戲界面就不會突兀,讓玩家有準(zhǔn)備時間對于歡迎界面所有的代碼:
void welcome(){//初始畫布initgraph(550,660);//設(shè)置窗口標(biāo)題:利用window接口HWND hwnd=GetHWnd();//hwnd變量來接收,句柄,用于SetWindowText(hwnd,_T("俄羅斯方塊 宋同學(xué)2019.12.18"));//設(shè)置文本的字體樣式setfont(40,0,_T("微軟雅黑"));//高度40個像素寬度自己適應(yīng)setcolor(WHITE);outtextxy(205,200,_T("俄羅斯方塊"));setfont(22,0,_T("楷體"));outtextxy(175,300,_T("編程從俄羅斯方塊開始!"));Sleep(3000);//休眠3秒鐘那樣轉(zhuǎn)到游戲界面就不會突兀,讓玩家有準(zhǔn)備時間}初始化游戲界面
有些變量是設(shè)置為全局變量所以先不要太在意,等會兒都會有源碼分享出來
由于代碼量還是有一些的所以接下來就不每一個函數(shù)都進(jìn)行分開講,感覺那樣那這篇博客就有點長了,后面有源碼有挺多注釋的,大家可以慢慢看。
游戲設(shè)計流程介紹
- 設(shè)計圖形界面
- 更新"下一個"框框(利用隨機取,當(dāng)主窗口的圖形落到最底下就更新(初始就直接先來一個))
- 將下一個框框中的形態(tài)的圖形復(fù)刻到主窗口的中
- 實現(xiàn)主游戲窗口的方塊進(jìn)行移動
- 進(jìn)行消行處理
- 更新分?jǐn)?shù)以及等級
注意
其實這游戲設(shè)計起來其實不難,在我看來難點可能是出現(xiàn)在消行和更新方塊的操作上,還有一點要注意的是,圖形的更新可以通過先用黑方塊把指定位置都給覆蓋然后再重新畫一個(這一點即適用于方塊的下落也適用于下一個方塊的更新上),對于消行的處理要注意的是:如果第i行的方塊滿了我們把這一行消了以后那么我們還要再次判斷i行而不能直接判斷i+1行,因為當(dāng)我們第i行消了以后上面的方塊壓下來那么第i行可能還可以再消一次。
由于可能圖形界面大家沒怎么接觸過所以我就單獨的寫了一下,其他的部分可能還是得靠大家自己的理解了,也沒辦法把所有的想法全部都寫出來,下面的源碼中加了蠻多的注釋還是希望大家看看代碼看看注釋這樣也能理解的深刻一些。
可執(zhí)行源碼-全部
頭文件
#include<stdio.h> #include<string> #include<iostream> #include<graphics.h>//圖形界面編程 #include<time.h> #include<conio.h>//kbhit()鍵盤捕捉的使用需要的頭文件各種宏定義及全局變量
#define BLOCK_COUNT 5 #define BlOCK_WIDTH 5 #define BLOCK_HEIGHT 5 #define UNIT_SIZE 20//右上角打印的方塊的像素 #define START_X 130 #define START_Y 30 #define KEY_UP 72//向上按鍵ASCII碼值為72 #define KEY_RIGHT 77 #define KEY_LEFT 75 #define KEY_DOWN 80 #define KEY_SPACE 32 int score=0;//分?jǐn)?shù) int rank=0;//等級int speed=500; int minX=30; int minY=30;//設(shè)置枚舉值 typedef enum{BLOCK_UP,BLOCK_RIGHT,BLOCK_DOWN,BLOCK_LEFT}block_dir_t;typedef enum{MOVE_DOWN,MOVE_LEFT,MOVE_RIGHT }move_dir_t;int NextIndex=-1;//下一個方塊的種類 int BlockIndex=-1;//當(dāng)前方塊的種類int color[BLOCK_COUNT]={GREEN,CYAN,MAGENTA,BROWN,YELLOW };int visit[30][15];int markcolor[30][15];//表示對應(yīng)位置的方塊的顏色//初始化各種方塊,包括其形態(tài)變換 int block[BLOCK_COUNT*4][BlOCK_WIDTH][BLOCK_HEIGHT]={// | 形方塊{ 0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0 },{ 0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0 },{ 0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0 },{ 0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0 },// L 形方塊{ 0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0 },{ 0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,0,0,0,0,0,0,0,0 },{ 0,0,0,0,0,0,1,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,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,0,0 },// 田 形方塊{ 0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 },{ 0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 },{ 0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 },{ 0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 },// T 形方塊{ 0,0,0,0,0,0,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0 },{ 0,0,0,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,1,0,0,0,0,0,0 },{ 0,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0 },{ 0,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0 },// Z 形方塊{ 0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0 },{ 0,0,0,0,0,0,0,1,0,0,0,1,1,0,0,0,1,0,0,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,0,0,0 },{ 0,0,0,0,0,0,0,1,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0 }, };//5種方塊每種方塊4種形態(tài)方塊的表示是用5行5列來表示各類方法
void welcome(){//初始畫布initgraph(550,660);//設(shè)置窗口標(biāo)題:利用window接口HWND hwnd=GetHWnd();//hwnd變量來接收SetWindowText(hwnd,_T("俄羅斯方塊 宋同學(xué)2019.12.18"));//設(shè)置文本的字體樣式setfont(40,0,_T("微軟雅黑"));//高度40個像素寬度自己適應(yīng)setcolor(WHITE);outtextxy(205,200,_T("俄羅斯方塊"));setfont(22,0,_T("楷體"));outtextxy(175,300,_T("編程從俄羅斯方塊開始!"));Sleep(3000);//休眠3秒鐘那樣轉(zhuǎn)到游戲界面就不會突兀,讓玩家有準(zhǔn)備時間} //初始化游戲界面 void initGameScene(){char str[16];cleardevice();rectangle(27,27,336,635);rectangle(29,29,334,633);rectangle(370,50,515,195);setfont(24,0,_T("楷體"));setcolor(LIGHTGRAY);outtextxy(405,215,_T("下一個"));setcolor(RED);outtextxy(405,280,_T("分?jǐn)?shù)"));sprintf(str,"%d",score);outtextxy(415,310,str);outtextxy(405,375,_T("等級"));sprintf(str,"%d",rank);outtextxy(415,405,str);//操作說明setcolor(LIGHTBLUE);outtextxy(390,475,_T("操作說明"));outtextxy(390,500,"↑:旋轉(zhuǎn)");outtextxy(390,525,"↓:下降");outtextxy(390,550,"←:左移");outtextxy(390,575,"→:右移");outtextxy(390,600,"空格:暫停");} //清除右上角方塊(用一個又一個的小黑方塊把它遮住) void clearBlock(){setcolor(BLACK);setfont(23,0,"楷體");//高度23for(int i=0;i<BLOCK_HEIGHT;i++){for(int j=0;j<BlOCK_WIDTH;j++){//"■"int x=391+UNIT_SIZE*j;int y=71+UNIT_SIZE*i;outtextxy(x,y, "■"); }} }//在右上角區(qū)域繪制下一個方塊 void drawBlock(int x,int y){setcolor(color[NextIndex]);setfont(23,0,"楷體");//高度23for(int i=0;i<BLOCK_HEIGHT;i++){for(int j=0;j<BlOCK_WIDTH;j++){//"■"if(block[NextIndex*4][i][j]==1){//對應(yīng)哪一種方塊(NextIndex*4)int x2=x+UNIT_SIZE*j;int y2=y+UNIT_SIZE*i;outtextxy(x2,y2, "■"); }}}}//繪制方塊,特定位置特定方向的方塊 void drawBlock(int x,int y,int blockIndex,block_dir_t dir){setcolor(color[NextIndex]);setfont(23,0,"楷體");//高度23,寬度自動適應(yīng)int id=blockIndex*4+dir;for(int i=0;i<BLOCK_HEIGHT;i++){for(int j=0;j<BlOCK_WIDTH;j++){//"■"if(block[id][i][j]==1){//對應(yīng)哪一種方塊(NextIndex*4)int x2=x+UNIT_SIZE*j;int y2=y+UNIT_SIZE*i;outtextxy(x2,y2, "■"); }}} }//下降過程中的清除方塊的過程,一個一個的 //清除指定位置指定方向的方塊 //參數(shù)x:方塊的左上角的x坐標(biāo) //參數(shù)y:方塊的左上角在游戲區(qū)域內(nèi)的坐標(biāo),距離游戲區(qū)域頂部的距離 void clearBlock(int x,int y,block_dir_t dir){setcolor(BLACK);int id=BlockIndex*4+dir;y+=START_Y ;for(int i=0;i<5;i++){for(int j=0;j<5;j++){if(block[id][i][j]==1){ //擦除該方塊的第i行第j列outtextxy(x+20*j,y+20*i,"■");}}} }void nextblock(){clearBlock();//清除//隨機選擇一種方塊srand(time(NULL));//使用時間函數(shù)的返回值來作為時間種子NextIndex=rand()%BLOCK_COUNT;drawBlock(391,71);//畫方塊(根據(jù)序號) }//如果可以在指定位置可以向指定位置移動就返回1,否則返回0 int moveAble(int x0,int y0,move_dir_t moveDir,block_dir_t blockDir){//計算當(dāng)前方塊的左上角在30*15的游戲區(qū)域中的位置(多少行多少列)int x=(y0-minY)/UNIT_SIZE;//一個方塊表示20像素int y=(x0-minX)/UNIT_SIZE;int id=BlockIndex*4+blockDir;//哪個方塊哪種狀態(tài)int ret=1;if(moveDir==MOVE_DOWN){for(int i=0;i<5;i++){for(int j=0;j<5;j++){if(block[id][i][j]==1&&(x+i+1>=30||visit[x+i+1][y+j]==1)){ret=0;}}}}else if(moveDir==MOVE_LEFT){for(int i=0;i<5;i++){for(int j=0;j<5;j++){if(block[id][i][j]==1&&(y+j==0||visit[x+i][y+j-1]==1)){ret=0; }}}}else if(moveDir==MOVE_RIGHT){for(int i=0;i<5;i++){for(int j=0;j<5;j++){if(block[id][i][j]==1&&(y+j+1==15||visit[x+i][y+j+1]==1)){ret=0; }}}}return ret; }//檢測函數(shù)是否結(jié)束 void failCheck(){//剛下來的時候都是向上的if(!moveAble(START_X,START_Y,MOVE_DOWN,BLOCK_UP)){setcolor(WHITE);setfont(45,0,"隸體");outtextxy(75,300,"GAME OVER!");Sleep(1000);system("pause");closegraph();exit(0);} }//等待函數(shù),這樣比sleep函數(shù)好的地方是不會卡頓 void wait(int interval){int count=interval/10;for(int i=0;i<count;i++){Sleep(10);if(kbhit()){return;}} }//很多時候可以利用反向思維去完成函數(shù) //判斷當(dāng)前方塊能否轉(zhuǎn)向到指定方向 //注意此時還沒開始轉(zhuǎn) int rotatable(int x,int y,block_dir_t Dir){int id=BlockIndex*4+Dir;int xIndex=(y-minY)/20;int yIndex=(x-minX)/20;if(!moveAble(x,y,MOVE_DOWN,Dir)){return 0;}for(int i=0;i<5;i++){for(int j=0;j<5;j++){if(block[id][i][j]==1&&(yIndex+j<0||yIndex+j>=15||visit[xIndex+i][yIndex+j]==1)){return 0;}}}return 1; }//方塊固定函數(shù) void mark(int x,int y,int blockIndex,block_dir_t dir){int id=blockIndex*4+dir;int x2=(y-minX)/20;int y2=(x-minY)/20;for(int i=0;i<5;i++){for(int j=0;j<5;j++){if(block[id][i][j]==1){visit[x2+i][y2+j]=1;markcolor[x2+i][y2+j]=color[blockIndex];}}}} //進(jìn)行上下左右的移動 void move(){int x=START_X;int y=START_Y;int k=0;//偏移量block_dir_t blockDir=BLOCK_UP;int cur_speed=speed;//先檢測游戲是否失敗,再確認(rèn)下降failCheck();//持續(xù)下降while(1){if(kbhit()){int key=getch();//和kbhit()組合使用if(key==KEY_SPACE){//碰到空格getch();//實現(xiàn)暫停}}//清除當(dāng)前方塊clearBlock(x,k,blockDir);//清除特定位置特定朝向的方塊if(kbhit()){int key=getch();if(key==KEY_UP){/*********************變形*要求:要判斷能不能翻轉(zhuǎn)-》定義函數(shù)********************/block_dir_t nextDir=(block_dir_t)((blockDir+1)%4);if(rotatable(x,y+k,nextDir)){blockDir=nextDir;}}else if(key==KEY_DOWN){cur_speed=50;}else if(key==KEY_LEFT){if(moveAble(x,y+k+20,MOVE_LEFT,blockDir)){x-=UNIT_SIZE;}}else if(key==KEY_RIGHT){if(moveAble(x,y+k+20,MOVE_RIGHT,blockDir)){x+=UNIT_SIZE;}}}k+=20;//繪制當(dāng)前方塊drawBlock(x,y+k,BlockIndex,blockDir);wait(cur_speed);//利用sleep函數(shù)不太靈活容易出現(xiàn)卡頓所以就自己設(shè)計一個函數(shù)//方塊的“固化”處理if(!moveAble(x,y+k,MOVE_DOWN,blockDir)){mark(x,y+k,BlockIndex,blockDir);//固定函數(shù)break;}}}void newblock(){//確定即將使用的方塊的類別BlockIndex=NextIndex;//繪制剛從頂部下降的方塊drawBlock(START_X,START_Y);//讓新出現(xiàn)的方塊停頓一會兒,讓用戶識別到Sleep(100);//0.1秒//右上角區(qū)域繪制下一個方塊nextblock();//方塊降落move(); }void down(int x){for(int i=x;i>0;i--){//清除第i行,第j列的方塊消失for(int j=0;j<15;j++){if(visit[i-1][j]){visit[i][j]=1;markcolor[i][j]=markcolor[i-1][j];setcolor(markcolor[i][j]);outtextxy(20*j+minX,20*i+minY,"■");}else{visit[i][j]=0;setcolor(BLACK);outtextxy(20*j+minX,20*i+minY,"■");}}}//清除最上面行setcolor(BLACK);for(int j=0;j<15;j++){visit[0][j]=0;outtextxy(20*j+minX,minY,"■");} }//更新分?jǐn)?shù) void addScore(int lines){char str[10];setcolor(RED);score+=lines*10;sprintf(str,"%d",score);outtextxy(415,310,str); }//更新等級 void updateGrade(){//更新等級提示//50分隔一級char str[16];rank=score/50+1;sprintf(str,"%d",rank);outtextxy(425,405,str);//更新速度,自己可調(diào)整speed=500-rank*100;if(speed<=100){speed=100;}}//消行處理 void check(){int i;int j;int clearLines=0;for(i=29;i>=0;i--){//利用visit函數(shù)可以提高效率for(j=0;j<15&&visit[i][j];j++);//執(zhí)行到此處時有兩種情況//1.i行沒滿,此時j<15//2.i行滿了,此時j>=15if(j>=15){//此時第i行已經(jīng)滿了//此時來消除第i行down(i);i++;//可能那一行可能還是可以消的所以再檢測一遍clearLines++;}}//更新分?jǐn)?shù)addScore(clearLines);//更新等級updateGrade(); }主方法:
int main(){welcome();initGameScene();//產(chǎn)生新方塊nextblock();Sleep(500);//初始化訪問數(shù)組memset(visit,0,sizeof(visit));//從地址開始清為零大小為整個visit的數(shù)組大小while(1){newblock();//消除滿行,并更新分?jǐn)?shù)和等級check();}system("pause");closegraph();return 0; }總結(jié)
以上是生活随笔為你收集整理的【C语言】百行代码实现—俄罗斯方块的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 每个 Linux 用户都应该知道的命令行
- 下一篇: Ubuntu开启NFS、SSH服务(驱动