資料結構 - 陣列 - 遊戲 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\)
玩家向右滑動一次,之後在編號 \(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;
}