C语言实现2048游戏(控制台版)
原创2022年2月17日...大约 9 分钟
C语言实现2048游戏(控制台版)
问题描述
一个4X4的矩阵,开始随机产生两个数(2或4),接下来的每次操作都会再次产生一个数(2或4)
操作要求:按W,S,A,D进行上,下,左,右操作
操作结果:每次操作都会合并操作方向上相同的数,即将两个相同的数合并在一起产生一个新的数,该数为两个数的和,并将该行(列)上的数移动至 操作方向的顶端(保证数字原有顺序不变)
解决思路
定义一个二维数组用来存储4X4矩阵上的数字
开始游戏时先产生两个随机数,然后开始正常游戏操作
操作:
根据操作方向遍历每行(列),检查是否存在相同的数字
如果该行(列)存在挨着的(即两个数字中间没有其它数字隔开)相同的数字,将操作方向顶端的数字变为原来的2倍,并将被合并掉的数变为0
合并结束后将数字移动到操作方向顶端,如果顶端已经存在数,则按操作顺序排放至后方
操作结束后产生新的随机数
程序关键功能
产生随机数字
读取输入的操作方向
执行方向上数字的合并/移动操作
记录分数和合并出的最大数字
判断是否可以结束游戏
输出游戏界面
代码(详细注释)
见下方
测试
对程序进行方向上的操作,检查数字是否合并正确,移动正确,并对合并的数字作记录检查得分是否正确,合并出的最大数是否正确
当4X4的矩阵格子满后,检查是否存在可以合并的数字
如果存在,则进行非合并方向上的操作,检查游戏是否结束(正常应为不结束游戏);然后进行合并方向上的操作,检查游戏是否结束(正常应为不结束游戏)
如果不存在,则随意方向上进行一次操作,检查游戏是否结束(正常应为结束游戏)
游戏截图

