首頁 > 軟體

C/C++實現俄羅斯方塊遊戲

2022-02-16 10:04:43

一、遊戲效果展示

每個方塊的朝向使用二維陣列表示

二、完整程式碼

可以直接拷貝執行

#include<graphics.h>
#include<stdio.h>
#include<time.h>
#include<conio.h>    //kbhit()
 
int score = 0;    //總分
int rank = 0;    //等級
 
#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 87        //使用者操作
#define KEY_LEFT 65
#define KEY_RIGHT 68
#define KEY_DOWN 83
#define KEY_SPACE 32
 
#define MinX 30        //遊戲左上角位置
#define MinY 30
int speed = 500;    //方塊降落速度
 
 
int NextIndex = -1;        //下一個方塊
int BlockIndex = -1;        //當前方塊
 
typedef enum {        //方塊朝向
    BLOCK_UP,
    BLOCK_RIGHT,
    BLOCK_LEFT,
    BLOCK_DOWN
}block_dir_t;
 
typedef enum {        //方塊移動方向
    MOVE_DOWN,
    MOVE_LEFT,
    MOVE_RIGHT
}move_dir_t;
 
//方塊顏色
int color[BLOCK_COUNT] = {
    GREEN,
    CYAN,
    MAGENTA,
    YELLOW,
    BROWN
};
int visit[30][15];    //存取陣列visit[i][j] = 1表示該位置有方塊
int markColor[30][15];    //對應位置顏色
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 },
};
 
/***************************
 * 功能:歡迎頁面
 * 輸入:
 *        無
 * 返回:
 *        無
 **************************/
void welcome() {
    //1.初始化畫布
    initgraph(550, 660);
    system("pause");
    //2.設定視窗標題
    HWND window = GetHWnd();//獲得視窗,獲得當前視窗
    SetWindowText(window, _T("俄羅斯方塊  小明來嘍"));    //設定標題
 
    //3.設定遊戲初始頁面
    setfont(40, 0, _T("微軟雅黑"));        //設定文字的字型樣式(高,寬(0表示自適應),字型)
    setcolor(WHITE);    // 設定顏色
    outtextxy(205, 200, _T("俄羅斯方法"));
 
    setfont(20, 0, _T("楷體"));
    setcolor(WHITE);    // 設定顏色
    outtextxy(175, 300, _T("程式設計,從俄羅斯方塊開始"));
 
    Sleep(3000);
}
 
/***************************
 * 功能:初始化遊戲場景
 * 輸入:
 *        無
 * 返回:
 *        無
 **************************/
void initGameSceen() {
    char str[16];    //存放分數
    //1.清屏
    cleardevice();
    //2.畫場景
    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("分數:"));
 
    //按指定格式,將score寫入str
    sprintf_s(str, 16, "%d", score);
    //這裡設定字元集為多字元,保證outtextxy可以寫出變數str
    outtextxy(415, 310, str);
 
    outtextxy(405, 375, _T("等級:"));    //等級
 
    //按指定格式,將rank寫入str
    sprintf_s(str, 16, "%d", rank);
    //這裡設定字元集為多字元,保證outtextxy可以寫出變數str
    outtextxy(415, 405, str);
 
    setcolor(LIGHTBLUE);    //操作說明
    outtextxy(390, 475, "操作說明:");
    outtextxy(390, 500, "↑: 旋轉");
    outtextxy(390, 525, "↓: 下降");
    outtextxy(390, 550, "←: 左移");
    outtextxy(390, 575, "→: 右移");
    outtextxy(390, 600, "空格: 暫停");
}
 
/*****************************************
 * 功能:清空方塊提示框裡的方塊
 * 輸入:
 *        無
 * 返回:
 *        無
 ****************************************/
void clearBlock() {
    setcolor(BLACK);
    setfont(23, 0, "楷體");
    for (int i = 0; i < BLOCK_HEIGHT; ++i) {
        for (int j = 0; j < BLOCK_WIDTH; ++j) {
            int x = 391 + j * UNIT_SIZE;
            int y = 71 + i * UNIT_SIZE;
            outtextxy(x, y, "■");
        }
    }
}
 
