rt 主要是给朋友和某学长检查开源用的,懒得打书面介绍了。
资源,思路和部分算法逻辑来自一篇公众号的文章。添加了宏定义地图大小,动态难度和计时器,其他部分也是按自己理解敲的,差距会比较大。
由于运行和绘制逻辑导致计时器并不能实时显示,需要操作之后才会更新在屏幕上。
安装Easyx库,复制源代码,复制资源到源代码根目录改名后即可运行(大概
BGM自己随便加一首喜欢的jpg格式歌曲改名bgm即可。
#include
#include
#include
#include
#include
#include
#pragma comment(lib,"winmm.lib")
using namespace std;
/*
资源背景:宽46高56
资源人物:宽39高39
*/
#define MAP_ROW 10//宽度上的地图元素
#define MAP_COL 10//高度上的地图元素
#define MAP_SIZE_X (MAP_ROW+2)//地图总宽度
#define MAP_SIZE_Y (MAP_COL+2)//地图总高度
#define MAP_POINT (MAP_ROW*MAP_COL)//总元素数量
#define LEVELS 3//关卡总数
int GAME_DIFFICULTY = 10;//方块的种类数,决定了游戏难度
int levels = 1;//当前关卡数
int map_point;//总元素数量
int g_map[MAP_ROW + 2][MAP_COL + 2];//每个地图的状态
//左右留出两个空行
IMAGE bk;//背景
IMAGE animal;//动物方块
int using_bk[MAP_SIZE_X][MAP_SIZE_Y];//使用的背景部分
COORD point[2] = {
-1,-1 };//存放待处理方块
int chickindex = 0;//保存点动态下标
double game_time = 0;//游戏总时间
double startlevel_time;//开始关卡的时间
double now_time;//当前时间
void InItGame()//游戏初始化
{
startlevel_time = clock();//重置关卡时间
//加载资源图片
loadimage(&bk, "./block.jpg");
loadimage(&animal, "./animal.jpg");
//播放bgm
mciSendString("open bgm.mp3 alias BGM", NULL, 0, NULL);
mciSendString("play BGM repeat", NULL, 0, NULL);
//初始化画线数据
setlinecolor(GREEN);
setlinestyle(PS_SOLID | PS_ENDCAP_ROUND, 4);
map_point = MAP_POINT;//初始化总数
for (int i = 0; i < 2; i++)//初始化待处理方块
{
point[i].X = -1;
point[i].Y = -1;
}
int game_data[MAP_POINT] = {
0 };
int i, j, k;
for (i = 0; i < MAP_POINT; i += 2)
{
game_data[i] = rand() % GAME_DIFFICULTY + 1;//随机赋值方块种类
game_data[i + 1] = game_data[i];//对称
}
for (i = 0; i < MAP_POINT; i++)//打乱顺序
{
int index = rand() % MAP_POINT;
if (index != i)
swap(game_data[i], game_data[index]);
}
k = 0;
for (i = 1; i <= MAP_ROW; i++)//将数据存放进二维数组
for (j = 1; j <= MAP_COL; j++)
g_map[i][j] = game_data[k++];
for (i = 0; i < MAP_SIZE_X; i++)//给每个格子赋随机背景
for (j = 0; j < MAP_SIZE_Y; j++)
using_bk[i][j] = rand() % 6 + 1;
}
void DrawTime()//时间处理函数
{
setfillcolor(WHITE);
solidrectangle(0, 56 * MAP_SIZE_Y, 46 * MAP_SIZE_X, 56 * MAP_SIZE_Y + 50);//打底
now_time = clock() - startlevel_time;//计算当前时间
char timestr[20];
sprintf_s(timestr, "用时:%.2f秒", now_time / 1000);//clock()函数精确到毫秒,进行换算
settextcolor(GREEN);
settextstyle(40, 25, "微软雅黑");
setbkmode(TRANSPARENT);
int width = textwidth(timestr) / 2;
outtextxy(46 * MAP_SIZE_X * 0.5 - width, 56 * MAP_SIZE_Y + 12, timestr);
}
void DrawGame()
{
BeginBatchDraw();
int i, j;
/*
背景:宽46高56
方块:宽39高39
*/
for (i = 0; i < MAP_SIZE_X; i++)
{
for (j = 0; j < MAP_SIZE_Y; j++)
{
putimage(i * 46, j * 56, 46, 56, &bk, 0, 56 * using_bk[i][j], SRCCOPY);//绘制随机背景
if (g_map[i][j])//如果存在方格
{
putimage(i * 46 + 1, j * 56 + 6, 39, 39, &animal, 39, (g_map[i][j] - 1) * 39, SRCAND);
//SRCAND:目标图像=目标图像AND源图像,此处是绘制的白底黑色的遮罩图,三元光栅的效果就是过滤掉白色背景,投射上无背景的目标图像黑色遮罩
putimage(i * 46 + 1, j * 56 + 6, 39, 39, &animal, 0, (g_map[i][j] - 1) * 39, SRCPAINT);
//SRCPAINT:目标图像=目标图像OR源图像,绘制效果图,实现最终效果
}
}
}
if (chickindex > 0)
{
rectangle(point[0].X * 46 + 4, point[0].Y * 56 + 4, point[0].X * 46 + 36, point[0].Y * 56 + 44);//绘制第一个点的提示矩形
}
DrawTime();
EndBatchDraw();
}
bool HavePath0(COORD p1, COORD p2)//无转折点情况,即两个方块在同一条线
{
if (p1.X != p2.X && p1.Y != p2.Y)//首先判断满不满足条件
{
return 0;
}
int max, min;//不能确定输入数据的大小,需要手动排序
if (p1.X == p2.X)//x轴方向共线
{
max = max(p1.Y, p2.Y);
min = min(p1.Y, p2.Y);//保存大小坐标
for (min++; min < max; min++)//在他们之间枚举,遇到非空格直接跳出
if (g_map[p1.X][min]) return 0;
}
else if (p1.Y == p2.Y)//y轴方向共线
{
max = max(p1.X, p2.X);
min = min(p1.X, p2.X);
for (min++; min < max; min++)
if (g_map[min][p1.Y]) return 0;
}
return 1;//可以消
//由于之后需要分步使用这个函数,可能出现第一步(该函数)可行第二步不可行的情况,所以不能直接画线
}
bool HavePath1(COORD p1, COORD p2)//单转折点,把两个点当成矩形一条对角线上的顶点,判断通过矩形的两条边是否可以与其相连
{
if (p1.X == p2.X || p1.Y == p2.Y)//不满足条件直接爬
return 0;
COORD temp[2];//两个临时点作另两个顶点
temp[0].X = p1.X;
temp[0].Y = p2.Y;
temp[1].X = p2.X;
temp[1].Y = p1.Y;
if (g_map[temp[0].X][temp[0].Y] == 0)//转折点上显然不能有东西
if (HavePath0(p1, temp[0]) && HavePath0(temp[0], p2))
{
//在后面的分步计算中这也是最后一步了,满足条件可以直接画
line(p1.X * 46 + 20, p1.Y * 56 + 26, temp[0].X * 46 + 20, temp[0].Y * 56 + 26);//第一个点连转折点
line(temp[0].X * 46 + 20, temp[0].Y * 56 + 26, p2.X * 46 + 20, p2.Y * 56 + 26);//第二个点连转折点
return 1;//返回true
}
if (g_map[temp[1].X][temp[1].Y] == 0)
if (HavePath0(p1, temp[1]) && HavePath0(temp[1], p2))
{
line(p1.X * 46 + 20, p1.Y * 56 + 26, temp[1].X * 46 + 20, temp[1].Y * 56 + 26);
line(temp[1].X * 46 + 20, temp[1].Y * 56 + 26, p2.X * 46 + 20, p2.Y * 56 + 26);
return 1;
}
return 0;//消不了,没救了,告辞.jpg
}
bool HavePath2(COORD p1, COORD p2)//两个转折点,思路是从一个点出发向四周枚举第一个转折点,再从这个转折点出发经一次转折到达另一个点
{
int i;
COORD temp;
for (i = p1.X + 1; i < MAP_SIZE_X; i++)//向x轴正方向枚举,最多枚举到边界
{
if (g_map[i][p1.Y] == 0)//必须是空格
{
temp.X = i;
temp.Y = p1.Y;
if (HavePath1(temp, p2) == 1)//成了
{
line(temp.X * 46 + 20, temp.Y * 56 + 26, p1.X * 46 + 20, p1.Y * 56 + 26);//记得画线。直接用存放两点的全局变量画线会布朗运动,血的教训...
return 1;//返回true
}
}
else
break;
}
for (i = p1.X - 1; i >= 0; i--)//向x轴负方向枚举
{
if (g_map[i][p1.Y] == 0)
{
temp.X = i;
temp.Y = p1.Y;
if (HavePath1(temp, p2) == 1)
{
line(temp.X * 46 + 20, temp.Y * 56 + 26, p1.X * 46 + 20, p1.Y * 56 + 26);
return 1;
}
}
else
break;
}
for (i = p1.Y + 1; i < MAP_SIZE_Y; i++)//向y轴正方向(向下)枚举
{
if (g_map[p1.X][i] == 0)
{
temp.X = p1.X;
temp.Y = i;
if (HavePath1(temp, p2) == 1)
{
line(temp.X * 46 + 20, temp.Y * 56 + 26, p1.X * 46 + 20, p1.Y * 56 + 26);
return 1;
}
}
else
break;
}
for (i = p1.Y - 1; i >= 0; i--)//向y轴负方向(向上)枚举
{
if (g_map[p1.X][i] == 0)
{
temp.X = p1.X;
temp.Y = i;
if (HavePath1(temp, p2) == 1)
{
line(temp.X * 46 + 20, temp.Y * 56 + 26, p1.X * 46 + 20, p1.Y * 56 + 26);
return 1;
}
}
else
break;
}
return 0;//治不了,没救了,告辞.jpg
}
bool HavePath()
{
if (HavePath0(point[0], point[1]) == 1)
{
line(point[0].X * 46 + 20, point[0].Y * 56 + 26, point[1].X * 46 + 20, point[1].Y * 56 + 26);//连线
return 1;
}
if (HavePath1(point[0], point[1]) == 1)
{
return 1;
}
if (HavePath2(point[0], point[1]) == 1)
{
return 1;
}
return 0;
}
void Update()
{
now_time = clock() - startlevel_time;
MOUSEMSG msg = GetMouseMsg();
int col = msg.x / 45;//地图x轴坐标
int row = msg.y / 56;//地图y轴坐标
switch (msg.uMsg)
{
case WM_LBUTTONDOWN:
if (g_map[col][row] == 0)//如果点击位置没有方块
return;
point[chickindex].X = col; //保存点击数据
point[chickindex].Y = row;
if (++chickindex > 1)//如果已经有两个数据,就开始判断是否满足消除
{
chickindex = 0;
//同位置,直接返回
if (point[0].X == point[1].X && point[0].Y == point[1].Y)
return;
//不同位置不同图案,保存第二次点击后返回
if (g_map[point[0].X][point[0].Y] != g_map[point[1].X][point[1].Y])
{
chickindex = 1;//第二个数据存在1下标中
point[0] = point[1];
return;
}
//至此满足消除条件:不同位置,相同图案
if (HavePath() == true)//判断消除
{
rectangle(point[1].X * 46 + 4, point[1].Y * 56 + 4, point[1].X * 46 + 36, point[1].Y * 56 + 44);//由于第一个消除的方块矩形已画好,因此只画第二个就行
Sleep(200);
for (int i = 0; i < 2; i++)
{
g_map[point[i].X][point[i].Y] = 0;//更改单元格的状态为已消除(空)
}
map_point -= 2;//地图上又少了两个带恶人
}
else//消除失败
{
point[0] = point[1];//保存第二次点击的数据
chickindex = 1;//保留一次点击机会
}
}
break;
}
}
void IsWin()
{
char str[505];
if (map_point <= 0 && levels < LEVELS)//关卡胜利但还有关卡
{
game_time += now_time;//计算总时间
sprintf_s(str, "你赢了!\n本关用时%.2fs,总用时%.2fs\n是否进入下一关?", now_time / 1000, game_time / 1000);
HWND hwnd = GetHWnd();
int ok = MessageBox(hwnd, str, "Vectory", MB_OKCANCEL);//弹窗庆祝胜利
if (ok == IDOK)//下一关
{
GAME_DIFFICULTY += 2;
levels++;
InItGame();
}
else//退出游戏
{
exit(0);
}
}
else if (map_point <= 0 && levels >= levels)//通关
{
game_time += now_time;
sprintf_s(str, "恭喜你,成功通关!\n本关用时%.2fs,总用时%.2fs\n", now_time / 1000, game_time / 1000);
HWND hwnd = GetHWnd();
int ok = MessageBox(hwnd, str, "Vectory", MB_OK);
if (ok == IDOK)
exit(0);
}
}
void welcome()//绘制开始菜单
{
setfillcolor(WHITE);
solidrectangle(0, 0, 46 * MAP_SIZE_X, 56 * MAP_SIZE_Y + 60);
LPCSTR str;
str = "连连看";
LPCSTR word;//存放字体
word = "宋体";
settextcolor(GREEN);
settextstyle(100, 50, word);
setbkmode(TRANSPARENT);
int width = textwidth(str) / 2;
int height = textheight(str) / 2;
outtextxy(46 * MAP_SIZE_X * 0.5 - width, 56 * MAP_SIZE_Y * 0.3 - height, str);
str = "按下回车开始游戏";
settextcolor(BLACK);
settextstyle(50, 25, word);
width = textwidth(str) / 2;
height = textheight(str) / 2;
outtextxy(46 * MAP_SIZE_X * 0.5 - width, 56 * MAP_SIZE_Y * 0.7 - height, str);
getchar();
}
int main()
{
initgraph(46 * MAP_SIZE_X, 56 * MAP_SIZE_Y + 60);//绘制窗口
welcome();
InItGame();
DrawGame();
while (1)//程序主体:初始化,渲染,运行
{
Update();
DrawGame();
IsWin();
}
return 0;
}