如何用c語(yǔ)言完成俄羅斯方塊小游戲
這估計(jì)是你在編程學(xué)習(xí)過(guò)程中的第一個(gè)小游戲開(kāi)發(fā),怎么說(shuō)呢,在這里只針對(duì)剛學(xué)程序設(shè)計(jì)的學(xué)生,就是說(shuō)剛接觸C語(yǔ)言沒(méi)多久,有一點(diǎn)功底的學(xué)生看看,簡(jiǎn)陋的代碼,簡(jiǎn)陋的實(shí)現(xiàn)
注:該代碼內(nèi)注釋加入個(gè)人理解,若有錯(cuò)誤,多多包涵
由于不需要插件,繪制出來(lái)的玩意恐怕不夠美觀,多多理解
語(yǔ)言:C的不能再C。(沒(méi)涉及啥很高深的玩意)
(高級(jí)一點(diǎn)的話用EasyX插件里面的玩意,那就很炫酷了)
涉及內(nèi)容:光標(biāo)移動(dòng),顏色,圖形繪制、消去、覆蓋和打印,鍵盤(pán)按鍵敲擊的讀取,計(jì)時(shí)器,文件的創(chuàng)建與讀取
看似簡(jiǎn)單,實(shí)際上一點(diǎn)也不難
1. 游戲概述
俄羅斯方塊是一款經(jīng)典的益智游戲,主要功能包括:
- 顯示游戲界面
- 隨機(jī)生成方塊并控制其移動(dòng)和旋轉(zhuǎn)
- 消除完整行
- 記錄并保存最高分
- 游戲結(jié)束判斷
本代碼使用控制臺(tái)操作,并通過(guò)系統(tǒng)函數(shù)控制光標(biāo)位置、打印字符實(shí)現(xiàn)圖形效果。
2. 游戲開(kāi)發(fā)所需知識(shí)
在實(shí)現(xiàn)類似游戲之前,需掌握以下知識(shí):
- 控制臺(tái)操作:控制光標(biāo)位置 (
CursorJump
) 和隱藏光標(biāo) (HideCursor
)。 - 顏色設(shè)置:通過(guò)
SetConsoleTextAttribute
改變文本顏色。 - 二維數(shù)組操作:表示游戲區(qū)域與方塊信息。
- 隨機(jī)數(shù)生成:使用
rand()
生成隨機(jī)方塊。 - 鍵盤(pán)事件:讀取按鍵事件(如
_kbhit
和_getch
)。 - 文件操作:保存和讀取游戲歷史最高分。
3. 代碼解讀
3.1 數(shù)據(jù)結(jié)構(gòu)
Face
- 表示整個(gè)游戲界面。
data
: 標(biāo)記某處是否有方塊(0無(wú),1有)。color
: 存儲(chǔ)方塊顏色信息。
Block
- 存儲(chǔ)7種方塊的4種形態(tài)。
- 通過(guò)二維數(shù)組
space
表示方塊形狀。
struct Face { int data[ROW][COL + 10]; int color[ROW][COL + 10]; }; struct Block { int space[4][4]; } block[7][4];
3.2 核心模塊
(1) 界面初始化
使用InitInterface
繪制邊框與提示信息:
- 通過(guò)
CursorJump
設(shè)置光標(biāo)位置。 - 使用
printf("■")
繪制邊框。 - 邊框信息存入
face.data
。
void InitInterface() { color(7); for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL + 10; j++) { if (j == 0 || j == COL - 1 || j == COL + 9) { face.data[i][j] = 1; CursorJump(2 * j, i); printf("■"); } } } }
(2) 方塊初始化
InitBlockInfo
存儲(chǔ)方塊的7種形狀和4種旋轉(zhuǎn)狀態(tài):
- 每種形狀的基本狀態(tài)由
block[shape][0]
定義。 - 使用二維旋轉(zhuǎn)公式生成其余三種形態(tài)。
for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { block[shape][form + 1].space[i][j] = temp[3 - j][i]; } }
(3) 方塊繪制與移動(dòng)
DrawBlock
:在指定位置打印方塊。DrawSpace
:用空格覆蓋指定位置,實(shí)現(xiàn)“擦除”效果。
void DrawBlock(int shape, int form, int x, int y) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (block[shape][form].space[i][j] == 1 && y + i > 0) { CursorJump(2 * (x + j), y + i); printf("■"); } } } }
(4) 鍵盤(pán)事件處理
通過(guò)_getch
檢測(cè)按鍵:
LEFT
:左移RIGHT
:右移DOWN
:快速下落SPACE
:旋轉(zhuǎn)
switch (ch) { case LEFT: if (IsLegal(shape, form, x - 1, y)) x--; break; case RIGHT: if (IsLegal(shape, form, x + 1, y)) x++; break; case SPACE: if (IsLegal(shape, (form + 1) % 4, x, y)) form = (form + 1) % 4; break; }
(5) 行清除與得分判斷
JudeFunc
實(shí)現(xiàn):
- 檢查行是否滿。
- 若滿,則消除行并將上方行下移。
- 更新得分。
if (sum == COL - 2) { grade += 10; for (int j = 1; j < COL - 1; j++) { face.data[i][j] = 0; CursorJump(2 * j, i); printf(" "); } }
(6) 游戲邏輯與結(jié)束判斷
StartGame
控制方塊下落、鍵盤(pán)響應(yīng)與結(jié)束判斷:
- 隨機(jī)生成方塊并控制其運(yùn)動(dòng)。
- 檢測(cè)頂層是否有方塊,若有則結(jié)束游戲。
(7) 文件操作
ReadGrade
與WriteGrade
負(fù)責(zé)讀取和保存最高分:
- 通過(guò)
fopen
打開(kāi)文件,保存最高分。 - 使用二進(jìn)制方式存儲(chǔ)成績(jī),防止文本存儲(chǔ)帶來(lái)的額外處理。
FILE* pf = fopen("record.txt", "r"); fread(&max, sizeof(int), 1, pf);
4. 編寫(xiě)俄羅斯方塊的步驟
- 明確需求:
- 游戲邏輯(下落、移動(dòng)、旋轉(zhuǎn)、消除)。
- 數(shù)據(jù)存儲(chǔ)(方塊形態(tài)、游戲區(qū)域)。
- 圖形展示(控制臺(tái)或GUI)。
- 搭建基礎(chǔ)框架:
- 初始化游戲區(qū)域、邊框與方塊。
- 定義隨機(jī)生成與旋轉(zhuǎn)邏輯。
- 逐步完善:
- 實(shí)現(xiàn)消除與得分邏輯。
- 增加鍵盤(pán)事件處理。
- 添加文件操作功能。
- 優(yōu)化與美化:
- 增加動(dòng)畫(huà)效果。
- 提高界面美觀度(如使用插件或圖形庫(kù))。
通過(guò)本代碼,你可以學(xué)習(xí)到俄羅斯方塊的實(shí)現(xiàn)核心邏輯,同時(shí)可以在此基礎(chǔ)上嘗試更高級(jí)的圖形化實(shí)現(xiàn),如使用EasyX插件或其他圖形庫(kù),進(jìn)一步提升游戲的視覺(jué)體驗(yàn)和功能豐富性。
您說(shuō):
對(duì)每一步進(jìn)行詳細(xì)的介紹
ChatGPT 說(shuō):
ChatGPT
編寫(xiě)俄羅斯方塊游戲的詳細(xì)步驟與說(shuō)明
本文將從代碼結(jié)構(gòu)與邏輯出發(fā),逐步拆解如何實(shí)現(xiàn)一個(gè)俄羅斯方塊游戲,幫助理解并指導(dǎo)如何從零開(kāi)始構(gòu)建類似的游戲。
1. 明確需求與設(shè)計(jì)思路
俄羅斯方塊的基本玩法包括以下幾部分:
- 隨機(jī)生成方塊:從7種形狀中隨機(jī)生成一個(gè)方塊,并在游戲區(qū)域頂部下落。
- 方塊運(yùn)動(dòng)控制:通過(guò)鍵盤(pán)實(shí)現(xiàn)左右移動(dòng)、加速下落和旋轉(zhuǎn)操作。
- 碰撞檢測(cè)與消行:檢測(cè)方塊是否碰到其他方塊或到達(dá)底部,并判斷是否需要消行。
- 得分與游戲結(jié)束:計(jì)算得分,判斷是否達(dá)到游戲結(jié)束條件。
- 數(shù)據(jù)持久化:保存和讀取最高分記錄。
設(shè)計(jì)中還需考慮:
- 游戲的界面繪制。
- 使用二維數(shù)組存儲(chǔ)游戲區(qū)域狀態(tài)。
- 持續(xù)的方塊生成與處理邏輯。
2. 搭建基礎(chǔ)框架
2.1 確定游戲區(qū)域和方塊結(jié)構(gòu)
俄羅斯方塊的核心在于使用二維數(shù)組存儲(chǔ)數(shù)據(jù)狀態(tài):
Face
結(jié)構(gòu)體:存儲(chǔ)整個(gè)游戲區(qū)域狀態(tài)。
data
數(shù)組:記錄每個(gè)位置是否有方塊。color
數(shù)組:記錄每個(gè)位置的顏色。
Block
結(jié)構(gòu)體:存儲(chǔ)所有方塊的形狀及其4種旋轉(zhuǎn)狀態(tài)。
#define ROW 29 // 游戲區(qū)域行數(shù) #define COL 20 // 游戲區(qū)域列數(shù) struct Face { int data[ROW][COL + 10]; // 游戲區(qū)域方塊標(biāo)記 int color[ROW][COL + 10]; // 方塊顏色 }; struct Block { int space[4][4]; // 存儲(chǔ)方塊形狀 } block[7][4]; // 7種形狀,每種4種狀態(tài)
2.2 初始化游戲區(qū)域與邊框
繪制游戲界面的邊框,展示基本信息(如得分、提示):
- 利用雙重循環(huán),遍歷所有格子。
- 使用特定符號(hào)(如
■
)繪制邊框。 - 在右側(cè)區(qū)域打印提示文字。
代碼實(shí)現(xiàn):
void InitInterface() { color(7); // 設(shè)置邊框顏色為白色 for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL + 10; j++) { if (j == 0 || j == COL - 1 || j == COL + 9) { face.data[i][j] = 1; // 邊框標(biāo)記為有方塊 CursorJump(2 * j, i); printf("■"); } else if (i == 0 || i == ROW - 1) { face.data[i][j] = 1; // 頂部和底部邊框 printf(" ■"); } else { face.data[i][j] = 0; // 非邊框區(qū)域標(biāo)記為空 } } } // 打印右側(cè)提示信息 CursorJump(2 * COL, 1); printf("下一個(gè)方塊:"); CursorJump(2 * COL + 4, ROW - 3); printf("當(dāng)前分?jǐn)?shù):%d", grade); }
3. 初始化方塊形狀與邏輯
3.1 定義方塊形狀
俄羅斯方塊共有7種形狀,每種形狀有4種旋轉(zhuǎn)狀態(tài)。通過(guò)二維數(shù)組定義每種形狀的初始狀態(tài),并通過(guò)旋轉(zhuǎn)公式生成其他狀態(tài)。
代碼實(shí)現(xiàn):
void InitBlockInfo() { // 定義“T”形 for (int i = 0; i <= 2; i++) block[0][0].space[1][i] = 1; block[0][0].space[2][1] = 1; // 定義其他形狀... // 通過(guò)旋轉(zhuǎn)公式生成每種形狀的其他3種狀態(tài) for (int shape = 0; shape < 7; shape++) { for (int form = 0; form < 3; form++) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { block[shape][form + 1].space[i][j] = block[shape][form].space[3 - j][i]; } } } } }
4. 方塊的運(yùn)動(dòng)控制
4.1 鍵盤(pán)事件檢測(cè)
通過(guò)_kbhit
檢測(cè)鍵盤(pán)輸入,并用_getch
捕獲按鍵值:
LEFT
:左移。RIGHT
:右移。DOWN
:加速下落。SPACE
:旋轉(zhuǎn)。
實(shí)現(xiàn)示例:
char ch = getch(); switch (ch) { case LEFT: if (IsLegal(shape, form, x - 1, y)) x--; break; case RIGHT: if (IsLegal(shape, form, x + 1, y)) x++; break; case SPACE: if (IsLegal(shape, (form + 1) % 4, x, y)) form = (form + 1) % 4; break; case ESC: exit(0); // 退出游戲 }
4.2 碰撞檢測(cè)
使用IsLegal
函數(shù)判斷方塊是否可以移動(dòng)或旋轉(zhuǎn):
- 檢查方塊每個(gè)格子是否會(huì)越界或與已有方塊重疊。
- 返回
1
表示合法,0
表示不合法。
int IsLegal(int shape, int form, int x, int y) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (block[shape][form].space[i][j] == 1 && (x + j < 0 || x + j >= COL || y + i >= ROW || face.data[y + i][x + j] == 1)) { return 0; // 非法 } } } return 1; // 合法 }
5. 方塊落地與消行
5.1 方塊落地
每次方塊下落時(shí):
- 檢查其下一步是否越界或碰到其他方塊。
- 如果不能繼續(xù)下落,則將當(dāng)前方塊寫(xiě)入
Face
結(jié)構(gòu)。
5.2 行滿判斷與消除
當(dāng)某一行填滿時(shí):
- 刪除該行并將上方所有行下移。
- 更新分?jǐn)?shù)。
代碼實(shí)現(xiàn):
int JudeFunc() { for (int i = ROW - 2; i > 4; i--) { int sum = 0; for (int j = 1; j < COL - 1; j++) sum += face.data[i][j]; if (sum == COL - 2) { // 滿行 grade += 10; for (int j = 1; j < COL - 1; j++) { face.data[i][j] = 0; CursorJump(2 * j, i); printf(" "); } for (int m = i; m > 1; m--) { // 上方行下移 for (int n = 1; n < COL - 1; n++) { face.data[m][n] = face.data[m - 1][n]; face.color[m][n] = face.color[m - 1][n]; } } } } return 0; }
6. 游戲結(jié)束與得分記錄
6.1 游戲結(jié)束判斷
若方塊到達(dá)頂層,則游戲結(jié)束,提示玩家是否重開(kāi)一局。
6.2 保存與讀取最高分
通過(guò)文件操作保存最高分:
- 使用
fopen
以只讀或?qū)懩J酱蜷_(kāi)文件。 - 利用
fwrite
保存分?jǐn)?shù)。
代碼實(shí)現(xiàn):
void ReadGrade() { FILE* pf = fopen("record.txt", "r"); if (pf == NULL) { pf = fopen("record.txt", "w"); fwrite(&grade, sizeof(int), 1, pf); } fread(&max, sizeof(int), 1, pf); fclose(pf); }
7. 整合與優(yōu)化
- 游戲主循環(huán): 控制方塊生成、運(yùn)動(dòng)、碰撞和消除,維護(hù)游戲邏輯流。
- 視覺(jué)優(yōu)化: 增加更多顏色和圖案設(shè)計(jì),使游戲界面更美觀。
以上是詳細(xì)的游戲開(kāi)發(fā)步驟,你可以在此基礎(chǔ)上進(jìn)行個(gè)性化擴(kuò)展,比如增加背景音樂(lè)、提升圖形效果,或者引入難度等級(jí)功能。
c(下面的代碼經(jīng)過(guò)了詳細(xì)的注釋,希望大家慢慢研究)
//作品:tetris(帶注釋) // by:BoBoo文睡不醒 //注:該代碼內(nèi)注釋加入個(gè)人理解,若有錯(cuò)誤,多多包涵 //由于不需要插件,繪制出來(lái)的玩意恐怕不夠美觀,多多理解 //語(yǔ)言:C的不能再C。(沒(méi)涉及啥很高深的玩意)(高級(jí)一點(diǎn)的話用EasyX插件里面的玩意,那就很炫酷了) //涉及內(nèi)容:光標(biāo)移動(dòng),顏色,圖形繪制、消去、覆蓋和打印,鍵盤(pán)按鍵敲擊的讀取,計(jì)時(shí)器,文件的創(chuàng)建與讀取 //看似簡(jiǎn)單,實(shí)際上一點(diǎn)也不難 //頭文件部分: #include <stdio.h> #include <Windows.h> //提供了一些在控制臺(tái)程序中進(jìn)行輸入輸出、控制光標(biāo)位置、設(shè)置文本屬性等方面的函數(shù)和常量 //在代碼中使用了其中的一些函數(shù)比如SetConsoleCursorPosition()、SetConsoleTextAttribute()來(lái)控制光標(biāo)位置和設(shè)置文本顏色 #include <stdlib.h> //這個(gè)頭文件包含了一些常用的函數(shù)和類型定義,用于進(jìn)行內(nèi)存分配、進(jìn)程控制、字符串轉(zhuǎn)換等操作 #include <time.h> //這個(gè)頭文件包含了一些與時(shí)間相關(guān)的函數(shù)和類型定義 #include <conio.h> //于檢測(cè)鍵盤(pán)輸入字符,以及在控制臺(tái)上進(jìn)行一些簡(jiǎn)單的控制,比如隱藏光標(biāo)等 //在代碼中用于于檢測(cè)鍵盤(pán)輸入,比如判斷是否按下了特定的方向鍵或者空格鍵,從而控制俄羅斯方塊的移動(dòng)、旋轉(zhuǎn)等操作。 //宏定義 #define ROW 29 //游戲區(qū)行數(shù) #define COL 20 //游戲區(qū)列數(shù) //接下來(lái)說(shuō)明敲擊鍵盤(pán)該按鍵的值 #define DOWN 80 //方向鍵:下 #define LEFT 75 //方向鍵:左 #define RIGHT 77 //方向鍵:右 #define SPACE 32 //空格鍵 #define ESC 27 //Esc鍵 //兩個(gè)結(jié)構(gòu)體 struct Face { int data[ROW][COL + 10]; //用于標(biāo)記指定位置是否有方塊(1為有,0為無(wú)) int color[ROW][COL + 10]; //用于記錄指定位置的方塊顏色編碼 }face; struct Block { int space[4][4]; }block[7][4]; //用于存儲(chǔ)7種基本形狀方塊的各自的4種形態(tài)的信息,共28種 //函數(shù)部分?函數(shù)。 //隱藏光標(biāo) void HideCursor(); //光標(biāo)跳轉(zhuǎn) void CursorJump(int x, int y); //初始化界面 void InitInterface(); //初始化方塊信息 void InitBlockInfo(); //顏色設(shè)置 void color(int num); //畫(huà)出方塊 void DrawBlock(int shape, int form, int x, int y); //空格覆蓋 void DrawSpace(int shape, int form, int x, int y); //合法性判斷 int IsLegal(int shape, int form, int x, int y); //判斷得分與結(jié)束 int JudeFunc(); //游戲主體邏輯函數(shù) void StartGame(); //從文件讀取最高分 void ReadGrade(); //更新最高分到文件 void WriteGrade(); //全局變量 int max, grade; //主函數(shù) int main() { #pragma warning (disable:4996) //消除警告 max = 0, grade = 0; //初始化變量 system("title 俄羅斯方塊"); //設(shè)置cmd窗口的名字 system("mode con lines=29 cols=60"); //設(shè)置cmd窗口的大小 HideCursor(); //隱藏光標(biāo) ReadGrade(); //從文件讀取最高分到max變量 InitInterface(); //初始化界面 InitBlockInfo(); //初始化方塊信息 srand((unsigned int)time(NULL)); //設(shè)置隨機(jī)數(shù)生成的起點(diǎn) StartGame(); //開(kāi)始游戲 return 0; } //關(guān)于隱藏光標(biāo)的操作(不懂他在干啥)(估計(jì)就是隱藏光標(biāo)) void HideCursor() { CONSOLE_CURSOR_INFO curInfo; //定義光標(biāo)信息的結(jié)構(gòu)體變量 curInfo.dwSize = 1; //如果沒(méi)賦值的話,隱藏光標(biāo)無(wú)效 curInfo.bVisible = FALSE; //將光標(biāo)設(shè)置為不可見(jiàn) HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //獲取控制臺(tái)句柄 SetConsoleCursorInfo(handle, &curInfo); //設(shè)置光標(biāo)信息 } //為了解清晰其中,關(guān)鍵結(jié)構(gòu)CONSOLE_CURSOR_INFO在其頭文件當(dāng)中的內(nèi)容如下: // (自己去查,我也不知道) //光標(biāo)跳轉(zhuǎn)(在屏幕上進(jìn)行輸出時(shí),我們需要光標(biāo)先移動(dòng)到目標(biāo)位置再進(jìn)行輸出,因此,光標(biāo)跳轉(zhuǎn)函數(shù)也是必不可少的) void CursorJump(int x, int y) { COORD pos; //定義光標(biāo)位置的結(jié)構(gòu)體變量 pos.X = x; //橫坐標(biāo)設(shè)置 pos.Y = y; //縱坐標(biāo)設(shè)置 HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //獲取控制臺(tái)句柄 SetConsoleCursorPosition(handle, pos); //設(shè)置光標(biāo)位置 } //初始化界面 //主界面的外層的邊框打印 void InitInterface() { color(7); //顏色設(shè)置為白色 for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL + 10; j++) { if (j == 0 || j == COL - 1 || j == COL + 9) { face.data[i][j] = 1; //標(biāo)記該位置有方塊 CursorJump(2 * j, i); printf("■"); } else if (i == ROW - 1 || i == 0)//首末兩行邊框的打印 { face.data[i][j] = 1; //標(biāo)記該位置有方塊 printf(" ■");//此處予以說(shuō)明,為了整齊起見(jiàn),每行方塊之間加入空格。下方代碼同理 } else face.data[i][j] = 0; //標(biāo)記該位置無(wú)方塊 } } for (int i = COL; i < COL + 10; i++)//右側(cè)邊框分隔線 { face.data[8][i] = 1; //標(biāo)記該位置有方塊 CursorJump(2 * i, 8); printf("■"); } //從這里開(kāi)始就是寫(xiě)右側(cè)邊框中的文字與提示語(yǔ)句(無(wú)需細(xì)看,無(wú)腦打印,想整什么活都可以整) CursorJump(2 * COL, 1); printf("下一個(gè)方塊:"); CursorJump(2 * COL + 4, ROW - 19); printf("左移:←"); CursorJump(2 * COL + 4, ROW - 17); printf("右移:→"); CursorJump(2 * COL + 4, ROW - 15); printf("加速:↓"); CursorJump(2 * COL + 4, ROW - 13); printf("旋轉(zhuǎn):空格"); CursorJump(2 * COL + 4, ROW - 11); printf("暫停: S"); CursorJump(2 * COL + 4, ROW - 9); printf("退出: Esc"); CursorJump(2 * COL + 4, ROW - 7); printf("重新開(kāi)始:R"); CursorJump(2 * COL + 4, ROW - 5); printf("最高紀(jì)錄:%d", max); CursorJump(2 * COL + 4, ROW - 3); printf("當(dāng)前分?jǐn)?shù):%d", grade); } //初始化方塊信息 //存儲(chǔ)各種形狀的俄羅斯方塊 void InitBlockInfo() { //“T”形 for (int i = 0; i <= 2; i++) block[0][0].space[1][i] = 1; block[0][0].space[2][1] = 1; //“L”形 for (int i = 1; i <= 3; i++) block[1][0].space[i][1] = 1; block[1][0].space[3][2] = 1; //“J”形 for (int i = 1; i <= 3; i++) block[2][0].space[i][2] = 1; block[2][0].space[3][1] = 1; for (int i = 0; i <= 1; i++) { //“Z”形 block[3][0].space[1][i] = 1; block[3][0].space[2][i + 1] = 1; //“S”形 block[4][0].space[1][i + 1] = 1; block[4][0].space[2][i] = 1; //“O”形 block[5][0].space[1][i + 1] = 1; block[5][0].space[2][i + 1] = 1; } //“I”形 for (int i = 0; i <= 3; i++) block[6][0].space[i][1] = 1; int temp[4][4]; for (int shape = 0; shape < 7; shape++) //7種形狀 { for (int form = 0; form < 3; form++) //4種形態(tài)(已經(jīng)有了一種,這里每個(gè)還需增加3種) { //獲取第form種形態(tài) for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { temp[i][j] = block[shape][form].space[i][j]; } } //將第form種形態(tài)順時(shí)針旋轉(zhuǎn),得到第form+1種形態(tài) for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { block[shape][form + 1].space[i][j] = temp[3 - j][i]; } } } } } //顏色設(shè)置 void color(int c) { switch (c) { case 0: c = 13; //“T”形方塊設(shè)置為紫色 break; case 1: case 2: c = 12; //“L”形和“J”形方塊設(shè)置為紅色 break; case 3: case 4: c = 10; //“Z”形和“S”形方塊設(shè)置為綠色 break; case 5: c = 14; //“O”形方塊設(shè)置為黃色 break; case 6: c = 11; //“I”形方塊設(shè)置為淺藍(lán)色 break; default: c = 7; //其他默認(rèn)設(shè)置為白色(若需將顏色重置可把color變?yōu)?) break; } SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //顏色設(shè)置 //注:SetConsoleTextAttribute是一個(gè)API(應(yīng)用程序編程接口)(from百度,自行了解) } //畫(huà)出方塊 void DrawBlock(int shape, int form, int x, int y) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (block[shape][form].space[i][j] == 1 && y + i > 0) //如果該位置有方塊 { CursorJump(2 * (x + j), y + i); //光標(biāo)跳轉(zhuǎn)到指定位置 printf("■"); //輸出方塊 } } } //由于不怎么會(huì)如何讓打印的圖形不影響邊框(上邊框),于是愚蠢的我決定再打印一遍上邊框 //大家有更好的方法自行嘗試 color(7);//不加的話第一行的顏色會(huì)與圖形的顏色相同 for (int j = 0; j < COL + 10; j++) { if (j == 0 || j == COL - 1 || j == COL + 9) { CursorJump(2 * j, 0); printf("■"); } else { printf(" ■"); } } } //空格覆蓋!??! void DrawSpace(int shape, int form, int x, int y) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (block[shape][form].space[i][j] == 1) //如果該位置有方塊 { CursorJump(2 * (x + j), y + i); //光標(biāo)跳轉(zhuǎn)到指定位置 printf(" "); //打印空格覆蓋(兩個(gè)空格) } } } } //合法性判斷 int IsLegal(int shape, int form, int x, int y) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { //如果方塊落下的位置本來(lái)就已經(jīng)有方塊了,則不合法 if ((block[shape][form].space[i][j] == 1) && (face.data[y + i][x + j] == 1)) return 0; //不合法 } } return 1; //合法 } //判斷得分與結(jié)束 int JudeFunc() { //判斷是否得分 for (int i = ROW - 2; i > 4; i--) { int sum = 0; //記錄第i行的方塊個(gè)數(shù) for (int j = 1; j < COL - 1; j++) { sum += face.data[i][j]; //統(tǒng)計(jì)第i行的方塊個(gè)數(shù) } if (sum == 0) //該行沒(méi)有方塊,無(wú)需再判斷其上的層次(無(wú)需再繼續(xù)判斷是否得分) break; //跳出循環(huán) if (sum == COL - 2) //該行全是方塊,可得分 { grade += 10; //滿一行加10分 color(7); //顏色設(shè)置為白色 CursorJump(2 * COL + 4, ROW - 3); //光標(biāo)跳轉(zhuǎn)到顯示當(dāng)前分?jǐn)?shù)的位置 printf("當(dāng)前分?jǐn)?shù):%d", grade); //更新當(dāng)前分?jǐn)?shù) for (int j = 1; j < COL - 1; j++) //清除得分行的方塊信息 { face.data[i][j] = 0; //該位置得分后被清除,標(biāo)記為無(wú)方塊 CursorJump(2 * j, i); //光標(biāo)跳轉(zhuǎn)到該位置 printf(" "); //打印空格覆蓋(兩個(gè)空格) } //把被清除行上面的行整體向下挪一格 for (int m = i; m > 1; m--) { sum = 0; //記錄上一行的方塊個(gè)數(shù) for (int n = 1; n < COL - 1; n++) { sum += face.data[m - 1][n]; //統(tǒng)計(jì)上一行的方塊個(gè)數(shù) face.data[m][n] = face.data[m - 1][n]; //將上一行方塊的標(biāo)識(shí)移到下一行 face.color[m][n] = face.color[m - 1][n]; //將上一行方塊的顏色編號(hào)移到下一行 if (face.data[m][n] == 1) //上一行移下來(lái)的是方塊,打印方塊 { CursorJump(2 * n, m); //光標(biāo)跳轉(zhuǎn)到該位置 color(face.color[m][n]); //顏色設(shè)置為還方塊的顏色 printf("■"); //打印方塊 } else //上一行移下來(lái)的是空格,打印空格 { CursorJump(2 * n, m); //光標(biāo)跳轉(zhuǎn)到該位置 printf(" "); //打印空格(兩個(gè)空格) } } if (sum == 0) //上一行移下來(lái)的全是空格,無(wú)需再將上層的方塊向下移動(dòng)(移動(dòng)結(jié)束) return 1; //返回1,表示還需調(diào)用該函數(shù)進(jìn)行判斷(移動(dòng)下來(lái)的可能還有滿行) } } } //判斷游戲是否結(jié)束 for (int j = 1; j < COL - 1; j++) { if (face.data[1][j] == 1) //頂層有方塊存在(以第1行為頂層,不是第0行) { Sleep(1000); //留給玩家反應(yīng)時(shí)間 system("cls"); //清空屏幕 color(7); //顏色設(shè)置為白色 CursorJump(2 * (COL / 3), ROW / 2 - 9); if (grade > max)//接下來(lái)就是曼妙的人機(jī)交互部分 { printf("恭喜你打破最高記錄,最高記錄更新為%d", grade); WriteGrade(); } else if (grade == max) { printf("與最高記錄持平,加油再創(chuàng)佳績(jī)", grade); } else { printf("請(qǐng)繼續(xù)加油,當(dāng)前與最高記錄相差%d", max - grade); } CursorJump(2 * (COL / 3), ROW / 2 - 6); printf("GAME OVER"); while (1) { char ch; CursorJump(2 * (COL / 3), ROW / 2 - 3); printf("再來(lái)一局?"); CursorJump(2 * (COL / 3), ROW / 2); printf("輸入y并敲擊回車(y/n)進(jìn)入下一局;"); CursorJump(2 * (COL / 3), ROW / 2 + 3); printf("輸入n并敲擊回車(n/n)結(jié)束游戲;"); CursorJump(2 * (COL / 3), ROW / 2 + 6); printf("敲擊esc則退出游戲;"); if (kbhit() != 0) { printf("Y"); if (getch() == 27) { printf("Y"); system("cls"); //清空屏幕 color(7); CursorJump(COL, ROW / 2); printf(" 游戲結(jié)束 "); CursorJump(COL, ROW / 2 + 2); exit(0); //結(jié)束程序 } //疑點(diǎn):上面的if判斷和執(zhí)行代碼無(wú)法實(shí)現(xiàn),還未研究明白****** } scanf("%c", &ch); if (ch == 'y' || ch == 'Y') { system("cls"); main(); } else if (ch == 'n' || ch == 'N') { CursorJump(2 * (COL / 3), ROW / 2 + 5); exit(0); } else { CursorJump(2 * (COL / 3), ROW / 2 + 4); printf("選擇錯(cuò)誤,請(qǐng)?jiān)俅芜x擇"); } } } } return 0; //判斷結(jié)束,無(wú)需再調(diào)用該函數(shù)進(jìn)行判斷 } //游戲主體邏輯函數(shù)!??! void StartGame() { int shape = rand() % 7, form = rand() % 4; //隨機(jī)獲取方塊的形狀和形態(tài) while (1) { int t = 0; int nextShape = rand() % 7, nextForm = rand() % 4; //隨機(jī)獲取下一個(gè)方塊的形狀和形態(tài) int x = COL / 2 - 2, y = 0; //方塊初始下落位置的橫縱坐標(biāo) color(nextShape); //顏色設(shè)置為下一個(gè)方塊的顏色 DrawBlock(nextShape, nextForm, COL + 3, 3); //將下一個(gè)方塊顯示在右上角 while (1) { color(shape); //顏色設(shè)置為當(dāng)前正在下落的方塊 DrawBlock(shape, form, x, y); //將該方塊顯示在初始下落位置 if (t == 0) { t = 15000; //這里t越小,方塊下落越快(可以根據(jù)此設(shè)置游戲難度,我的評(píng)價(jià)是可以搞事) } while (--t) { if (kbhit() != 0) //若鍵盤(pán)被敲擊,則退出循環(huán) break; } if (t == 0) //鍵盤(pán)未被敲擊 { if (IsLegal(shape, form, x, y + 1) == 0) //方塊再下落就不合法了(已經(jīng)到達(dá)底部) { //將當(dāng)前方塊的信息錄入face當(dāng)中 //face:記錄界面的每個(gè)位置是否有方塊,若有方塊還需記錄該位置方塊的顏色。 for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (block[shape][form].space[i][j] == 1) { face.data[y + i][x + j] = 1; //將該位置標(biāo)記為有方塊 face.color[y + i][x + j] = shape; //記錄該方塊的顏色數(shù)值 } } } while (JudeFunc()); //判斷此次方塊下落是否得分以及游戲是否結(jié)束 break; //跳出當(dāng)前死循環(huán),準(zhǔn)備進(jìn)行下一個(gè)方塊的下落 } else //未到底部 { DrawSpace(shape, form, x, y); //用空格覆蓋當(dāng)前方塊所在位置 y++; //縱坐標(biāo)自增(下一次顯示方塊時(shí)就相當(dāng)于下落了一格了) } } else //鍵盤(pán)被敲擊 { //對(duì)敲擊鍵盤(pán)的按鍵的判斷 //(不理解為什么這個(gè)對(duì),上面那個(gè)ESC的判斷就有問(wèn)題)****** char ch = getch(); //讀取keycode switch (ch) { case DOWN: //方向鍵:下 if (IsLegal(shape, form, x, y + 1) == 1) //判斷方塊向下移動(dòng)一位后是否合法 { //方塊下落后合法才進(jìn)行以下操作 DrawSpace(shape, form, x, y); //用空格覆蓋當(dāng)前方塊所在位置 y++; //縱坐標(biāo)自增(下一次顯示方塊時(shí)就相當(dāng)于下落了一格了) } break; case LEFT: //方向鍵:左 if (IsLegal(shape, form, x - 1, y) == 1) //判斷方塊向左移動(dòng)一位后是否合法 { //方塊左移后合法才進(jìn)行以下操作 DrawSpace(shape, form, x, y); //用空格覆蓋當(dāng)前方塊所在位置 x--; //橫坐標(biāo)自減(下一次顯示方塊時(shí)就相當(dāng)于左移了一格了) } break; case RIGHT: //方向鍵:右 if (IsLegal(shape, form, x + 1, y) == 1) //判斷方塊向右移動(dòng)一位后是否合法 { //方塊右移后合法才進(jìn)行以下操作 DrawSpace(shape, form, x, y); //用空格覆蓋當(dāng)前方塊所在位置 x++; //橫坐標(biāo)自增(下一次顯示方塊時(shí)就相當(dāng)于右移了一格了) } break; case SPACE: //空格鍵 if (IsLegal(shape, (form + 1) % 4, x, y + 1) == 1) //判斷方塊旋轉(zhuǎn)后是否合法 { //方塊旋轉(zhuǎn)后合法才進(jìn)行以下操作 DrawSpace(shape, form, x, y); //用空格覆蓋當(dāng)前方塊所在位置 y++; //縱坐標(biāo)自增(總不能原地旋轉(zhuǎn)吧) form = (form + 1) % 4; //方塊的形態(tài)自增(下一次顯示方塊時(shí)就相當(dāng)于旋轉(zhuǎn)了) } break; case ESC: //Esc鍵 system("cls"); //清空屏幕 color(7); CursorJump(COL, ROW / 2); printf(" 游戲結(jié)束 "); CursorJump(COL, ROW / 2 + 2); exit(0); //結(jié)束程序 case 's': case 'S': //暫停 system("pause>nul"); //暫停(按任意鍵繼續(xù)) break; case 'r': case 'R': //重新開(kāi)始 system("cls"); //清空屏幕 main(); //重新執(zhí)行主函數(shù) } } } shape = nextShape, form = nextForm; //獲取下一個(gè)方塊的信息 DrawSpace(nextShape, nextForm, COL + 3, 3); //將右上角的方塊信息用空格覆蓋 } } //從文件讀取最高分 void ReadGrade() { FILE* pf = fopen("俄羅斯方塊最高得分記錄.txt", "r"); //以只讀方式打開(kāi)文件 if (pf == NULL) //打開(kāi)文件失敗 { pf = fopen("俄羅斯方塊最高得分記錄.txt", "w"); //以只寫(xiě)方式打開(kāi)文件(文件不存在可以自動(dòng)創(chuàng)建該文件) fwrite(&grade, sizeof(int), 1, pf); //將max寫(xiě)入文件(此時(shí)max為0),即將最高歷史得分初始化為0 } fseek(pf, 0, SEEK_SET); //使文件指針pf指向文件開(kāi)頭 fread(&max, sizeof(int), 1, pf); //讀取文件中的最高歷史得分到max當(dāng)中 fclose(pf); //關(guān)閉文件 pf = NULL; //文件指針及時(shí)置空 } //更新最高分到文件 void WriteGrade() { FILE* pf = fopen("俄羅斯方塊最高得分記錄.txt", "w"); //以只寫(xiě)方式打開(kāi)文件 if (pf == NULL) //打開(kāi)文件失敗 { printf("保存最高得分記錄失敗\n"); exit(0); } fwrite(&grade, sizeof(int), 1, pf); //將本局游戲得分寫(xiě)入文件當(dāng)中(更新最高歷史得分) fclose(pf); //關(guān)閉文件 pf = NULL; //文件指針及時(shí)置空 } //over // //還是那句話 //看似簡(jiǎn)單,實(shí)際上一點(diǎn)也不難
總結(jié)
到此這篇關(guān)于如何用c語(yǔ)言完成俄羅斯方塊小游戲的文章就介紹到這了,更多相關(guān)c語(yǔ)言俄羅斯方塊小游戲內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++中關(guān)于std::queue?中遇到釋放內(nèi)存錯(cuò)誤的問(wèn)題
這篇文章主要介紹了std::queue中遇到釋放內(nèi)存錯(cuò)誤的問(wèn)題,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07在C語(yǔ)言中對(duì)utmp文件進(jìn)行查找和寫(xiě)入操作的函數(shù)小結(jié)
這篇文章主要介紹了在C語(yǔ)言中對(duì)utmp文件進(jìn)行查找和寫(xiě)入操作的函數(shù)小結(jié),包括pututline()函數(shù)和getutline()函數(shù)以及getutid()函數(shù),需要的朋友可以參考下2015-08-08Qt創(chuàng)建項(xiàng)目實(shí)戰(zhàn)之手把手創(chuàng)建第一個(gè)Qt項(xiàng)目
我們?cè)谶M(jìn)行軟件開(kāi)發(fā)學(xué)習(xí)時(shí),有時(shí)候需要qt軟件進(jìn)行代碼的敲寫(xiě),下面這篇文章主要給大家介紹了關(guān)于Qt創(chuàng)建項(xiàng)目實(shí)戰(zhàn)之手把手創(chuàng)建第一個(gè)Qt項(xiàng)目的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04一篇文章帶你了解C語(yǔ)言二分查找的簡(jiǎn)單應(yīng)用
這篇文章主要介紹了二分查找算法在C語(yǔ)言程序中的使用示例,文中最后提到了使用二分查找法一個(gè)需要注意的地方,需要的朋友可以參考下2021-08-08