/*****************************************
 * 功能:清除降落過程中的方塊
 * 輸入:
 *        x,y - 方塊的座標(二維陣列左上角位置)
 *        block_dir_t - 方塊方向
 * 返回:
 *        無
 ****************************************/
void clearBlock(int x, int y, block_dir_t blockDir) {
    setcolor(BLACK);
    //    setfont(23, 0, "楷體");
    int id = BlockIndex * 4 + blockDir;
    for (int i = 0; i < BLOCK_HEIGHT; ++i) {
        for (int j = 0; j < BLOCK_WIDTH; ++j) {
            if (block[id][i][j] == 1) {
                int x2 = x + j * UNIT_SIZE;
                int y2 = y + i * UNIT_SIZE;
                outtextxy(x2, y2, "■");
            }
        }
    }
}
 
/*****************************************
 * 功能:在提示框 與 降落框的起始位置畫方塊
 * 輸入:
 *        x,y - 方塊的座標(二維陣列左上角位置)
 * 返回:
 *        無
 ****************************************/
void drawBlock(int x, int y) {
    setcolor(color[NextIndex]);
    setfont(23, 0, "楷體");
    for (int i = 0; i < BLOCK_HEIGHT; ++i) {
        for (int j = 0; j < BLOCK_WIDTH; ++j) {
            if (block[NextIndex * 4][i][j] == 1) {
                int x2 = x + j * UNIT_SIZE;
                int y2 = y + i * UNIT_SIZE;
                outtextxy(x2, y2, "■");
            }
        }
    }
}
 
/*****************************************
 *功能:繪製下降過程中的方塊
 *輸入:
 *        x,y - 方塊的座標(二維陣列左上角位置)
 *        block_dir_t - 方塊方向
 * 返回:
 *        無
 ****************************************/
void drawBlock(int x, int y, block_dir_t dir) {
    setcolor(color[BlockIndex]);
    setfont(23, 0, "楷體");
    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) {
                //擦除該方塊的第i行第j列
                outtextxy(x + j * UNIT_SIZE, y + i * UNIT_SIZE, "■");
            }
        }
    }
}
 
/*****************************************
 *功能:方塊提示框中產生新方塊
 *輸入:
 *        無
 *返回:
 *        無
 ****************************************/
void nextblock() {
    clearBlock();
    //產生亂數,隨機選擇方塊
    srand((unsigned)time(NULL));    //使用時間函數的返回值,來作為隨機種子
    NextIndex = rand() % BLOCK_COUNT;    //產生0~5的亂數
    drawBlock(391, 71);
}
 
 /*****************************************
  *功能:判斷在指定位置向指定方向是否可以移動
  *輸入:
  *        x,y - 方塊位置
  *        moveDir - 下一步想要移動的方向
  *        blockDir - 當前方塊的方向
  * 返回:
  *        true - 可以移動
  *        false - 不可以移動
  ****************************************/
