文章目錄
- 游戲說明
- 游戲效果展示
- 游戲代碼
- 游戲代碼詳解
- 游戲框架構建
- 隱藏游標
- 游標跳轉
- 初始化界面
- 初始化方塊資訊
- 顏色設定
- 畫出方塊
- 空格覆寫
- 合法性判斷
- 判斷得分與結束
- 游戲主體邏輯函式
- 從檔案讀取最高分
- 更新最高分到檔案
- 主函式
游戲說明
俄羅斯方塊相信大家都知道,這里就不再介紹什么游戲背景了,我這里對本代碼實作的俄羅斯方塊作一些說明:
- 按方向鍵的左右鍵可實作方塊的左右移動,
- 按方向鍵的下鍵可實作方塊的加速下落,
- 按空格鍵可實作方塊的順時針旋轉,
- 按Esc鍵可退出游戲,
- 按S鍵可暫停游戲,暫停游戲后按任意鍵繼續游戲,
- 按R鍵可重新開始游戲,
除此之外,本游戲還擁有計分系統,可保存玩家的歷史最高記錄,
游戲效果展示

游戲代碼
博友們可以將以下代碼復制到自己的編譯器當中運行:
#include <stdio.h>
#include <Windows.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#define ROW 29 //游戲區行數
#define COL 20 //游戲區列數
#define DOWN 80 //方向鍵:下
#define LEFT 75 //方向鍵:左
#define RIGHT 77 //方向鍵:右
#define SPACE 32 //空格鍵
#define ESC 27 //Esc鍵
struct Face
{
int data[ROW][COL + 10]; //用于標記指定位置是否有方塊(1為有,0為無)
int color[ROW][COL + 10]; //用于記錄指定位置的方塊顏色編碼
}face;
struct Block
{
int space[4][4];
}block[7][4]; //用于存盤7種基本形狀方塊的各自的4種形態的資訊,共28種
//隱藏游標
void HideCursor();
//游標跳轉
void CursorJump(int x, int y);
//初始化界面
void InitInterface();
//初始化方塊資訊
void InitBlockInfo();
//顏色設定
void color(int num);
//畫出方塊
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);
//判斷得分與結束
int JudeFunc();
//游戲主體邏輯函式
void StartGame();
//從檔案讀取最高分
void ReadGrade();
//更新最高分到檔案
void WriteGrade();
int max, grade; //全域變數
int main()
{
#pragma warning (disable:4996) //消除警告
max = 0, grade = 0; //初始化變數
system("title 俄羅斯方塊"); //設定cmd視窗的名字
system("mode con lines=29 cols=60"); //設定cmd視窗的大小
HideCursor(); //隱藏游標
ReadGrade(); //從檔案讀取最高分到max變數
InitInterface(); //初始化界面
InitBlockInfo(); //初始化方塊資訊
srand((unsigned int)time(NULL)); //設定亂數生成的起點
StartGame(); //開始游戲
return 0;
}
//隱藏游標
void HideCursor()
{
CONSOLE_CURSOR_INFO curInfo; //定義游標資訊的結構體變數
curInfo.dwSize = 1; //如果沒賦值的話,隱藏游標無效
curInfo.bVisible = FALSE; //將游標設定為不可見
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //獲取控制臺句柄
SetConsoleCursorInfo(handle, &curInfo); //設定游標資訊
}
//游標跳轉
void CursorJump(int x, int y)
{
COORD pos; //定義游標位置的結構體變數
pos.X = x; //橫坐標設定
pos.Y = y; //縱坐標設定
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //獲取控制臺句柄
SetConsoleCursorPosition(handle, pos); //設定游標位置
}
//初始化界面
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("■");
}
else if (i == ROW - 1)
{
face.data[i][j] = 1; //標記該位置有方塊
printf("■");
}
else
face.data[i][j] = 0; //標記該位置無方塊
}
}
for (int i = COL; i < COL + 10; i++)
{
face.data[8][i] = 1; //標記該位置有方塊
CursorJump(2 * i, 8);
printf("■");
}
CursorJump(2 * COL, 1);
printf("下一個方塊:");
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("旋轉:空格");
CursorJump(2 * COL + 4, ROW - 11);
printf("暫停: S");
CursorJump(2 * COL + 4, ROW - 9);
printf("退出: Esc");
CursorJump(2 * COL + 4, ROW - 7);
printf("重新開始:R");
CursorJump(2 * COL + 4, ROW - 5);
printf("最高紀錄:%d", max);
CursorJump(2 * COL + 4, ROW - 3);
printf("當前分數:%d", grade);
}
//初始化方塊資訊
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種形態(已經有了一種,這里每個還需增加3種)
{
//獲取第form種形態
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
temp[i][j] = block[shape][form].space[i][j];
}
}
//將第form種形態順時針旋轉,得到第form+1種形態
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];
}
}
}
}
}
//顏色設定
void color(int c)
{
switch (c)
{
case 0:
c = 13; //“T”形方塊設定為紫色
break;
case 1:
case 2:
c = 12; //“L”形和“J”形方塊設定為紅色
break;
case 3:
case 4:
c = 10; //“Z”形和“S”形方塊設定為綠色
break;
case 5:
c = 14; //“O”形方塊設定為黃色
break;
case 6:
c = 11; //“I”形方塊設定為淺藍色
break;
default:
c = 7; //其他默認設定為白色
break;
}
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //顏色設定
//注:SetConsoleTextAttribute是一個API(應用程式編程介面)
}
//畫出方塊
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) //如果該位置有方塊
{
CursorJump(2 * (x + j), y + i); //游標跳轉到指定位置
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); //游標跳轉到指定位置
printf(" "); //列印空格覆寫(兩個空格)
}
}
}
}
//合法性判斷
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) && (face.data[y + i][x + j] == 1))
return 0; //不合法
}
}
return 1; //合法
}
//判斷得分與結束
int JudeFunc()
{
//判斷是否得分
for (int i = ROW - 2; i > 4; i--)
{
int sum = 0; //記錄第i行的方塊個數
for (int j = 1; j < COL - 1; j++)
{
sum += face.data[i][j]; //統計第i行的方塊個數
}
if (sum == 0) //該行沒有方塊,無需再判斷其上的層次(無需再繼續判斷是否得分)
break; //跳出回圈
if (sum == COL - 2) //該行全是方塊,可得分
{
grade += 10; //滿一行加10分
color(7); //顏色設定為白色
CursorJump(2 * COL + 4, ROW - 3); //游標跳轉到顯示當前分數的位置
printf("當前分數:%d", grade); //更新當前分數
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--)
{
sum = 0; //記錄上一行的方塊個數
for (int n = 1; n < COL - 1; n++)
{
sum += face.data[m - 1][n]; //統計上一行的方塊個數
face.data[m][n] = face.data[m - 1][n]; //將上一行方塊的標識移到下一行
face.color[m][n] = face.color[m - 1][n]; //將上一行方塊的顏色編號移到下一行
if (face.data[m][n] == 1) //上一行移下來的是方塊,列印方塊
{
CursorJump(2 * n, m); //游標跳轉到該位置
color(face.color[m][n]); //顏色設定為還方塊的顏色
printf("■"); //列印方塊
}
else //上一行移下來的是空格,列印空格
{
CursorJump(2 * n, m); //游標跳轉到該位置
printf(" "); //列印空格(兩個空格)
}
}
if (sum == 0) //上一行移下來的全是空格,無需再將上層的方塊向下移動(移動結束)
return 1; //回傳1,表示還需呼叫該函式進行判斷(移動下來的可能還有滿行)
}
}
}
//判斷游戲是否結束
for (int j = 1; j < COL - 1; j++)
{
if (face.data[1][j] == 1) //頂層有方塊存在(以第1行為頂層,不是第0行)
{
Sleep(1000); //留給玩家反應時間
system("cls"); //清空螢屏
color(7); //顏色設定為白色
CursorJump(2 * (COL / 3), ROW / 2 - 3);
if (grade>max)
{
printf("恭喜你打破最高記錄,最高記錄更新為%d", grade);
WriteGrade();
}
else if (grade == max)
{
printf("與最高記錄持平,加油再創佳績", grade);
}
else
{
printf("請繼續加油,當前與最高記錄相差%d", max - grade);
}
CursorJump(2 * (COL / 3), ROW / 2);
printf("GAME OVER");
while (1)
{
char ch;
CursorJump(2 * (COL / 3), ROW / 2 + 3);
printf("再來一局?(y/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("選擇錯誤,請再次選擇");
}
}
}
}
return 0; //判斷結束,無需再呼叫該函式進行判斷
}
//游戲主體邏輯函式
void StartGame()
{
int shape = rand() % 7, form = rand() % 4; //隨機獲取方塊的形狀和形態
while (1)
{
int t = 0;
int nextShape = rand() % 7, nextForm = rand() % 4; //隨機獲取下一個方塊的形狀和形態
int x = COL / 2 - 2, y = 0; //方塊初始下落位置的橫縱坐標
color(nextShape); //顏色設定為下一個方塊的顏色
DrawBlock(nextShape, nextForm, COL + 3, 3); //將下一個方塊顯示在右上角
while (1)
{
color(shape); //顏色設定為當前正在下落的方塊
DrawBlock(shape, form, x, y); //將該方塊顯示在初始下落位置
if (t == 0)
{
t = 15000; //這里t越小,方塊下落越快(可以根據此設定游戲難度)
}
while (--t)
{
if (kbhit() != 0) //若鍵盤被敲擊,則退出回圈
break;
}
if (t == 0) //鍵盤未被敲擊
{
if (IsLegal(shape, form, x, y + 1) == 0) //方塊再下落就不合法了(已經到達底部)
{
//將當前方塊的資訊錄入face當中
//face:記錄界面的每個位置是否有方塊,若有方塊還需記錄該位置方塊的顏色,
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; //將該位置標記為有方塊
face.color[y + i][x + j] = shape; //記錄該方塊的顏色數值
}
}
}
while (JudeFunc()); //判斷此次方塊下落是否得分以及游戲是否結束
break; //跳出當前死回圈,準備進行下一個方塊的下落
}
else //未到底部
{
DrawSpace(shape, form, x, y); //用空格覆寫當前方塊所在位置
y++; //縱坐標自增(下一次顯示方塊時就相當于下落了一格了)
}
}
else //鍵盤被敲擊
{
char ch = getch(); //讀取keycode
switch (ch)
{
case DOWN: //方向鍵:下
if (IsLegal(shape, form, x, y + 1) == 1) //判斷方塊向下移動一位后是否合法
{
//方塊下落后合法才進行以下操作
DrawSpace(shape, form, x, y); //用空格覆寫當前方塊所在位置
y++; //縱坐標自增(下一次顯示方塊時就相當于下落了一格了)
}
break;
case LEFT: //方向鍵:左
if (IsLegal(shape, form, x - 1, y) == 1) //判斷方塊向左移動一位后是否合法
{
//方塊左移后合法才進行以下操作
DrawSpace(shape, form, x, y); //用空格覆寫當前方塊所在位置
x--; //橫坐標自減(下一次顯示方塊時就相當于左移了一格了)
}
break;
case RIGHT: //方向鍵:右
if (IsLegal(shape, form, x + 1, y) == 1) //判斷方塊向右移動一位后是否合法
{
//方塊右移后合法才進行以下操作
DrawSpace(shape, form, x, y); //用空格覆寫當前方塊所在位置
x++; //橫坐標自增(下一次顯示方塊時就相當于右移了一格了)
}
break;
case SPACE: //空格鍵
if (IsLegal(shape, (form + 1) % 4, x, y + 1) == 1) //判斷方塊旋轉后是否合法
{
//方塊旋轉后合法才進行以下操作
DrawSpace(shape, form, x, y); //用空格覆寫當前方塊所在位置
y++; //縱坐標自增(總不能原地旋轉吧)
form = (form + 1) % 4; //方塊的形態自增(下一次顯示方塊時就相當于旋轉了)
}
break;
case ESC: //Esc鍵
system("cls"); //清空螢屏
color(7);
CursorJump(COL, ROW / 2);
printf(" 游戲結束 ");
CursorJump(COL, ROW / 2 + 2);
exit(0); //結束程式
case 's':
case 'S': //暫停
system("pause>nul"); //暫停(按任意鍵繼續)
break;
case 'r':
case 'R': //重新開始
system("cls"); //清空螢屏
main(); //重新執行主函式
}
}
}
shape = nextShape, form = nextForm; //獲取下一個方塊的資訊
DrawSpace(nextShape, nextForm, COL + 3, 3); //將右上角的方塊資訊用空格覆寫
}
}
//從檔案讀取最高分
void ReadGrade()
{
FILE* pf = fopen("俄羅斯方塊最高得分記錄.txt", "r"); //以只讀方式打開檔案
if (pf == NULL) //打開檔案失敗
{
pf = fopen("俄羅斯方塊最高得分記錄.txt", "w"); //以只寫方式打開檔案(檔案不存在可以自動創建該檔案)
fwrite(&grade, sizeof(int), 1, pf); //將max寫入檔案(此時max為0),即將最高歷史得分初始化為0
}
fseek(pf, 0, SEEK_SET); //使檔案指標pf指向檔案開頭
fread(&max, sizeof(int), 1, pf); //讀取檔案中的最高歷史得分到max當中
fclose(pf); //關閉檔案
pf = NULL; //檔案指標及時置空
}
//更新最高分到檔案
void WriteGrade()
{
FILE* pf = fopen("俄羅斯方塊最高得分記錄.txt", "w"); //以只寫方式打開檔案
if (pf == NULL) //打開檔案失敗
{
printf("保存最高得分記錄失敗\n");
exit(0);
}
fwrite(&grade, sizeof(int), 1, pf); //將本局游戲得分寫入檔案當中(更新最高歷史得分)
fclose(pf); //關閉檔案
pf = NULL; //檔案指標及時置空
}
游戲代碼詳解
游戲框架構建
首先我們定義一下界面的大小,我們這里定義游戲區的行數和列數,
#define ROW 29 //游戲區行數
#define COL 20 //游戲區列數
我這里將方塊堆積的區域稱為游戲區,將按鍵提示以及方塊提示的區域稱為提示區,

