目录
项目分析
库函数的基本运用
游戏界面盘子的绘制
绘制游戏界面盘子
操作界面进一步的完善
游戏的实现
数字在盘子中的位置
重新初始化游戏
实现向左移动合并数字
实现向其它方向的移动并合并数字
项目完整代码
对于2048这个小游戏大家应该都玩过,没玩过的话,可以点击下方的链接进去玩一下。
2048游戏网页链接
2048是一个风靡全球的益智类小游戏,通过上下左右控制来合并盘子里的数字,直到盘子里出现2048。
我们今天在Linux下用C++实现2048编程小项目,自己动手实践一下。
首先前提是,小伙伴们必须要有Linux操作系统,没有的小伙伴可以自己去网上百度安装一个。
我们先简单了解一下2048的原理:
1. 通过上下左右来控制合并盘子里的数字。
2. 每次合并只能合并相邻的两个数字,例如 [2 2 2 2] ,向右合并以后是 [空 空 4 4] ,不是 [空 空 空 8]。
3. 每次合并的时候,合并那个方向的数字优先级高,例如 [空 2 2 2],向右合并以后是 [空 空 2 4],不是 [空 空 4 2]。
4. 判断游戏胜利的条件是盘子里出现数字2048。
5. 判断游戏失败的条件是盘子里的数字占满了,此时盘子中没有出现数字2048,且相邻的数字之间不满足合并要求,例如
此时盘子里相邻的数字之间都不能再合并了。
6. 盘子中每次重新开始会随机在两个空的位置产生数字2或者4。
7. 盘子中每次合并后出现新数字4的概率为10%。
8. 每次移动之后会在空的位置中随机产生一个数字2或者4。
我们在Linux中依赖库函数
initscr(); | ncurses初始化,初始化一下屏幕 |
cbreak(); | 按键不需要输入回车直接交互,也就是我输入一个字符不需要回车 ,输入函数就可以得到这个字符了 |
noecho(); | 按键不显示,使你输入的字符不在屏幕上显示,就和Linux输密码一样,屏幕上看不到你输入了什么 |
curs_set(0); | 隐藏输入光标,平时输入时,输入位置会有一个光标在一闪一闪的,这个函数将光标隐藏起来了。 |
endwin(); | ncurses清理,清理屏幕。 |
move(5,5); 和addch(ch); | 将输入光标移到屏幕坐标(5,5)的位置上,然后使你要输入的字符显示在屏幕位置(5,5)上 |
mvprintw(8,8,"2048小游戏"); | 在指定位置打印字符串,在坐标(8,8)处打印字符串“Game 2048” |
这几个就是我们要使用的库
注意:函数move(row, column); 和函数mvprintw(row, column,"***"); 中的坐标row代表第几行,column代表第几空格。例如
move(0,0);//将光标移到0*0的位置上
addch(ch);//使你要输入的字符显示在屏幕位置0*0上
mvprintw(1,2,"Game 2048");//在1*2的位置上打印一个字符串
这几个代码显示在屏幕上就是:
来仔细说明一下:
字符n的位置为(0,0),就是第0行第0个空格的位置;字符串“GAME 2048”中G的位置为(1,2),也就是第1行第2个空格的位置。所以说这两个函数是有坐标(0,0)的,也就是我们通常意义上的第一行第一个空格,但其实在函数中是第0行第0个空格的位置。
我们将这几个函数写成一个简易的程序来执行一下。代码如下所示:
//step1.cpp
#include
#include
using namespace std;
void initialize()
{
//ncurse初始化
initscr(); //初始化一下屏幕
//按键不需要输入回车直接交互
cbreak();//也就是我输入一个字符不需要回车 输入函数就可以得到这个字符了
//按键不显示
noecho();//使你输入的字符不在屏幕上显示,就和Linux输密码一样 屏幕上看不到你输入了什么
//隐藏光标
curs_set(0);//将输入为止的光标隐藏
}
void shutdown()
{
//ncurses清理
endwin();
}
int main()
{
initialize();
char ch='n'; //将ch初始化为n
do
{
move(5,5);//将光标移到5*5的位置上
addch(ch);//使你要输入的字符显示在屏幕位置5*5上
mvprintw(8,8,"Game 2048");//在2*2的位置上打印一个字符串 在指定位置打印字符串
ch=getch();//获取你输入的字符 赋值给ch
}while(ch!='Q'&&ch !='q');//判断循环是否继续
shutdown();
return 0;
}
我们来看一下执行效果:
你随意按一个字符,屏幕上字符a就会变成你所按的字符,且字符a处没有光标闪烁。
在了解了我们所要使用的几个函数之后,我们创建一个游戏类Game,用来实现2048游戏的主体。接下来我们来绘制一下简易的盘子。我们可以用符号+、-、|来绘制盘子。绘制的大概效果图如下所示:
我们可以将函数move();和函数addch();包装成一个函数drawch(int row, int col, char c);调用这个函数时传入坐标和要显示的字符就可以了,代码如下所示:
// 在指定的位置画一个字符
void drawch(int row, int col, char c)
{
move(row, col);
addch(c);
}
我们结合函数drawch();和for循环就能画出来,因为符号的位置是有规律的,我们可以用for循环将坐标传入给函数drawch();让符号在指定的位置显示即可。代码如下所示:
//step2.cpp
#include
#include
using namespace std;
#define N 4 //格子数量
#define W 5 //格子的宽度
class Game
{
public:
Game(){} //构造函数
//绘制游戏界面
void draw()
{
//清理屏幕
clear();
//居中偏移
for(int i = 0; i<=N; ++i)
{
for(int j = 0; j<=N ;++j)
{
//绘制相交点
drawch(i*2, j*W, '+');
//绘制竖线
if(i != N)
{
drawch(i*2+1, j*W, '|');
}
//绘制横线
for(int k=0; j != N && k
运行结果为:
和step1.cpp相比较,step2.cpp创建了一个游戏类Game,将在屏幕指定位置上输出字符包装成了一个函数drawch(),并将其放在了游戏类Game中,这里我们还用到了预处理命令#define,其为宏定义命令,我们要画一个4*4的盘子,所以宏定义N代表格子数量4,我们设置各自中间的空格数为4个,也就是每次向后移动五个空格,在第五个空格输出字符‘|’,所以设置W代表格子的宽度5。然后利用for循环和函数drawch()编写一个函数draw(),来绘制游戏界面盘子。
我们观察上图,绘制出来的游戏界面,发现贴着右边,不是很美观,我们宏定义M来控制盘子向右移动的距离,我们来看一下修改后的代码:
//step2.cpp
#include
#include
using namespace std;
#define N 4 //格子数量
#define W 5 //格子的宽度
#define M 18 //盘子向右移动的距离
class Game
{
public:
Game(){} //构造函数
//绘制游戏界面
void draw()
{
//清理屏幕
clear();
//居中偏移
for(int i = 0; i<=N; ++i)
{
for(int j = 0; j<=N ;++j)
{
//绘制相交点
drawch(i*2, j*W + M, '+');
//绘制竖线
if(i != N)
{
drawch(i*2+1, j*W + M, '|');
}
//绘制横线
for(int k=0; j != N && k
运行结果为:
这样看起来就舒服多了,接着我们要在屏幕合适位置显示一些提示信息,也就是,告诉玩家要怎么玩这个游戏,我们设置键盘上的W为向上合并数字,A为向左合并数字,S为向下合并数字,D为向右合并数字,Q为退出游戏,R为重新开始。这是我们就要用库函数
mvprintw(2 * (N + 1), 2 * N, "W(UP),S(DOWN),A(LEFT),D(RIGHT),R(RESTART),Q(QUIT)");
我们看一下效果:
我们要对玩家按键要做出反应,所以我们要将主函数进行修改一下,我们先看看之前的主函数:
int main()
{
initialize();//函数调用
Game game;
char ch='n';
do
{
game.draw();//调用对象game的子函数绘制游戏界面
ch = getch();
}while(ch != 'q' && ch != 'Q');//当输入的字符为q或Q时退出游戏界面
shutdown();
return 0;
}
我们要对变量ch的处理写成类Game中的函数,对变量ch的处理的函数命名为processInput(),我们再增加一个Game类的成员变量status来控制游戏的状态,用函数getStatus(),返回status的值,我们宏定义S_FAIL表示失败、S_WIN表示胜利、S_NORMAL表示游戏正常进行、S_QUIT表示退出游戏。对其设置相应的值。 代码如下所示:
#include
#include
using namespace std;
#define N 4 //格子数量
#define W 5 //格子的宽度
#define M 18 //盘子向右移动的距离 居中偏移量
//游戏状态
#define S_WIN 0 //胜利
#define S_FAIL 1 //失败
#define S_NORMAL 2 //游戏正常进行
#define S_QUIT 3 //退出
class Game
{
public:
Game():status(S_NORMAL) {}
//返回status的值
int getStatus()
{
return status;
}
//处理按键
void processInput()
{
char ch = getch();
//将字母转化为大写
if(ch >= 'a' && ch <= 'z')
{
ch -= 32;
}
if(ch == 'Q')
{
status = S_QUIT;
}
else
{
//目前先不做处理,只在输赢提示语中转换
status = (status+1) % 2;
}
}
//绘制游戏界面
void draw()
{
//清理屏幕
clear();
for(int i = 0; i<=N; ++i)
{
for(int j = 0; j<=N ;++j)
{
//绘制相交点
drawch(i*2, j*W + M, '+');
//绘制竖线
if(i != N)
{
drawch(i*2+1, j*W + M, '|');
}
//绘制横线
for(int k=0; j != N && k0)
{
drawch(row, col, num%10+'0');
num /= 10;
--col;
}
}
private:
int status;
};
void initialize()
{
//ncurse初始化
initscr();
//按键不需要输入回车直接交互
cbreak();
//按键不显示
noecho();
//隐藏光标
curs_set(0);
}
void shutdown()
{
//ncurses清理
endwin();
}
int main()
{
initialize();
Game game;
do
{
game.draw();
game.processInput();
}while(game.getStatus() != S_QUIT);
shutdown();
return 0;
}
运行结果如下所示:
现在我们使数字在盘子中正确的位置出现,对于要显示的数字,我们利用函数drawch();和while循环将数字按位的显示出来,大部分的人以前应该写过这样的一道题,将输入的数字123,倒序输出来,321。这种题很简单,我们将数字123倒序每位数转化为字符型输出出来,我们来看一下实现代码:
#include
int main()
{
int num=123;
char c;
while(num!=0)
{
c=num%10 + '0';
num/=10;
printf("%c",c);
}
return 0;
}
这个代码我就不细细讲解了,稍微有点基础的人一下就能明白。
我们可以利用例题的解题思路,将数字显示在盘子中指定的位置,我们利用函数drawch();和for循环将数字化成字符显示在指定的位置,我们先创建一个函数setTestData(),这个函数是为了方便程序调制,随时设置数据。代码如下:
//为了方便程序调制,随时设置数据
//数组data定义在类中以声明
void setTestData()
{
for(int i = 0; i < N; ++i)
{
for(int j = 0; j < N; ++j)
{
//设置交点值
num[i][j] = 16<
数组设置好以后,我们将数组的元素显示在屏幕的盘子中,我们先看一下盘子:
数字显示,我们设置为右对齐,也就是如图中红色数字显示的那样,我们可以看出,数字是在符号 ‘ | ’ 的左边显示的,我们可以将从左往右数第二个符号 ‘ | ’ 的坐标的空格数减一,就可以得到数字个位数的坐标了,再减一就可以得到十位数的坐标了,以此类推就可以得到每位数字的坐标了,我们设置函数drawNum()对数字每一位求余并转化为字符,然后一位一位的画出来,我们来看一下实现代码:
#include
#include
using namespace std;
#define N 4 //格子数量
#define W 5 //格子的宽度
#define M 18 //盘子向右移动的距离
//游戏状态
#define S_WIN 0 //胜利
#define S_FAIL 1 //失败
#define S_NORMAL 2 //游戏正常进行
#define S_QUIT 3 //退出
class Game
{
public:
Game():status(S_NORMAL) { setTestData(); }//数组初始化
//返回status的值
int getStatus()
{
return status;
}
//处理按键
void processInput()
{
char ch = getch();
//将字母转化为大写
if(ch >= 'a' && ch <= 'z')
{
ch -= 32;
}
if(ch == 'Q')
{
status = S_QUIT;
}
else
{
//目前先不做处理,只在输赢提示语中转换
status = (status+1) % 2;
}
}
//绘制游戏界面
void draw()
{
//清理屏幕
clear();
for(int i = 0; i<=N; ++i)
{
for(int j = 0; j<=N ;++j)
{
//绘制相交点
drawch(i*2, j*W + M, '+');
//绘制竖线
if(i != N)
{
drawch(i*2+1, j*W + M, '|');
}
//每个格子中的数字
if(i!=N && j!=N)
{
drawNum(i*2+1, (j+1)*W - 1 + M, num[i][j]);
}
//绘制横线
for(int k=0; j != N && k0)
{
drawch(row, col, num%10+'0');
num /= 10;
--col;
}
}
private:
int status;
int num[N][N];
};
void initialize()
{
//ncurse初始化
initscr();
//按键不需要输入回车直接交互
cbreak();
//按键不显示
noecho();
//隐藏光标
curs_set(0);
}
void shutdown()
{
//ncurses清理
endwin();
}
int main()
{
initialize();
Game game;
do
{
game.draw();
game.processInput();
}while(game.getStatus() != S_QUIT);
shutdown();
return 0;
}
运行结果如图所示:
我们每次玩2048游戏时,开始的时候我们会发现,每次开始都是在盘子不同的位置出现两个数字2或者4,出现的位置是随机的,我们将每次重新开始调用的函数命名为restart(),我们来看一下实现的代码:
// 重新开始
void restart()
{
for (int i = 0; i < N; ++i)
{
for (int j = 0; j < N; ++j)
{
num[i][j] = 0;//将数组元素全部初始化为0
}
}
randNew();
randNew();//随机产生一个数字
status = S_NORMAL;
}
首先我们将数组元素全部初始化为0,然后我们调用函数randNew(),在随机空位置产生一个数字,调用这个函数两次就可以在不同的两个位置随机产生数字2或者4,函数randNew()的返回值类型为bool型,我们来看randNew()函数的代码:
// 随机产生一个新的数字
bool randNew()
{
vector emptyPos;
// 把空位置先存起来
for (int i = 0; i < N; ++i)
{
for (int j = 0; j < N; ++j)
{
if (num[i][j] == 0)
{
emptyPos.push_back(i * N + j);//将数组中数值为0的元素的坐标保存到emptyPos中
}
}
}
if (emptyPos.size() == 0)
{
return false;
}
// 随机找个空位置
int value = emptyPos[rand() % emptyPos.size()];
// 10%的概率产生4
num[value / N][value % N] = rand() % 10 == 1 ? 4 : 2;
return true;
}
这里创建一个vector对象emptyPos,用其保存数组中值为0的元素的坐标,然后判断一下emptyPos长度是否为空,如果是空的,说明数组中没有值为0的元素了,也就是,盘子中没有空位置了,返回false,代表游戏结束了,如果盘子中没有数字达到2048,说明你失败了,否则你胜利了,当然这是后面考虑的。如果盘子中还有空位置,这时我们就需要使用库函数
我们得出盘子中的空位置之后,rand() % 10 == 1 ? 4 : 2;中的rand() % 10会随机产生整数0~9任意一个数,如果该数为1,则在盘子的空位置出生成数字4,否则是数字2,这样就达到了项目的要求在盘子空位置出现数字4的概率为10%,然后返回true。我们来看一下完整的代码:
#include
#include
#include
#include
#include
using namespace std;
#define N 4 //格子数量
#define W 5 //格子的宽度
#define M 18 //盘子向右移动的距离
//游戏状态
#define S_WIN 0 //胜利
#define S_FAIL 1 //失败
#define S_NORMAL 2 //游戏正常进行
#define S_QUIT 3 //退出
class Game
{
public:
Game():status(S_NORMAL) { setTestData(); }//数组初始化
//返回status的值
int getStatus()
{
return status;
}
//处理按键
void processInput()
{
char ch = getch();
//将字母转化为大写
if(ch >= 'a' && ch <= 'z')
{
ch -= 32;
}
if(ch == 'Q')//退出游戏
{
status = S_QUIT;
}
else if (ch == 'R') //重新开始
{
restart();
}
else
{
//目前先不做处理,只在输赢提示语中转换
status = (status+1) % 2;
}
}
//绘制游戏界面
void draw()
{
//清理屏幕
clear();
for(int i = 0; i<=N; ++i)
{
for(int j = 0; j<=N ;++j)
{
//绘制相交点
drawch(i*2, j*W + M, '+');
//绘制竖线
if(i != N)
{
drawch(i*2+1, j*W + M, '|');
}
//每个格子中的数字
if(i!=N && j!=N)
{
drawNum(i*2+1, (j+1)*W - 1 + M, num[i][j]);
}
//绘制横线
for(int k=0; j != N && k emptyPos;
// 把空位置先存起来
for (int i = 0; i < N; ++i)
{
for (int j = 0; j < N; ++j)
{
if (num[i][j] == 0) {
emptyPos.push_back(i * N + j);//将数组中数值为0的元素的坐标保存到emptyPos中
}
}
}
if (emptyPos.size() == 0)
{
return false;
}
// 随机找个空位置
int value = emptyPos[rand() % emptyPos.size()];
// 10%的概率产生4
num[value / N][value % N] = rand() % 10 == 1 ? 4 : 2;
return true;
}
//初始化数组
void setTestData()
{
for(int i = 0; i < N; ++i)
{
for(int j = 0; j0)
{
drawch(row, col, num%10+'0');
num /= 10;
--col;
}
}
private:
int status;
int num[N][N];
};
void initialize()
{
//ncurse初始化
initscr();
//按键不需要输入回车直接交互
cbreak();
//按键不显示
noecho();
//隐藏光标
curs_set(0);
// 随机数
srand(time(NULL));
}
void shutdown()
{
//ncurses清理
endwin();
}
int main()
{
initialize();
Game game;
do
{
game.draw();
game.processInput();
}while(game.getStatus() != S_QUIT);
shutdown();
return 0;
}
运行结果如下所示:(第一张为初始界面,第二张为按R键后的界面,盘子中随机两个位置产生数字)
完成了这些功能之后,我们接下来完成向左移动合并数字,我们先回忆一下项目要求:
1. 通过上下左右来控制合并盘子里的数字。
2. 每次合并只能合并相邻的两个数字,例如 [2 2 2 2] ,向右合并以后是 [空 空 4 4] ,不是 [空 空 空 8]。
3. 每次合并的时候,合并那个方向的数字优先级高,例如 [空 2 2 2],向右合并以后是 [空 空 2 4],不是 [空 空 4 2]。
4. 每次移动之后会在空的位置中随机产生一个数字2或者4。
我们设置键盘上的W为向上合并数字,A为向左合并数字,S为向下合并数字,D为向右合并数字,Q为退出游戏,R为重新开始。当玩家按下A时,盘子中的数字会左移,并且合并相邻且相同的数字,当玩家按下D时,盘子中的数字会右移,并且合并相邻且相同的数字,当玩家按下W时,盘子中的数字会上移,并且合并相邻且相同的数字,当玩家按下S时,盘子中的数字会下移,并且合并相邻且相同的数字。
我们完善一下处理按键的函数processInput():
//处理按键
void processInput()
{
char ch = getch();
//将字母转化为大写
if(ch >= 'a' && ch <= 'z')
{
ch -= 32;
}
if(status == S_NORMAL)//如果游戏正常进行
{
bool updated = false;//用来判断盘子中的数是否移动了或者合并了
if(ch == 'A')
{
//向左移动
updated = moveLeft();//如果盘子中的数发生了变化,会返回true,否则返回false
}
else if(ch == 'D')
{
//向右移动
}
else if(ch =='W')
{
//向上移动
}
else if(ch == 'S')
{
//向下移动
}
if(updated)//如果盘子中的数发生了变化,就在所有的空位置中,随机产生一个数字
{
randNew();//在盘子中的空位置中的随机一个位置产生一个新数
}
}
if(ch == 'Q')//判断是否退出游戏
{
status = S_QUIT;
}
else if (ch == 'R')//判断是否重新开始游戏
{
restart();
}
}
向左移动的函数为moveLeft(),该函数返回值为bool类型,如果盘子中的数据发生了变化就返回true,否则就返回false,如果盘子中的数据没有发生变化,就不在盘子中随机产生新数,如果发生了变化就在盘子中的空位置中,随机一个空位置产生一个新数2或者4。我们来看一下代码:
#include
#include
#include
#include
#include
using namespace std;
#define N 4 //格子数量
#define W 5 //格子的宽度
#define M 18 //盘子向右移动的距离
//游戏状态
#define S_WIN 0 //胜利
#define S_FAIL 1 //失败
#define S_NORMAL 2 //游戏正常进行
#define S_QUIT 3 //退出
//胜利条件
#define TARGET 2048
class Game
{
public:
Game():status(S_NORMAL) { setTestData(); }//数组初始化
//返回status的值
int getStatus()
{
return status;
}
//处理按键
void processInput()
{
char ch = getch();
//将字母转化为大写
if(ch >= 'a' && ch <= 'z')
{
ch -= 32;
}
if(status == S_NORMAL)//如果游戏正常进行
{
bool updated = false;//用来判断盘子中的数是否移动了或者合并了
if(ch == 'A')
{
//向左移动
updated = moveLeft();//如果盘子中的数发生了变化,会返回true,否则返回false
}
else if(ch == 'D')
{
//向右移动
}
else if(ch =='W')
{
//向上移动
}
else if(ch == 'S')
{
//向下移动
}
if(updated)//如果盘子中的数发生了变化,就在所有的空位置中,随机产生一个数字
{
randNew();//在盘子中的空位置中的随机一个位置产生一个新数
}
}
if(ch == 'Q')//判断是否退出游戏
{
status = S_QUIT;
}
else if (ch == 'R')//判断是否重新开始游戏
{
restart();
}
}
//绘制游戏界面
void draw()
{
//清理屏幕
clear();
//居中偏移
//const int offs`et = 18;
for(int i = 0; i<=N; ++i)
{
for(int j = 0; j<=N ;++j)
{
//绘制相交点
drawch(i*2, j*W + M, '+');
//绘制竖线
if(i != N)
{
drawch(i*2+1, j*W + M, '|');
}
//每个格子中的数字
if(i!=N && j!=N)
{
drawNum(i*2+1, (j+1)*W - 1 + M, num[i][j]);
}
//绘制横线
for(int k=0; j != N && k emptyPos;
// 把空位置先存起来
for (int i = 0; i < N; ++i)
{
for (int j = 0; j < N; ++j)
{
if (num[i][j] == 0)
{
emptyPos.push_back(i * N + j);
}
}
}
if (emptyPos.size() == 0)
{
return false;
}
// 随机找个空位置
int value = emptyPos[rand() % emptyPos.size()];
// 10%的概率产生4
num[value / N][value % N] = rand() % 10 == 1 ? 4 : 2;
return true;
}
//初始化数组
void setTestData()
{
for(int i = 0; i < N; ++i)
{
for(int j = 0; j0)
{
drawch(row, col, num%10+'0');
num /= 10;
--col;
}
}
private:
int status;
int num[N][N];
};
void initialize()
{
//ncurse初始化
initscr();
//按键不需要输入回车直接交互
cbreak();
//按键不显示
noecho();
//隐藏光标
curs_set(0);
// 随机数
srand(time(NULL));
}
void shutdown()
{
//ncurses清理
endwin();
}
int main()
{
initialize();
Game game;
do
{
game.draw();
game.processInput();
}while(game.getStatus() != S_QUIT);
shutdown();
return 0;
}
假如我们有下图所示的第一个盘子的数据,我们现在要将其向上移动合并数字,利用数学思维,我们将其逆时针旋转90°之后,向左移,再逆时针旋转270°,这样我们就实现了向上移动合并数字;向右移动,我们就我们将其逆时针旋转180°之后,向左移,再逆时针旋转180°,这样就是实现了向右移;向下移动,我们就我们将其逆时针旋转270°之后,向左移,再逆时针旋转90°,这样就实现了向下移动。这样处理代码是实现起来就很简洁了,而且很巧妙。
我们来看一下实现逆时针旋转90°的代码:
//数组逆时针旋转90度
void rotate()
{
int tmp[N][N] = {0};
for(int i = 0;i
我们还要增加一个函数isOver()判断盘子中如果还有空位,相邻元素之间还能合并,则返回true,否则返回false。代码如下所示:
//判断游戏是否结束
bool isOver()
{
for(int i = 0;i
我们看一下完善之后的处理按键的函数processInput():
//处理按键
void processInput()
{
char ch = getch();
//将字母转化为大写
if(ch >= 'a' && ch <= 'z')
{
ch -= 32;
}
if(status == S_NORMAL)//如果游戏正常进行
{
bool updated = false;//用来判断盘子中的数是否移动了或者合并了
if(ch == 'A')
{
//向左移动
updated = moveLeft();//如果盘子中的数发生了变化,会返回true,否则返回false
}
else if(ch == 'D')
{
//向右移动
rotate();
rotate();
updated = moveLeft();
rotate();
rotate();
}
else if(ch =='W')
{
//向上移动
rotate();
updated = moveLeft();
rotate();
rotate();
rotate();
}
else if(ch == 'S')
{
//向下移动
rotate();
rotate();
rotate();
updated = moveLeft();
rotate();
}
if(updated)//如果盘子中的数发生了变化,就在所有的空位置中,随机产生一个数字
{
randNew();//在盘子中的空位置中的随机一个位置产生一个新数
if(isOver())//判断盘子中是否还有空位置,相邻元素之间是否还能合并
{
status = S_FAIL;
}
}
}
if(ch == 'Q')//判断是否退出游戏
{
status = S_QUIT;
}
else if (ch == 'R')//判断是否重新开始游戏
{
restart();
}
}
我们将新增的几个函数,加进去之后,我们的项目2048小游戏就圆满完成了。当然还能变得更加完美,结合数据库,保留每次的成绩,还有最高和最低成绩,等等,如果之后有时间在完善该功能。
#include
#include
#include
#include
#include
using namespace std;
#define N 4 //格子数量
#define W 5 //格子的宽度
#define M 18 //盘子向右移动的距离
//游戏状态
#define S_WIN 0 //胜利
#define S_FAIL 1 //失败
#define S_NORMAL 2 //游戏正常进行
#define S_QUIT 3 //退出
//胜利条件
#define TARGET 2048
class Game
{
public:
Game():status(S_NORMAL) { setTestData(); }//数组初始化
//返回status的值
int getStatus()
{
return status;
}
//处理按键
void processInput()
{
char ch = getch();
//将字母转化为大写
if(ch >= 'a' && ch <= 'z')
{
ch -= 32;
}
if(status == S_NORMAL)//如果游戏正常进行
{
bool updated = false;//用来判断盘子中的数是否移动了或者合并了
if(ch == 'A')
{
//向左移动
updated = moveLeft();//如果盘子中的数发生了变化,会返回true,否则返回false
}
else if(ch == 'D')
{
//向右移动
rotate();
rotate();
updated = moveLeft();
rotate();
rotate();
}
else if(ch =='W')
{
//向上移动
rotate();
updated = moveLeft();
rotate();
rotate();
rotate();
}
else if(ch == 'S')
{
//向下移动
rotate();
rotate();
rotate();
updated = moveLeft();
rotate();
}
if(updated)//如果盘子中的数发生了变化,就在所有的空位置中,随机产生一个数字
{
randNew();//在盘子中的空位置中的随机一个位置产生一个新数
if(isOver())//判断盘子中是否还有空位置,相邻元素之间是否还能合并
{
status = S_FAIL;
}
}
}
if(ch == 'Q')//判断是否退出游戏
{
status = S_QUIT;
}
else if (ch == 'R')//判断是否重新开始游戏
{
restart();
}
}
//绘制游戏界面
void draw()
{
//清理屏幕
clear();
//居中偏移
//const int offs`et = 18;
for(int i = 0; i<=N; ++i)
{
for(int j = 0; j<=N ;++j)
{
//绘制相交点
drawch(i*2, j*W + M, '+');
//绘制竖线
if(i != N)
{
drawch(i*2+1, j*W + M, '|');
}
//每个格子中的数字
if(i!=N && j!=N)
{
drawNum(i*2+1, (j+1)*W - 1 + M, num[i][j]);
}
//绘制横线
for(int k=0; j != N && k emptyPos;
// 把空位置先存起来
for (int i = 0; i < N; ++i)
{
for (int j = 0; j < N; ++j)
{
if (num[i][j] == 0)
{
emptyPos.push_back(i * N + j);
}
}
}
if (emptyPos.size() == 0)
{
return false;
}
// 随机找个空位置
int value = emptyPos[rand() % emptyPos.size()];
// 10%的概率产生4
num[value / N][value % N] = rand() % 10 == 1 ? 4 : 2;
return true;
}
//初始化数组
void setTestData()
{
for(int i = 0; i < N; ++i)
{
for(int j = 0; j0)
{
drawch(row, col, num%10+'0');
num /= 10;
--col;
}
}
private:
int status;
int num[N][N];
};
void initialize()
{
//ncurse初始化
initscr();
//按键不需要输入回车直接交互
cbreak();
//按键不显示
noecho();
//隐藏光标
curs_set(0);
// 随机数
srand(time(NULL));
}
void shutdown()
{
//ncurses清理
endwin();
}
int main()
{
initialize();
Game game;
do
{
game.draw();
game.processInput();
}while(game.getStatus() != S_QUIT);
shutdown();
return 0;
}
运行结果,以及部分操作图:
好吧,我玩不到2048。。。