bool moveable(int x0, int y0, move_dir_t moveDir, block_dir_t blockDir) {
    //計算方塊左上角在30×15的遊戲區位置(第多少行, 第多少列)
    int x = (y0 - MinY) / UNIT_SIZE;
    int y = (x0 - MinX) / UNIT_SIZE;
    int ret = 1;
    int id = BlockIndex * 4 + blockDir;
    if (moveDir == MOVE_DOWN) {
        for (int i = 0; i < BLOCK_HEIGHT; ++i) {
            for (int j = 0; j < BLOCK_WIDTH; ++j) {
                //向下不能運動的條件:實心方塊已經達到底部(x+i+1==30),或者底部已有方塊    
                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 < BLOCK_HEIGHT; ++i) {
            for (int j = 0; j < BLOCK_WIDTH; ++j) {
                //向左不能運動的條件:實心方塊已經達到左邊界(y+j==0),或者左邊已有方塊
                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 < BLOCK_HEIGHT; ++i) {
            for (int j = 0; j < BLOCK_WIDTH; ++j) {
                //向下不能運動的條件:實心方塊已經達到右邊界(y+j+1>=15),或者右邊已有方塊
                if (block[id][i][j] == 1 &&
                    (y + j + 1 >= 15 || visit[x + i][y + j + 1] == 1)) {
                    ret = 0;
                }
            }
        }
    }
    return ret;
}
 
/*****************************
 *功能:檢測遊戲是否結束
 *輸入:
 *        無
 * 返回:
 *        無
 *****************************/
void failCheck() {
    //遊戲結束條件是頂部新被繪製出的方塊就要「固化」,頂部新繪製的方塊方向朝上,運動方向朝下
    if (!moveable(START_X, START_Y, MOVE_DOWN, (block_dir_t)BLOCK_UP)) {
        setcolor(WHITE);
        setfont(45, 0, "隸體");
        outtextxy(75, 300, "Game Over!");
        Sleep(1000);
        system("pause");
        closegraph();
        exit(0);
    }
}
 
/**************************
 * 功能:延時等待
 * 輸入:
 *
 * 返回:
 *        無
 *************************/
void wait(int interval) {
    int count = interval / 10;
    for (int i = 0; i < count; ++i) {
        Sleep(10);
        //如果休眠期間使用者有按鍵,則結束休眠
        if (_kbhit()) {
            return;
        }
    }
}
 
/*****************************************
 * 功能:判斷當前方塊是否可以向指定方向旋轉
 * 輸入:
 *        x,y - 方塊位置(二維陣列座標)
 *        dir - 方塊旋轉方向
 * 返回:
 *        true - 可以旋轉
 *        false - 不可以旋轉
 ****************************************/
bool rotatable(int x, int y, block_dir_t dir) {
    //首先判斷是否可以繼續向下移動
    if (!moveable(x, y, MOVE_DOWN, dir)) {
        return false;
    }
    int x2 = (y - MinY) / UNIT_SIZE;
    int y2 = (x - MinX) / UNIT_SIZE;
    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 && (y2 + j < 0 || y2 + j >= 15 || visit[x2 + i][y2 + j] == 1)) {
                return false;
            }
        }
    }
    return true;
}
 
/*****************************************
 * 功能:
 * 輸入:
 *
 * 返回:
 *        無
 ****************************************/
void mark(int x, int y, block_dir_t dir) {
    int id = BlockIndex * 4 + dir;
    int x2 = (y - MinY) / UNIT_SIZE;
    int y2 = (x - MinX) / UNIT_SIZE;
    for (int i = 0; i < BLOCK_HEIGHT; ++i) {
        for (int j = 0; j < BLOCK_WIDTH; ++j) {
            if (block[id][i][j] == 1) {
                visit[x2 + i][y2 + j] = 1;
                markColor[x2 + i][y2 + j] = color[BlockIndex];
            }
        }
    }
}
 
/*****************************************
 * 功能:讀取使用者操作,時時更新降落的方塊
 * 輸入:
 *        無
 * 返回:
 *        無
 ****************************************/
void move() {
    int x = START_X;    //方塊起始位置
    int y = START_Y;
    int k = 0;
    block_dir_t blockDir = (block_dir_t)BLOCK_UP;
    int curSpeed = speed;    //定義當前方塊降落速度
    //讀取使用者操作前判斷遊戲是否結束
    failCheck();
    //持續向下降落
    while (1) {
        int curSpeed = speed;    //定義當前方塊降落速度
        //清除方塊
        clearBlock(x, k + y, blockDir);
        //判斷選擇的方向
        if (_kbhit()) {
            int key = _getch();
            if (key == KEY_SPACE) {
                system("pause");
            }
            else if (key == KEY_UP) {
                block_dir_t nextDir = (block_dir_t)((blockDir + 1) % 4);
                if (rotatable(x, y + k, nextDir)) {
                    blockDir = nextDir;
                }
            }
            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;
                }
            }
            else if (key == KEY_DOWN) {
                curSpeed = 50;
            }
        }
        k += 20;
        //繪製方塊
        drawBlock(x, y + k, blockDir);
        //休眠
        wait(curSpeed);
        //方塊的固化處理,方塊固定後結束迴圈,當前一個方塊的move執行完畢
        if (!moveable(x, y + k, MOVE_DOWN, blockDir)) {
            mark(x, y + k, blockDir);
            break;
        }
    }
}
 
/*****************************************
 *功能:繪製剛從頂部降落的方塊,更新提示框內的方塊,呼叫方塊降落函數move()
 *輸入:
 *        無
 * 返回:
 *        無
 ****************************************/