我們還需要一個結構體,該結構體記錄界面的每個位置是否有方塊,若有方塊還需記錄該位置方塊的顏色,
struct Face
{
int data[ROW][COL + 10]; //用于標記指定位置是否有方塊(1為有,0為無)
int color[ROW][COL + 10]; //用于記錄指定位置的方塊顏色編碼
}face;
其次,我們還需要一個結構體,該結構體當中存盤著一個4行4列的二維陣列,這個二維陣列就用于存盤單個方塊的基本資訊,(眾所周知,4行4列的二維陣列可以容納下游戲當中的每一種方塊)
而俄羅斯方塊當中有7種基本形狀的方塊,而每種方塊通過旋轉后又可以得到3種方塊,共28種,
因此,我們可以用該結構體定義一個7行4列的二維陣列存盤這28個方塊的資訊,
struct Block
{
int space[4][4];
}block[7][4]; //用于存盤7種基本形狀方塊的各自的4種形態的資訊,共28種
做到這里框架已經基本構建好了,為了提高代碼的可讀性,我們再根據需要用到的按鍵的鍵碼值對其進行宏定義,
#define DOWN 80 //方向鍵:下
#define LEFT 75 //方向鍵:左
#define RIGHT 77 //方向鍵:右
#define SPACE 32 //空格鍵
#define ESC 27 //Esc鍵
隱藏游標
游標的作用在于提醒使用者,你接下來的輸入將會在該位置出現,但在進行游戲時我們并不需要用到游標,游標在那里一閃一閃的顯然是不行的,這時我們需要將游標隱藏,
//隱藏游標
void HideCursor()
{
CONSOLE_CURSOR_INFO curInfo; //定義游標資訊的結構體變數
curInfo.dwSize = 1; //如果沒賦值的話,隱藏游標無效
curInfo.bVisible = FALSE; //將游標設定為不可見
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //獲取控制臺句柄
SetConsoleCursorInfo(handle, &curInfo); //設定游標資訊
}
其中,關鍵結構CONSOLE_CURSOR_INFO在其頭檔案當中的內容如下:

