实况图
开发工具
我使用的是Visual Studio 2022,其他版本也是可以的。由于技术问题,我用的是C语言(不是C++),能学会数组,函数,数据结构(连指针都不用)及easyx基本应用即可看懂。使用前要安装easyx库(直接网页上搜索easyx安装即可。如图:
作为本人的第一篇博客,我会尽量讲清楚2048怎么做出来的。如果有哪一点讲的不够好可以给我提出建议哈(提前叠甲)。那么现在我们开始吧:
程序设计思路
每次有数字变化(注意是数字变化不是按键)就在随机空白位上产生2或4。直到合成出2048或没法再变化数字则游戏结束。在结尾我会把文中所用图片发出来,有需要自取哈。
正式代码
头文件
//2048小游戏
#include <stdio.h>
#include <graphics.h>
#include <conio.h>
#include <time.h>
#include <mmsyscom.h>
#pragma comment(lib,"winmm.lib")
<stdio.h>用来使用C语言
<graphics.h>调用图形库
<conio.h>用来获取键盘信息
<time.h>通过时间播散随机数
#include <mmsyscom.h>
#pragma comment(lib,"winmm.lib") 播放音乐
如果不了解不要急,下面我会在各板块解释。
函数间的中转站“数据结构”
struct fff {
int a[4][4] = { //数据分析
{0,0,0,0},
{0,0,0,0},
{0,0,0,0},
{0,0,0,0},
};
POINT shu[4][4]; //每一方格坐标设定
bool ch;
int max=2; //设计得分:即数字最大值
}win;
数据结构包含2048数据,画图时每一方格左上角坐标,随机数节点,分数。
准备各项数据
void prepare() {
initgraph(900, 480,SHOWCONSOLE);
for (int i = 0; i < 4; i++) { //设定方格左上角坐标
for (int g = 0; g < 4; g++) {
if (i==0&&g==0){
win.shu[0][0].x = 200;
win.shu[0][0].y = 100;}
else {
win.shu[i][g].x = win.shu[0][0].x + 70 * g;
win.shu[i][g].y = win.shu[0][0].y + 70 * i;
}
}
}
win.ch = TRUE;
srand(time(0)); //播散随机数种子
int i = rand() % 4;
int t = rand() % 4;
win.a[i][t] = 2; //定义开始数为2
IMAGE msg; //加点背景
loadimage(&msg, "./背景.jpg",640,480);
putimage(0, 0, &msg);
mciSendString("open ./太空漫步.mp3 alias BGM", 0, 0, 0); //加点音乐
mciSendString("play BGM repeat", 0, 0, 0); //注意要加头文件
//#include <mmsyscom.h>
//#pragma comment(lib,"winmm.lib")
}
放音乐时要使音乐和程序在同一文件夹中,注意音乐为mp3模式。
头文件
#include <mmsyscom.h>
#pragma comment(lib,"winmm.lib")记得加上。
画图(重点)
void gamedraw() {
BeginBatchDraw();
setfillcolor(BLACK);
fillrectangle(640, 0, 900, 250);
setfillcolor(RGB(252, 157, 154));
fillrectangle(640, 250, 900, 480);
settextcolor(WHITE);
settextstyle(50, 0, "黑体");
setbkmode(TRANSPARENT);
outtextxy(700, 50, "得分:");
settextcolor(RGB(251, 178, 23));
settextstyle(70, 0, "黑体");
char s[8]; //打出分数
sprintf_s(s, "%d", win.max); //因为outtextxy只能打出字符,故用sprintf进行转换
outtextxy(700,150, s);
setlinecolor(RED);
setlinestyle(BS_SOLID,3);
rectangle(195,95,195+285,95+285);
settextcolor(BLACK);
setbkmode(TRANSPARENT);
for (int i = 0; i < 4; i++) {
for (int g = 0; g < 4; g++) {
setfillcolor(RGB(200, 200, 169)); //画出数字背景方格
solidroundrect(win.shu[i][g].x, win.shu[i][g].y, win.shu[i][g].x + 66, win.shu[i][g].y + 66, 25, 25);
if (win.a[i][g] != 0) { //设置每个数字方格颜色都不相同
switch (win.a[i][g]) {
case 2:
setfillcolor(RGB(249, 205, 173));
break;
case 4:
setfillcolor(RGB(252, 157, 154));
break;
case 8:
setfillcolor(RGB(244, 208, 0));
break;
case 16:
setfillcolor(RGB(69, 137, 148));
break;
case 32:
setfillcolor(RGB(255, 245, 247));
break;
case 64:
setfillcolor(RGB(172, 81, 24));
break;
case 128:
setfillcolor(RGB(64, 116, 52));
break;
case 256:
setfillcolor(RGB(222, 125, 44));
break;
case 512:
setfillcolor(RGB(225, 238, 210));
break;
case 1024:
setfillcolor(RGB(224, 160, 158));
break;
}
solidroundrect(win.shu[i][g].x, win.shu[i][g].y, win.shu[i][g].x + 66, win.shu[i][g].y + 66, 25, 25); //画出数字特有方格
if (win.a[i][g] < 10) { //打印数字
settextstyle(50, 0, "黑体");
char s[8];
sprintf_s(s, "%d", win.a[i][g]);
outtextxy(win.shu[i][g].x + 20, win.shu[i][g].y + 10, s);}
else if (win.a[i][g] < 100) {
settextstyle(50, 0, "黑体");
char s[8];
sprintf_s(s, "%d", win.a[i][g]);
outtextxy(win.shu[i][g].x + 13, win.shu[i][g].y + 10, s);}
else if (win.a[i][g] < 1000) {
settextstyle(30, 0, "黑体");
char s[8];
sprintf_s(s, "%d", win.a[i][g]);
outtextxy(win.shu[i][g].x + 8, win.shu[i][g].y + 8, s); }
else if (win.a[i][g] < 10000) {
settextstyle(30, 0, "黑体");
char s[8];
sprintf_s(s, "%d", win.a[i][g]);
outtextxy(win.shu[i][g].x + 4, win.shu[i][g].y + 8, s);
}
}
}
}
EndBatchDraw();
}
可能问题:
1.字符串输出报错
点击屏幕上方调试--调试属性--高级--字符集->改为高字节字符集。
2.颜色不喜欢
可以去网上搜喜欢的颜色的配成
按键控制
//小键盘上,下,左,右分别为72,80,75,77
void control() {
if (_kbhit) { //判断是否有按下按键,记得加头文件#include <conio.h>
char a = _getch();
switch (a) {
case 'w':
case 'W':
case 72:
for (int t = 0; t < 3; t++) { //重复三次,确保数字全部移动
for (int i = 1; i < 4; i++) {
for (int g = 0; g < 4; g++) {
if (win.a[i][g] != 0) {
if (i > 0 && win.a[i - 1][g] == 0) { //数字移动
win.a[i - 1][g] = win.a[i][g]; win.a[i][g] = 0;
win.ch = FALSE; //若有数字变化就变化节点
}
if (i >0 && win.a[i - 1][g] == win.a[i][g]) { //数字加成
win.a[i - 1][g] = 2 * win.a[i][g]; win.a[i][g] = 0;
win.ch = FALSE;
if (win.a[i - 1][g] > win.max) { win.max = win.a[i - 1][g]; }
}
}
}
}
}
break;
case 's':
case 'S':
case 80:
for (int t = 0; t < 3; t++) { //重复三次,确保数字全部移动
for (int i = 3; i >=0; i--) {
for (int g = 0; g < 4; g++) {
if (win.a[i][g] != 0) {
if (i < 3 && win.a[i + 1][g] == 0) {
win.a[i + 1][g] = win.a[i][g]; win.a[i][g] = 0;
win.ch = FALSE;
}
if (i <3 && win.a[i + 1][g] == win.a[i][g]) {
win.a[i + 1][g] = 2 * win.a[i][g]; win.a[i][g] = 0;
win.ch = FALSE;
if (win.a[i + 1][g] > win.max) { win.max = win.a[i + 1][g]; }
}
}
}
}
}
break;
case 'a':
case 'A':
case 75:
for (int t = 0; t < 3; t++) { //重复三次,确保数字全部移动
for (int i = 0; i < 4; i++) {
for (int g = 0; g<4; g++) {
if (win.a[i][g] != 0) {
if (g > 0 && win.a[i][g - 1] == 0) {
win.a[i][g - 1] = win.a[i][g]; win.a[i][g] = 0;
win.ch = FALSE;
}
if (g > 0 && win.a[i][g - 1] == win.a[i][g]) {
win.a[i][g - 1] = 2 * win.a[i][g]; win.a[i][g] = 0;
win.ch = FALSE;
if (win.a[i ][g-1] > win.max) { win.max = win.a[i][g-1]; }
}
}
}
}
}
break;
case 'd':
case 'D':
case 77:
for (int t = 0; t < 3; t++) { //重复三次,确保数字全部移动
for (int i = 0; i < 4; i++) {
for (int g = 3; g >=0; g--) {
if (win.a[i][g] != 0) {
if (g < 3 && win.a[i][g + 1] == 0) {
win.a[i][g + 1] = win.a[i][g]; win.a[i][g] = 0;
win.ch = FALSE;
}
if (g<3 && win.a[i][g+1] == win.a[i][g]) {
win.a[i][g+1] = 2 * win.a[i][g]; win.a[i][g] = 0;
win.ch = FALSE;
if (win.a[i][g+1] > win.max) { win.max = win.a[i][g+1]; }
}
}
}
}
}
break;
}
}
if (win.ch == FALSE) { //如果节点有变化
int i = rand() % 4; //设置一个随机格子
int t = rand() % 4;
while (win.a[i][t]!=0){ //注意要在空白格子出现随机数
i = rand() % 4;
t = rand() % 4;
}
int shu = rand() % 4;
if (shu == 0) { //随机出4的概率为1/4
shu = 4;}
else {
shu = 2;}
win.a[i][t] = shu;
win.ch = TRUE; //将节点改回去
}
}
注意
1.判断条件时不能连等
2.放入随机数之前要判断该位是否有数字
游戏结束
void gameover() {
for (int i = 0; i < 4; i++) {
for (int g = 0; g < 4; g++) {
if (win.a[i][g] == 2048) { //当合成2048时游戏胜利
settextcolor(WHITE);
settextstyle(70, 0, "黑体");
outtextxy(200, 150, "You win.");
getchar(); //卡住页面,让玩家得意一会
closegraph(); //关闭界面
}
}
}
int pan=1; //设置节点(这里我不想用bool判定,临时设置一个节点)
for (int i = 0; i < 4; i++) {
if (pan == 0) { break; } //节省时间,不重复判定
for (int g = 0; g < 4; g++) {
if (win.a[i][g] == 0) { //只要还有空格不结束
pan = 0;
break;
}
if (win.a[i][g] == win.a[i + 1][g]||win.a[i][g]==win.a[i][g+1]) { //只要数字能加和不结束
pan = 0;
break;
}
}
}
if (pan == 1) { //如果节点不变游戏结束
settextcolor(WHITE);
settextstyle(70, 0, "黑体");
outtextxy(200, 150, "GAMEOVER");
getchar(); //卡住页面
closegraph();
}
}
函数组装
//2048小游戏
#include <stdio.h>
#include <graphics.h>
#include <conio.h>
#include <time.h>
#include <mmsyscom.h>
#pragma comment(lib,"winmm.lib")
//用于函数间的中转站“数据结构”
struct fff {
int a[4][4] = { //数据分析
{0,0,0,0},
{0,0,0,0},
{0,0,0,0},
{0,0,0,0},
};
POINT shu[4][4]; //每一方格坐标设定
bool ch;
int max=2; //设计得分:即数字最大值
}win;
//事前准备
void prepare() {
initgraph(900, 480,SHOWCONSOLE);
for (int i = 0; i < 4; i++) { //设定方格左上角坐标
for (int g = 0; g < 4; g++) {
if (i==0&&g==0){
win.shu[0][0].x = 200;
win.shu[0][0].y = 100;}
else {
win.shu[i][g].x = win.shu[0][0].x + 70 * g;
win.shu[i][g].y = win.shu[0][0].y + 70 * i;
}
}
}
win.ch = TRUE;
srand(time(0)); //播散随机数种子
int i = rand() % 4;
int t = rand() % 4;
win.a[i][t] = 2; //定义开始数为2
IMAGE msg; //加点背景
loadimage(&msg, "./背景.jpg",640,480);
putimage(0, 0, &msg);
mciSendString("open ./太空漫步.mp3 alias BGM", 0, 0, 0); //加点音乐
mciSendString("play BGM repeat", 0, 0, 0); //注意要加头文件
//#include <mmsyscom.h>
//#pragma comment(lib,"winmm.lib")
}
//画图吧
void gamedraw() {
BeginBatchDraw();
setfillcolor(BLACK);
fillrectangle(640, 0, 900, 250);
setfillcolor(RGB(252, 157, 154));
fillrectangle(640, 250, 900, 480);
settextcolor(WHITE);
settextstyle(50, 0, "黑体");
setbkmode(TRANSPARENT);
outtextxy(700, 50, "得分:");
settextcolor(RGB(251, 178, 23));
settextstyle(70, 0, "黑体");
char s[8]; //打出分数
sprintf_s(s, "%d", win.max); //因为outtextxy只能打出字符,故用sprintf进行转换
outtextxy(700,150, s);
setlinecolor(RED);
setlinestyle(BS_SOLID,3);
rectangle(195,95,195+285,95+285);
settextcolor(BLACK);
setbkmode(TRANSPARENT);
for (int i = 0; i < 4; i++) {
for (int g = 0; g < 4; g++) {
setfillcolor(RGB(200, 200, 169)); //画出数字背景方格
solidroundrect(win.shu[i][g].x, win.shu[i][g].y, win.shu[i][g].x + 66, win.shu[i][g].y + 66, 25, 25);
if (win.a[i][g] != 0) { //设置每个数字方格颜色都不相同
switch (win.a[i][g]) {
case 2:
setfillcolor(RGB(249, 205, 173));
break;
case 4:
setfillcolor(RGB(252, 157, 154));
break;
case 8:
setfillcolor(RGB(244, 208, 0));
break;
case 16:
setfillcolor(RGB(69, 137, 148));
break;
case 32:
setfillcolor(RGB(255, 245, 247));
break;
case 64:
setfillcolor(RGB(172, 81, 24));
break;
case 128:
setfillcolor(RGB(64, 116, 52));
break;
case 256:
setfillcolor(RGB(222, 125, 44));
break;
case 512:
setfillcolor(RGB(225, 238, 210));
break;
case 1024:
setfillcolor(RGB(224, 160, 158));
break;
}
solidroundrect(win.shu[i][g].x, win.shu[i][g].y, win.shu[i][g].x + 66, win.shu[i][g].y + 66, 25, 25); //画出数字特有方格
if (win.a[i][g] < 10) { //打印数字
settextstyle(50, 0, "黑体");
char s[8];
sprintf_s(s, "%d", win.a[i][g]);
outtextxy(win.shu[i][g].x + 20, win.shu[i][g].y + 10, s);}
else if (win.a[i][g] < 100) {
settextstyle(50, 0, "黑体");
char s[8];
sprintf_s(s, "%d", win.a[i][g]);
outtextxy(win.shu[i][g].x + 13, win.shu[i][g].y + 10, s);}
else if (win.a[i][g] < 1000) {
settextstyle(30, 0, "黑体");
char s[8];
sprintf_s(s, "%d", win.a[i][g]);
outtextxy(win.shu[i][g].x + 8, win.shu[i][g].y + 8, s); }
else if (win.a[i][g] < 10000) {
settextstyle(30, 0, "黑体");
char s[8];
sprintf_s(s, "%d", win.a[i][g]);
outtextxy(win.shu[i][g].x + 4, win.shu[i][g].y + 8, s);
}
}
}
}
EndBatchDraw();
}
//按键控制
//小键盘上,下,左,右分别为72,80,75,77
void control() {
if (_kbhit) { //判断是否有按下按键,记得加头文件#include <conio.h>
char a = _getch();
switch (a) {
case 'w':
case 'W':
case 72:
for (int t = 0; t < 3; t++) { //重复三次,确保数字全部移动
for (int i = 1; i < 4; i++) {
for (int g = 0; g < 4; g++) {
if (win.a[i][g] != 0) {
if (i > 0 && win.a[i - 1][g] == 0) { //数字移动
win.a[i - 1][g] = win.a[i][g]; win.a[i][g] = 0;
win.ch = FALSE; //若有数字变化就变化节点
}
if (i >0 && win.a[i - 1][g] == win.a[i][g]) { //数字加成
win.a[i - 1][g] = 2 * win.a[i][g]; win.a[i][g] = 0;
win.ch = FALSE;
if (win.a[i - 1][g] > win.max) { win.max = win.a[i - 1][g]; }
}
}
}
}
}
break;
case 's':
case 'S':
case 80:
for (int t = 0; t < 3; t++) { //重复三次,确保数字全部移动
for (int i = 3; i >=0; i--) {
for (int g = 0; g < 4; g++) {
if (win.a[i][g] != 0) {
if (i < 3 && win.a[i + 1][g] == 0) {
win.a[i + 1][g] = win.a[i][g]; win.a[i][g] = 0;
win.ch = FALSE;
}
if (i <3 && win.a[i + 1][g] == win.a[i][g]) {
win.a[i + 1][g] = 2 * win.a[i][g]; win.a[i][g] = 0;
win.ch = FALSE;
if (win.a[i + 1][g] > win.max) { win.max = win.a[i + 1][g]; }
}
}
}
}
}
break;
case 'a':
case 'A':
case 75:
for (int t = 0; t < 3; t++) { //重复三次,确保数字全部移动
for (int i = 0; i < 4; i++) {
for (int g = 0; g<4; g++) {
if (win.a[i][g] != 0) {
if (g > 0 && win.a[i][g - 1] == 0) {
win.a[i][g - 1] = win.a[i][g]; win.a[i][g] = 0;
win.ch = FALSE;
}
if (g > 0 && win.a[i][g - 1] == win.a[i][g]) {
win.a[i][g - 1] = 2 * win.a[i][g]; win.a[i][g] = 0;
win.ch = FALSE;
if (win.a[i ][g-1] > win.max) { win.max = win.a[i][g-1]; }
}
}
}
}
}
break;
case 'd':
case 'D':
case 77:
for (int t = 0; t < 3; t++) { //重复三次,确保数字全部移动
for (int i = 0; i < 4; i++) {
for (int g = 3; g >=0; g--) {
if (win.a[i][g] != 0) {
if (g < 3 && win.a[i][g + 1] == 0) {
win.a[i][g + 1] = win.a[i][g]; win.a[i][g] = 0;
win.ch = FALSE;
}
if (g<3 && win.a[i][g+1] == win.a[i][g]) {
win.a[i][g+1] = 2 * win.a[i][g]; win.a[i][g] = 0;
win.ch = FALSE;
if (win.a[i][g+1] > win.max) { win.max = win.a[i][g+1]; }
}
}
}
}
}
break;
}
}
if (win.ch == FALSE) { //如果节点有变化
int i = rand() % 4; //设置一个随机格子
int t = rand() % 4;
while (win.a[i][t]!=0){ //注意要在空白格子出现随机数
i = rand() % 4;
t = rand() % 4;
}
int shu = rand() % 4;
if (shu == 0) { //随机出4的概率为1/4
shu = 4;}
else {
shu = 2;}
win.a[i][t] = shu;
win.ch = TRUE; //将节点改回去
}
}
//游戏结束条件
void gameover() {
for (int i = 0; i < 4; i++) {
for (int g = 0; g < 4; g++) {
if (win.a[i][g] == 2048) { //当合成2048时游戏胜利
settextcolor(WHITE);
settextstyle(70, 0, "黑体");
outtextxy(200, 150, "You win.");
getchar(); //卡住页面,让玩家得意一会
closegraph(); //关闭界面
}
}
}
int pan=1; //设置节点(这里我不想用bool判定,临时设置一个节点)
for (int i = 0; i < 4; i++) {
if (pan == 0) { break; } //节省时间,不重复判定
for (int g = 0; g < 4; g++) {
if (win.a[i][g] == 0) { //只要还有空格不结束
pan = 0;
break;
}
if (win.a[i][g] == win.a[i + 1][g]||win.a[i][g]==win.a[i][g+1]) { //只要数字能加和不结束
pan = 0;
break;
}
}
}
if (pan == 1) { //如果节点不变游戏结束
settextcolor(WHITE);
settextstyle(70, 0, "黑体");
outtextxy(200, 150, "GAMEOVER");
getchar(); //卡住页面
closegraph();
}
}
//函数组装
int main() {
prepare();
while (1) {
gamedraw();
control();
gameover();
}
return 0;
}
使用函数可以使主函数部分极为简介,同时分区块解决问题也可以使你的思路更为清晰,但这样当你顺着主函数读代码时会比较麻烦。
背景分享(音乐可以自行下载)
本篇就这样,之后会不定时更新小游戏(就酱)。