void newblock() {
    BlockIndex = NextIndex;
    //繪製剛從頂部下降的方塊
    drawBlock(START_X, START_Y);
    //讓新出現方塊暫停一會
    Sleep(200);
    //右上角區域繪製下一個方塊
    nextblock();
    //方塊降落
    move();
}
 
/*****************************************
 * 功能:消除第i行,並把上面的行都往下移
 * 輸入:
 *        無
 * 返回:
 *        無
 ****************************************/
void down(int x) {
    for (int i = x; i > 0; --i) {
        for (int j = 0; j < 15; ++j) {
            if (visit[i - 1][j] == 1) {
                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, "■");
    }
}
 
/*****************************************
 * 功能:更新分數
 * 輸入:
 *        無
 * 返回:
 *        無
 ****************************************/
void addScore(int lines) {
    char str[32];
    score += lines * 10;
    sprintf_s(str, 32, "%d", score);
    setcolor(RED);
    outtextxy(415, 310, str);
 
}
 
/*************************
 * 功能:更新等級
 * 輸入:
 *        無
 * 返回:
 *        無
 *************************/
void updateGrade() {
    //更新等級
    //假設50分一級
    rank = score / 50;
    char str[32];
    sprintf_s(str, 32, "%d", rank);
    setcolor(RED);
    outtextxy(415, 405, str);
    //更新速度
    if (speed <= 100) {
        speed = 100;
    }
    else {
        speed = 500 - rank * 20;
    }
}
 
/*************************
 * 功能:檢查是否有滿行方塊
 * 輸入:
 *        無
 * 返回:
 *        無
 *************************/
void check() {
    int i, j;
    int clearLines = 0;
    for (i = 29; i >= 0; i--) {
        // 檢查第i行有沒有滿
        for (j = 0; j < 15 && visit[i][j]; j++);
        //執行到此處時,有兩種情況:
        // 1. 第i行沒有滿,即表示有空位 此時 j<15
        // 2. 第i行已滿了,此時 j>=15
        if (j >= 15) {
            // 此時,第i行已經滿了,就需要消除第i行
            down(i);  //消除第i行,並把上面的行都下移
            i++;  // 因為最外層的迴圈中有 i--, 所以我們先i++, 使得下次迴圈時,再把這一行檢查一下
            clearLines++;
        }
    }
    // 更新分數
    addScore(clearLines);
 
    // 更新等級(更新等級提示,更新速度)
    updateGrade();
}
 
int main() {
    welcome();
    initGameSceen();
    //產生新方塊
    nextblock();
    //    system("pause");
    Sleep(800);
 
    //初始化存取陣列
    memset(visit, 0, sizeof(visit));
 
    while (1) {
        newblock();
        //消除滿行,並更新分數和速度
        check();
    }
    system("pause");
    closegraph();
    return 0;
}

三、所需開發環境

1)安裝VS2019,或VS其他版本

2)安裝easyX圖形庫

四、具體專案實現

①遊戲歡迎介面 welcome( )

在遊戲開始前會有一個遊戲歡迎頁面,整個頁面會持續大概三秒,之後便進入遊戲場景。在遊戲歡迎頁面中,除了將顯示遊戲名稱外,還要修改視窗標題。

/***************************
 * 功能:歡迎頁面
 * 輸入:
 *        無
 * 返回:
 *        無
 **************************/
void welcome() {
    //1.初始化畫布
    initgraph(550, 660);
    //2.設定視窗標題
    HWND window = GetHWnd();//獲得視窗,獲得當前視窗
    SetWindowText(window, _T("俄羅斯方塊  小明來嘍"));    //設定標題
 
    //3.設定遊戲初始頁面
    setfont(40, 0, _T("微軟雅黑"));        //設定文字的字型樣式(高,寬(0表示自適應),字型)
    setcolor(WHITE);    // 設定顏色
    outtextxy(205, 200, _T("俄羅斯方法"));
 
    setfont(20, 0, _T("楷體"));
    setcolor(WHITE);    // 設定顏色
    outtextxy(175, 300, _T("程式設計,從俄羅斯方塊開始"));
 
    Sleep(3000);
}

②遊戲背景 initGameScreen( ) 

繪製遊戲場景

