用C++命令行实现2048小游戏,基本思路如下。
先凭空在命令行绘制一个4×4的方格,作为游戏界面,中间填充数字的部分用空格表示。定义一个长度为4,4的二维int型数组,不显示数据的地方用0表示,将它动态地填入到游戏界面中。
在接收到按键事件后,对二维数组进行遍历,针对每一个元素,获取它的下一位元素,这里的下一位元素要根据按键事件动态获取(如果按键是向下,则下一位元素就是 arr [ i + 1 ][ j ]),并且对下一位元素进行判断:如果下一位元素是0,则将当前元素赋值给下一位元素,并且将当前下标位置设为0;如果下一位元素等于当前元素(且都不等于0),则进行合并操作。
最后刷新命令行,根据重新赋值的数组生成界面。
关于这个游戏需要注意以下几点:
1,字体颜色:写一个根据数字返回string字符串的函数,将字符串直接插入到数字前面。颜色的实现要用到“\033”相关知识,可以搜索此关键字查看详情,下面是我的函数:
/*
用法:
elem是要改变的字体颜色,可直接插入到此数据两端,
elem是int型,所以要to_string()
string str = setcolor(elem) + to_string(elem) + setcolor(-1);
*/
string setcolor(int elem) {
if (elem == -1) {return "\033[0m";}
else if (elem == 2) { return "\033[1m\033[33m"; }
else if (elem == 4) { return "\033[2m\033[33m"; }
else if (elem == 8) { return "\033[1m\033[32m"; }
else if (elem == 16) { return "\033[2m\033[32m"; }
else if (elem == 32) { return "\033[1m\033[36m"; }
else if (elem == 64) { return "\033[2m\033[36m"; }
else if (elem == 128) { return "\033[1m\033[34m"; }
else if (elem == 256) { return "\033[2m\033[34m"; }
else if (elem == 512) { return "\033[1m\033[32m"; }
else if (elem == 1024) { return "\033[2m\033[32m"; }
else if (elem == 2048) { return "\033[1m\033[31m"; }
}
2,此游戏用二维数组代替坐标,极易混淆x和y的关系,假设arr[1][2],其中1才是Y,2才是X。在编码途中要格外注意。
3,倘若按照我的编码逻辑,很容易出现一个问题:连续合并的问题。假如有一行数据是2248,2和2合并后变成了4,继续遍历,就会和4再次合并,合并后变成8又会和8再次合并。这不符合游戏逻辑。
倘若加入一个flag变量控制,似乎能解决这样的问题,但又会出现新的问题。假如有一行存在2288,按照原来的游戏规则,此处2和2,以及8和8都应该各自合并,但由于flag存在,导致只会合并其中一项。
此时就需要重新设置一个4×4的数组,作为flag[4][4],来存储合并标记。在每次合并前,判断当前位置是否经历过合并,合并后,在该数组的对应位置记录标记。
以下是我的代码:
int arr[4][4];
int _flag[4][4];
//初始化flag数组
void initflag() {
_flag[0][0] = 0; _flag[0][1] = 0; _flag[0][2] = 0; _flag[0][3] = 0;
_flag[1][0] = 0; _flag[1][1] = 0; _flag[1][2] = 0; _flag[1][3] = 0;
_flag[2][0] = 0; _flag[2][1] = 0; _flag[2][2] = 0; _flag[2][3] = 0;
_flag[3][0] = 0; _flag[3][1] = 0; _flag[3][2] = 0; _flag[3][3] = 0;
}
//初始化数组
void initdata() {
arr[0][0] = 2; arr[0][1] = 2; arr[0][2] = 4; arr[0][3] = 8;
arr[1][0] = 0; arr[1][1] = 0; arr[1][2] = 0; arr[1][3] = 0;
arr[2][0] = 0; arr[2][1] = 0; arr[2][2] = 0; arr[2][3] = 0;
arr[3][0] = 0; arr[3][1] = 0; arr[3][2] = 0; arr[3][3] = 0;
showpage();
}
//根据数组生成界面
void showpage() {
system("cls");
cout << endl;
cout << "\t" << "|-----|-----|-----|-----|" << endl;
cout << "\t" << "|"<<setelement(arr[0][0]) << "|" << setelement(arr[0][1]) << "|" << setelement(arr[0][2]) << "|" << setelement(arr[0][3]) << "|" << endl;
cout << "\t" << "|-----|-----|-----|-----|" << endl;
cout << "\t" << "|" << setelement(arr[1][0]) << "|" << setelement(arr[1][1]) << "|" << setelement(arr[1][2]) << "|" << setelement(arr[1][3]) << "|" << endl;
cout << "\t" << "|-----|-----|-----|-----|" << endl;
cout << "\t" << "|" << setelement(arr[2][0]) << "|" << setelement(arr[2][1]) << "|" << setelement(arr[2][2]) << "|" << setelement(arr[2][3]) << "|" << endl;
cout << "\t" << "|-----|-----|-----|-----|" << endl;
cout << "\t" << "|" << setelement(arr[3][0]) << "|" << setelement(arr[3][1]) << "|" << setelement(arr[3][2]) << "|" << setelement(arr[3][3]) << "|" << endl;
cout << "\t" << "|-----|-----|-----|-----|" << endl;
}
//根据数字长度,返回不同的字符串,用来填充界面
string setelement(int &elem) {
if (elem == 0) {
return " ";
}
if (elem >0 && elem < 10) {
return " " + setcolor(elem) + to_string(elem) + setcolor(-1) + " ";
}
else if (elem > 9 && elem < 100) {
return " " + setcolor(elem) + to_string(elem) + setcolor(-1) + " ";
}
else if(elem > 99 && elem < 1000){
return " " + setcolor(elem) + to_string(elem) + setcolor(-1) + " ";
}
else {
return "" + setcolor(elem) + to_string(elem) + setcolor(-1) + " ";
}
}
//根据不同数字设置颜色
string setcolor(int elem) {
if (elem == -1) {return "\033[0m";}
else if (elem == 2) { return "\033[1m\033[33m"; }
else if (elem == 4) { return "\033[2m\033[33m"; }
else if (elem == 8) { return "\033[1m\033[32m"; }
else if (elem == 16) { return "\033[2m\033[32m"; }
else if (elem == 32) { return "\033[1m\033[36m"; }
else if (elem == 64) { return "\033[2m\033[36m"; }
else if (elem == 128) { return "\033[1m\033[34m"; }
else if (elem == 256) { return "\033[2m\033[34m"; }
else if (elem == 512) { return "\033[1m\033[32m"; }
else if (elem == 1024) { return "\033[2m\033[32m"; }
else if (elem == 2048) { return "\033[1m\033[31m"; }
}
//写一个死循环用来获取按键
void getkeyboard() {
while (true) {
if (_kbhit()) {
int ch = _getch();//使用_getch()函数获取按下的键值
keyfunc(ch);
}
}
}
void keyfunc(int& inbtn) {
for (int i = 0; i < 3; i++) {//最外层循环,数字移动最多移动三个位置
for (int indexY = 0;indexY < 4;indexY++) {//此处的双层循环,是二维数组的正常循环
for (int indexX = 0;indexX < 4;indexX++) {
//获取当前坐标的“下一坐标”,并用pair接收
pair<int, int> p = getlastIndex(indexX, indexY, inbtn);
//当数字一致且不为0时进行合并(为什么要不为0,虽然00合并也是0,但是可能会影响到flag数组)
if (arr[p.first][p.second] == arr[indexY][indexX] && arr[indexY][indexX] != 0) {
//循环到数组末端时,它的“下一位”就是它自己,在此排除掉这种情况
if (p.first != indexY || p.second != indexX) {
//对flag标记进行判断
if (_flag[p.first][p.second] == 0 && _flag[indexY][indexX] == 0) {
arr[p.first][p.second] = (arr[indexY][indexX]) * 2;
arr[indexY][indexX] = 0;
_flag[p.first][p.second] = 1;
_flag[indexY][indexX] = 1;
}
}
}
//当下一位是0时,移动数据
else if (arr[p.first][p.second] == 0) {
arr[p.first][p.second] = arr[indexY][indexX];
arr[indexY][indexX] = 0;
}
}
}
}
initflag();
showpage();
insertdata();
}
//根据按键,获取“下一位置”坐标
pair<int,int> getlastIndex(int &indexX, int &indexY,int &keydown) {
pair<int, int> p;
p.first = indexY;
p.second = indexX;
if (keydown == 119) {
if (indexY>0) {
p.first = indexY - 1;
}
}
else if (keydown == 115) {
if (indexY < 3) {
p.first = indexY + 1;
}
}
else if (keydown == 97) {
if (indexX > 0) {
p.second = indexX - 1;
}
}
else if (keydown == 100) {
if (indexX < 3) {
p.second = indexX + 1;
}
}
return p;
}
//每次按键后随机在空格处生成新的数字,此处用了14%的概率生成4
void insertdata() {
int insertdata = 2;
int randnum = rand() % 100 + 1;
if (randnum < 14) {
insertdata = 4;
}
vector<pair<int, int>> v;
for (int y = 0;y < 4;y++) {
for (int x = 0;x < 4;x++) {
if (arr[y][x] == 0) {
pair<int, int> p;
p.first = y; p.second = x;
v.push_back(p);
}
}
}
int rand1 = rand() % v.size();
pair<int,int> p1 = v.at(rand1);
arr[p1.first][p1.second] = insertdata;
showpage();
}