C++贪吃蛇游戏

引言

这段时间正在学习移动规划的相关知识,但是发现大部分的代码都是用的C++来编写,而自己只用c写过代码,c++的知识实属是薄弱到了极致,所以用了半年突击了一下从C转到C++的视频,但是还是缺少实战经验,所幸网上贪吃蛇这个游戏非常简单,所以作为新手练手非常的合适。

函数代码

drawsnake

这一函数的主要目的在于在屏幕上根据当前snake的坐标,利用easyx库将其画出来。

void drawsnake(std::deque<Snake>& snake)
{
    setfillcolor(GREEN);
    for (const auto& body : snake)
    {
        fillrectangle(body.x * size, body.y * size, (body.x + 1) * size, (body.y + 1) * size);
    }
    Sleep(speed);
}
  1. Isover

这一函数的主要目的是判断游戏是否结束了,判断的条件也很简单,要么就是蛇头超过了设定的屏幕边界,要么就是蛇头碰到了蛇身自己,然后标记游戏结束,同时退出游戏。

void isover(std::deque<Snake>& snake)
{
    for (auto iter = snake.begin() + 1; iter != snake.end(); iter++)
    {
        if ((snake.front().x < 0 || snake.front().x >= weight / size
            || snake.front().y < 0 || snake.front().y >= hight / size) ||
            (snake.front().x == iter->x && snake.front().y == iter->y))
        {
            settextcolor(RED);
            settextstyle(100, 0,_T("Consolas"));
            TCHAR endStar[16];
            _stprintf_s(endStar,_T("GAME OVER!!!"));
            outtextxy(weight / size, hight / size, endStar);
            Sleep(3000);
            closegraph();
            exit(0);
        }
  }
}
  1. Movesnake 以及changedir

两个函数的目的主要是首先通过按键捕获,获取用户当前按下的键值(还是判定一下当前的方向,比如向前就算键值为向后也不可以),然后改变蛇头的移动方向。同时判断蛇头是否触碰到食物,如果没触碰到就将双端队列的最后一位(蛇尾的位置)进行去掉,触碰则进行保留,蛇身长度加一。

void movesnake(std::deque<Snake>& snake, bool grow = false)

{

    Snake newHead = snake.front();

    switch (newHead.dir)

    {

    case UP:

        newHead.y--;

        break;

    case DOWN:

        newHead.y++;

        break;

    case RIGHT:

        newHead.x++;

        break;

    case LEFT:

        newHead.x--;

        break;

    }

    snake.push_front(newHead);

    isover(snake);

    if (!grow)

    {

        clearrectangle(snake.back().x * size, snake.back().y * size,

            (snake.back().x + 1) * size, (snake.back().y + 1) * size);

        snake.pop_back();

    }

    drawsnake(snake);

}

void changedir(std::deque<Snake>& snake)

{

    ExMessage msg = { 0 };

    peekmessage(&msg, EX_KEY);

    if (msg.message == WM_KEYDOWN)

    {

        switch (msg.vkcode)

        {

        case 'W' :case 'w ':case VK_UP:

            if (snake.front().dir != DOWN)

            {snake.front().dir = UP;}

            break;

        case 's':case 'S ':case VK_DOWN:

            if (snake.front().dir != UP)

            { snake.front().dir = DOWN; }

            break;

        case 'A': case'a ':case VK_LEFT:

            if (snake.front().dir != RIGHT)

            {snake.front().dir = LEFT;}

            break;

        case 'D':case 'd ':case VK_RIGHT:

            if (snake.front().dir != LEFT)

            {snake.front().dir = RIGHT;}

            break;

        }

    }

}
  1. Genertedfood 以及 cheakeat

这两个函数的主要目的还是在于产生食物,以及判断蛇头是否与食物相碰撞。判断逻辑非常的简单,即蛇头的x y坐标是否等于到食物的x y坐标,返回值为一个布尔值表示是否触碰。产生食物的时候,限制了一个范围,同时限制不能产生在蛇的身上。

void genertedfood(Food& food, std::deque<Snake>& snake)

{

    while (true)

    {

        food.x = rand() % ((weight-200) / size);

        food.y = rand() % ((hight-200) / size);

        bool overlap = false;

        for (const auto& body : snake)

        {

            if (food.x == body.x && food.y == body.y)

            {

                overlap = true;

            }

        }

        if (!overlap)

        {

            break;

        }

    }

}

bool cheakeat(Food& food, std::deque<Snake>& snake)

{

    if (snake.front().x == food.x && snake.front().y == food.y)

    {

        return true;

    }

    return false;

}
  1. 主函数 main

程序从main函数开始执行,首先通过initgraph初始化一个800×800像素的图形窗口,创建表示蛇体的双端队列snake,并将初始蛇头(位置居中、方向向右)压入队列。调用drawsnake函数绘制初始蛇体后,初始化计分器score为0,并通过genertedfood函数生成第一个食物,用红色方块绘制在随机位置。进入无限循环后,每帧先执行movesnake移动蛇体,此时会根据当前方向计算新蛇头坐标并插入队列头部,如果是正常移动则删除尾部元素实现位置更新,如果检测到吃食物则保留尾部实现生长。移动后立即调用changedir函数检测键盘输入,通过peekmessage非阻塞获取方向键或WSAD键值,在确保不反向移动的前提下更新蛇头方向。接着通过cheakeat判断蛇头是否与食物坐标重合,若发生吞噬则分数自增、生成新食物并重新绘制,此时通过movesnake的第二个参数true触发蛇体生长。每帧最后都会用黄色字体在窗口右下角刷新当前分数。当发生撞墙或自碰时,isover函数会显示红色"GAME OVER"提示,暂停3秒后直接退出程序,因此最后的closegraph实际上不会被执行到。整个游戏通过循环持续更新蛇体位置、处理用户输入并响应状态变化,构成完整的游戏运行流程。

int main()

{

    initgraph(weight, hight); //画出一个窗口 800*1000

    std::deque<Snake> snake; //定义了一条蛇

    snake.push_front({ weight / size / 2, hight / size / 2,RIGHT }); //在双端队列头部添加一个元素作为蛇头

    drawsnake(snake); //把蛇画出来

    int score = 0;

    Food food;

    genertedfood(food, snake);

    setfillcolor(RED);

    fillrectangle(food.x * size, food.y * size, (food.x + 1) * size, (food.y + 1) * size);

    while (1)

    {

        movesnake(snake);

        changedir(snake);

        if (cheakeat(food, snake))

        {

            score++;

            genertedfood(food, snake);

            movesnake(snake, true);

            setfillcolor(RED);

            fillrectangle(food.x * size, food.y * size, (food.x + 1) * size, (food.y + 1) * size);

        }

        settextcolor(YELLOW);

        settextstyle(40, 0, _T("Consolas"));

        TCHAR stescore[16];

        _stprintf_s(stescore, _T("Score:%d"), score);

        outtextxy(weight / size, hight / size, stescore);

    }

    system("pause");

    closegraph();

    return 0;

}
  • 写在最后

其实这个代码的逻辑并不算很复杂,主要目的还是练习从c转到c++的写法上的区别。同时,这个代码中并没有涉及到面向对象的编程,下一步将找一个面向对象编程的项目进行实战。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值