/***************************
 * 功能:初始化遊戲場景
 * 輸入:
 *        無
 * 返回:
 *        無
 **************************/
void initGameSceen() {
    char str[16];    //存放分數
    //1.清屏
    cleardevice();
    //2.畫場景
    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("分數:"));
 
    //按指定格式,將score寫入str
    sprintf_s(str, 16, "%d", score);
    //這裡設定字元集為多字元,保證outtextxy可以寫出變數str
    outtextxy(415, 310, str);
 
    outtextxy(405, 375, _T("等級:"));    //等級
 
    //按指定格式,將rank寫入str
    sprintf_s(str, 16, "%d", rank);
    //這裡設定字元集為多字元,保證outtextxy可以寫出變數str
    outtextxy(415, 405, str);
 
    setcolor(LIGHTBLUE);    //操作說明
    outtextxy(390, 475, "操作說明:");
    outtextxy(390, 500, "W: 旋轉");
    outtextxy(390, 525, "S: 下降");
    outtextxy(390, 550, "A: 左移");
    outtextxy(390, 575, "D: 右移");
    outtextxy(390, 600, "空格: 暫停");
    system("pause");
}

③方塊表示 int block[ ][ ][ ]

將每個方塊的朝向用二維陣列表示。此次共設計了五種方塊,每個方塊四種朝向,使用三維陣列儲存每個剛快及其朝向。1代表該點是方格。

#define BLOCK_COUNT 5
#define BLOCK_WIDTH 5
#define BLOCK_HEIGHT 5
 
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 },
};

④新方塊表示nextBlock( )

在方塊提示框中每次生成新方塊由兩個動作組成,首先是擦除方塊,接著是繪製新方塊。

/*****************************************
 * 功能:清空方塊提示框裡的方塊
 * 輸入:
 *        無
 * 返回:
 *        無
 ****************************************/
void clearBlock() {
    setcolor(BLACK);
    setfont(23, 0, "楷體");
    for (int i = 0; i < BLOCK_HEIGHT; ++i) {
        for (int j = 0; j < BLOCK_WIDTH; ++j) {
            int x = 391 + j * UNIT_SIZE;
            int y = 71 + i * UNIT_SIZE;
            outtextxy(x, y, "■");
        }
    }
}
 
 
/*****************************************
 * 功能:在提示框 與 降落框的起始位置畫方塊
 * 輸入:
 *        x,y - 方塊的座標(二維陣列左上角位置)
 * 返回:
 *        無
 ****************************************/
void drawBlock(int x, int y) {
    setcolor(color[NextIndex]);
    setfont(23, 0, "楷體");
    for (int i = 0; i < BLOCK_HEIGHT; ++i) {
        for (int j = 0; j < BLOCK_WIDTH; ++j) {
            if (block[NextIndex * 4][i][j] == 1) {
                int x2 = x + j * UNIT_SIZE;
                int y2 = y + i * UNIT_SIZE;
                outtextxy(x2, y2, "■");
            }
        }
    }
}
 
/*****************************************
 *功能:方塊提示框中產生新方塊
 *輸入:
 *        無
 *返回:
 *        無
 ****************************************/
void nextblock() {
    clearBlock();
    //產生亂數,隨機選擇方塊
    srand((unsigned)time(NULL));    //使用時間函數的返回值,來作為隨機種子
    NextIndex = rand() % BLOCK_COUNT;    //產生0~5的亂數
    drawBlock(391, 71);
}

⑤設計遊戲迴圈main( )

在遊戲框中每次生成新方塊會進入對新方塊降落處理,等處理完後便會迴圈

int main() {
    welcome();
    initGameSceen();
    //產生新方塊
    nextblock();
    Sleep(800);
 
    //初始化存取陣列
    memset(visit, 0, sizeof(visit));
 
    while (1) {
        newblock();
    }
    system("pause");
    closegraph();
    return 0;
}
 
/*****************************************
 *功能:繪製剛從頂部降落的方塊,更新提示框內的方塊,呼叫方塊降落函數move()
 *輸入:
 *        無
 * 返回:
 *        無
 ****************************************/
void newblock() {
    BlockIndex = NextIndex;
    //繪製剛從頂部下降的方塊
    drawBlock(START_X, START_Y);
    //讓新出現方塊暫停一會
    Sleep(200);
    //右上角區域繪製下一個方塊
    nextblock();
    //方塊降落
    move();
}

