2048 游戏算法
游戏介绍
2048 游戏是一款非常经典的游戏,游戏界面是一个 4 × 4 4 \times 4 4×4 的方格,每个方格可以存储一个数字或为空,数字的取值为 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048。
游戏规则如下
- 开始时,界面上只有一个方格,其值为 2。
- 可以向四个方向滑动,每次滑动时,将所有方格向对应方向移动。每次滑动后新生成一个值为 2 或 4 的方格,方格位置随机。
- 移动时,如果方格前方是空格,则方格向前移动,如果与前方方格值相同,则方格与前方方格合并,方格值变为两倍。
- 当产生值 2048 的方格时,游戏结束,游戏胜利。
- 当移动后没有方格可移动时,游戏结束,游戏失败。
游戏算法实现
先通过最简单的方式来实现 2048 游戏,游戏中的方格可以用一个二维数组表示,数组的行和列分别表示方格行和列,数组中的每个元素表示方格中的数字。然后进行算法的分析。
算法详情
首先声明全局数组arr
,用于表示格子情况;
private int[][] arr;
接下来考虑到每次滑动方向不同,可以用 1,2,3,4 分别表示上下左右,由于每次对数组处理时下标不同,如果直接对整个数组进行处理,则需要给每个移动方向写一个方法,但那是多余的,因为每次移动时的处理原理是一样的,都能看做从远端到近端的向前移动,就比如向左移动看作从右端到左端的向前移动。因此我们将向上滑动作为默认方向,将其他方向转变成向上滑动再进行处理。方法如下:
- 可以按照滑动方向由前向后,提取每一个格子数据,并依次填充到一维数组中;
- 使用处理向上滑动的方法对一维数组进行处理,得到新的一维数组;
- 将新的一维数组按照移动方向倒推回去正确填充二维数组中。
例如如下状态进行向下滑动:
2 2 0 2 0 0 0 0 4 2 0 0 0 0 0 0 \begin{matrix} 2 & 2 & 0 & 2 \\ 0 & 0 & 0 & 0 \\ 4 & 2 & 0 & 0 \\ 0 & 0 & 0 & 0 \\ \end{matrix} 2040202000002000
将二维数组每一列由下到上(即滑动方向的由前向后)提取出来:
L(0) = (0,4,0,2)
,L(1) = (0,2,0,2)
,
L(2) = (0,0,0,0)
,L(3) = (0,0,0,2)
;
进行处理(提取出的数组视为向左合并),得到新的一维数组:
L(0) = (4,2,0,0)
,L(1) = (4,0,0,0)
,
L(2) = (0,0,0,0)
,L(3) = (2,0,0,0)
;
按照移动方向填充回去,得到新的二维数组:
0 0 0 0 0 0 0 0 2 0 0 0 4 4 0 2 \begin{matrix} 0 & 0 & 0 & 0 \\ 0 & 0& 0&0\\ 2&0&0&0\\ 4&4&0&2 \\ \end{matrix} 0024000400000002
按照以上逻辑,处理二维数组的方法由于是在滑动时调用可以取名为onMove()
,然后将处理提取出的一维数组的方法命名为dataProcess()
,onMove()
代码如下:
//滑动时调用,接受一个方向参数
public void onMove(int direction)
{
int[] L = new int[4]; // 变形后的数组
switch (direction)
{
case 1: // 上
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
L[j] = arr[j][i]; // 每一列由上往下赋值
}
L = dataProcess(L);
for (int j = 0; j < 4; j++)
{
arr[j][i] = L[j];
}
}
break;
case 2: // 下
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
L[j] = arr[3 - j][i]; // 每一列由下往上赋值
}
L = dataProcess(L);
for (int j = 0; j < 4; j++)
{
arr[j][i] = L[3 - j];
}
}
break;
case 3: // 左
for (int i = 0; i < 4; i++)
{
L = arr[i]; // 复制一行
L = dataProcess(L);
arr[i] = L;
}
break;
case 4: // 右
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
L[j] = arr[i][3 - j]; // 反方向赋值一行
}
L = dataProcess(L);
for (int j = 0; j < 4; j++)
{
arr[i][j] = L[3 - j];
}
}
break;
}
}
dataProcess()
方法用于对提取出的数组进行处理,接受一个一维数组,返回一个一维数组。处理思路是将传入数组去零的同时向左对齐,然后开始进行多轮比较,算法步骤如下:
- 遍历数组元素,如果有0,则将其后元素前移,零放到末尾;
- 遍历数组元素,如果遍历到的第一个元素为0,则退出以下步骤;如果与后一个位置元素相等,则将前一个元素乘以2,并将后面的元素依次前移,最后一个置零;如果不相等则往后遍历,遍历到最后两个元素也不相等,则退出以下步骤;
- 如果步骤2遍历到的元素与下一个元素相等,则处理完后将遍历起始点改为第一轮时的遍历到的位置往前移一位(已经是第一位则不用前移),重复第2步。
代码如下:
public int[] dataProcess(int[] M)
{
int j = 0; // 定位器,指明下一次处理数组的几号元素
int[] T = {0, 0, 0, 0};
for (int i = 0; i < 4; i++) // 遍历数组,将0元素移除
{
if (M[i] != 0)
{
T[j] = M[i];
j++; // 依次将数组中非0元素放入新数组中
}
}
j = 0;
int m = 0; // 记录循环次数
// 循环判断是否可以合并,当判断到最后一个元素时,循环结束
while (j < 3)
{
if (T[j] == 0)
break; // 如果检索到0,则后面都为0,跳出循环
// 判断相邻元素是否相等
if (T[j] == T[j + 1])
{
// 相等则前元素乘2,后续元素依次向前一格,最后一个置零
T[j] = T[j] * 2;
for (int i = j + 1; i < 3; i++)
{
T[i] = T[i + 1];
}
T[3] = 0;
if (j > 0) j--; // 当处理完两个相等元素,应该将定位器前移一格
} else
j++; // 两元素不等,继续往后对比
m++;
if (m == 100) // 避免死循环
break;
}
return T;
}
至此该算法算法已介绍完毕,此算法获取滑动方向,然后将矩阵中的数据提取成一维数组,对提取出的一维数组先去零,再进行相同元素合并,最后将处理完的一维数组还原成矩阵,则完成一次滑动。
测试代码
以下给出测试代码:
/ --Game类-- /
package project;
public class Game
{
private int[][] arr;
public Game(int[][] arr)
{
this.arr = arr;
}
// 移动函数
public void onMove(int direction)
{
int[] L = new int[4]; // 变形后的数组
switch (direction)
{
case 1: // 上
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
L[j] = arr[j][i]; // 每一列由上往下赋值
}
L = dataProcess(L);
for (int j = 0; j < 4; j++)
{
arr[j][i] = L[j];
}
}
break;
case 2: // 下
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
L[j] = arr[3 - j][i]; // 每一列由下往上赋值
}
L = dataProcess(L);
for (int j = 0; j < 4; j++)
{
arr[j][i] = L[3 - j];
}
}
break;
case 3: // 左
for (int i = 0; i < 4; i++)
{
L = arr[i]; // 复制一行
L = dataProcess(L);
arr[i] = L;
}
break;
case 4: // 右
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
L[j] = arr[i][3 - j]; // 反方向赋值一行
}
L = dataProcess(L);
for (int j = 0; j < 4; j++)
{
arr[i][j] = L[3 - j];
}
}
break;
}
printArray(arr);
}
// 数据处理
public int[] dataProcess(int[] M)
{
int j = 0; // 定位器,指明下一次处理数组的几号元素
int[] T = {0, 0, 0, 0};
for (int i = 0; i < 4; i++) // 遍历数组,将0元素移除
{
if (M[i] != 0)
{
T[j] = M[i];
j++; // 依次将数组中非0元素放入新数组中
}
}
j = 0;
int m = 0; // 记录循环次数
// 循环判断是否可以合并,当判断到最后一个元素时,循环结束
while (j < 3)
{
if (T[j] == 0)
break; // 如果检索到0,则后面都为0,跳出循环
// 判断相邻元素是否相等
if (T[j] == T[j + 1])
{
// 相等则前元素乘2,后续元素依次向前一格,最后一个置零
T[j] = T[j] * 2;
for (int i = j + 1; i < 3; i++)
{
T[i] = T[i + 1];
}
T[3] = 0;
if (j > 0) j--; // 当处理完两个相等元素,应该将定位器前移一格
} else
j++; // 两元素不等,继续往后对比
m++;
if (m == 100) // 避免死循环
break;
}
return T;
}
//可用于打印一维数组
public void printA(int[] array)
{
System.out.print("打印一维数组");
for (int i = 0; i < array.length; i++)
{
System.out.print(array[i] + " ");
}
System.out.println();
}
//可用于打印二维数组
public void printArray(int[][] array)
{
System.out.print("打印二维数组");
System.out.println();
for (int i = 0; i < array.length; i++)
{
for (int j = 0; j < array[i].length; j++)
{
System.out.print(array[i][j] + " ");
}
System.out.println();
}
}
}
/ --main函数-- /
package project;
public class Main
{
public static void main(String[] args)
{
// 测试矩阵,可自行修改
int[][] G = new int[][] {
{2, 0, 0, 2},
{0, 0, 2, 4},
{2, 2, 8, 8},
{8, 4, 2, 2}
};
Game m = new Game(G);
// 移动方向:上、下、左、右对应参数1、2、3、4
m.onMove(3);
}
}
测试时请将以上两个类放在project文件夹中,类的文件名要与类名相同。