每天一个C语言小项目,提升你的编程能力!
这是个益智游戏,规则很简单,按上、下、左、右键,使游戏区域的所有方块向该方向滑动,每次滑动时,相碰的两个相同数字会合并。同时,空白的地方也会在随机出现一个数字方块(2 或者 4)。游戏目标,是想办法合成 2048 这个数字方块。
执行效果如下:
简单了解游戏后我们就来试试吧!(直接上源码,大家可以看注释)
代码展示:
/*
项目名称:2048
开发工具:Visual Studio 2019,EasyX 20190415(beta)
作者:洛枫
*/includeincludeincludeincludeincludeincludepragmacomment( lib,"MSIMG32.LIB")// 方块的状态enumState
{
EXIST,// 存在DESTORY// 销毁};// 二维向量,用于表示位置或者大小structVector2{floatx;floaty;
};// 符号函数intsgn(floatd){if(d <0)return-1;if(d >0)return1;return0;
}classBlock{private:
State currentState;// 当前的状态State targetState;// 移动后的状态Vector2 size;
Vector2 currentPos;// 当前位置Vector2 targetPos;// 目标位置IMAGE *img;
IMAGE *newImg;floatdeltaPos;// 每秒移动多少位置floatdeltaSize;// 每秒变大多少floatanimationSpeed;// 动画速度public:
Block(constVector2 &pos, IMAGE *img)
{
currentPos = targetPos = pos;
currentState = targetState = EXIST;
size = {50,50};this->img =this->newImg = img;
deltaPos =100;
deltaSize =40;
animationSpeed =20.0f;
}voidupdate(floatdeltaTime){// 改变方块大小(图片刚生成时的由小到大的动画)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)
{intdirectionX = sgn(targetPos.x - currentPos.x);intdirectionY = 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;
}
}voiddraw(){
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);
}// 把方块从当前位置移动到目标位置,移动后改变状态voidMoveTo(constVector2 &pos, IMAGE *newImg, State state = EXIST){
targetPos = pos;
targetState = state;this->newImg = newImg;
}StategetState(){returncurrentState;
}
};intmap[4][4];// 4 * 4 地图Block *blockMap[4][4];// 方块索引intscore;// 得分intmaxScore;// 最高得分intcurrentMaxBlock;// 当前最大方块intmaxBlock;// 历史最大方块intgameLoop;// 游戏循环floatkeyTime =0;// 按键间隔std::map<int, IMAGE> image;// 存储所有数字图像boolgameOver =false;// 游戏是否结束floatoverTime;// 游戏结束后不会立刻退出循环,而是等待 0.5s 更新动画// 判断是否有可移动的方式,有返回 1 ,没有返回 0// 检测思路:如果碰到为 0 的格子,或者两个相邻的格子数字相等,则返回 1intJudge(){// 横向检测for(inti =0; i <4; i++)
{for(intj =0; j <3; j++)
{if(map[i][j] ==0||map[i][j] ==map[i][j +1] ||map[i][j +1] ==0)return1;
}
}// 纵向检测for(inti =0; i <4; i++)
{for(intj =0; j <3; j++)
{if(map[j][i] ==0||map[j][i] ==map[j +1][i] ||map[j +1][i] ==0)return1;
}
}return0;
}// 上移voidUp(){intmoveFlag =0;// 记录是否进行过移动intmergeFlag =0;// 记录是否合并过for(inti =0; i <4; i++)
{for(intj =0; j <3; j++)
{intk, 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])
{// 可以合并intvalue =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{// 不可以合并intvalue =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 或 4if(moveFlag || mergeFlag)
{intindex;// 随机位置的索引// 直到随机到一个空位置退出循环do{
index = rand() %4;
}while(map[3][index] !=0);// 80% 生成 2 , 20% 生成 4intnum = rand() %10;if(num <8)
{map[3][index] =2;
blockMap[3][index] =newBlock({25.0f+100* index,225.0f+100*3}, &image[2]);
}else{map[3][index] =4;
blockMap[3][index] =newBlock({25.0f+100* index,225.0f+100*3}, &image[4]);
}
}
}// 下移voidDown(){intmoveFlag =0;// 记录是否进行过移动intmergeFlag =0;// 记录是否合并过for(inti =0; i <4; i++)
{for(intj =3; j >0; j--)
{intk, 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])
{// 可以合并intvalue =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{// 不可以合并intvalue =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 或 4if(moveFlag || mergeFlag)
{intindex;// 随机位置的索引// 直到随机到一个为 0 的位置退出循环do{
index = rand() %4;
}while(map[0][index] !=0);// 80% 生成 2 , 20% 生成 4intnum = rand() %10;if(num <8)
{map[0][index] =2;
blockMap[0][index] =newBlock({25.0f+100* index,225.0f+100*0}, &image[2]);
}else{map[0][index] =4;
blockMap[0][index] =newBlock({25.0f+100* index,225.0f+100*0}, &image[4]);
}
}
}// 左移voidLeft(){intmoveFlag =0;// 记录是否进行过移动intmergeFlag =0;// 记录是否合并过for(inti =0; i <4; i++)
{for(intj =0; j <3; j++)
{intk, 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])
{// 可以合并intvalue =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{// 不可以合并intvalue =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 或 4if(moveFlag || mergeFlag)
{intindex;// 随机位置的索引// 直到随机到一个为 0 的位置退出循环do{
index = rand() %4;
}while(map[index][3] !=0);// 80% 生成 2 , 20% 生成 4intnum = rand() %10;if(num <8)
{map[index][3] =2;
blockMap[index][3] =newBlock({25.0f+100*3,225.0f+100* index }, &image[2]);
}else{map[index][3] =4;
blockMap[index][3] =newBlock({25.0f+100*3,225.0f+100* index }, &image[4]);
}
}
}// 右移voidRight(){intmoveFlag =0;// 记录是否进行过移动intmergeFlag =0;// 记录是否合并过for(inti =0; i <4; i++)
{for(intj =3; j >0; j--)
{intk, 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])
{// 可以合并intvalue =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{// 不可以合并intvalue =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 或 4if(moveFlag || mergeFlag)
{intindex;// 随机位置的索引do{
index = rand() %4;
}while(map[index][0] !=0);// 80% 生成 2 , 20% 生成 4intnum = rand() %10;if(num <8)
{map[index][0] =2;
blockMap[index][0] =newBlock({25.0f+100*0,225.0f+100* index }, &image[2]);
}else{map[index][0] =4;
blockMap[index][0] =newBlock({25.0f+100*0,225.0f+100* index }, &image[4]);
}
}
}voidUpdate(floatdeltaTime){// 更新方块for(inti =0; i <4; i++)
{for(intj =0; j <4; j++)
{if(blockMap[i][j] !=NULL)
{
blockMap[i][j]->update(deltaTime);if(blockMap[i][j]->getState() == DESTORY)
{deleteblockMap[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;
}elseif((GetAsyncKeyState(VK_DOWN) &0x8000) || (GetAsyncKeyState(S) &0x8000))// 下{
Down();if(!Judge())
{
gameOver =true;
}
keyTime =0;
}elseif((GetAsyncKeyState(VK_LEFT) &0x8000) || (GetAsyncKeyState(A) &0x8000))// 左{
Left();if(!Judge())
{
gameOver =true;
}
keyTime =0;
}elseif((GetAsyncKeyState(VK_RIGHT) &0x8000) || (GetAsyncKeyState(D) &0x8000))// 右{
Right();if(!Judge())
{
gameOver =true;
}
keyTime =0;
}
}// 设置文字样式和颜色voidsettext(intheight,intweight, 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);
}// 在指定矩形区域内居中输出字符串voidprinttext(LPCTSTR s,intleft,inttop,intright,intwidth){
RECT r = { left, top, right, width };
drawtext(s, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}// 绘制界面voidDraw(){// 历史最大方块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(inti =0; i <4; i++)
{for(intj =0; j <4; j++)
{
putimage(25+100* j,225+100* i, &image[0]);
}
}for(inti =0; i <4; i++)
{for(intj =0; j <4; j++)
{if(blockMap[i][j] !=NULL)
blockMap[i][j]->draw();
}
}
}// 初始化游戏voidInit(){
srand((unsignedint)time(NULL));// 初始化随机数种子memset(map,0,4*4*sizeof(int));// 把地图初始化为 0memset(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] =newBlock({25,225}, &image[2]);
blockMap[0][1] =newBlock({125,225}, &image[2]);
setbkcolor(WHITE);
setbkmode(TRANSPARENT);
}// 游戏结束界面 返回 1 表示继续游戏 返回 0 表示结束游戏intOverInterface(){// 保存最高纪录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 Oversettext(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);// ReStartsetfillcolor(0x9dadba);
solidroundrect(120,310,319,389,10,10);
settext(36,1000, WHITE);
printtext(_T("ReStart"),120,310,319,389);// Exitsolidroundrect(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)
{intx = msg.x;inty = msg.y;if(x >=120&& x <=319&& y >=310&& y <=389)return1;if(x >=120&& x <=319&& y >=460&& y <=539)return0;
}
}
Sleep(100);
}return1;
}// 释放内存voidFreeMem(){for(inti =0; i <4; i++)for(intj =0; j <4; j++)if(blockMap[i][j] !=NULL)deleteblockMap[i][j];
}// 用于生成方块图片// img: 方块图片指针// num: 方块上的数字// imgColor: 方块颜色// fontSize: 字体大小// fontColor: 字体颜色voidCreateImage(IMAGE *img, LPCTSTR num, COLORREF imgColor,intfontSize, 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);
}// 绘制图片缓存voidLoad(){IMAGEtemp(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);
}// 主函数intmain(){floatdeltaTime =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_tstart = clock();
cleardevice();
Update(deltaTime);
Draw();
FlushBatchDraw();
Sleep(1);clock_tend = clock();
deltaTime = (end - start) /1000.0f;
}
FreeMem();if(OverInterface() ==0)break;
FlushMouseMsgBuffer();
}
closegraph();
}
大家赶紧去动手试试吧!
此外,我也给大家分享我收集的其他资源,从最零基础开始的教程到C语言C++项目案例,帮助大家在学习C语言的道路上披荆斩棘!
编程学习书籍分享:
编程学习视频分享:
整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)最重要的是你可以在群里面交流提问编程问题哦!
对于C/C++感兴趣可以关注小编在后台私信我:【编程交流】一起来学习哦!可以领取一些C/C++的项目学习视频资料哦!已经设置好了关键词自动回复,自动领取就好了!