2048
一、设计思路
1、游戏规则
想要制作游戏,首先需要了解游戏的规则,下面就来介绍2048的游戏规则
2048游戏共有16个格子,初始时初始数字由2或者4构成。
-
手指向一个方向滑动,所有格子会向那个方向运动。
-
相同数字的两个格子,相撞时数字会相加。
-
每次滑动时,空白处会随机刷新出一个数字的格子。
-
当界面不可运动时(当界面全部被数字填满时),游戏结束;当界面中最大数字是2048时,游戏胜利。
2、思路
了解了游戏规则接下来就好办多了,规则中最重要的是:向某个方向滑动格子就会向哪儿滑,并且相撞的数字相加
根据最重要的规则,我偷了一个懒,想到了以下策略
- 首先完成
向左滑动
功能 向右滑动
的功能可以借助向左滑动
的功能- 将游戏界面逆时针旋转180°
向左滑动
- 还原界面,将界面逆时针旋转180°,归回原位
向上滑动
的功能可以借助向左滑动
的功能- 将游戏界面逆时针旋转270°
向左滑动
- 还原界面,将界面逆时针旋转90°
向下滑动
的功能可以借助向左滑动
的功能- 将游戏界面逆时针旋转90°
向左滑动
- 还原界面,将界面逆时针旋转270°
通过以上分析,可以得知向左滑动
的功能是最重要,其他方向都可以借助它来完成,因此接下来将分析怎么完成向左滑动
的功能
- 游戏中说共有4行4列16个格子,有4行我们就可以采用循环,一次合并一行,总共4个循环,所以
向左滑动
又可以分解为向左合并一行 - 向左合并,首先不管有没有相同的数字,都要将所有的数字紧凑到最左边,两个数字中间不能有空
- 最后采用合并算法,将一行中相同的数字合并
至此这个游戏的基本就可以设计好了,下面开始实现
二、代码实现
1、存储结构
根据游戏规则很容易想到使用二维数组来存储游戏数据,如下
int data[4][4];
2、初始化游戏数据
规则说初始时初始数字由2或者4构成,这里偷个懒,设置所有的初始数字都是2,接下来要解决的就是初始数字的位置坐标,所以获取随机左边的函数就行。
获取随机数可以使用以下函数
//获取随机数
void randArray(int a[], int n) {
for (int i = 0; i < n; i++) {//产生随机坐标
srand((unsigned)time(NULL) + rand() + i);
a[i] = rand() % 4;
}
}
要产生不一样的随机数,首先需要置不一样的随机数种子,置随机数种子可以采用srand()
函数,然后我们把当前时间作为参数也就是srand((unsigned)time(NULL))
,但是在后期时16个坐标产生相同的随机坐标的几率非常大,而计算机的速度又非常快,相同的随机种子产生的随机数序列是一样的,这样随机性不高,很容易产生相同的坐标,所以参数还要加上rand()+i,也就是srand((unsigned)time(NULL) + rand() + i)
,不信的同学可以试试按之前的写法游戏玩到后期每产生一块新的坐标都需要不少时间。
接着按以下写法就可以产生两个不一样的随机坐标
//初始化数据,initNum初始数字
void init(int data[4][4], int initNum) {
int random[4];
randArray(random, 4);
if (random[0] == random[2] && random[1] == random[3]) {
init(data, 2);//若坐标相同,则重新生成坐标
} else {
data[random[0]][random[1]] = initNum;
data[random[2]][random[3]] = initNum;
}
}
3、向左合并
按照之前的思路,首先要将所有的方块移到最右侧且中间不能有空,可以通过如下代码实现
//紧凑数组(向左就凑)
void compact(int data[4]) {
int i, j = 0;
for (i = 0; i < 4; i++) {
if (data[i] != 0) {
data[j] = data[i];
j++;
}
}
for (; j < 4; j++)data[j] = 0;
}
接着完成向左合并函数
//向左合并
void mergerLeft(int data[4][4]) {
for (int i = 0; i < 4; i++) {
compact(data[i]);
for (int j = 0; j < 3; j++) {
if (data[i][j] == data[i][j + 1]) {
data[i][j] = 2 * data[i][j];
data[i][j + 1] = 0;
compact(data[i]);
}
}
}
}
4、其他方向合并
虽然有了向左合并的函数,但是怎么模拟出旋转界面呢,这里只要编写一个可以旋转矩阵的函数就行,如下
//拷贝数组
void copyArray(int data[4][4], int src[4][4]) {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
data[i][j] = src[i][j];
}
}
}
//旋转矩阵,count旋转次数
void rotateMatrix(int data[4][4], int count) {
int temp[4][4];
copyArray(temp, data);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
data[i][j] = temp[4 - j - 1][i];
}
}
if (count > 1)rotateMatrix(data, count-1);
}
有了旋转矩阵的函数,那么其他方向就很好解决了
//向右合并
void mergerRight(int data[4][4]) {
rotateMatrix(data, 2);
mergerLeft(data);
rotateMatrix(data, 2);
}
//向上合并
void mergerUp(int data[4][4]) {
rotateMatrix(data, 3);
mergerLeft(data);
rotateMatrix(data, 1);
}
//向下合并
void mergerDown(int data[4][4]) {
rotateMatrix(data, 1);
mergerLeft(data);
rotateMatrix(data, 3);
}
5、产生新的方块
通过之前的函数很容易编写出来
//生成新数字
void newNum(int data[4][4], int initNum) {
int random[2];
while (1) {
int flag = 1;
randArray(random, 2);//随即产生坐标
for (int i = 0; i < 4; i++) {//检测坐标是否重复
for (int j = 0; j < 4; j++) {
if (data[i][j] != 0 && i == random[0] && j == random[1]) {
flag = 0;
}
}
}
if (flag)break;
}
data[random[0]][random[1]] = initNum;
}
6、源代码
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<conio.h>
//函数声明
void randArray(int a[], int n);
void rotateMatrix(int data[4][4], int count);
void copyArray(int data[4][4], int src[4][4]);
//全局变量定义
int data[4][4];
//初始化数据
void init(int data[4][4], int initNum) {
int random[4];
randArray(random, 4);
if (random[0] == random[2] && random[1] == random[3]) {
init(data, 2);//若坐标相同,则重新生成坐标
} else {
data[random[0]][random[1]] = initNum;
data[random[2]][random[3]] = initNum;
}
}
//紧凑数组(向左就凑)
void compact(int data[4]) {
int i, j = 0;
for (i = 0; i < 4; i++) {
if (data[i] != 0) {
data[j] = data[i];
j++;
}
}
for (; j < 4; j++)data[j] = 0;
}
//向左合并
void mergerLeft(int data[4][4]) {
for (int i = 0; i < 4; i++) {
compact(data[i]);
for (int j = 0; j < 3; j++) {
if (data[i][j] == data[i][j + 1]) {
data[i][j] = 2 * data[i][j];
data[i][j + 1] = 0;
compact(data[i]);
}
}
}
}
//向右合并
void mergerRight(int data[4][4]) {
rotateMatrix(data, 2);
mergerLeft(data);
rotateMatrix(data, 2);
}
//向上合并
void mergerUp(int data[4][4]) {
rotateMatrix(data, 3);
mergerLeft(data);
rotateMatrix(data, 1);
}
//向下合并
void mergerDown(int data[4][4]) {
rotateMatrix(data, 1);
mergerLeft(data);
rotateMatrix(data, 3);
}
//生成新数字
void newNum(int data[4][4], int initNum) {
int random[2];
while (1) {
int flag = 1;
randArray(random, 2);//随即产生坐标
for (int i = 0; i < 4; i++) {//检测坐标是否重复
for (int j = 0; j < 4; j++) {
if (data[i][j] != 0 && i == random[0] && j == random[1]) {
flag = 0;
}
}
}
if (flag)break;
}
data[random[0]][random[1]] = initNum;
}
//获取随机数
void randArray(int a[], int n) {
for (int i = 0; i < n; i++) {//产生随机坐标
srand((unsigned)time(NULL) + rand() + i);
a[i] = rand() % 4;
}
}
//显示界面
void show(int data[4][4]) {
printf("
2048小游戏");
for (int i = 0; i < 4; i++) {
printf("
|-----|-----|-----|-----|
|");
for (int j = 0; j < 4; j++) {
if (data[i][j] == 0) {
printf("%5c|", '*');
} else {
printf("%5d|", data[i][j]);
}
}
}
printf("
|-----|-----|-----|-----|
");
}
//拷贝数组
void copyArray(int data[4][4], int src[4][4]) {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
data[i][j] = src[i][j];
}
}
}
//旋转矩阵
void rotateMatrix(int data[4][4], int count) {
int temp[4][4];
copyArray(temp, data);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
data[i][j] = temp[4 - j - 1][i];
}
}
if (count > 1)rotateMatrix(data, count-1);
}
//保存文件
void saveFile() {
FILE* f = fopen("2048.dat", "w");
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
fprintf(f, "%d ", data[i][j]);
}
fprintf(f, "
");
}
fclose(f);
}
//读取文件
void readFile() {
FILE* f = fopen("2048.dat", "r");
if (f == NULL) {
printf("
无游戏存档,开始新游戏
");
init(data, 2);
return;
}
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
fscanf(f, "%d ", &data[i][j]);
}
fscanf(f, "
");
}
fclose(f);
}
int main() {
char ch;//方向
printf("1、新游戏
");
printf("2、继续游戏
");
ch = getch();
if(ch == '1'){
init(data, 2);
} else {
readFile();
}
while (1) {
show(data);
ch = getch();
switch (ch) {
case 72://上
mergerUp(data);
newNum(data, 2);
break;
case 77://右
mergerRight(data);
newNum(data, 2);
break;
case 80://下
mergerDown(data);
newNum(data, 2);
break;
case 75://左
mergerLeft(data);
newNum(data, 2);
break;
case 27://ESC
saveFile();
return 0;
}
saveFile();
system("cls");
}
return 0;
}
7、实例演示
三、问题
这个程序当初只是为了帮助同学妹妹完成一下c语言作业,所以偷了个懒,有部分功能都没完成,比如判断2048游戏结束的功能等