连连看课设

rt 主要是给朋友和某学长检查开源用的,懒得打书面介绍了。

资源,思路和部分算法逻辑来自一篇公众号的文章。添加了宏定义地图大小,动态难度和计时器,其他部分也是按自己理解敲的,差距会比较大。

由于运行和绘制逻辑导致计时器并不能实时显示,需要操作之后才会更新在屏幕上。

安装Easyx库,复制源代码,复制资源到源代码根目录改名后即可运行(大概

BGM自己随便加一首喜欢的jpg格式歌曲改名bgm即可。

在这里插入图片描述
在这里插入图片描述

Talk is cheap,show you my code.

#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;
}

运行效果

连连看课设_第1张图片
别问为什么开始界面这么简陋,问就是懒

连连看课设_第2张图片
连连看课设_第3张图片

你可能感兴趣的:(课设)