⑥搭建使用者操作框架move( )Ⅰ

使用者操作框架:斷遊戲是否結束 → 擦除當前方塊 → 使用者按鍵操作 → 繪製新的方塊 → 延時等待 → 方塊是否要固化(固化則則表明對當前方塊操作結束)。

#define KEY_UP 87        //使用者操作
#define KEY_LEFT 65
#define KEY_RIGHT 68
#define KEY_DOWN 83
#define KEY_SPACE 32
 
/*****************************************
 * 功能:讀取使用者操作,時時更新降落的方塊
 * 輸入:
 *        無
 * 返回:
 *        無
 ****************************************/
void move() {
    //讀取使用者操作前判斷遊戲是否結束
    failCheck();
    //持續向下降落
    while (1) {
        //清除方塊
            //to do
        //判斷選擇的方向
        if (_kbhit()) {
            int key = _getch();
            if (key == KEY_SPACE) {
                //to do
            }
            else if (key == KEY_UP) {
                //to do
            }
            else if (key == KEY_LEFT) {
                //to do
            }
            else if (key == KEY_RIGHT) {
                //to do
            }
            else if (key == KEY_DOWN) {
                //to do
            }
        }
        //繪製方塊
            //to do
        //休眠
            //to do
        //方塊的固化處理,方塊固定後結束迴圈,當前一個方塊的move執行完畢
            //to do
    }
}

⑦判斷方塊能否向指定方向移動 moveable( )

當新方塊剛從頂部繪製時就碰到了“固化”方塊時則表明遊戲結束,因此我們只需判斷方塊能否向下移動即可。這裡先實現判斷方塊能否向指定方向移動功能。

/*****************************************
  *功能:判斷在指定位置向指定方向是否可以移動
  *輸入:
  *        x,y - 方塊位置
  *        moveDir - 下一步想要移動的方向
  *        blockDir - 當前方塊的方向
  * 返回:
  *        true - 可以移動
  *        false - 不可以移動
  ****************************************/
bool moveable(int x0, int y0, move_dir_t moveDir, block_dir_t blockDir) {
    //計算方塊左上角在30×15的遊戲區位置(第多少行, 第多少列)
    int x = (y0 - MinY) / UNIT_SIZE;
    int y = (x0 - MinX) / UNIT_SIZE;
    int ret = 1;
    int id = BlockIndex * 4 + blockDir;
    if (moveDir == MOVE_DOWN) {
        for (int i = 0; i < BLOCK_HEIGHT; ++i) {
            for (int j = 0; j < BLOCK_WIDTH; ++j) {
                //向下不能運動的條件:實心方塊已經達到底部(x+i+1==30),或者底部已有方塊    
                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 < BLOCK_HEIGHT; ++i) {
            for (int j = 0; j < BLOCK_WIDTH; ++j) {
                //向左不能運動的條件:實心方塊已經達到左邊界(y+j==0),或者左邊已有方塊
                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 < BLOCK_HEIGHT; ++i) {
            for (int j = 0; j < BLOCK_WIDTH; ++j) {
                //向下不能運動的條件:實心方塊已經達到右邊界(y+j+1>=15),或者右邊已有方塊
                if (block[id][i][j] == 1 &&
                    (y + j + 1 >= 15 || visit[x + i][y + j + 1] == 1)) {
                    ret = 0;
                }
            }
        }
    }
    return ret;
}

⑧遊戲失敗檢查 failCheck( )

遊戲失敗檢測,當新繪製方塊無法向下移動時則表明遊戲失敗。

/*****************************
 *功能:檢測遊戲是否結束
 *輸入:
 *        無
 * 返回:
 *        無
 *****************************/
void failCheck() {
    //遊戲結束條件是頂部新被繪製出的方塊就要「固化」,頂部新繪製的方塊方向朝上,運動方向朝下
    if (!moveable(START_X, START_Y, MOVE_DOWN, (block_dir_t)BLOCK_UP)) {
        setcolor(WHITE);
        setfont(45, 0, "隸體");
        outtextxy(75, 300, "Game Over!");
        Sleep(1000);
        system("pause");
        closegraph();
        exit(0);
    }
}

⑨清除下降過程中的方塊 clearBlock( )

如果遊戲未失敗,則表明使用者可以繼續操作,在讀取使用者操作前要先將降落框內的方塊清除。

/*****************************************
 * 功能:清除降落過程中的方塊
 * 輸入:
 *        x,y - 方塊的座標(二維陣列左上角位置)
 *        block_dir_t - 方塊方向
 * 返回:
 *        無
 ****************************************/
void clearBlock(int x, int y, block_dir_t blockDir) {
    setcolor(BLACK);
    //    setfont(23, 0, "楷體");
    int id = BlockIndex * 4 + blockDir;
    for (int i = 0; i < BLOCK_HEIGHT; ++i) {
        for (int j = 0; j < BLOCK_WIDTH; ++j) {
            if (block[id][i][j] == 1) {
                int x2 = x + j * UNIT_SIZE;
                int y2 = y + i * UNIT_SIZE;
                outtextxy(x2, y2, "■");
            }
        }
    }
}

⑩判斷方塊旋轉 rotatable( )

如果方塊在待轉方向可以向下運動則表明方塊可以旋轉,因此這裡只需少加利用moveable函數即可實現。

/*****************************************
 * 功能:判斷當前方塊是否可以向指定方向旋轉
 * 輸入:
 *        x,y - 方塊位置(二維陣列座標)
 *        dir - 方塊旋轉方向
 * 返回:
 *        true - 可以旋轉
 *        false - 不可以旋轉
 ****************************************/
bool rotatable(int x, int y, block_dir_t dir) {
    //首先判斷是否可以繼續向下移動
    if (!moveable(x, y, MOVE_DOWN, dir)) {
        return false;
    }
    int x2 = (y - MinY) / UNIT_SIZE;
    int y2 = (x - MinX) / UNIT_SIZE;
    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 && (y2 + j < 0 || y2 + j >= 15 || visit[x2 + i][y2 + j] == 1)) {
                return false;
            }
        }
    }
    return true;
}