代码
//Setiuo Email:751255159@qq.com
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include <windows.h>
#define Max(a, b) ((a > b) ? a : b)
typedef byte bool;
#define true 1
#define false 0
//Score为记录得分的变量,MaxNum为记录最大的方块数字的变量
int Score = 0, MaxNum = 2;
byte GetType(int Num) //根据数字大小返回一个颜色类型
{
switch (Num)
{
case 2:
return 13;
case 4:
return 1;
case 8:
return 2;
case 16:
return 3;
case 32:
return 4;
case 64:
return 5;
case 128:
return 6;
case 256:
return 7;
case 512:
return 8;
case 1024:
return 9;
case 2048:
return 10;
case 4096:
return 11;
case 8192:
return 12;
default:
return 14;
}
}
//这个函数用于判断是否可以结束游戏,结束游戏的条件为:所有格子都有数字且不可以合并
//所以直接判断每两个挨着的数字是否可以合并即可
bool HaveIdentical(int Data[][5]) //判断是否存在可以合并的数字
{
//判断横排是否存在
for (byte i = 1; i <= 4; i++) //遍历每行
{
for (byte j = 2; j <= 4; j++)
{
if (Data[i][j] == Data[i][j - 1]) //存在挨着的且相同的数字
return true; //可以合并,返回true
}
}
//判断竖排是否存在
for (byte i = 1; i <= 4; i++) //遍历每列
{
for (byte j = 2; j <= 4; j++)
{
if (Data[j][i] == Data[j - 1][i]) //存在挨着的且相同的数字
return true; //可以合并,返回true
}
}
return false; //不存在挨着的且相同的数字,返回假
}
//判断是否存在空的格子
bool HaveEmpty(int Data[][5])
{
byte iNum = 0; //记录空格子数
for (byte i = 1; i <= 4; i++) //遍历每行
{
for (byte j = 1; j <= 4; j++) //判断每行中的每一个格子
{
if (Data[i][j] == 0) //如果当前格子的数为0,即为存在空的格子
iNum++;
}
}
if (iNum == 0) //如果不存在空的格子
return false; //返回假
return true; //否则返回真
}
//游戏结束,输出结果
void PrintResult()
{
system("cls"); //清空控制台文字
printf("========游戏结束========\n\n");
printf(" 您最终的得分是: %d\n", Score);
printf(" 最大方块的数是: %d\n\n", MaxNum);
printf("========================\n\n");
system("pause"); //防止控制台退出
}
//输出数字
void PrintNum(int Data[][5])
{
HANDLE hOut;
hOut = GetStdHandle(STD_OUTPUT_HANDLE); //取输入输出设备句柄
SetConsoleTextAttribute(hOut, 0x0007); //设置颜色
system("cls"); //清空控制台文字
printf("========2048========\n");
printf("食用方法:\n");
printf(" w、s、a、d分别为上下左右\n");
printf(" 输入字符后按回车\n");
printf(" 当前得分:%d\n", Score);
printf("====================\n\n");
//遍历数组,输出界面格子和每个位置的数字
for (byte i = 1; i <= 4; i++)
{
for (byte u = 1; u <= 4; u++)
printf("—————");
printf("\n|");
for (byte u = 1; u <= 4; u++)
printf(" |");
printf("\n");
for (byte j = 1; j <= 4; j++)
{
printf("|");
byte iType = GetType(Data[i][j]);
SetConsoleTextAttribute(hOut, iType);
if (Data[i][j] == 0) //如果当前位置数字为0,则不输出数字
printf(" ");
else
printf("%5d ", Data[i][j]);
SetConsoleTextAttribute(hOut, 0x0007);
}
printf("|\n|");
for (byte u = 1; u <= 4; u++)
printf(" |");
printf("\n");
}
for (byte u = 1; u <= 4; u++)
printf("—————");
SetConsoleTextAttribute(hOut, 0x0007);
printf("\n===================\n");
}
//空位置产生随机数
void RandNum(int Data[][5])
{
//防止产生死循环,所以要判断是否存在空位置
if (!HaveEmpty(Data))
return;
byte X, Y; //记录产生数字的位置
do
{
X = rand() % 4 + 1;
Y = rand() % 4 + 1;
} while (Data[X][Y] != 0); //如果当前位置已经有数字了,则继续随机换位置
int iType = rand() % 3; //产生随机数,0, 1, 2,用于判断是否产生数字4
Data[X][Y] = (iType == 2) ? 4 : 2; //产生2的概率更大
MaxNum = Max(MaxNum, Data[X][Y]); //刷新最大块数字
}
//按下了向上键,其它方向键同理
bool ChangeNum_Up(int Data[][5])
{
bool CanDown = false; //记录是否可以移动方块,即如果方块全部在边缘位置,且没有相同的数字,则不能移动
//合并数字操作
for (byte i = 1; i <= 4; i++) //遍历每列
{
for (byte j = 1; j <= 3; j++) //从上向下寻找是否存在相同的数字
{
byte k = j + 1; //记录当前格的向下一个位置
//如果当前格和下一个格的数字不一样且下一个位置的数字是0且当前位置的数字不是0,则循环寻找下一个位置
while (Data[j][i] != Data[k][i] && k <= 3 && Data[j][i] != 0 && Data[k][i] == 0)
{
k++;
}
//如果当前位置的数字和下一个位置的数字一样而且不是0
if (Data[j][i] == Data[k][i] && Data[j][i] != 0)
{
Score += Data[j][i]; //加分
CanDown = true; //已经移动
Data[j][i] *= 2; //当前格数字*2,即把两个数字合并
Data[k][i] = 0; //数字已经合并,被合并的数字清0
MaxNum = Max(MaxNum, Data[j][i]); //刷新最大值
}
}
}
for (byte i = 1; i <= 4; i++) //遍历每列
{
for (byte j = 1; j <= 3; j++) //从上向下寻找是否存在空位置
{
byte k = j + 1;
while (Data[j][i] == 0 && k <= 4) //如果当前位置的数字是0且将下一位置的数赋值给当前位置后仍然是0,则继续寻找下一个位置
{
if (Data[k][i] != 0) //找到不为0的位置
{
CanDown = true; //已经移动
Data[j][i] = Data[k][i]; //将下一位置的数赋值给当前位置
Data[k][i] = 0; //下一位置的数清0
}
k++; //寻找下一个位置
}
}
}
return CanDown; //返回操作结果,即刚刚的操作是否合并或移动了格子
}
bool ChangeNum_Down(int Data[][5])
{
bool CanDown = false;
for (byte i = 1; i <= 4; i++)
{
for (byte j = 4; j >= 2; j--)
{
byte k = j - 1;
while (Data[j][i] != Data[k][i] && k >= 2 && Data[j][i] != 0 && Data[k][i] == 0)
{
k--;
}
if (Data[j][i] == Data[k][i] && Data[j][i] != 0)
{
CanDown = true;
Score += Data[j][i];
Data[j][i] *= 2;
Data[k][i] = 0;
MaxNum = Max(MaxNum, Data[j][i]);
}
}
}
for (byte i = 1; i <= 4; i++)
{
for (byte j = 4; j >= 2; j--)
{
byte k = j - 1;
while (Data[j][i] == 0 && k >= 1)
{
if (Data[k][i] != 0)
{
CanDown = true;
Data[j][i] = Data[k][i];
Data[k][i] = 0;
}
k--;
}
}
}
return CanDown;
}
bool ChangeNum_Left(int Data[][5])
{
bool CanDown = false;
for (byte i = 1; i <= 4; i++)
{
for (byte j = 1; j <= 3; j++)
{
byte k = j + 1;
while (Data[i][j] != Data[i][k] && k <= 3 && Data[i][j] != 0 && Data[i][k] == 0)
{
k++;
}
if (Data[i][j] == Data[i][k] && Data[i][j] != 0)
{
CanDown = true;
Score += Data[i][j];
Data[i][j] *= 2;
Data[i][k] = 0;
MaxNum = Max(MaxNum, Data[i][j]);
}
}
}
for (byte i = 1; i <= 4; i++)
{
for (byte j = 1; j <= 3; j++)
{
byte k = j + 1;
while (Data[i][j] == 0 && k <= 4)
{
if (Data[i][k] != 0)
{
CanDown = true;
Data[i][j] = Data[i][k];
Data[i][k] = 0;
}
k++;
}
}
}
return CanDown;
}
bool ChangeNum_Right(int Data[][5])
{
bool CanDown = false;
for (byte i = 1; i <= 4; i++)
{
for (byte j = 4; j >= 2; j--)
{
byte k = j - 1;
while (Data[i][j] != Data[i][k] && k >= 2 && Data[i][j] != 0 && Data[i][k] == 0)
{
k--;
}
if (Data[i][j] == Data[i][k] && Data[i][j] != 0)
{
CanDown = true;
Score += Data[i][j];
Data[i][j] *= 2;
Data[i][k] = 0;
MaxNum = Max(MaxNum, Data[i][j]);
}
}
}
for (byte i = 1; i <= 4; i++)
{
for (byte j = 4; j >= 2; j--)
{
byte k = j - 1;
while (Data[i][j] == 0 && k >= 1)
{
if (Data[i][k] != 0)
{
CanDown = true;
Data[i][j] = Data[i][k];
Data[i][k] = 0;
}
k--;
}
}
}
return CanDown;
}
//按下移动键
bool DownKey(int Data[][5], char KEY)
{
bool CanDown = false; //初始化变量,赋值为没有成功进行操作
//判断按下的是哪个键并进行移动
switch (KEY)
{
case 'w':
{
CanDown = ChangeNum_Up(Data);
break;
}
case 's':
{
CanDown = ChangeNum_Down(Data);
break;
}
case 'a':
{
CanDown = ChangeNum_Left(Data);
break;
}
case 'd':
{
CanDown = ChangeNum_Right(Data);
break;
}
}
return CanDown; //返回操作结果
}
int main()
{
int Data[5][5] = {}; //记录4X4矩阵中的数字
srand((unsigned int)time(NULL)); //利用系统时间改变种子值,用来产生随机数
//开局先产生两个随机数
RandNum(Data);
RandNum(Data);
//输出数字界面
PrintNum(Data);
while (true)
{
char KEY = _getch();; //取得按下的键
if (KEY != 'w' && KEY != 's' && KEY != 'a' && KEY != 'd') //如果输入的字符不合法,则跳出本次操作
continue;
if (DownKey(Data, KEY)) //如果操作成功,则产生随机数
RandNum(Data);
else //如果没有操作成功
{
if (!HaveIdentical(Data) && !HaveEmpty(Data)) //判断是否存在空的格子且存在可以合并的数字,如果没有,则跳出循环结束游戏
{
break;
}
}
PrintNum(Data); //输出数字界面
}
PrintResult(); //输出得分结果
return 0;
}
如果代码有问题欢迎指正