1. 程式人生 > 其它 >資料結構 - 陣列 - 遊戲 2048

資料結構 - 陣列 - 遊戲 2048

用陣列來模擬著名遊戲 2048。

遊戲《2048》

《2048》是一款數字益智遊戲,在 \(4*4\) 的方格中通過上下左右滑動來控制數字的變化,遊戲勝利的條件是出現 \(2048\) 這個數字。

遊戲規則如下:

  • 玩家每次可以選擇上下左右其中一個方向去滑動,定義滑動的方向為前,滑動的反方向為後,每滑動一次,所有的數字方塊都會向前移動靠攏至邊緣。

  • 每一行(列)從最前方第二個方塊依次向前方方塊發起撞擊,相撞的兩個方塊數字不同時不發生變化,撞擊發起塊向後順延,相撞的兩個方塊相同時變成一個新的數值相加的數字塊,後續的數字塊依次向前遞補空位,撞擊發起塊變為新生成數字塊的後面第二個數字塊。

  • 撞擊結束後系統會在空白的地方隨機出現一個數字方塊2或者4。

\(4*4\) 方格中的 \(16\) 個格子分別賦予編號 \(1-16\),即類似下述矩陣

\[\begin{align*} \begin{bmatrix} 1 & 2 & 3 & 4 \\ 5 & 6 & 7 & 8 \\ 9 & 10 & 11 & 12 \\ 13 & 14 & 15 & 16 \end{bmatrix} \end{align*} \]

輸入格式

  • 輸入 \(1\)

輸入按格子編號 \(1-16\) 輸入遊戲《2048》的一個狀態序列,編號對應的格子沒有數字則輸入0

,如輸入0 0 0 0 4 0 2 0 4 0 2 2 2 8 8 8表示狀態1

  • 輸入 \(2\)

輸入一個使用者操作和新增塊地址序列,a表示向左滑動,s表示向下滑動,d表示向右滑動,w表示向上滑動

如輸入w 1 2 a 5 4 s 11 2 d 13 4 d 9 2,表示使用者依次進行了如下5次操作:

玩家向上滑動一次,之後在編號 \(1\) 的位置新出現一個數值為 \(2\) 的新增塊

玩家向左滑動一次,之後在編號 \(5\) 的位置新出現一個數值為 \(4\) 的新增塊

玩家向下滑動一次,之後在編號 \(11\) 的位置新出現一個數值為 \(2\) 的新增塊

玩家向右滑動一次,之後在編號 \(13\)

的位置新出現一個數值為 \(4\) 的新增塊

玩家向右滑動一次,之後在編號 \(9\) 的位置新出現一個數值為 \(2\) 的新增塊

如果編號所在的位置不為空,就重新指定一個新編號進行修改,新編號按 新編號 = 原編號%16+1 進行選擇,並判斷新編號所在位置是否為空,若編號位置不為空,則新新編號 = 新編號%16+1,直至探索編號位置為空,然後在該編號位置增加新數字塊。

輸出格式

輸出:從編號 \(1\) 到編號 \(16\) 方格的數字,格子為空則輸出 \(0\)

輸入輸出樣例

樣例 1

輸入

0 0 0 0 0 0 4 4 4 4 8 16 4 8 16 16
a 6 2

輸出

0 0 0 0 8 2 0 0 8 8 16 0 4 8 32 0

樣例 2

輸入

0 0 0 0 0 0 4 4 2 4 8 16 4 8 16 32
d 5 2 s 4 4 a 8 4 w 9 2

輸出

4 16 32 4 16 0 0 32 4 2 0 0 0 0 0 0

注意上述的輸入輸出每行末尾都有一個換行符\n

題解

直接使用 \(4 \times 4\) 的陣列進行模擬,預先開一個 \(5 \times 5\) 的陣列,編號 \(1-16\) 分佈在行號 \(1-4\),列號 \(1-4\) 的矩陣中。

該模擬題主要有兩步操作:按指令的滑動操作按指令的生成新數字塊操作

按指令的滑動操作