①①繪製下降過程中的方塊 drawBlock( )

每次根據使用者操作繪製新的方塊

/*****************************************
 *功能:繪製下降過程中的方塊
 *輸入:
 *        x,y - 方塊的座標(二維陣列左上角位置)
 *        block_dir_t - 方塊方向
 * 返回:
 *        無
 ****************************************/
void drawBlock(int x, int y, block_dir_t dir) {
    setcolor(color[BlockIndex]);
    setfont(23, 0, "楷體");
    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) {
                //擦除該方塊的第i行第j列
                outtextxy(x + j * UNIT_SIZE, y + i * UNIT_SIZE, "■");
            }
        }
    }
}

①②延時等待 wait ( )

每次 處理完使用者操作後會進入延時等待,等待時長會根據當前方塊降落速度而定,在延時等待期間如果檢測到使用者有按鍵操作時則會結束等待。

/**************************
 * 功能:延時等待
 * 輸入:
 *
 * 返回:
 *        無
 *************************/
void wait(int interval) {
    int count = interval / 10;
    for (int i = 0; i < count; ++i) {
        Sleep(10);
        //如果休眠期間使用者有按鍵,則結束休眠
        if (_kbhit()) {
            return;
        }
    }
}

①③固定方塊 mark( )

每次繪製出新方塊後判斷方塊是否還能繼續移動,如果不能移動則表明方塊需要固化。

/*****************************************
 * 功能:方塊固定
 * 輸入:
 *        x,y - 方塊座標
 *        dir - 方塊朝向
 * 返回:
 *        無
 ****************************************/
void mark(int x, int y, block_dir_t dir) {
    int id = BlockIndex * 4 + dir;
    int x2 = (y - MinY) / UNIT_SIZE;
    int y2 = (x - MinX) / UNIT_SIZE;
    for (int i = 0; i < BLOCK_HEIGHT; ++i) {
        for (int j = 0; j < BLOCK_WIDTH; ++j) {
            if (block[id][i][j] == 1) {
                visit[x2 + i][y2 + j] = 1;
                markColor[x2 + i][y2 + j] = color[BlockIndex];
            }
        }
    }
}

①④使用者操作框架完善Ⅱ mov( )

