贪吃蛇—C—基于easyx图形库(2):从画图程序到贪吃蛇【自带穿墙术】

上节我们用方向控制函数写了个小画图程序,它虽然简单好玩,但我们不应该止步于此。革命尚未成功,同志还需努力。

先复习一下贪吃蛇的结构:

开始实现之前,我们先理清一下思路。和前面画图程序不同,贪吃蛇可以有很多节,可以用一个足够大的结构体数组来储存它。 还需要一个食物坐标。定义如下:

typedef struct Position  //坐标结构
{
    int x;
    int y;
}Pos;

Pos array;                         //移动方向向量
Pos snake[300000] = {};  //蛇的结构体数组,谁能够无聊到吃299999个食物~_~long len=1;                      //蛇的长度Pos egg; //食物坐标

之前的画图程序是四个方向都可以走,可蛇是不能倒着走的,所以方向控制函数要改成这样:

void command()                              //获取键盘命令
{
    if (_kbhit())       //如果有键盘消息
        switch (_getch())      /*这里不能用getchar()*/
        {
        case ‘a‘:
            if (array.x != 1 || array.y != 0) {//如果命令不是倒着走,就修正方向向量,否则不做改变,下同。
                array.x = -1;
                array.y = 0;
            }
            break;
        case ‘d‘:
            if (array.x != -1 || array.y != 0) {
                array.x = 1;
                array.y = 0;
            }
            break;
        case ‘w‘:
            if (array.x != 0 || array.y != 1) {
                array.x = 0;
                array.y = -1;
            }
            break;
        case ‘s‘:
            if (array.x != 0 || array.y != -1) {
                array.x = 0;
                array.y = 1;
            }
            break;
        }
} 

蛇可能不止一节,所以移动函数需要做出改变。仔细一想就知道,除了头结点外,每个节点的下一个坐标为它前一个结点当前的坐标,而头节点的坐标等于它本身坐标加上移动向量(这里是 方向向量*10)

还有个问题是蛇走过的痕迹需要擦除,每走一步,它留下的痕迹应该是走这一步之前蛇的最末一个结点的坐标,我们需要擦除掉它。

结果如下:

void move()    //修改各节点坐标以达到移动的目的
{
    setcolor(BLACK);        //覆盖尾部走过的痕迹
    rectangle(snake[len-1].x - 5, snake[len-1].y - 5, snake[len-1].x + 5, snake[len-1].y + 5);

    for (int i = len-1; i >0; i--)    //除了头结点外,每个节点的下一个坐标为它前一个结点当前的坐标
    {
        snake[i].x = snake[i - 1].x;
        snake[i].y = snake[i - 1].y;
    }
    snake[0].x += array.x*10;             //头节点的坐标等于它本身坐标加上移动向量(这里是 方向向量*10)
    snake[0].y += array.y*10;
}

另外,我们的蛇是有穿墙术的~~~它的实现方法非常简单:

void break_wall()
{
    if (snake[0].x >= 640)             //如果越界,从另一边出来
        snake[0].x = 0;
    else if (snake[0].x <= 0)
        snake[0].x = 640;
    else if (snake[0].y >= 480)
        snake[0].y = 0;
    else if (snake[0].y <= 0)
        snake[0].y = 480;
}

接下来是食物相关函数,这个算是重点。

1. 食物生成

我们希望食物每次出现的位置都是随机的, 可以这样实现。

1         srand((unsigned)time(NULL));
2         egg.x = rand() % 80 * 5 + 100;    //头节点位置随机化
3         egg.y = rand() % 50 * 5 + 100;

而且食物不能与蛇重合,最好也不要离蛇太近。综合起来就是这样:(srand在初始化中会被调用,所以这里略去了)

void creat_egg()
{
    while (true)
    {
        int ok = 0;   //这是个标记,用于判断函数是否进入了某一分支
        egg.x = rand() % 80 * 5 + 100;    //头节点位置随机化
        egg.y = rand() % 50 * 5 + 100;
        for (int i = 0; i < len; i++)     //判断是否离蛇太近
        {
            if (snake[i].x == 0 && snake[i].y == 0)
                continue;
            if (fabs(snake[i].x - egg.x) <= 10 && fabs(snake[i].y - egg.y) <= 10)
                ok = -1;   //如果,进入此分支,改变标记
                break;
        }
        if (ok == 0)    //如果不重合了,跳出函数
            return;
    }
}

2. 吃到食物

如果吃到食物,那么需要消除被吃掉的食物,生成新食物,蛇也要增长一节。

