数据结构 - 数组 - 游戏 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;
}