共有四個滑動方向,其實只要實現向一個方向的滑動函式(其餘方向是類似的)。不妨考慮向上滑動,我們再將該滑動操作分解為空位遞補撞擊響應。顯然這兩個分解操作是互不關聯的,而實現了這兩個操作也就可以完成一次滑動操作。撞擊響應是其中實現起來最為複雜的一塊,也是問題的核心,但花一些時間詳細討論該過程,可以發現程式碼並不難實現(不超過 \(20\) 行程式碼)。

按指令的生成新數字塊操作

如果編號所在的位置不為空,要注意到利用新編號 = 原編號%16+1進行新編號選擇的次數不超過 \(16\) 次。

另外的實現方式

可以見另一種利用這四種方向滑動的相同特性的一種實現方式,較我自己的實現方式有更高的程式碼重用度。

題解程式碼

程式碼如下:

#include <cstdio>
#include <cstdlib>
#include <iostream>

using namespace std;

int grids[5][5]; // 2048遊戲的方格

int row(int x) {
    return ((x - 1) / 4 + 1);
}

int col(int x) {
    return ((x % 4 == 0) ? 4 : (x % 4));
}

// 滑動函式組(分別實現四個方向的滑動函式,我自己的實現方式程式碼重用度不高)
// 另一種利用這四種方向滑動的相同特性的一種實現方式,見 https://blog.csdn.net/weixin_41207175/article/details/84836749
void wsl()
{
    // 向上滑動(遍歷 grids 的每一列)
    for (int j = 1; j <= 4; ++j) {
        int index = 0;
        for (int i = 1; i <= 4; ++i) {
            // 空位遞補
            if (grids[i][j] != 0) {
                ++index;
                if (index != i) {
                    grids[index][j] = grids[i][j];
                    grids[i][j] = 0;
                }
            }
        }
        int hit_index = 2;
        int hitted_index = 1;
        while(hit_index <= 4) {
            // 撞擊響應(問題的核心)
            if (grids[hit_index][j] == 0) {
                ++hit_index;
                continue;
            }
            if (grids[hitted_index][j] == grids[hit_index][j]) {
                grids[hitted_index][j] += grids[hit_index][j];
                grids[hit_index][j] = 0;
                hitted_index = ++hit_index;
                while (hitted_index <= 4 && grids[hitted_index][j] == 0) 
                    ++hitted_index;
                hit_index = hitted_index + 1;
            }
            else {
                hitted_index = hit_index;
                ++hit_index;
            }
        }
        index = 0;
        for (int i = 1; i <= 4; ++i) {
            // 空位遞補
            if (grids[i][j] != 0) {
                ++index;
                if (index != i) {
                    grids[index][j] = grids[i][j];
                    grids[i][j] = 0;
                }
            }
        }
    }
}
void asl()
{
    // 向左滑動(遍歷 grids 的每一行)
    for (int i = 1; i <= 4; ++i) {
        int index = 0;
        for (int j = 1; j <= 4; ++j) {
            // 空位遞補
            if (grids[i][j] != 0) {
                ++index;
                if (index != j) {
                    grids[i][index] = grids[i][j];
                    grids[i][j] = 0;
                }
            }
        }
        int hit_index = 2;
        int hitted_index = 1;
        while (hit_index <= 4) {
            // 撞擊響應(問題的核心)
            if (grids[i][hit_index] == 0) {
                ++hit_index;
                continue;
            }
            if (grids[i][hitted_index] == grids[i][hit_index]) {
                grids[i][hitted_index] += grids[i][hit_index];
                grids[i][hit_index] = 0;
                hitted_index = ++hit_index;
                while (hitted_index <= 4 && grids[i][hitted_index] == 0)
                    ++hitted_index;
                hit_index = hitted_index + 1;
            }
            else {
                hitted_index = hit_index;
                ++hit_index;
            }
        }
        index = 0;
        for (int j = 1; j <= 4; ++j) {
            // 空位遞補
            if (grids[i][j] != 0) {
                ++index;
                if (index != j) {
                    grids[i][index] = grids[i][j];
                    grids[i][j] = 0;
                }
            }
        }
    }
}
void ssl()
{
    // 向下滑動(遍歷 grids 的每一列)
    for (int j = 1; j <= 4; ++j) {
        int index = 5;
        for (int i = 4; i >= 1; --i) {
            // 空位遞補
            if (grids[i][j] != 0) {
                --index;
                if (index != i) {
                    grids[index][j] = grids[i][j];
                    grids[i][j] = 0;
                }
            }
        }
        int hit_index = 3;
        int hitted_index = 4;
        while (hit_index >= 1) {
            // 撞擊響應(問題的核心)
            if (grids[hit_index][j] == 0) {
                --hit_index;
                continue;
            }
            if (grids[hitted_index][j] == grids[hit_index][j]) {
                grids[hitted_index][j] += grids[hit_index][j];
                grids[hit_index][j] = 0;
                hitted_index = --hit_index;
                while (hitted_index >= 1 && grids[hitted_index][j] == 0)
                    --hitted_index;
                hit_index = hitted_index - 1;
            }
            else {
                hitted_index = hit_index;
                --hit_index;
            }
        }
        index = 5;
        for (int i = 4; i >= 1; --i) {
            // 空位遞補
            if (grids[i][j] != 0) {
                --index;
                if (index != i) {
                    grids[index][j] = grids[i][j];
                    grids[i][j] = 0;
                }
            }
        }
    }
}
void dsl()
{
    // 向右滑動(遍歷 grids 的每一行)
    for (int i = 1; i <= 4; ++i) {
        int index = 5;
        for (int j = 4; j >= 1; --j) {
            // 空位遞補
            if (grids[i][j] != 0) {
                --index;
                if (index != j) {
                    grids[i][index] = grids[i][j];
                    grids[i][j] = 0;
                }
            }
        }
        int hit_index = 3;
        int hitted_index = 4;
        while (hit_index >= 1) {
            // 撞擊響應(問題的核心)
            if (grids[i][hit_index] == 0) {
                --hit_index;
                continue;
            }
            if (grids[i][hitted_index] == grids[i][hit_index]) {
                grids[i][hitted_index] += grids[i][hit_index];
                grids[i][hit_index] = 0;
                hitted_index = --hit_index;
                while (hitted_index >= 1 && grids[i][hitted_index] == 0)
                    --hitted_index;
                hit_index = hitted_index - 1;
            }
            else {
                hitted_index = hit_index;
                --hit_index;
            }
        }
        index = 5;
        for (int j = 4; j >= 1; --j) {
            // 空位遞補
            if (grids[i][j] != 0) {
                --index;
                if (index != j) {
                    grids[i][index] = grids[i][j];
                    grids[i][j] = 0;
                }
            }
        }
    }
}
// 滑動函式
void slide(char ch) 
{
    switch (ch)
    {
    case 'w':
        // 向上滑動
        wsl();
        break;
    case 'a':
        // 向左滑動
        asl();
        break;
    case 's':
        // 向下滑動
        ssl();
        break;
    case 'd':
        // 向右滑動
        dsl();
        break;
    default:
        break;
    }
}
// 按指令生成新的數字塊
void add_grid(int x, int y)
{
    for (int i = 1; i <= 16; ++i) {
        if (grids[row(x)][col(x)] == 0) {
            grids[row(x)][col(x)] = y;
            break;
        }
        else x = x % 16 + 1;
    }
}
int main()
{
    int x, y;
    // 輸入初始方格狀態
    for (int i = 1; i <= 16; ++i) {
        cin >> x;
        grids[row(i)][col(i)] = x;
    }
    char ch;
    ch = getchar();
    while ((ch = getchar())) {
        cin >> x >> y;
        // 根據指令滑動方格
        slide(ch);
        // 根據指令生成方格
        add_grid(x, y);
        // 判斷輸入是否終止
        if ((ch = getchar()) && ch == '\n') break;
    }
    // 輸出方格
    for (int i = 1; i <= 16; ++i) {
        if (i == 1)
            cout << grids[row(i)][col(i)];
        else
            cout << ' ' << grids[row(i)][col(i)];
    }
    printf("\n");
    return 0;
}