我觉得这里最麻烦的就是蛇变长的实现:是在蛇头添加一节,还是在蛇尾?添加在蛇头(尾)的上下左右哪一边?

想来想去,只有在蛇头位置,我们可以根据当前方向向量,在移动方向上新添一节。这对应的代码如下:

        //add snake node
        len += 1;
        for (int i = len - 1; i >0; i--)    //所有数据后移一个单位,腾出snake[0]给新添的一节
        {
            snake[i].x = snake[i - 1].x;
            snake[i].y = snake[i - 1].y;
        }
        snake[0].x += array.x * 10;             //这就是新添的这一节的位置
        snake[0].y += array.y * 10;

吃到食物的完整代码如下:

void eat_egg()
{
    if (fabs(snake[0].x - egg.x)<=5 && fabs(snake[0].y - egg.y)<=5)    //判断是否吃到食物,因为食物位置有点小偏差,只好使用范围判定~~
    {
        setcolor(BLACK);          //hide old egg
        circle(egg.x, egg.y, 5);
        creat_egg();            //create new egg
        //add snake node
        len += 1;
        for (int i = len - 1; i >0; i--)
        {
            snake[i].x = snake[i - 1].x;
            snake[i].y = snake[i - 1].y;
        }
        snake[0].x += array.x * 10;             //每次移动10pix
        snake[0].y += array.y * 10;
    }
}

游戏结束判定

最后,我们还差一个死亡判定,因为自带穿墙术,所以实际的死亡判定只有一个,就是咬到自己,代码如下:

void eat_self()
{
    if (len == 1)             //只有一节当然吃不到自己~~
        return;
    for (int i = 1; i < len; i++)
        if (fabs(snake[i].x - snake[0].x) <= 5 && fabs(snake[i].y - snake[0].y) <= 5)            //如果咬到自己(为了不出bug,使用了范围判定)
        {
            outtextxy(250, 200, "GAME OVER!");  //你的蛇死了~
            Sleep(3000);      //3s时间让你看看你的死相~~
            closegraph();
            exit(0);     //退出
        }
}

当然,你也可以直接丢掉这个函数,然后开心地狂咬自己—_—||

最后:画图函数

画出食物和蛇,其实蛇没必要全部画出来,只要画蛇头就可以了,但这之中有些小问题,谁有兴趣可以自己玩玩,我是懒得动了~

void draw()     //画出蛇和食物
{
    setcolor(BLUE);
    for (int i = 0; i < len; i++)
    {
        rectangle(snake[i].x - 5, snake[i].y - 5, snake[i].x + 5, snake[i].y + 5);
    }
    setcolor(RED);        //画蛋(怎么感觉怪怪的~)
    circle(egg.x, egg.y, 5);
    Sleep(100);
}

到这里,游戏大功告成~~  什么?你说运行不起来?那是因为少了初始化函数,和游戏循环啦~~这几个都比较简单,就直接放下面了:

void init()              //初始化
{
    initgraph(640, 480);                    //初始化图形界面
    srand((unsigned)time(NULL));            //初始化随机函数
    snake[0].x = rand() % 80 * 5 + 100;    //头节点位置随机化
    snake[0].y = rand() % 50 * 5 + 100;
    array.x = pow(-1,rand());        //初始化方向向量,左或者右
    array.y = 0;
    creat_egg();
}

int main()
{
    init();
    while (true)
    {
        command();      //获取键盘消息
        move();         //修改头节点坐标-蛇的移动
        eat_egg();
        draw();         //作图
        eat_self();
    }

    return 0;
}

好了,这是真的大功告成了。给你们看看死亡方式之自尽:

