链表——贪吃蛇

      

 

        众所周知,贪吃蛇有很多的方法可以实现,主流的方法有链表法,和数组法。

        数组法的优势是很明显的,但是缺点也很大。

        缺点在于:

        1,在对蛇这一个数组进行定义的时候,要预留出足够大的空间,这一段空间是没有任何作                 用的。

        2,每一次显示的时候都要重新对数组进行遍历,使程序的更加复杂。

        3,数组只是在显示上,让游戏的画面看起来像是贪吃蛇,但是本质上并没有什么关系。

         优势又在于:

        1,在判断游戏中的各种关键时刻的时候,数组更加的容易判断。

        2,不用特意的设置复杂的坐标进行打印。(数组本身就有坐标的性质)

        链表则是于数组的优势完完全全的相反。

        缺点在于:

        1,对程序员的要求较大,要设计复杂的坐标。

        2,在判断游戏的关键时刻的时候较为的困难。

        优势在于:

        1,思路清晰,逻辑易于理解。

        2,打印的本质是更新而不是遍历,程序运行不复杂。

        3,没有一丝丝没有用的空间。 

        上效果:

        上代码!

  头文件的引入:

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <malloc.h>

#include"cursor.h"

        写代码的第一步就是把代码的流程构建出来,然后更具各种功能,制作各种函数。

int main()
{
    //开始游戏,把控制台变得可以控制
    //隐藏光标
    HideCursor();
    //开始界面
    GameStart();
    Welcome();
    //画地图
    CreatMap();
    //画蛇
    InitialSnake();
    //放食物
    setFood();
    //游戏相关的运行
    RunGame();
}

#include <conio.h>
#include <windows.h>

///设置光标的函数,使控制控制台变得简单

void gotoxy(int x,int y)
{
    COORD pos={x,y};
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleCursorPosition(hOut,pos);
}

