C/C++项目实战:《2048》,900行源码带你完美实现经典数字游戏
Posted 一起学编程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C/C++项目实战:《2048》,900行源码带你完美实现经典数字游戏相关的知识,希望对你有一定的参考价值。
每天一个C语言小项目,提升你的编程能力!
这是个益智游戏,规则很简单,按上、下、左、右键,使游戏区域的所有方块向该方向滑动,每次滑动时,相碰的两个相同数字会合并。同时,空白的地方也会在随机出现一个数字方块(2 或者 4)。游戏目标,是想办法合成 2048 这个数字方块。
执行效果如下:
简单了解游戏后我们就来试试吧!(直接上源码,大家可以看注释)
代码展示:
/*
项目名称:2048
开发工具:Visual Studio 2019,EasyX 20190415(beta)
作者:洛枫
*/
#include <easyx.h>
#include <string.h>
#include <map>
#include <sstream>
#include <time.h>
#include <conio.h>
#pragma comment( lib, "MSIMG32.LIB")
// 方块的状态
enum State
EXIST, // 存在
DESTORY // 销毁
;
// 二维向量,用于表示位置或者大小
struct Vector2
float x;
float y;
;
// 符号函数
int sgn(float d)
if (d < 0) return -1;
if (d > 0) return 1;
return 0;
class Block
private:
State currentState; // 当前的状态
State targetState; // 移动后的状态
Vector2 size;
Vector2 currentPos; // 当前位置
Vector2 targetPos; // 目标位置
IMAGE *img;
IMAGE *newImg;
float deltaPos; // 每秒移动多少位置
float deltaSize; // 每秒变大多少
float animationSpeed; // 动画速度
public:
Block(const Vector2 &pos, IMAGE *img)
currentPos = targetPos = pos;
currentState = targetState = EXIST;
size = 50,50 ;
this->img = this->newImg = img;
deltaPos = 100;
deltaSize = 40;
animationSpeed = 20.0f;
void update(float deltaTime)
// 改变方块大小(图片刚生成时的由小到大的动画)
if (size.x < img->getwidth())
size.x = size.y = size.x + deltaSize * deltaTime * animationSpeed / 2;
if (size.x > img->getwidth())
size.x = size.y = (float)img->getwidth();
// 更新方块位置
if (currentPos.x != targetPos.x || currentPos.y != targetPos.y)
int directionX = sgn(targetPos.x - currentPos.x);
int directionY = sgn(targetPos.y - currentPos.y);
currentPos.x += deltaPos * directionX * deltaTime * animationSpeed;
// 相距小于 5 视为在同一位置
if (fabs(currentPos.x - targetPos.x) < 5)
currentPos.x = targetPos.x;
currentPos.y += deltaPos * directionY * deltaTime * animationSpeed;
if (fabs(currentPos.y - targetPos.y) < 5)
currentPos.y = targetPos.y;
if (currentPos.x == targetPos.x &¤tPos.y == targetPos.y)
currentState = targetState;
img = newImg;
void draw()
TransparentBlt(GetImageHDC(NULL), int(currentPos.x + (90 - size.x) / 2), int(currentPos.y + (90 - size.y) / 2),
(int)size.x, (int)size.y, GetImageHDC(img), 0, 0, img->getwidth(), img->getheight(), BLACK);
// 把方块从当前位置移动到目标位置,移动后改变状态
void MoveTo(const Vector2 &pos, IMAGE *newImg, State state = EXIST)
targetPos = pos;
targetState = state;
this->newImg = newImg;
State getState()
return currentState;
;
int map[4][4]; // 4 * 4 地图
Block *blockMap[4][4]; // 方块索引
int score; // 得分
int maxScore; // 最高得分
int currentMaxBlock; // 当前最大方块
int maxBlock; // 历史最大方块
int gameLoop; // 游戏循环
float keyTime = 0; // 按键间隔
std::map<int, IMAGE> image; // 存储所有数字图像
bool gameOver = false; // 游戏是否结束
float overTime; // 游戏结束后不会立刻退出循环,而是等待 0.5s 更新动画
// 判断是否有可移动的方式,有返回 1 ,没有返回 0
// 检测思路:如果碰到为 0 的格子,或者两个相邻的格子数字相等,则返回 1
int Judge()
// 横向检测
for (int i = 0; i < 4; i++)
for (int j = 0; j < 3; j++)
if (map[i][j] == 0 || map[i][j] == map[i][j + 1] || map[i][j + 1] == 0)
return 1;
// 纵向检测
for (int i = 0; i < 4; i++)
for (int j = 0; j < 3; j++)
if (map[j][i] == 0 || map[j][i] == map[j + 1][i] || map[j + 1][i] == 0)
return 1;
return 0;
// 上移
void Up()
int moveFlag = 0; // 记录是否进行过移动
int mergeFlag = 0; // 记录是否合并过
for (int i = 0; i < 4; i++)
for (int j = 0; j < 3; j++)
int k, z;
// 找到一个不为 0 的方块向上移动,并判断是否可以和下边的方块合并
for (k = j; k < 4; k++)
if (map[k][i] != 0)
break;
// 寻找右边不为 0 的方块
for (z = k + 1; z < 4; z++)
if (map[z][i] != 0)
break;
// 当前行有非 0 方块
if (k < 4)
if (z < 4 && map[k][i] == map[z][i])
// 可以合并
int value = map[k][i] + map[z][i];
map[k][i] = map[z][i] = 0;
map[j][i] = value;
// 开启动画
Block *temp = blockMap[k][i];
blockMap[k][i] = NULL;
blockMap[j][i] = temp;
blockMap[j][i]->MoveTo( 25.0f + 100 * i,225.0f + 100 * j , &image[map[j][i]]);
blockMap[z][i]->MoveTo( 25.0f + 100 * i,225.0f + 100 * (j + 1) , &image[map[z][i]], DESTORY);
// 更新分数
score += map[j][i];
if (score > maxScore) maxScore = score;
// 更新方块
if (value > currentMaxBlock) currentMaxBlock = value;
if (currentMaxBlock > maxBlock) maxBlock = currentMaxBlock;
mergeFlag = 1;
else
// 不可以合并
int value = map[k][i];
map[k][i] = 0;
map[j][i] = value;
// 判断是否可以移动
if (k != j)
moveFlag = 1;
// 开启动画
Block *temp = blockMap[k][i];
blockMap[k][i] = NULL;
blockMap[j][i] = temp;
blockMap[j][i]->MoveTo( 25.0f + 100 * i,225.0f + 100 * j , &image[map[j][i]]);
else // 判断下一行
break;
// 如果发生了移动或合并,随机生成一个 2 或 4
if (moveFlag || mergeFlag)
int index; // 随机位置的索引
// 直到随机到一个空位置退出循环
do
index = rand() % 4;
while (map[3][index] != 0);
// 80% 生成 2 , 20% 生成 4
int num = rand() % 10;
if (num < 8)
map[3][index] = 2;
blockMap[3][index] = new Block( 25.0f + 100 * index, 225.0f + 100 * 3 , &image[2]);
else
map[3][index] = 4;
blockMap[3][index] = new Block( 25.0f + 100 * index, 225.0f + 100 * 3 , &image[4]);
// 下移
void Down()
int moveFlag = 0; // 记录是否进行过移动
int mergeFlag = 0; // 记录是否合并过
for (int i = 0; i < 4; i++)
for (int j = 3; j > 0; j--)
int k, z;
// 找到一个不为 0 的方块向下移动,并判断是否可以和上边的方块合并
for (k = j; k >= 0; k--)
if (map[k][i] != 0)
break;
// 寻找右边不为 0 的方块
for (z = k - 1; z >= 0; z--)
if (map[z][i] != 0)
break;
// 当前行有非 0 方块
if (k >= 0)
if (z >= 0 && map[k][i] == map[z][i])
// 可以合并
int value = map[k][i] + map[z][i];
map[k][i] = map[z][i] = 0;
map[j][i] = value;
// 开启动画
Block *temp = blockMap[k][i];
blockMap[k][i] = NULL;
blockMap[j][i] = temp;
blockMap[j][i]->MoveTo( 25.0f + 100 * i,225.0f + 100 * j , &image[map[j][i]]);
blockMap[z][i]->MoveTo( 25.0f + 100 * i,225.0f + 100 * (j - 1) , &image[map[z][i]], DESTORY);
// 更新分数
score += map[j][i];
if (score > maxScore) maxScore = score;
// 更新方块
if (value > currentMaxBlock) currentMaxBlock = value;
if (currentMaxBlock > maxBlock) maxBlock = currentMaxBlock;
mergeFlag = 1;
else
// 不可以合并
int value = map[k][i];
map[k][i] = 0;
map[j][i] = value;
// 判断是否可以移动
if (k != j)
moveFlag = 1;
// 开启动画
Block *temp = blockMap[k][i];
blockMap[k][i] = NULL;
blockMap[j][i] = temp;
blockMap[j][i]->MoveTo( 25.0f + 100 * i,225.0f + 100 * j , &image[map[j][i]]);
else // 判断下一行
break;
// 如果发生了移动或合并,随机生成一个 2 或 4
if (moveFlag || mergeFlag)
int index; // 随机位置的索引
// 直到随机到一个为 0 的位置退出循环
do
index = rand() % 4;
while (map[0][index] != 0);
// 80% 生成 2 , 20% 生成 4
int num = rand() % 10;
if (num < 8)
map[0][index] = 2;
blockMap[0][index] = new Block( 25.0f + 100 * index,225.0f + 100 * 0 , &image[2]);
else
map[0][index] = 4;
blockMap[0][index] = new Block( 25.0f + 100 * index,225.0f + 100 * 0 , &image[4]);
// 左移
void Left()
int moveFlag = 0; // 记录是否进行过移动
int mergeFlag = 0; // 记录是否合并过
for (int i = 0; i < 4; i++)
for (int j = 0; j < 3; j++)
int k, z;
// 找到一个不为 0 的方块向左移动,并判断是否可以和右边的方块合并
for (k = j; k < 4; k++)
if (map[i][k] != 0)
break;
// 寻找右边不为 0 的方块
for (z = k + 1; z < 4; z++)
if (map[i][z] != 0)
break;
// 当前行有非 0 方块
if (k < 4)
if (z < 4 && map[i][k] == map[i][z])
// 可以合并
int value = map[i][k] + map[i][z];
map[i][k] = map[i][z] = 0;
map[i][j] = value;
// 开启动画
Block *temp = blockMap[i][k];
blockMap[i][k] = NULL;
blockMap[i][j] = temp;
blockMap[i][j]->MoveTo( 25.0f + 100 * j,225.0f + 100 * i , &image[value]);
blockMap[i][z]->MoveTo( 25.0f + 100 * (j + 1),225.0f + 100 * i , &image[map[z][i]], DESTORY);
// 更新分数
score += map[i][j];
if (score > maxScore) maxScore = score;
// 更新方块
if (value > currentMaxBlock) currentMaxBlock = value;
if (currentMaxBlock > maxBlock) maxBlock = currentMaxBlock;
mergeFlag = 1;
else
// 不可以合并
int value = map[i][k];
map[i][k] = 0;
map[i][j] = value;
// 判断是否可以移动
if (k != j)
moveFlag = 1;
// 开启动画
Block *temp = blockMap[i][k];
blockMap[i][k] = NULL;
blockMap[i][j] = temp;
blockMap[i][j]->MoveTo( 25.0f + 100 * j,225.0f + 100 * i , &image[value]);
else // 判断下一行
break;
// 如果发生了移动或合并,随机生成一个 2 或 4
if (moveFlag || mergeFlag)
int index; // 随机位置的索引
// 直到随机到一个为 0 的位置退出循环
do
index = rand() % 4;
while (map[index][3] != 0);
// 80% 生成 2 , 20% 生成 4
int num = rand() % 10;
if (num < 8)
map[index][3] = 2;
blockMap[index][3] = new Block( 25.0f + 100 * 3,225.0f + 100 * index , &image[2]);
else
map[index][3] = 4;
blockMap[index][3] = new Block( 25.0f + 100 * 3,225.0f + 100 * index , &image[4]);
// 右移
void Right()
int moveFlag = 0; // 记录是否进行过移动
int mergeFlag = 0; // 记录是否合并过
for (int i = 0; i < 4; i++)
for (int j = 3; j > 0; j--)
int k, z;
// 找到一个不为 0 的方块向右移动,并判断是否可以和左边的方块合并
for (k = j; k >= 0; k--)
if (map[i][k] != 0)
break;
// 寻找右边不为 0 的方块
for (z = k - 1; z >= 0; z--)
if (map[i][z] != 0)
break;
// 当前行有非 0 方块
if (k >= 0)
if (z >= 0 && map[i][k] == map[i][z])
// 可以合并
int value = map[i][k] + map[i][z];
map[i][k] = map[i][z] = 0;
map[i][j] = value;
// 开启动画
Block *temp = blockMap[i][k];
blockMap[i][k] = NULL;
blockMap[i][j] = temp;
blockMap[i][j]->MoveTo( 25.0f + 100 * j,225.0f + 100 * i , &image[value]);
blockMap[i][z]->MoveTo( 25.0f + 100 * (j - 1),225.0f + 100 * i , &image[map[z][i]], DESTORY);
// 更新分数
score += map[i][j];
if (score > maxScore) maxScore = score;
// 更新方块
if (value > currentMaxBlock) currentMaxBlock = value;
if (currentMaxBlock > maxBlock) maxBlock = currentMaxBlock;
mergeFlag = 1;
else
// 不可以合并
int value = map[i][k];
map[i][k] = 0;
map[i][j] = value;
// 判断是否可以移动
if (k != j)
moveFlag = 1;
// 开启动画
Block *temp = blockMap[i][k];
blockMap[i][k] = NULL;
blockMap[i][j] = temp;
blockMap[i][j]->MoveTo( 25.0f + 100 * j,225.0f + 100 * i , &image[value]);
else // 判断下一行
break;
// 如果发生了移动或合并,随机生成一个 2 或 4
if (moveFlag || mergeFlag)
int index; // 随机位置的索引
do
index = rand() % 4;
while (map[index][0] != 0);
// 80% 生成 2 , 20% 生成 4
int num = rand() % 10;
if (num < 8)
map[index][0] = 2;
blockMap[index][0] = new Block( 25.0f + 100 * 0,225.0f + 100 * index , &image[2]);
else
map[index][0] = 4;
blockMap[index][0] = new Block( 25.0f + 100 * 0,225.0f + 100 * index , &image[4]);
void Update(float deltaTime)
// 更新方块
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
if (blockMap[i][j] != NULL)
blockMap[i][j]->update(deltaTime);
if (blockMap[i][j]->getState() == DESTORY)
delete blockMap[i][j];
blockMap[i][j] = NULL;
if (gameOver)
overTime -= deltaTime;
if (overTime <= 0)
gameLoop = 0;
keyTime += deltaTime;
// 0.2s 可以按键一次
if (keyTime < 0.2f || gameOver)
return;
if ((GetAsyncKeyState(VK_UP) & 0x8000) || (GetAsyncKeyState('W') & 0x8000)) // 上
Up();
if (!Judge())
gameOver = true;
keyTime = 0;
else if ((GetAsyncKeyState(VK_DOWN) & 0x8000) || (GetAsyncKeyState('S') & 0x8000)) // 下
Down();
if (!Judge())
gameOver = true;
keyTime = 0;
else if ((GetAsyncKeyState(VK_LEFT) & 0x8000) || (GetAsyncKeyState('A') & 0x8000)) // 左
Left();
if (!Judge())
gameOver = true;
keyTime = 0;
else if ((GetAsyncKeyState(VK_RIGHT) & 0x8000) || (GetAsyncKeyState('D') & 0x8000)) // 右
Right();
if (!Judge())
gameOver = true;
keyTime = 0;
// 设置文字样式和颜色
void settext(int height, int weight, UINT color)
settextstyle(height, 0, _T("Arial"), 0, 0, weight, false, false, false, ANSI_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH);
settextcolor(color);
// 在指定矩形区域内居中输出字符串
void printtext(LPCTSTR s, int left, int top, int right, int width)
RECT r = left, top, right, width ;
drawtext(s, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
// 绘制界面
void Draw()
// 历史最大方块
TransparentBlt(GetImageHDC(NULL), 12, 30, 90, 90, GetImageHDC(&image[maxBlock]), 0, 0, 90, 90, 0x9eaebb);
setfillcolor(0x9eaebb);
// 绘制当前分数
solidroundrect(112, 30, 264, 119, 10, 10);
settext(28, 800, 0xdbe6ee);
printtext(_T("SCORE"), 112, 40, 264, 69);
std::wstringstream ss;
ss << score;
settext(44, 800, WHITE);
printtext(ss.str().c_str(), 112, 70, 264, 114);
ss.str(_T(""));
// 绘制最高分数
solidroundrect(275, 30, 427, 119, 10, 10);
settext(28, 800, 0xdbe6ee);
printtext(_T("BEST"), 275, 40, 427, 69);
ss << maxScore;
settext(44, 800, WHITE);
printtext(ss.str().c_str(), 275, 70, 427, 114);
ss.str(_T(""));
// 绘制提示信息
settextcolor(BLACK);
ss << "Join the numbers and get to the " << currentMaxBlock * 2 << " tile!";
settext(24, 800, 0x707b83);
printtext(ss.str().c_str(), 0, 120, 439, 211);
// 绘制方块底板
solidroundrect(12, 212, 427, 627, 10, 10);
// 绘制方块
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
putimage(25 + 100 * j, 225 + 100 * i, &image[0]);
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
if (blockMap[i][j] != NULL)
blockMap[i][j]->draw();
// 初始化游戏
void Init()
srand((unsigned int)time(NULL)); // 初始化随机数种子
memset(map, 0, 4 * 4 * sizeof(int)); // 把地图初始化为 0
memset(blockMap, 0, 4 * 4 * sizeof(Block*));
score = 0;
gameLoop = 1;
gameOver = false;
overTime = 0.5f;
currentMaxBlock = 2;
map[0][0] = 2;
map[0][1] = 2;
blockMap[0][0] = new Block( 25,225 , &image[2]);
blockMap[0][1] = new Block( 125,225 , &image[2]);
setbkcolor(WHITE);
setbkmode(TRANSPARENT);
// 游戏结束界面 返回 1 表示继续游戏 返回 0 表示结束游戏
int OverInterface()
// 保存最高纪录
std::wstringstream ss;
ss << maxScore;
WritePrivateProfileString(_T("2048"), _T("MaxScore"), ss.str().c_str(), _T(".\\\\data.ini"));
ss.str(_T(""));
ss << maxBlock;
WritePrivateProfileString(_T("2048"), _T("MaxBlock"), ss.str().c_str(), _T(".\\\\data.ini"));
setbkmode(TRANSPARENT);
setbkcolor(0x8eecff);
cleardevice();
// Game Over
settext(60, 1000, 0x696f78);
printtext(_T("Game Over!"), 0, 0, 439, 199);
// 绘制最大方块
TransparentBlt(GetImageHDC(NULL), 175, 150, 90, 90, GetImageHDC(&image[currentMaxBlock]), 0, 0, 90, 90, 0x9eaebb);
// ReStart
setfillcolor(0x9dadba);
solidroundrect(120, 310, 319, 389, 10, 10);
settext(36, 1000, WHITE);
printtext(_T("ReStart"), 120, 310, 319, 389);
// Exit
solidroundrect(120, 460, 319, 539, 10, 10);
printtext(_T("Exit"), 120, 460, 319, 539);
FlushBatchDraw();
FlushMouseMsgBuffer();
while (1)
while (MouseHit())
MOUSEMSG msg = GetMouseMsg();
if (msg.mkLButton)
int x = msg.x;
int y = msg.y;
if (x >= 120 && x <= 319 && y >= 310 && y <= 389)
return 1;
if (x >= 120 && x <= 319 && y >= 460 && y <= 539)
return 0;
Sleep(100);
return 1;
// 释放内存
void FreeMem()
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
if (blockMap[i][j] != NULL)
delete blockMap[i][j];
// 用于生成方块图片
// img: 方块图片指针
// num: 方块上的数字
// imgColor: 方块颜色
// fontSize: 字体大小
// fontColor: 字体颜色
void CreateImage(IMAGE *img, LPCTSTR num, COLORREF imgColor, int fontSize, COLORREF fontColor)
SetWorkingImage(img);
setbkmode(TRANSPARENT);
setbkcolor(0x9eaebb);
settext(fontSize, 1000, fontColor);
setfillcolor(imgColor);
settextcolor(fontColor);
cleardevice();
solidroundrect(0, 0, img->getwidth() - 1, img->getheight() - 1, 10, 10);
RECT r = 0,0,img->getwidth() - 1,img->getheight() - 1 ;
drawtext(num, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
// 绘制图片缓存
void Load()
IMAGE temp(90, 90);
CreateImage(&temp, _T(""), 0xb5becc, 72, WHITE); image[0] = temp;
CreateImage(&temp, _T("2"), 0xdbe6ee, 72, 0x707b83); image[2] = temp;
CreateImage(&temp, _T("4"), 0xc7e1ed, 72, 0x707b83); image[4] = temp;
CreateImage(&temp, _T("8"), 0x78b2f4, 72, WHITE); image[8] = temp;
CreateImage(&temp, _T("16"), 0x538ded, 72, WHITE); image[16] = temp;
CreateImage(&temp, _T("32"), 0x607df6, 72, WHITE); image[32] = temp;
CreateImage(&temp, _T("64"), 0x3958e9, 72, WHITE); image[64] = temp;
CreateImage(&temp, _T("128"), 0x6bd9f5, 56, WHITE); image[128] = temp;
CreateImage(&temp, _T("256"), 0x4bd0f2, 56, WHITE); image[256] = temp;
CreateImage(&temp, _T("512"), 0x2ac0e4, 56, WHITE); image[512] = temp;
CreateImage(&temp, _T("1024"), 0x13b8e3, 40, WHITE); image[1024] = temp;
CreateImage(&temp, _T("2048"), 0x00c5eb, 40, WHITE); image[2048] = temp;
CreateImage(&temp, _T("4096"), 0x3958e9, 40, WHITE); image[4096] = temp;
CreateImage(&temp, _T("8192"), 0x3958e9, 40, WHITE); image[8192] = temp;
SetWorkingImage(NULL);
// 主函数
int main()
float deltaTime = 0; // 每帧耗时
initgraph(440, 650);
Load();
BeginBatchDraw();
maxScore = 0;
// 读取最高分
maxScore = GetPrivateProfileInt(_T("2048"), _T("MaxScore"), 0, _T(".\\\\data.ini"));
// 读取最大方块
maxBlock = GetPrivateProfileInt(_T("2048"), _T("MaxBlock"), 2, _T(".\\\\data.ini"));
while (1)
Init();
while (gameLoop)
clock_t start = clock();
cleardevice();
Update(deltaTime);
Draw();
FlushBatchDraw();
Sleep(1);
clock_t end = clock();
deltaTime = (end - start) / 1000.0f;
FreeMem();
if (OverInterface() == 0)
break;
FlushMouseMsgBuffer();
closegraph();
大家赶紧去动手试试吧!
此外,我也给大家分享我收集的其他资源,从最零基础开始的教程到C语言C++项目案例,帮助大家在学习C语言的道路上披荆斩棘!
整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)最重要的是你可以在群里面交流提问编程问题哦!
欢迎转行和学习编程的伙伴,利用更多的资料学习成长比自己琢磨更快哦!
(领取方式↓↓↓↓↓↓)
以上是关于C/C++项目实战:《2048》,900行源码带你完美实现经典数字游戏的主要内容,如果未能解决你的问题,请参考以下文章
项目实战C/C++语言带你实现:围棋游戏丨详细逻辑+核心源码
C/C++项目实战:《是男人就下一百层》,530 行源码分享
C/C++项目实战:《是男人就下一百层》,530 行源码分享