完整代码如下:

  1 #include<graphics.h>
  2 #include<conio.h>
  3 #include<time.h>
  4 #include<math.h>
  5
  6 typedef struct Position  //坐标结构
  7 {
  8     int x;
  9     int y;
 10 }Pos;
 11
 12 Pos snake[300000] = {};
 13 Pos array;
 14 Pos egg;
 15 long len=1;
 16
 17 void creat_egg()
 18 {
 19     while (true)
 20     {
 21         int ok = 0;
 22         srand((unsigned)time(NULL));            //初始化随机函数
 23         egg.x = rand() % 80 * 5 + 100;    //头节点位置随机化
 24         egg.y = rand() % 50 * 5 + 100;
 25         for (int i = 0; i < len; i++)
 26         {
 27             if (snake[i].x == 0 && snake[i].y == 0)
 28                 continue;
 29             if (fabs(snake[i].x - egg.x) <= 10 && fabs(snake[i].y - egg.y) <= 10)
 30                 ok = -1;
 31                 break;
 32         }
 33         if (ok == 0)
 34             return;
 35     }
 36 }
 37
 38 void init()              //初始化
 39 {
 40     initgraph(640, 480);                    //初始化图形界面
 41     srand((unsigned)time(NULL));            //初始化随机函数
 42     snake[0].x = rand() % 80 * 5 + 100;    //头节点位置随机化
 43     snake[0].y = rand() % 50 * 5 + 100;
 44     array.x = pow(-1,rand());        //初始化方向向量
 45     array.y = 0;
 46     creat_egg();
 47 }
 48
 49 void command()                              //获取键盘命令
 50 {
 51     if (_kbhit())       //如果有键盘消息
 52         switch (_getch()/*这里不能用getchar()*/)
 53         {
 54         case ‘a‘:
 55             if (array.x != 1 || array.y != 0) {//如果不是反方向
 56                 array.x = -1;
 57                 array.y = 0;
 58             }
 59             break;
 60         case ‘d‘:
 61             if (array.x != -1 || array.y != 0) {
 62                 array.x = 1;
 63                 array.y = 0;
 64             }
 65             break;
 66         case ‘w‘:
 67             if (array.x != 0 || array.y != 1) {
 68                 array.x = 0;
 69                 array.y = -1;
 70             }
 71             break;
 72         case ‘s‘:
 73             if (array.x != 0 || array.y != -1) {
 74                 array.x = 0;
 75                 array.y = 1;
 76             }
 77             break;
 78         }
 79 }
 80
 81 void move()    //修改各节点坐标以达到移动的目的
 82 {
 83     setcolor(BLACK);        //覆盖尾部走过的痕迹
 84     rectangle(snake[len-1].x - 5, snake[len-1].y - 5, snake[len-1].x + 5, snake[len-1].y + 5);
 85
 86     for (int i = len-1; i >0; i--)
 87     {
 88         snake[i].x = snake[i - 1].x;
 89         snake[i].y = snake[i - 1].y;
 90     }
 91     snake[0].x += array.x*10;             //每次移动10pix
 92     snake[0].y += array.y*10;
 93
 94     if (snake[0].x >= 640)             //如果越界,从另一边出来
 95         snake[0].x = 0;
 96     else if (snake[0].x <= 0)
 97         snake[0].x = 640;
 98     else if (snake[0].y >= 480)
 99         snake[0].y = 0;
100     else if (snake[0].y <= 0)
101         snake[0].y = 480;
102 }
103
104 void eat_egg()
105 {
106     if (fabs(snake[0].x - egg.x)<=5 && fabs(snake[0].y - egg.y)<=5)
107     {
108         setcolor(BLACK);          //shade old egg
109         circle(egg.x, egg.y, 5);
110         creat_egg();
111         //add snake node
112         len += 1;
113         for (int i = len - 1; i >0; i--)
114         {
115             snake[i].x = snake[i - 1].x;
116             snake[i].y = snake[i - 1].y;
117         }
118         snake[0].x += array.x * 10;             //每次移动10pix
119         snake[0].y += array.y * 10;
120     }
121 }
122
123 void draw()     //画出蛇和食物
124 {
125     setcolor(BLUE);
126     for (int i = 0; i < len; i++)
127     {
128         rectangle(snake[i].x - 5, snake[i].y - 5, snake[i].x + 5, snake[i].y + 5);
129     }
130     setcolor(RED);
131     circle(egg.x, egg.y, 5);
132     Sleep(100);
133 }
134
135 void eat_self()
136 {
137     if (len == 1)
138         return;
139     for (int i = 1; i < len; i++)
140         if (fabs(snake[i].x - snake[0].x) <= 5 && fabs(snake[i].y - snake[0].y) <= 5)
141         {
142             Sleep(1000);
143             outtextxy(250, 200, "GAME OVER!");
144             Sleep(3000);
145             closegraph();
146             exit(0);
147         }
148 }
149
150 int main()
151 {
152     init();
153     while (true)
154     {
155         command();      //获取键盘消息
156         move();         //修改头节点坐标-蛇的移动
157         eat_egg();
158         draw();         //作图
159         eat_self();
160     }
161
162     return 0;
163 }

snakey

可能还有若干bug留存,欢迎大家指正~~

甲铁城镇~

时间: 2024-10-03 14:00:50

贪吃蛇—C—基于easyx图形库(2):从画图程序到贪吃蛇【自带穿墙术】的相关文章

贪吃蛇—C—基于easyx图形库(1):基本控制函数 实现 画图程序

