目录
1. 代码前的准备
2. 游戏思路及代码分析
2.1 game.h 代码分析
2.2 test.cpp代码分析
3. 完整代码
3.1 game.h
3.2 game.cpp
3.3 test.cpp
嘿嘿嘿,写游戏还是挺高兴的撒,如果你还不知道2048这个小游戏的规则,那么快去试试吧。不然你怎么会写得出来嘞!
2048 游戏 - 在线玩在线玩 2048 游戏!使用箭头键移动瓷砖并尝试获得 2048 瓷砖!https://2048.gg/zh
1. 代码前的准备
本游戏使用了C++的EasyX图形库,咱就默认你对其有一定的了解哈!!
这里有EasyX的参考文档:EasyX 文档 - solidroundrecthttps://docs.easyx.cn/zh-cn/solidroundrect
在写代码之前你需要下载EasyX的图形库:EasyX Graphics Library for C++https://easyx.cn/
下载完成后安装:
系统会自动检测你的电脑上可以安装EasyX的IDE,选择你要安装的即可。你也可以安装EasyX的参考文档。
2. 游戏思路及代码分析
1):我们还是选择创建一个头文件:game.h,两个.cpp文件:game.cpp和 test.cpp,EasyX不支持.c文件哦!!!
2):我们需要把项目的字符集改成“多字节字符集”,不然的话EasyX图形库里面的某些函数会报错的哦!
2.1 game.h 代码分析
在game.h这个文件中我们需要包含头文件,定义宏,声明函数等等。
3-10行:包含必须的头文件 。
12-21行:定义宏。关于棋盘的计算肯定是难不到大家的相关的大小我就帮大家测量好了。
24-39行:将不同数字的颜色弄到枚举类型里面,RGB就是Red,Green,Blue,一种表示颜色的方式,不懂的可以参考文档,然后再把枚举类型的成员装到数组里面(这部分代码在后面一点),这样我们就可以通过下标访问枚举类型的成员啦。
two_10:2的10次方。
42行往后就是函数的声明。
2.2 test.cpp代码分析
2行:包含game.h才能用声明的枚举类型等等。
5行:将枚举类型的每一个成员装到一个数组里面。作用后面一点点讲。
8行:得讲讲游戏思路才能理解,当我们按下按键把数字向一个方向移动时会存在相同数字合并的情况。因为不同的数字对应不同的颜色,我们需要对合并成的数字进行判断,这时我们就可以遍历num这个数组,找到和合并产生的数字相等的下标,再在arr数组里面找到该下标的枚举类型成员就找到了该数字的颜色。
10行:POINT就是EasyX定义好的一个结构体:按住Ctrl 点击 POINT 转到他的定义,我们可以看到就是一个x坐标,一个y坐标。用其创建一个二维数组方便后续画出每一个格子,后面一点将哈。
14行:定义一个bool类型的变量,来判断是否产生新的数字。
我们都知道2048这个游戏只有数字产生了移动,或者出现了数字的合并才会生成一个新的数字。具体用法后面一点讲。
58-65行:没有啥好讲的,不懂请参考EasyX文档 。
67行:棋盘就是一个二维数组,我们将数字的改变通过二维数组存储,然后打印棋盘。
69行:MapInit函数:在game.cpp中定义的哈
遍历POINT pos 这个二维数组,并赋初值。初始化就是将每个格子左上角点的x坐标,y坐标存储到pos这个二维数组里面,以后在画格子的时候,只要再次 遍历pos数组,再在每个x,y的基础上加上格子的宽度就是格子右下角的的坐标了!
42行:生成2或者4数字装到map数组里面,因为是一开始游戏,所以生成两个。这部分代码比较简单可以看最后面完整代码里面的注释理解。你们肯定也写得出来撒。
70行:一个循环,我们需要重复移动嘛,直到2048,嘿嘿。
72行:DrawMap函数:画棋盘的函数,同样在game.cpp中定义的哈。
我们遍历装数字的map数组 ,然后在里面遍历num数组,找到相应数字的下标,通过下标找到对应数字的颜色,然后就是画格子,再把数字画到格子的中间位置就可以了。
怎样把数字弄到格子的中间位置,想必学前端的伙伴肯定知道。
得到了偏移量就可以将数字也画上去啦。同样下面的画法有不懂的请参考EasyX文档。
73行:ControlGame函数:这个函数在test.cpp中就行。
18行:从键盘读取一个字符。然后通过switch语句不同的键进行不同方向的移动就行。
因为移动的方法大同小异,咱分析一个就行以MoveUp为例哈,该函数定义在game.cpp中哈。
向上移动的整个步骤我们拆分着来:
1)合并:就拿向上移动来说,我们就需要对map数组的每一列进行向上移动的操作,所以需要在向上移动的外面套一层循环,对每一列进行向上移动。我们在对每一列操作时,维护两个数begin和end不同的值指向每一列的不同位置。我们先让begin指向map数组该列中第一个不为0的位置,即对该列进行检索,可以定义一个函数FindUpIndex来检索该列第一个不为0的位置,(注意:向上移动就是从该列行下标为0的位置开始检索,向下的话就从格子数MAX_GRID_NUM - 1的位置开始),然后以同样的方法从begin+1的位置检索,给给end如果该列的begin和end下标位置的数字相等,那么就合并同时将end的位置改成0,如果过程中没有检索到begin或者end那么,就不合并即可。
2)移动:这部分代码的 解释在完整代码里面的注释有详解。
74行:Judge函数:用来判断是否产生新的数字。
flag1在ControlGame函数对数字移动过程中,如果发生了数字的合并,数字的移动就会被改成true,如果flag1为true那么满足产生新的数字的条件,生成一个新的数字。
3. 完整代码
3.1 game.h
#pragma once//包含easyx的头文件
#include<graphics.h>
//包含监听键盘输入的头文件
#include<conio.h>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<stdbool.h>//每行的格子数目
#define MAX_GRID_NUM 4
//格子的宽度
#define GRID_WIDTH 100
//格子之间的间隙
#define GAP 15
//整个棋盘宽度
#define MAP_WIDTH_SIZE MAX_GRID_NUM * GRID_WIDTH + GAP * (MAX_GRID_NUM + 1)
//赢的数字
#define WIN_NUMBER 2048//枚举格子的颜色,不同数字对应不同颜色
enum GridColor
{zero = RGB(205, 193, 180), //无数字的颜色two_1 = RGB(238, 228, 218), //数字2的颜色two_2 = RGB(237, 224, 200), //数字4的颜色two_3 = RGB(242, 177, 121), //数字8的颜色two_4 = RGB(245, 149, 99), //数字16的颜色two_5 = RGB(246, 124, 95), //数字32的颜色two_6 = RGB(246, 94, 59), //数字64的颜色two_7 = RGB(237, 207, 114), //数字128的颜色two_8 = RGB(237, 204, 97), //数字256的颜色two_9 = RGB(255, 0, 128), //数字512的颜色two_10 = RGB(145, 0, 72), //数字1024的颜色two_11 = RGB(242, 17, 158), //数字2048的颜色
};//棋盘的初始化
void MapInit(int map[MAX_GRID_NUM][MAX_GRID_NUM]);//随机的初始数字2或者4
int GetInitNum();//生成随机的2或者4
void CreateNumber(int map[MAX_GRID_NUM][MAX_GRID_NUM]);//画出棋盘
void DrawMap(int map[MAX_GRID_NUM][MAX_GRID_NUM]);//对是否产生移动进行判断
void Judge(int map[MAX_GRID_NUM][MAX_GRID_NUM]);//向上移动
void MoveUp(int map[MAX_GRID_NUM][MAX_GRID_NUM]);//向下移动
void MoveDown(int map[MAX_GRID_NUM][MAX_GRID_NUM]);//向左移动
void MoveLeft(int map[MAX_GRID_NUM][MAX_GRID_NUM]);//向右移动
void MoveRight(int map[MAX_GRID_NUM][MAX_GRID_NUM]);//判断游戏是否结束,返回-1代表你输了,1代表你赢了,0代表游戏继续。
int GameOver(int map[MAX_GRID_NUM][MAX_GRID_NUM]);
3.2 game.cpp
#define _CRT_SECURE_NO_WARNINGS#include"game.h"extern GridColor arr[12];extern int num[];extern POINT position[MAX_GRID_NUM][MAX_GRID_NUM];//判断移动
extern bool flag1;void CreateNumber(int map[MAX_GRID_NUM][MAX_GRID_NUM])
{while (1){int x = rand() % MAX_GRID_NUM;int y = rand() % MAX_GRID_NUM;if (map[x][y] == 0){map[x][y] = GetInitNum();break;}}
}//初始化棋盘
void MapInit(int map[MAX_GRID_NUM][MAX_GRID_NUM])
{int i, j;for (i = 0; i < MAX_GRID_NUM; i++){for (j = 0; j < MAX_GRID_NUM; j++){position[i][j].x = j * GRID_WIDTH + (j + 1) * GAP;position[i][j].y = i * GRID_WIDTH + (i + 1) * GAP;}}//生成随机的数,一开始产生两个CreateNumber(map);CreateNumber(map);}//随机的初始数字2或者4
int GetInitNum()
{//设置产生4的几率为八分之一if (rand() % 8 == 0){return 4;}else{return 2;}
}//画出棋盘
void DrawMap(int map[MAX_GRID_NUM][MAX_GRID_NUM])
{int i, j;for (i = 0; i < MAX_GRID_NUM; i++){for (j = 0; j < MAX_GRID_NUM; j++){for (int m = 0; m < 12; m++){if (map[i][j] == num[m]){setfillcolor(arr[m]);solidrectangle(position[i][j].x, position[i][j].y,position[i][j].x + GRID_WIDTH, position[i][j].y + GRID_WIDTH);if (map[i][j] != 0){char ret[5];sprintf(ret, "%d", map[i][j]);settextstyle(50, 0, "Courier New", 0, 0, 700, false, false, false);settextcolor(RGB(119, 110, 101));setbkmode(TRANSPARENT);int width_move = GRID_WIDTH / 2 - textwidth(ret) / 2;int height_move = GRID_WIDTH / 2 - textheight(ret) / 2;outtextxy(position[i][j].x + width_move, position[i][j].y + height_move, ret);}}}}}
}//上移时找map数组值不为0的下标
int FindUpIndex(int map[MAX_GRID_NUM][MAX_GRID_NUM], int begin, int i)
{//找不到就改变begin,如果找完一行或者一列都没找到返回-1while (map[begin][i] == 0 && begin < MAX_GRID_NUM){begin++;}if (begin == MAX_GRID_NUM){return -1;}else{return begin;}
}//下移时找map数组值不为0的下标
int FindDownIndex(int map[MAX_GRID_NUM][MAX_GRID_NUM], int begin, int i)
{//找不到就改变begin,如果找完一行或者一列都没找到返回-1while (map[begin][i] == 0 && begin >= 0){begin--;}if (begin < 0){return -1;}else{return begin;}
}//左移时找map数组值不为0的下标
int FindLeftIndex(int map[MAX_GRID_NUM][MAX_GRID_NUM], int begin, int i)
{//找不到就改变begin,如果找完一行或者一列都没找到返回-1while (map[i][begin] == 0 && begin < MAX_GRID_NUM){begin++;}if (begin == MAX_GRID_NUM){return -1;}else{return begin;}
}//右移时找map数组值不为0的下标
int FindRightIndex(int map[MAX_GRID_NUM][MAX_GRID_NUM], int begin, int i)
{//找不到就改变begin,如果找完一行或者一列都没找到返回-1while (map[i][begin] == 0 && begin >= 0){begin--;}if (begin < 0){return -1;}else{return begin;}
}//向上合并
void MergeUp(int map[MAX_GRID_NUM][MAX_GRID_NUM])
{//维护双变量方便合并int i, begin, end;for (i = 0; i < MAX_GRID_NUM; i++){//尝试找beginbegin = FindUpIndex(map, 0, i);//如果返回值不为-1就找到了beginif (begin != -1){//找到begin的前提下从begin的下面开始找endend = FindUpIndex(map, begin + 1, i);//如果end为-1无法进入循环while (end >= 0 && map[end][i]){//end不为-1表明找到了两个map数组值不为零的位置,判断他们的关系if (map[begin][i] == map[end][i]){//相等合并map[begin][i] += map[end][i];map[end][i] = 0;//合并则代表下一轮可以产生新的数字flag1 = true;//合并后end的位置被改成0,从end+1的位置找更新beginbegin = FindUpIndex(map, end + 1, i);if (begin != -1){//在找到新的begin的前提下再去找endend = FindUpIndex(map, begin + 1, i);}else{//找不到end退出循环break;}}else{//begin和end的map数组值不相等,将end给begin,从begin+1的位置找新的endbegin = end;end = FindUpIndex(map, begin + 1, i);}}}}
}//向下合并
void MergeDown(int map[MAX_GRID_NUM][MAX_GRID_NUM])
{int i, begin, end;for (i = 0; i < MAX_GRID_NUM; i++){begin = FindDownIndex(map, MAX_GRID_NUM - 1, i);if (begin != -1){end = FindDownIndex(map, begin - 1, i);while (end >= 0 && map[end][i]){if (map[end][i] == map[begin][i]){map[begin][i] += map[end][i];map[end][i] = 0;flag1 = true;begin = FindDownIndex(map, end - 1, i);if (begin != -1){end = FindDownIndex(map, begin - 1, i);}else{break;}}else{begin = end;end = FindDownIndex(map, begin - 1, i);;}}}}
}//向左合并
void MergeLeft(int map[MAX_GRID_NUM][MAX_GRID_NUM])
{int i, begin, end;for (i = 0; i < MAX_GRID_NUM; i++){begin = FindLeftIndex(map, 0, i);if (begin != -1){end = FindLeftIndex(map, begin + 1, i);while (end >= 0 && map[i][end]){if (map[i][begin] == map[i][end]){map[i][begin] += map[i][end];map[i][end] = 0;flag1 = true;begin = FindLeftIndex(map, end + 1, i);if (begin != -1){end = FindLeftIndex(map, begin + 1, i);}else{break;}}else{begin = end;end = FindLeftIndex(map, begin + 1, i);}}}}
}//向右合并
void MergeRight(int map[MAX_GRID_NUM][MAX_GRID_NUM])
{int i, begin, end;for (i = 0; i < MAX_GRID_NUM; i++){begin = FindRightIndex(map, MAX_GRID_NUM - 1, i);if (begin != -1){end = FindRightIndex(map, begin - 1, i);while (end >= 0 && map[i][end]){if (map[i][end] == map[i][begin]){map[i][begin] += map[i][end];map[i][end] = 0;flag1 = true;begin = FindRightIndex(map, end - 1, i);if (begin != -1){end = FindRightIndex(map, begin - 1, i);}else{break;}}else{begin = end;end = FindRightIndex(map, begin - 1, i);}}}}
}//向上移动
void MoveUp(int map[MAX_GRID_NUM][MAX_GRID_NUM])
{MergeUp(map);int i, begin;for (i = 0; i < MAX_GRID_NUM; i++){int temp = 0;for (begin = 1; begin < MAX_GRID_NUM; begin++){if (map[begin][i] != 0){if (map[temp][i] == 0){map[temp][i] = map[begin][i];map[begin][i] = 0;//产生移动flag1 = true;}else{map[temp + 1][i] = map[begin][i];if (temp + 1 != begin){map[begin][i] = 0;//产生移动flag1 = true;}}temp++;}}}
}//向下移动
void MoveDown(int map[MAX_GRID_NUM][MAX_GRID_NUM])
{//合并MergeDown(map);int i, begin;for (i = 0; i < MAX_GRID_NUM; i++){int temp = MAX_GRID_NUM - 1;for (begin = MAX_GRID_NUM - 2; begin >= 0; begin--){if (map[begin][i] != 0){if (map[temp][i] == 0){map[temp][i] = map[begin][i];map[begin][i] = 0;//产生移动flag1 = true;}else{map[temp - 1][i] = map[begin][i];//代表他们之间有0的格子,才移动if (temp - 1 != begin){map[begin][i] = 0;//产生移动flag1 = true;}}temp--;}}}
}//向左移动
void MoveLeft(int map[MAX_GRID_NUM][MAX_GRID_NUM])
{MergeLeft(map);int i, begin;for (i = 0; i < MAX_GRID_NUM; i++){int temp = 0;for (begin = 1; begin < MAX_GRID_NUM; begin++){if (map[i][begin] != 0){if (map[i][temp] == 0){map[i][temp] = map[i][begin];map[i][begin] = 0;//产生移动flag1 = true;}else{map[i][temp + 1] = map[i][begin];if (temp + 1 != begin){map[i][begin] = 0;//产生移动flag1 = true;}}temp++;}}}
}//向右移动
void MoveRight(int map[MAX_GRID_NUM][MAX_GRID_NUM])
{MergeRight(map);int i, begin;for (i = 0; i < MAX_GRID_NUM; i++){int temp = MAX_GRID_NUM - 1;for (begin = MAX_GRID_NUM - 2; begin >= 0; begin--){if (map[i][begin] != 0){if (map[i][temp] == 0){map[i][temp] = map[i][begin];map[i][begin] = 0;//产生移动flag1 = true;}else{map[i][temp - 1] = map[i][begin];if (temp - 1 != begin){map[i][begin] = 0;//产生移动flag1 = true;}}temp--;}}}
}//判断是否产生新的数字
void Judge(int map[MAX_GRID_NUM][MAX_GRID_NUM])
{if (flag1){CreateNumber(map);flag1 = false;}
}//判断游戏是否结束,返回-1代表你输了,1代表你赢了,0代表游戏继续。
int GameOver(int map[MAX_GRID_NUM][MAX_GRID_NUM])
{int flag = 0;int i, j;for (i = 0; i < MAX_GRID_NUM; i++){for (j = 0; j < MAX_GRID_NUM; j++){if (map[i][j] == 0){flag = 1;}else if (map[i][j] == WIN_NUMBER){return 1;}}}//flag==1代表有空位置,游戏继续if (flag == 1){return 0;}else{int media[MAX_GRID_NUM][MAX_GRID_NUM];int k, m;for (k = 0; k < MAX_GRID_NUM; k++){for (m = 0; m < MAX_GRID_NUM; m++){media[k][m] == map[k][m];}}MergeUp(media);MergeDown(media);MergeLeft(media);MergeRight(media);if (flag1){flag = false;return 0;}else{return 1;}}
}
3.3 test.cpp
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"//将枚举类型的成员装到数组里面方便后续改变棋盘颜色
GridColor arr[12] = { zero,two_1,two_2 ,two_3 ,two_4 ,two_5 ,two_6 ,two_7 ,two_8 ,two_9 ,two_10 ,two_11 };//方便对棋盘的数字进行判断,并且与GridColor arr[12]数组关联方便填色
int num[] = { 0,2,4,8,16,32,64,128,256,512,1024,2048 };//调用一个存储x,y坐标的结构体,创建一个数组
POINT position[MAX_GRID_NUM][MAX_GRID_NUM];//用来判断是否产生新的数字
bool flag1 = false;void ControlGame(int map[MAX_GRID_NUM][MAX_GRID_NUM])
{char key = _getch();switch (key){case 'W':case 'w':case 72:MoveUp(map);break;case 's':case 'S':case 80:MoveDown(map);break;case 'a':case 'A':case 75:MoveLeft(map);break;case 'd':case 'D':case 77:MoveRight(map);break;}
}void ClearArray(int map[MAX_GRID_NUM][MAX_GRID_NUM])
{int i, j;for (i = 0; i < MAX_GRID_NUM; i++){for (j = 0; j < MAX_GRID_NUM; j++){map[i][j] = 0;}}
}void Test()
{//设置随机数的种子srand((unsigned int)time(NULL));//创建窗口initgraph(MAP_WIDTH_SIZE, MAP_WIDTH_SIZE, 0);//设置背景颜色setbkcolor(RGB(187, 173, 160));//用背景色清空屏幕cleardevice();//棋盘的数组int map[MAX_GRID_NUM][MAX_GRID_NUM] = { 0 };//棋盘的初始化MapInit(map);while (1){DrawMap(map);ControlGame(map);Judge(map);}
}
int main()
{Test();return 0;
}