void HideCursor() {  // 隐藏光标位置 ,这个函数复制代码就行
  CONSOLE_CURSOR_INFO cursor_info = {1, 0};
  SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

        控制台的操作部分。

        gotoxy();是控制光标到位置。

        HideCursor();是在游戏中隐藏光标的函数。这一段不用特别的学习。

        贪吃蛇是游戏,所以要制作一个游戏进入的界面,各位可根据自己的需求自由的设置页面。你甚至可以多设置几个页面,把规则什么的都写进去。

void GameStart()
{
    system("mode con cols=100 lines=40");
}

void Welcome()
{
    printf("\n\n\n");
    printf("\t\t\t======================================================\n");
    printf("\t\t\t=                                                    =\n");
    printf("\t\t\t=            welcome to snake_t's world              =\n");
    printf("\t\t\t=                 Programmer : 乔子轩                =\n");
    printf("\t\t\t=                                                    =\n");
    printf("\t\t\t=                                                    =\n");
    printf("\t\t\t======================================================\n");
    system("pause");
    system("cls");
}

        对于第一个文件,我的建议是把控制台重新规划一下,这样的话,你自己对自己设置的坐标也有一个认知。

        特别要提的是

        system("pause");      这一个语句的意义是把现在控制台的内容定住,强制让他不进入到下一个阶段。

        system("cls");            这一句的作用是把控制台中的内容清空,使适合下一个阶段的显示 。
       (头文件:stdlib.h) 

        贪吃蛇要有一个地图:

        这里开始涉及到了坐标的问题,建议大家在开始写之前就把自己的地图坐标完成,不然在后面蛇的移动以及其他的步骤的时候会出现很多的bug。我就是一个过来人。具体怎么设定吗,都是没有问题的,看你自己怎么方便就怎么来。

#define X_MAX 30  /* 水平最大值 */
#define Y_MAX 30  /* 竖直最大值 */
#define X_CHAR_WITH 2 /* 汉字宽度为2,英文为1 */
#define X_OFFSET 8  /* X偏置 */

void CreatMap()
{
    for(int i=0;i<(X_MAX+2);i++)
    {
        gotoxy(X_CHAR_WITH * (i + X_OFFSET-1),0);
        printf("墙");
        gotoxy(X_CHAR_WITH * (i + X_OFFSET-1), Y_MAX+1);
        printf("墙");
    }
    for(int i=1; i<(Y_MAX+1);i++)
    {
        gotoxy( (X_OFFSET-1 )* X_CHAR_WITH ,i);
        printf("墙");
        gotoxy((X_OFFSET + X_MAX ) * X_CHAR_WITH, i);
        printf("墙");
    }
    showScore();
}

        这里小炫了一波技,注意,汉字在C语言程序中是属于特殊字符,而特殊字符在C语言中的打印是占据两个空位的,所以我就定义的一个“汉字的宽度”的宏变量,方便之后坐标的使用与打印。

        地图什么的也有了。剩下的需要的就是食物分布和蛇了。

·       仔细思考,食物不可以出现在蛇的身体上,所以果断的选择先设定蛇。

        蛇的构建:

//结构体定义蛇
typedef struct snake
{
    int x;
    int y;
    struct snake *next;
}snake_t;

snake_t *linkHead = NULL;
snake_t *linkEnd  = NULL;

        设置的同时,把链表的头尾都用指针定义,以后要用。但是注意,现在还没有意义,头尾是要之后赋予的。

        然后就是喜闻乐见的链表构建环节了。

void InitialSnake()
{
    snake_t *temp;

    linkHead       = (snake_t*)malloc(sizeof(snake_t));
    linkHead->x    = 5;
    linkHead->y    = Y_MAX / 2;
    linkHead->next = NULL;

    temp = linkHead ;

    for(int i = 1; i < 3; i++)   ///这里让循环的次数多一点点有利于我们检查
    {
        snake_t *p = (snake_t*)malloc(sizeof(snake_t));
        p->x       = linkHead->x + i;
        p->y       = linkHead->y;
        p->next    = NULL;

        temp->next = p;
        temp       = p;
    }
    linkEnd = temp; //找尾巴

    ShowSnake();
}

        这一段代码的作用很大,创建了链表的基本结构的同时把链表的头,和链表的尾部都做了赋值,这样的话,之后只要时时刻刻保持着对链表的尾部指针的更新,和头部指针的保护就没有问题了。

        蛇的问题我们解决了,之后就是食物的问题了。、

        首先对食物放置有一个基本的认识。食物不可以放在蛇的身上,食物不可以放出地图,食物一定要随机生成,啊,烦死了是不是。
 

typedef struct fd
{
    int x;
    int y;
}food_t;

 food_t *food = NULL;  //食物的初始设定

///放置食物
void setFood()
{
    int flgFood = 1;

    food = (food_t*)malloc(sizeof(food_t));
    do
    {
        snake_t *temp;

        srand((unsigned)time(NULL));
        food->x = rand() % X_MAX;
        food->y = rand() % Y_MAX;

        //开始遍历链表,确定食物不在蛇的身体上
        temp = linkHead;

        while (temp!= NULL)
        {
            if((temp->x == food->x)&&(temp->y == food->y))
            {
                flgFood = 0;
            }
            temp = temp->next;
        }

    }while(flgFood = 0);

    gotoxy(X_CHAR_WITH * (food->x + X_OFFSET), food->y +1 );
    printf("食");
}

        这段代码中细节满满,有着处理各种情况的样例,宝们慢慢看。(我认为重点是判断食物是不是在蛇身上的那一段哦)

        这之前的都是初始化的内容,正真的游戏内容现在才刚刚开始。

#define RIGHT 77    /*键盘上下左右的设定*/
#define LEFT  75
#define DOWN  80
#define UP    72

int flgEnd       = 1; //作为状态量去判断游戏是否结束
void RunGame()
{
    char ch       = RIGHT;
    int direction = RIGHT;
    int delay     = 0;

    while(1)
    {
        Sleep(1);//间隔3毫秒检测按键

        if(flgEnd == 0)
        {
            system("cls");
            GameOver();
            break;
        }



        //判断有没有输入
        if(kbhit())  //一直获取
        {

            ch = getch();
            switch(ch)
            {
            case RIGHT:
                direction = RIGHT;
                break;
            case UP:
                direction = UP;
                break;
            case LEFT:
                direction = LEFT;
                break;
            case DOWN:
                direction = DOWN;
                break;
            }
        }

        if(++delay > 10)
        {
             MoveSnake(direction);  //让蛇正真开始运动的函数,其中还包含了各种判断
             delay = 0;
        }
    }
}

        这一段代码和马上要展现的代码中,状态量的运用是程序的关键,我不敢说自己的状态量用的好,但是好歹是可以达成效果。

        同时完成延时的方法也是用了两种。第一种使用Sleep函数可以达到精确定时的效果;第二种设置delay变量,然后让delay加到特定的值也是一种方法,但是这种方法没有办法达到精确计时的效果。

        接下来的是贪吃蛇的核心程序,运动与判断。


int proDirection = RIGHT;  //判断输入的前后方向是否变化的状态量
void MoveSnake(int direction)  //移动蛇,给链表加上一个节点,  链表的头是蛇的尾巴!!!链表加上尾巴,删去链表的头
{
    //蛇头向前移动一步
    snake_t *temp = (snake_t*)malloc(sizeof(snake_t));
    temp->next    = NULL;
    linkEnd->next = temp;

    //判断方向的录入是否合理
    if(proDirection == UP && direction == DOWN)
    {
        direction = proDirection;
    }
    if(proDirection == DOWN && direction == UP)
    {
        direction = proDirection;
    }
    if(proDirection == RIGHT && direction == LEFT)
    {
        direction = proDirection;
    }
    if(proDirection == LEFT && direction == RIGHT)
    {
        direction = proDirection;
    }

    if(direction == RIGHT) //右
    {
        temp->x = linkEnd->x + 1;
        temp->y = linkEnd->y;       //这时候temp是链表的尾巴
    }
    else if(direction == LEFT)   //左
    {
        temp->x = linkEnd->x - 1;
        temp->y = linkEnd->y;      //这时候temp是链表的尾巴
    }
    else if(direction == UP)  //上
    {
        temp->x = linkEnd->x;
        temp->y = linkEnd->y-1;//这时候temp是链表的尾巴
    }
    else if(direction == DOWN)  //下
    {
        temp->x = linkEnd->x;
        temp->y = linkEnd->y+1;//这时候temp是链表的尾巴
    }
    proDirection = direction;
    linkEnd      = temp;  //蛇头

    //先打印后释放,这样只用打印一个多出来的节点
    gotoxy((temp->x + X_OFFSET) * X_CHAR_WITH, temp->y + 1);
    printf( SNAKE );

    //判断撞到了墙,看蛇头的位置,就是链表尾部的坐标
    if(linkEnd->x == -1 ||
            linkEnd->y == -1 ||
            linkEnd->x == X_MAX ||
            linkEnd->y == Y_MAX )
    {
        flgEnd = 0;
    }


    //吃食物
    if( (linkEnd->x == food->x )&& (linkEnd->y == food->y) )
    {
        free(food);
        setFood();
        Score++;
        showScore();
    }
    else
    {
        gotoxy((linkHead->x + X_OFFSET) * X_CHAR_WITH, linkHead->y+1);
        printf("  ");//注意这里以前是特殊字符,所以要有两个空格
        temp = linkHead->next;
        free(linkHead);
        linkHead = temp;  //把下一个节点又变成了头,temp到了头的位置
    }

    temp = linkHead;
    while (temp!= NULL)    //判断是不是咬到了自己
    {
        if(temp != linkEnd )
        {
            if((linkEnd->x == temp->x)&&(linkEnd->y == temp->y))
            {
                flgEnd = 0;
            }
        }
        temp = temp->next;
    }
}

        这里面完成的功能有

1,更新链表的尾部。

2,判断现在按下的按键于之前的方向是不是又冲突(冲突的话,比如蛇是往前走到,你让他往后,就会直接死掉)

3,蛇的移动。

4,游戏结束的判断。

5,是否吃到食物。

        请宝们一个一个对比作用,我就不叙述了。

        至于结束函数于分数都是很简单的东西,不在我的讲述范围,要是想要学习的话就自己去看源代码吧。

源代码:

    头文件

#ifndef _CURSOR_
#define _CURSOR_

void gotoxy(int x,int y);
void HideCursor();


#endif // _CURSOR_

控制台文件:

#include <conio.h>
#include <windows.h>

///设置光标的函数,使控制控制台变得简单

void gotoxy(int x,int y)
{
    COORD pos={x,y};
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleCursorPosition(hOut,pos);
}

void HideCursor() {  // 隐藏光标位置 ,这个函数复制代码就行
  CONSOLE_CURSOR_INFO cursor_info = {1, 0};
  SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

游戏文件:

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <malloc.h>

#include"cursor.h"

//结构体定义蛇
typedef struct snake
{
    int x;
    int y;
    struct snake *next;
}snake_t;

snake_t *linkHead = NULL;
snake_t *linkEnd  = NULL;

typedef struct fd
{
    int x;
    int y;
}food_t;

 food_t *food = NULL;

#define X_MAX 30  /* 水平最大值 */
#define Y_MAX 30  /* 竖直最大值 */
#define X_CHAR_WITH 2 /* 汉字宽度为2,英文为1 */
#define X_OFFSET 8  /* X偏置 */
#define RIGHT 77    /*键盘上下左右的设定*/
#define LEFT  75
#define DOWN  80
#define UP    72

#define SNAKE "蛇"

int flgEnd       = 1; //作为状态量去判断游戏是否结束
int proDirection = RIGHT;  //判断输入的前后方向是否变化的状态量
int Score        = 0;

void GameStart()
{
    system("mode con cols=100 lines=40");
}

void Welcome()
{
    printf("\n\n\n");
    printf("\t\t\t======================================================\n");
    printf("\t\t\t=                                                    =\n");
    printf("\t\t\t=            welcome to snake_t's world              =\n");
    printf("\t\t\t=                 Programmer : 乔子轩                =\n");
    printf("\t\t\t=                                                    =\n");
    printf("\t\t\t=                                                    =\n");
    printf("\t\t\t======================================================\n");
    system("pause");
    system("cls");
}

void showScore()
{
    gotoxy((X_OFFSET ) * X_CHAR_WITH, Y_MAX+2);
    printf("分数:%d", Score);
}

void CreatMap()
{
    for(int i=0;i<(X_MAX+2);i++)
    {
        gotoxy(X_CHAR_WITH * (i + X_OFFSET-1),0);
        printf("墙");
        gotoxy(X_CHAR_WITH * (i + X_OFFSET-1), Y_MAX+1);
        printf("墙");
    }
    for(int i=1; i<(Y_MAX+1);i++)
    {
        gotoxy( (X_OFFSET-1 )* X_CHAR_WITH ,i);
        printf("墙");
        gotoxy((X_OFFSET + X_MAX ) * X_CHAR_WITH, i);
        printf("墙");
    }
    showScore();
}

///显示蛇
void ShowSnake()
{
    snake_t *temp;
    //显示
    temp = linkHead;

    while (temp!= NULL)
    {
        gotoxy((temp->x + X_OFFSET) * X_CHAR_WITH, temp->y + 1);
        printf(SNAKE);
        temp = temp->next;
    }
    gotoxy(0,31);
}

///放置食物
void setFood()
{
    int flgFood = 1;

    food = (food_t*)malloc(sizeof(food_t));
    do
    {
        snake_t *temp;

        srand((unsigned)time(NULL));
        food->x = rand() % X_MAX;
        food->y = rand() % Y_MAX;

        //开始遍历链表,确定食物不在蛇的身体上
        temp = linkHead;

        while (temp!= NULL)
        {
            if((temp->x == food->x)&&(temp->y == food->y))
            {
                flgFood = 0;
            }
            temp = temp->next;
        }

    }while(flgFood = 0);

    gotoxy(X_CHAR_WITH * (food->x + X_OFFSET), food->y +1 );
    printf("食");
}



///初始化蛇
void InitialSnake()
{
    snake_t *temp;

    linkHead       = (snake_t*)malloc(sizeof(snake_t));
    linkHead->x    = 5;
    linkHead->y    = Y_MAX / 2;
    linkHead->next = NULL;

    temp = linkHead ;

    for(int i = 1; i < 3; i++)   ///这里让循环的次数多一点点有利于我们检查
    {
        snake_t *p = (snake_t*)malloc(sizeof(snake_t));
        p->x       = linkHead->x + i;
        p->y       = linkHead->y;
        p->next    = NULL;

        temp->next = p;
        temp       = p;
    }
    linkEnd = temp; //找尾巴

    ShowSnake();
}

void MoveSnake(int direction)  //移动蛇,给链表加上一个节点,  链表的头是蛇的尾巴!!!链表加上尾巴,删去链表的头
{
    //蛇头向前移动一步
    snake_t *temp = (snake_t*)malloc(sizeof(snake_t));
    temp->next    = NULL;
    linkEnd->next = temp;

    //判断方向的录入是否合理
    if(proDirection == UP && direction == DOWN)
    {
        direction = proDirection;
    }
    if(proDirection == DOWN && direction == UP)
    {
        direction = proDirection;
    }
    if(proDirection == RIGHT && direction == LEFT)
    {
        direction = proDirection;
    }
    if(proDirection == LEFT && direction == RIGHT)
    {
        direction = proDirection;
    }

    if(direction == RIGHT) //右
    {
        temp->x = linkEnd->x + 1;
        temp->y = linkEnd->y;       //这时候temp是链表的尾巴
    }
    else if(direction == LEFT)   //左
    {
        temp->x = linkEnd->x - 1;
        temp->y = linkEnd->y;      //这时候temp是链表的尾巴
    }
    else if(direction == UP)  //上
    {
        temp->x = linkEnd->x;
        temp->y = linkEnd->y-1;//这时候temp是链表的尾巴
    }
    else if(direction == DOWN)  //下
    {
        temp->x = linkEnd->x;
        temp->y = linkEnd->y+1;//这时候temp是链表的尾巴
    }
    proDirection = direction;
    linkEnd      = temp;  //蛇头

    //先打印后释放,这样只用打印一个多出来的节点
    gotoxy((temp->x + X_OFFSET) * X_CHAR_WITH, temp->y + 1);
    printf( SNAKE );

    //判断撞到了墙,看蛇头的位置,就是链表尾部的坐标
    if(linkEnd->x == -1 ||
            linkEnd->y == -1 ||
            linkEnd->x == X_MAX ||
            linkEnd->y == Y_MAX )
    {
        flgEnd = 0;
    }


    //吃食物
    if( (linkEnd->x == food->x )&& (linkEnd->y == food->y) )
    {
        free(food);
        setFood();
        Score++;
        showScore();
    }
    else
    {
        gotoxy((linkHead->x + X_OFFSET) * X_CHAR_WITH, linkHead->y+1);
        printf("  ");//注意这里以前是特殊字符,所以要有两个空格
        temp = linkHead->next;
        free(linkHead);
        linkHead = temp;  //把下一个节点又变成了头,temp到了头的位置
    }

    temp = linkHead;
    while (temp!= NULL)    //判断是不是咬到了自己
    {
        if(temp != linkEnd )
        {
            if((linkEnd->x == temp->x)&&(linkEnd->y == temp->y))
            {
                flgEnd = 0;
            }
        }
        temp = temp->next;
    }
}


void GameOver()
{
    gotoxy(16,15);
    printf("GAME OVER");
    gotoxy(16,16);
    printf("你的最终得分是%d分",Score);
    system("pause");
}

//让蛇动起来
void RunGame()
{
    char ch       = RIGHT;
    int direction = RIGHT;
    int delay     = 0;

    while(1)
    {
        Sleep(1);//间隔3毫秒检测按键

        if(flgEnd == 0)
        {
            system("cls");
            GameOver();
            break;
        }



        //判断有没有输入
        if(kbhit())  //一直获取
        {

            ch = getch();
            switch(ch)
            {
            case RIGHT:
                direction = RIGHT;
                break;
            case UP:
                direction = UP;
                break;
            case LEFT:
                direction = LEFT;
                break;
            case DOWN:
                direction = DOWN;
                break;
            }
        }

        if(++delay > 10)
        {
             MoveSnake(direction);
             delay = 0;
        }
    }
}






int main()
{
    //开始游戏,把控制台变得可以控制
    //隐藏光标
    HideCursor();
    //开始界面
    GameStart();
    Welcome();
    //画地图
    CreatMap();
    //画蛇
    InitialSnake();
    //放食物
    setFood();
    //游戏相关的运行
    RunGame();
}

        要是大家不会文件处理,就把三段就粘贴在一个文件中也没有问题,就是少了一点层次感。

        拜拜了亲们,期待下次再见!

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JOJO桑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值