自从学了c语言,就一直想做个游戏,今天将之付之行动,第一次写的特别烂,各种bug:就不贴了.今天网上看了好几个贪吃蛇,重新写了一次,做出来的效果还可以. 下面是详细的构建过程,本节因为时间限制,先贴出比较重要的控制函数实现,并用它做一个很简单很简单很有趣的画图程序. 首先,要对贪吃蛇的结构有一个大概的了解:要有一个控制系统控制蛇上下左右移动,而且不能往反方向移动:要有食物产生系统,食物出现位置随机:吃到食物后蛇要变长:要有死亡判定系统...... 总结起来,就是这样: 我写的这个结构有点乱,不过

easyx图形库做贪吃蛇游戏

编程总是对着一个黑窗口,可以说是非常乏味了,于是喵喵就翻出来了以前用easyx图形库做图形界面的贪吃蛇游戏. 不过大家只是当做提高编程的乐趣来学习吧,想进一步做的话可以学习QT,还有其他的框架. 这是一个easyx图形库的学习教程,建议大家学完再看本文: https://www.easyx.cn/skills/List.aspx?id=7 首先看一下效果图: 实现的功能: 基本的吃食物长大,撞墙死亡,特殊食物,游戏暂停,重开游戏,记分数的功能. 游戏最高分的记录. 游戏关卡的选择. 加了游戏的音

WPF小程序:贪吃蛇

原文地址:http://hankjin.blog.163.com/blog/static/337319372009535108234/ 一共两个文件:EasterEgg.xaml + EasterEgg.xaml.csEasterEgg.xaml<Window x:Class="Inspect.UI.EasterEgg"    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  

C语言 linux环境基于socket的简易即时通信程序

转载请注明出处:http://www.cnblogs.com/kevince/p/3891033.html   By Kevince 最近在看linux网络编程相关,现学现卖,就写了一个简易的C/S即时通信程序,代码如下: head.h 1 /*头文件,client和server编译时都需要使用*/ 2 #include <unistd.h> 3 #include <stdio.h> 4 #include <sys/types.h> 5 #include <sys

如何修改基于Debian包管理dpkg的程序流程方法概述

/*********************************************************************  * Author  : Samson  * Date    : 05/14/2014  * Test platform:  *              Mint 15-3.8.13.13  *              GNU bash, version 4.2.45  * ***************************************

【开源下载】基于TCP网络通信的自动升级程序c#源码

本程序使用开源的来自英国的networkcomms2.3.1网络通讯框架(c#语言编写) [http://www.networkcomms.net] 使用networkcomms框架作为工作中的主要编程框架1年多的时间了,networkcomms的有优美.稳定深深打动了我,基于此框架开发了不少程序,特别的稳定. networkcomms框架由英国剑桥的2位工程师开发,支持.net2.0以上平台,原生态的支持xamarion.android(安卓),xamarin.ios,以及蓝牙等多平台开发.

最简单的基于FFmpeg的移动端样例附件:Android 自带播放器

===================================================== 最简单的基于FFmpeg的移动端样例系列文章列表: 最简单的基于FFmpeg的移动端样例:Android HelloWorld 最简单的基于FFmpeg的移动端样例:Android 视频解码器 最简单的基于FFmpeg的移动端样例:Android 视频解码器-单个库版 最简单的基于FFmpeg的移动端样例:Android 推流器 最简单的基于FFmpeg的移动端样例:Android 视频转

一个基于ATMEGA128的直流电机抱死程序(转)

源:一个基于ATMEGA128的直流电机抱死程序 先说一下我的硬件情况:一块ATMEGA128实验板:一个带编码器的80:1的变速电机,编码器的输出端连接到单片机的PD4和PD5引脚:一块电机驱动电路,该电路的输入为:24v电源.两路pwm信号输入,输出即为电机的正负极,要用该电路来驱动电机,则必须让两路pwm输入信号的一路占空比为0,另一路不为0,相当于让电机的一极接地,另一极接pwm,通过控制两路pwm的占空比来控制电机的转速和转动方向.pwm信号的输入端连接到单片机的PD6和PD7引脚.

吉首大学_编译原理实验题_基于预測方法的语法分析程序的设计【通过代码】

一.实验要求 实验二 基于预測方法的语法分析程序的设计 一.实验目的 了解预測分析器的基本构成及用自顶向下的预測法对表达式进行语法分析的方法,掌握预測语法分析程序的手工构造方法. 二.实验内容 1.了解编译程序的基于预測方法的语法分析过程. 2.依据预測分析原理设计一个基于预測方法的语法分析程序. 三.实验要求 对给定文法G[S]: S->AT       A->BU     T->+AT|$      U->*BU|$    B->(S)|m 当中,$表示空串. 1.推断上