將上述實現功能補充到操作框架中

void move() {
    int x = START_X;    //方塊起始位置
    int y = START_Y;
    int k = 0;
    block_dir_t blockDir = (block_dir_t)BLOCK_UP;
    int curSpeed = speed;    //定義當前方塊降落速度
    //讀取使用者操作前判斷遊戲是否結束
    failCheck();
    //持續向下降落
    while (1) {
        int curSpeed = speed;    //定義當前方塊降落速度
        //清除方塊
        clearBlock(x, k + y, blockDir);
        //判斷選擇的方向
        if (_kbhit()) {
            int key = _getch();
            if (key == KEY_SPACE) {
                system("pause");
            }
            else if (key == KEY_UP) {
                block_dir_t nextDir = (block_dir_t)((blockDir + 1) % 4);
                if (rotatable(x, y + k, nextDir)) {
                    blockDir = nextDir;
                }
            }
            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;
                }
            }
            else if (key == KEY_DOWN) {
                curSpeed = 50;
            }
        }
        k += 20;
        //繪製方塊
        drawBlock(x, y + k, blockDir);
        //休眠
        wait(curSpeed);
        //方塊的固化處理,方塊固定後結束迴圈,當前一個方塊的move執行完畢
        if (!moveable(x, y + k, MOVE_DOWN, blockDir)) {
            mark(x, y + k, blockDir);
            break;
        }
    }
}

①⑤消除方塊 check( ) + down( )

當對一個方塊下降操作結束後,在已固化方塊陣列裡查詢“滿行”方塊,如果存在“滿行”方塊則要進行清除操作,接著更新使用者分數和等級。

/************************
 * 功能:檢查是否有滿行方塊
 * 輸入:
 *        無
 * 返回:
 *        無
 *************************/
void check() {
    int i, j;
    int clearLines = 0;
    for (i = 29; i >= 0; i--) {
        // 檢查第i行有沒有滿
        for (j = 0; j < 15 && visit[i][j]; j++);
        //執行到此處時,有兩種情況:
        // 1. 第i行沒有滿,即表示有空位 此時 j<15
        // 2. 第i行已滿了,此時 j>=15
        if (j >= 15) {
            // 此時,第i行已經滿了,就需要消除第i行
            down(i);  //消除第i行,並把上面的行都下移
            i++;  // 因為最外層的迴圈中有 i--, 所以我們先i++, 使得下次迴圈時,再把這一行檢查一下
            clearLines++;
        }
    }
    // 更新分數
    addScore(clearLines);
 
    // 更新等級(更新等級提示,更新速度)
    updateGrade();
}
 
/*****************************************
 * 功能:消除第i行,並把上面的行都往下移
 * 輸入:
 *        無
 * 返回:
 *        無
 ****************************************/
void down(int x) {
    for (int i = x; i > 0; --i) {
        for (int j = 0; j < 15; ++j) {
            if (visit[i - 1][j] == 1) {
                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, "■");
    }
}

①⑥更新分數和等級 addScore( ) + updateGrade( )

根據清除方塊行數更新使用者分數和等級。

/*****************************************
 * 功能:更新分數
 * 輸入:
 *        無
 * 返回:
 *        無
 ****************************************/
void addScore(int lines) {
    char str[32];
    score += lines * 10;
    sprintf_s(str, 32, "%d", score);
    setcolor(RED);
    outtextxy(415, 310, str);
 
}
 
/*************************
 * 功能:更新等級
 * 輸入:
 *        無
 * 返回:
 *        無
 *************************/
void updateGrade() {
    //更新等級
    //假設50分一級
    rank = score / 50;
    char str[32];
    sprintf_s(str, 32, "%d", rank);
    setcolor(RED);
    outtextxy(415, 405, str);
    //更新速度
    if (speed <= 100) {
        speed = 100;
    }
    else {
        speed = 500 - rank * 20;
    }
}

程式碼整合執行

五 、不足之處

使用easyX繪圖,匯入遊戲圖片,從而使得遊戲效果更為逼真

遊戲戰績的儲存

操作控制略有卡頓 

以上就是C/C++實現俄羅斯方塊遊戲的詳細內容,更多關於C/C++ 俄羅斯方塊的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com