設定游標資訊函式在其頭檔案中的宣告如下:

游標跳轉
在螢屏上進行輸出時,我們需要游標先移動到目標位置再進行輸出,因此,游標跳轉函式也是必不可少的,
//游標跳轉
void CursorJump(int x, int y)
{
COORD pos; //定義游標位置的結構體變數
pos.X = x; //橫坐標設定
pos.Y = y; //縱坐標設定
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //獲取控制臺句柄
SetConsoleCursorPosition(handle, pos); //設定游標位置
}
其中,關鍵結構COORD在其頭檔案當中的內容如下:

設定游標位置函式在其頭檔案中的宣告如下:

初始化界面
初始化界面完成基本資訊的列印,包括由白色方塊構成的邊界和按鍵提示陳述句,

對照最終效果圖片,看著代碼很好理解,但是需要注意兩點:
- 一個小方塊在cmd命令視窗當中占兩個單位的橫坐標、一個單位的縱坐標,
- 游標跳轉函式CursorJump接收的是游標將要跳至的橫縱坐標,
例如,想要將游標跳轉到 i 行 j 列(這里所說的行和列都是以一個方塊為單位),就等價于讓游標跳轉到坐標(2*j,i)處,
//初始化界面
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("■");
}
else if (i == ROW - 1)
{
face.data[i][j] = 1; //標記該位置有方塊
printf("■");
}
else
face.data[i][j] = 0; //標記該位置無方塊
}
}
for (int i = COL; i < COL + 10; i++)
{
face.data[8][i] = 1; //標記該位置有方塊
CursorJump(2 * i, 8);
printf("■");
}
CursorJump(2 * COL, 1);
printf("下一個方塊:");
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("旋轉:空格");
CursorJump(2 * COL + 4, ROW - 11);
printf("暫停: S");
CursorJump(2 * COL + 4, ROW - 9);
printf("退出: Esc");
CursorJump(2 * COL + 4, ROW - 7);
printf("重新開始:R");
CursorJump(2 * COL + 4, ROW - 5);
printf("最高紀錄:%d", max);
CursorJump(2 * COL + 4, ROW - 3);
printf("當前分數:%d", grade);
}
初始化方塊資訊
上面說到俄羅斯方塊有7種基本形狀,便是以下7種:

我們先將這7種基本形狀的方塊資訊存盤在各自的第0種形態處,如下:

然后取第0種形態順時針旋轉后得到第1種形態,取第1種形態順時針旋轉后得到第2種形態,取第2種形態順時針旋轉后得到第3種形態,這7種形狀都按此方法操作,最終得到全部28種方塊資訊,如下:

在旋轉程序中,一個方塊順時針旋轉一次后其位置變換規律如下:

//初始化方塊資訊
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種形態(已經有了一種,這里每個還需增加3種)
{
//獲取第form種形態
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
temp[i][j] = block[shape][form].space[i][j];
}
}
//將第form種形態順時針旋轉,得到第form+1種形態
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];
}
}
}
}
}
顏色設定
這里的顏色設定函式所接收的引數c(0~6),代表7種形狀的方塊,每種方塊對應自己的顏色,所對應的顏色可以自己設定,

//顏色設定
void color(int c)
{
switch (c)
{
case 0:
c = 13; //“T”形方塊設定為紫色
break;
case 1:
case 2:
c = 12; //“L”形和“J”形方塊設定為紅色
break;
case 3:
case 4:
c = 10; //“Z”形和“S”形方塊設定為綠色
break;
case 5:
c = 14; //“O”形方塊設定為黃色
break;
case 6:
c = 11; //“I”形方塊設定為淺藍色
break;
default:
c = 7; //其他默認設定為白色
break;
}
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //顏色設定
//注:SetConsoleTextAttribute是一個API(應用程式編程介面)
}
設定顏色函式在其頭檔案中的宣告如下:

畫出方塊
方塊的資訊有了,接下來就是將方塊在螢屏上顯示出來,該函式的作用是,將第shape種形狀的第form種形態的方塊列印在螢屏的指定位置處,

所給x和y,指的是方塊資訊當中第一行第一列的方塊的列印位置,
//畫出方塊
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) //如果該位置有方塊
{
CursorJump(2 * (x + j), y + i); //游標跳轉到指定位置
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); //游標跳轉到指定位置
printf(" "); //列印空格覆寫(兩個空格)
}
}
}
}
合法性判斷
其實在方塊移動程序中,無時無刻都在判斷方塊下一次變化后的位置是否合法,只有合法才會允許該變化的進行,
所謂非法,就是指該方塊進行了該變化后落在了本來就有方塊的位置,
//合法性判斷
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) && (face.data[y + i][x + j] == 1))
return 0; //不合法
}
}
return 1; //合法
}
判斷得分與結束
判斷得分:
從下往上判斷,若某一行方塊全滿,則將改行方塊資料清空,并將該行上方的方塊全部下移,下移結束后回傳1,表示還需再次呼叫該函式進行判斷,因為被下移的行并沒有進行判斷,可能還存在滿行,
判斷結束:
- 直接判斷游戲區最上面的一行當中是否有方塊存在,若存在方塊,則游戲結束,
- 游戲結束后,除了給出游戲結束提示語之外,如果玩家本局游戲分數大于歷史最高記錄,則需要更新最高分到檔案當中,
- 游戲結束后詢問玩家是否再來一局,
//判斷得分與結束
int JudeFunc()
{
//判斷是否得分
for (int i = ROW - 2; i > 4; i--)
{
int sum = 0; //記錄第i行的方塊個數
for (int j = 1; j < COL - 1; j++)
{
sum += face.data[i][j]; //統計第i行的方塊個數
}
if (sum == 0) //該行沒有方塊,無需再判斷其上的層次(無需再繼續判斷是否得分)
break; //跳出回圈
if (sum == COL - 2) //該行全是方塊,可得分
{
grade += 10; //滿一行加10分
color(7); //顏色設定為白色
CursorJump(2 * COL + 4, ROW - 3); //游標跳轉到顯示當前分數的位置
printf("當前分數:%d", grade); //更新當前分數
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--)
{
sum = 0; //記錄上一行的方塊個數
for (int n = 1; n < COL - 1; n++)
{
sum += face.data[m - 1][n]; //統計上一行的方塊個數
face.data[m][n] = face.data[m - 1][n]; //將上一行方塊的標識移到下一行
face.color[m][n] = face.color[m - 1][n]; //將上一行方塊的顏色編號移到下一行
if (face.data[m][n] == 1) //上一行移下來的是方塊,列印方塊
{
CursorJump(2 * n, m); //游標跳轉到該位置
color(face.color[m][n]); //顏色設定為還方塊的顏色
printf("■"); //列印方塊
}
else //上一行移下來的是空格,列印空格
{
CursorJump(2 * n, m); //游標跳轉到該位置
printf(" "); //列印空格(兩個空格)
}
}
if (sum == 0) //上一行移下來的全是空格,無需再將上層的方塊向下移動(移動結束)
return 1; //回傳1,表示還需呼叫該函式進行判斷(移動下來的可能還有滿行)
}
}
}
//判斷游戲是否結束
for (int j = 1; j < COL - 1; j++)
{
if (face.data[1][j] == 1) //頂層有方塊存在(以第1行為頂層,不是第0行)
{
Sleep(1000); //留給玩家反應時間
system("cls"); //清空螢屏
color(7); //顏色設定為白色
CursorJump(2 * (COL / 3), ROW / 2 - 3);
if (grade>max)
{
printf("恭喜你打破最高記錄,最高記錄更新為%d", grade);
WriteGrade();
}
else if (grade == max)
{
printf("與最高記錄持平,加油再創佳績", grade);
}
else
{
printf("請繼續加油,當前與最高記錄相差%d", max - grade);
}
CursorJump(2 * (COL / 3), ROW / 2);
printf("GAME OVER");
while (1)
{
char ch;
CursorJump(2 * (COL / 3), ROW / 2 + 3);
printf("再來一局?(y/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("選擇錯誤,請再次選擇");
}
}
}
}
return 0; //判斷結束,無需再呼叫該函式進行判斷
}
游戲主體邏輯函式
主體邏輯:
- 在列印當前下落的方塊前,先隨機獲取下一次將要下落的方塊,并列印到提示區的右上角,
- 將當前下落的方塊首先列印到游戲區頂部,給定一定的時間間隔,若在該時間內鍵盤未被敲擊,則方塊下落一格,方塊下落前需先判斷下落后的合法性,
- 若在給定時間間隔內鍵盤被敲擊,則根據所敲擊的按鍵給出相應反饋,
- 若方塊落到底部,則呼叫“判斷得分與結束”函式進行判斷,
- 若游戲未結束,則回圈進行以上步驟,
//游戲主體邏輯函式
void StartGame()
{
int shape = rand() % 7, form = rand() % 4; //隨機獲取方塊的形狀和形態
while (1)
{
int t = 0;
int nextShape = rand() % 7, nextForm = rand() % 4; //隨機獲取下一個方塊的形狀和形態
int x = COL / 2 - 2, y = 0; //方塊初始下落位置的橫縱坐標
color(nextShape); //顏色設定為下一個方塊的顏色
DrawBlock(nextShape, nextForm, COL + 3, 3); //將下一個方塊顯示在右上角
while (1)
{
color(shape); //顏色設定為當前正在下落的方塊
DrawBlock(shape, form, x, y); //將該方塊顯示在初始下落位置
if (t == 0)
{
t = 15000; //這里t越小,方塊下落越快(可以根據此設定游戲難度)
}
while (--t)
{
if (kbhit() != 0) //若鍵盤被敲擊,則退出回圈
break;
}
if (t == 0) //鍵盤未被敲擊
{
if (IsLegal(shape, form, x, y + 1) == 0) //方塊再下落就不合法了(已經到達底部)
{
//將當前方塊的資訊錄入face當中
//face:記錄界面的每個位置是否有方塊,若有方塊還需記錄該位置方塊的顏色,
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; //將該位置標記為有方塊
face.color[y + i][x + j] = shape; //記錄該方塊的顏色數值
}
}
}
while (JudeFunc()); //判斷此次方塊下落是否得分以及游戲是否結束
break; //跳出當前死回圈,準備進行下一個方塊的下落
}
else //未到底部
{
DrawSpace(shape, form, x, y); //用空格覆寫當前方塊所在位置
y++; //縱坐標自增(下一次顯示方塊時就相當于下落了一格了)
}
}
else //鍵盤被敲擊
{
char ch = getch(); //讀取keycode
switch (ch)
{
case DOWN: //方向鍵:下
if (IsLegal(shape, form, x, y + 1) == 1) //判斷方塊向下移動一位后是否合法
{
//方塊下落后合法才進行以下操作
DrawSpace(shape, form, x, y); //用空格覆寫當前方塊所在位置
y++; //縱坐標自增(下一次顯示方塊時就相當于下落了一格了)
}
break;
case LEFT: //方向鍵:左
if (IsLegal(shape, form, x - 1, y) == 1) //判斷方塊向左移動一位后是否合法
{
//方塊左移后合法才進行以下操作
DrawSpace(shape, form, x, y); //用空格覆寫當前方塊所在位置
x--; //橫坐標自減(下一次顯示方塊時就相當于左移了一格了)
}
break;
case RIGHT: //方向鍵:右
if (IsLegal(shape, form, x + 1, y) == 1) //判斷方塊向右移動一位后是否合法
{
//方塊右移后合法才進行以下操作
DrawSpace(shape, form, x, y); //用空格覆寫當前方塊所在位置
x++; //橫坐標自增(下一次顯示方塊時就相當于右移了一格了)
}
break;
case SPACE: //空格鍵
if (IsLegal(shape, (form + 1) % 4, x, y + 1) == 1) //判斷方塊旋轉后是否合法
{
//方塊旋轉后合法才進行以下操作
DrawSpace(shape, form, x, y); //用空格覆寫當前方塊所在位置
y++; //縱坐標自增(總不能原地旋轉吧)
form = (form + 1) % 4; //方塊的形態自增(下一次顯示方塊時就相當于旋轉了)
}
break;
case ESC: //Esc鍵
system("cls"); //清空螢屏
color(7);
CursorJump(COL, ROW / 2);
printf(" 游戲結束 ");
CursorJump(COL, ROW / 2 + 2);
exit(0); //結束程式
case 's':
case 'S': //暫停
system("pause>nul"); //暫停(按任意鍵繼續)
break;
case 'r':
case 'R': //重新開始
system("cls"); //清空螢屏
main(); //重新執行主函式
}
}
}
shape = nextShape, form = nextForm; //獲取下一個方塊的資訊
DrawSpace(nextShape, nextForm, COL + 3, 3); //將右上角的方塊資訊用空格覆寫
}
}
注意: 這里只是概括性的說明了俄羅斯方塊的主體邏輯,代碼當中還有大量注釋以供大家理解,
從檔案讀取最高分
首先需要使用fopen函式打開“俄羅斯方塊最高記錄.txt”檔案,若是第一次運行該代碼,則會自動創建該檔案,并將歷史最高記錄設定為0,之后讀取檔案當中的歷史最高記錄存盤在max變數當中,并關閉檔案即可,
//從檔案讀取最高分
void ReadGrade()
{
FILE* pf = fopen("俄羅斯方塊最高得分記錄.txt", "r"); //以只讀方式打開檔案
if (pf == NULL) //打開檔案失敗
{
pf = fopen("俄羅斯方塊最高得分記錄.txt", "w"); //以只寫方式打開檔案(檔案不存在可以自動創建該檔案)
fwrite(&grade, sizeof(int), 1, pf); //將max寫入檔案(此時max為0),即將最高歷史得分初始化為0
}
fseek(pf, 0, SEEK_SET); //使檔案指標pf指向檔案開頭
fread(&max, sizeof(int), 1, pf); //讀取檔案中的最高歷史得分到max當中
fclose(pf); //關閉檔案
pf = NULL; //檔案指標及時置空
}
更新最高分到檔案
首先使用fopen函式打開“俄羅斯方塊最高記錄.txt”,然后將本局游戲的分數grade寫入檔案當中即可(覆寫式),
//更新最高分到檔案
void WriteGrade()
{
FILE* pf = fopen("俄羅斯方塊最高得分記錄.txt", "w"); //以只寫方式打開檔案
if (pf == NULL) //打開檔案失敗
{
printf("保存最高得分記錄失敗\n");
exit(0);
}
fwrite(&grade, sizeof(int), 1, pf); //將本局游戲得分寫入檔案當中(更新最高歷史得分)
fclose(pf); //關閉檔案
pf = NULL; //檔案指標及時置空
}
主函式
主函式里面就是依次呼叫以上函式,但有三點需要說明:
- 全域變數grade需要在主函式內初始化為0,不能在全域范圍初始化為0,因為當玩家按下R鍵進行重玩時我們需要將當前分數grade重新設定為0,
- 亂數的生成起點建議設定在主函式當中,
- 主函式當中的#pragma陳述句是用于消除類似以下警告的:

int max, grade; //全域變數
int main()
{
#pragma warning (disable:4996) //消除警告
max = 0, grade = 0; //初始化變數
system("title 俄羅斯方塊"); //設定cmd視窗的名字
system("mode con lines=29 cols=60"); //設定cmd視窗的大小
HideCursor(); //隱藏游標
ReadGrade(); //從檔案讀取最高分到max變數
InitInterface(); //初始化界面
InitBlockInfo(); //初始化方塊資訊
srand((unsigned int)time(NULL)); //設定亂數生成的起點
StartGame(); //開始游戲
return 0;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/294060.html
標籤:其他
上一篇:C語言實作三子棋(井字棋)
