C语言版贪吃蛇:第二部分

C语言版贪吃蛇:第二部分

本章将学习以下内容

  • 学习蛇的运动原理
    • 利用链表存储 蛇每一节的坐标
  • 根据坐标打印出蛇
  • 利用随机数生成食物
  • 打印食物

如何运动?

运动的原理:

类似动画的原理,蛇每移动一次,重新绘制一次图像,通过不断的重绘,形成连续的动作。

如何运动:

  • 方法一:
    • 用数组存储每一节蛇身的坐标
    • 运动一次,蛇头变换一次坐标,后面的蛇身拷贝前一节蛇身的坐标。
  • 方法二:

    • 考虑到,每运动一次,实际上只有蛇头和蛇尾的位置有变化。
    • 可以通过将蛇尾接到蛇头前面,就可以得到新的蛇位置。

比较两种方法,可以发现方法二更为简便。
基于方法二的特点,数组不能满足我们的需求

  • 若蛇头放在数组的首位,蛇尾加不到蛇头的前面。
  • 若蛇尾放在数组的首位,随着不断运动,数组不断扩大。

所以这里就需要介绍一种新的数据结构
也就是: 链表


蛇的存储结构——链表

这里的链表用数组实现,没有指针。

简单的说,这里的链表就是一个结构体数组,数组里的每一个元素都存有

  • 这一节蛇的坐标 ( x , y )
  • 下一节在数组中的位置 next
  • 上一节在数组中的位置pre

当我们需要把蛇尾接到蛇头的前面时,我们只需

  1. 要把蛇尾的 pre 设置为蛇头
  2. 把蛇头的next 设置为蛇尾
  3. head指向新的蛇头
  4. nail指向新的蛇尾(原蛇尾的前一个)

通过这种方法,我们能够保证蛇在数组里的存储区段不变。

除此之外,再定义两个变量,存储蛇头,蛇尾在数组中的位置。
打印时,从蛇头开始,利用 next,不断往蛇尾移即可。


蛇的打印

  1. 定义相关变量
// 定义一个结构体用来存储蛇的每节身体的坐标
struct snake
{
    int x, y;
    int next; 
    //保存当前节点的下一个节点的位置(数组下标)
    int pre;  
    //保存上一个节点的位置
} S[100];

// 定义三个变量存 蛇长,蛇头,蛇尾
int lenth, head, nail;
  1. 定义个函数printsnake( )打印蛇
// 打印蛇
void printsnake()
{
    int i, j = head;
    for (i = 1; i <= lenth; i++)
    {
        // 注意 里面的参数是j,j从蛇头移动到蛇尾
        gotoxy(Snake[j].x, Snake[j].y);
        printf("*");
        j = Snake[j].next;
    }
}
  1. 在init初始化函数加入蛇的初始化信息
void init()
{
    printwall();
    // 初始化蛇头蛇尾的坐标
    Snake[1].x = 4;
    Snake[1].y = 4;
    Snake[2].x = 4;
    Snake[2].y = 5;
    head = 1;
    nail = 2;
    lenth = 2;
    Snake[head].next = nail;
    Snake[nail].pre = head;
    printsnake();
}
  1. 编译运行,测试是否正常打印

这里写图片描述


食物

  1. 我们需要一个能够生成随机数的函数用来产生食物的坐标,
  2. 同时还要一个函数判断坐标是否合法

    1. 不能与围墙重合
    2. 不能与蛇重合


产生随机数的方法

srand((unsigned long)time(0));
    a = rand() % 10;
  • 在包含了头文件time.h后
  • 在程序的任意位置加上这两段代码
  • 运行后,a将获得一个小于10的一个随机整数。


判断食物是否合法

int ok_food()
{
    // 判断是否和墙重合
    // 横坐标不能等于1,或70 ; 纵坐标不能等于1,或20
    if(food_x==1||food_x==70) return 0;
    if(food_y==1||food_y==20) return 0;
    // 判断是否和蛇重合
    int j = head;
    for(int i = 1; i <= lenth; i++){
        if(food_x == Snake[j].x && food_y == Snake[j].y) return 0;
        j = Snake[j].next;
    }
    // 都没有,则返回1
    return 1;
}


随机打印食物

// 定义全局变量存储食物位置
int food_x,food_y;

// 打印食物 @
void printfood()
{
    // 不断产生随机数,知道坐标符合要求
    do
    {
        srand((unsigned long)time(0));
        food_x = rand() % 70;
        food_y = rand() % 20;
    }while(!ok_food());
    // ok_food()为判断食物是否合法的函数,合法返回1,不合法返回0
    gotoxy(food_x,food_y);
    printf("@");
}


在初始化函数init( ), 加入printfood( )

void init()
{
    //..
    //上一章代码
    //..
    // 第一次打印食物
    printfood();
}
测试是否正常工作

这里写图片描述


本章完整代码

/*
    这里是贪吃蛇源代码

    chapter 1:
    解释下头文件:
    time.h 生成随机数要用到
    windows.h 要用到里面的函数 gotoxy
    ---------
    1.画围墙

    ===========================

    chapter 2:
    目标:绘制蛇,食物
        蛇的存储结构:简单的链表(数组实现功能)
        食物:随机数的生成

*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <windows.h>

// 定义一个结构体用来存储蛇的每节身体的坐标
struct snake
{
    int x, y;
    int next; //保存当前节点的下一个节点的位置(数组下标)
    int pre;  //保存上一个节点的位置
} Snake[100];

// 定义三个变量存 蛇长,蛇头,蛇尾
int lenth, head, nail;
// 食物位置
int food_x, food_y;

// 光标移动函数
void gotoxy(int x, int y)
{
    COORD coord = {x, y};
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
// 打印蛇
void printsnake()
{
    int i, j = head;
    for (i = 1; i <= lenth; i++)
    {
        // 注意 里面的参数是j,j从蛇头移动到蛇尾
        gotoxy(Snake[j].x, Snake[j].y);
        printf("*");
        j = Snake[j].next;
    }
}
// 判断食物是否合法
int ok_food()
{
    // 判断是否和墙重合
    // 横坐标不能等于1,或70 ; 纵坐标不能等于1,或20
    if(food_x==1||food_x==70) return 0;
    if(food_y==1||food_y==20) return 0;
    // 判断是否和蛇重合
    int j = head;
    for(int i = 1; i <= lenth; i++){
        if(food_x == Snake[j].x && food_y == Snake[j].y) return 0;
        j = Snake[j].next;
    }
    // 都没有,则返回1
    return 1;
}
// 打印食物
void printfood()
{
    // 不断产生随机数,知道坐标符合要求
    do
    {
        srand((unsigned long)time(0));
        food_x = rand() % 70;
        food_y = rand() % 20;
    }while(!ok_food());
    // ok_food()为判断食物是否合法的函数,合法返回1,不合法返回0
    gotoxy(food_x,food_y);
    printf("@");
}

// 绘制围墙
void printwall()
{
    /*
        chapter 1 绘制围墙部分
        围墙大小 70*20, 长70,宽20
    */

    // 绘制水平围墙,
    for (int i = 1; i <= 70; i++)
    {
        gotoxy(i, 1);
        printf("#");
        gotoxy(i, 20);
        printf("#");
    }
    // 绘制竖直围墙
    for (int i = 1; i <= 20; i++)
    {
        gotoxy(1, i);
        printf("#");
        gotoxy(70, i);
        printf("#");
    }
}
void init()
{
    printwall();
    // 初始化蛇
    Snake[1].x = 4;
    Snake[1].y = 4;
    Snake[2].x = 4;
    Snake[2].y = 5;
    head = 1;
    nail = 2;
    lenth = 2;
    Snake[head].next = nail;
    Snake[nail].pre = head;
    // 第一次打印蛇
    printsnake();
    // 第一次打印食物
    printfood();
}
int main()
{
    init(); // 初始化函数,绘制围墙
    system("pause");
    return 0;
}
#include <stdio.h> #include <windows.h> #include <conio.h> #include <time.h> //游戏窗口 #define FrameX 4 //游戏窗口左上角的X轴坐标 #define FrameY 4 //游戏窗口左上角的Y轴坐标 #define Frame_height 20 //游戏窗口的高度 #define Frame_width 20 //游戏窗口的宽度 //定义全局变量 int i,j; int a[2]; //用于记住蛇尾坐标,其中a[0]、a[1]分别表示横、竖坐标 //声明蛇的结构体 struct Snake { int x[100]; //蛇的横坐标,其中x[0]表示蛇尾的横坐标,x[N-1]表示蛇头的横坐标 int y[100]; //蛇的竖坐标,其中y[0]表示蛇尾的竖坐标,y[N-1]表示蛇头的竖坐标 int count; //蛇吃食物的个数 int length; //蛇的长度 int speed; //蛇的速度 }; //声明食物的结构体 struct Food { int x; //食物的横坐标 int y; //食物的竖坐标 }; /******光标移到指定位置**************************************************************/ void gotoxy(HANDLE hOut, int x, int y) //UNKNOW { COORD pos; pos.X = x; //横坐标 pos.Y = y; //纵坐标 SetConsoleCursorPosition(hOut, pos); } /******设置文本为绿色*****************************************************************/ void Set_TextColor_Green (void) { HANDLE Handle = GetStdHandle(STD_OUTPUT_HANDLE); //UNKNOW SetConsoleTextAttribute(Handle, FOREGROUND_INTENSITY | FOREGROUND_GREEN); //UNKNOW } /******制作游戏窗口******************************************************************/ void make_frame() { HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); //定义显示器句柄变量 gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+13); //打印选择菜单 printf("Esc 退出游戏"); gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+15); printf("长按方向键:加速"); gotoxy(hOut,FrameX,FrameY); //打印框角 printf("╔"); gotoxy(hOut,FrameX+2*Frame_width-2,FrameY); printf("╗"); gotoxy(hOut,FrameX,FrameY+Frame_height); printf("╚"); gotoxy(hOut,FrameX+2*Frame_width-2,FrameY+Frame_height); printf("╝"); for(i=2;i<2*Frame_width-2;i+=2) { gotoxy(hOut,FrameX+i,FrameY); printf("═"); //打印上横框 } for(i=2;i<2*Frame_width-2;i+=2) { gotoxy(hOut,FrameX+i,FrameY+Frame_height); printf("═"); //打印下横框 } for(i=1;i<Frame_height;i++) { gotoxy(hOut,FrameX,FrameY+i); printf("║"); //打印左竖框 } for(i=1;i<Frame_height;i++) { gotoxy(hOut,FrameX+2*Frame_width-2,FrameY+i); printf("║"); //打印右竖框 } gotoxy(hOut,FrameX+Frame_width-5,FrameY-2); //打印游戏名称 Set_TextColor_Green (); //设置蛇为绿色 printf("贪吃蛇游戏"); } /******结束菜单*******************************************************************/ void over_game() { system("cls"); printf("\n\n\n\n\n\n\n\n\t\t\t\t游戏结束\n\n\n"); Sleep(2000); getch(); } /******菜单信息***************************************************************/ void print_information(HANDLE hOut,struct Snake *snake,struct Food *food) { gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+1); printf("level : %d",snake->count/5+1); //打印游戏等级 gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+3); printf("score : %d",10*snake->count); //打印游戏得分 gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+5); printf("eat food : %d",snake->count); //打印产生食物个数 gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+7); printf("speed : %dms",snake->speed); //打印游戏速度 gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+9); printf("foodX : %d",food->x); //打印食物的横坐标 gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+11); printf("foodY : %d",food->y); //打印食物的竖坐标 } /******初始化蛇**********************************************************************/ void init_snake(struct Snake *snake) { snake->x[0]=FrameX+2; //初始化蛇的横坐标 snake->y[0]=FrameY+Frame_height/2; //初始化蛇的竖坐标 snake->speed=300; //初始化蛇的速度为300ms snake->length=3; //初始化蛇的长度为3节 snake->count=0; //初始化蛇吃的个数为0 for(i=1;i<snake->length;i++) {/* 依次得到蛇身、蛇头的坐标 */ snake->x[i]=snake->x[i-1]+2; snake->y[i]=snake->y[i-1]; } } /******移动蛇*************************************************************************/ void move_snake(HANDLE hOut,struct Snake *snake) { gotoxy(hOut,snake->x[0],snake->y[0]); printf(" "); /* 清除蛇尾*/ for(i=1;i<snake->length;i++) {/* 后一节的坐标依次取代前一节的坐标 */ snake->x[i-1]=snake->x[i]; snake->y[i-1]=snake->y[i]; } } /******打印蛇*************************************************************************/ void print_snake(HANDLE hOut,struct Snake *snake) { for(i=0;i<snake->length;i++) { gotoxy(hOut,snake->x[i],snake->y[i]); if(i==0) { printf("○"); //打印蛇尾 } else if(i==snake->length-1) { printf("¤"); //打印蛇头 } else { printf("⊙"); //打印蛇身 } } } /******随机产生食物*******************************************************************/ void get_food(HANDLE hOut,struct Snake *snake,struct Food *food) { srand((unsigned)time(NULL)); //初始化随机数 while(1) {/* 产生食物的条件:1.在游戏窗口内 2.不在蛇的身上 */ food->x = rand() % (Frame_width-1); food->y = rand() % Frame_height; if( food->x==0 || food->y==0 ) { continue; } food->x = 2*food->x + FrameX; //得到食物的横坐标 food->y+=FrameY; //得到食物的竖坐标 for(i=0;i<snake->length;i++) {/* 判断食物是否在蛇的身上,如果在蛇身上,则重新产生;否则,打印蛇身 */ if( food->x==snake->x[i] && food->y==snake->y[i] ) { break; } } if(i==snake->length) { gotoxy(hOut,food->x,food->y); printf("⊙"); break; } } } /******吃食物***************************************************************************/ void eat_food(HANDLE hOut,struct Snake *snake,struct Food *food) { if( snake->x[snake->length-1]==food->x && snake->y[snake->length-1]==food->y ) {/* 如果蛇头位置与食物位置相同,吃食物 */ snake->length++; //吃一个食物,蛇身增长一节 for(i=snake->length-1;i>=1;i--) {/* 蛇后节坐标依次赋值给蛇前一节的坐标,依次得到蛇身及蛇头的坐标 */ snake->x[i]=snake->x[i-1]; snake->y[i]=snake->y[i-1]; } snake->x[0]=a[0]; //得到蛇尾移动前的横坐标 snake->y[0]=a[1]; //得到蛇尾移动前的竖坐标 get_food(hOut,snake,food); //重新产生食物 snake->count++; //食物的个数增1 if( snake->count%5==0 ) {/* 当蛇吃Up_level个食物时,速度加快Up_speed毫秒并且升一级 */ snake->speed-=50; } } } /******穿墙**********************************************************************************/ void through_wall(HANDLE hOut,struct Snake *snake,char ch) { if( ch==72 && snake->y[snake->length-1]==FrameY) { snake->y[snake->length-1] = FrameY+Frame_height-1; //如果蛇在上框且向上移动,穿墙 } if( ch==80 && snake->y[snake->length-1]==FrameY+Frame_height ) { snake->y[snake->length-1] = FrameY+1; //如果蛇在下框且向下移动,穿墙 } if( ch==75 && snake->x[snake->length-1]==FrameX ) { snake->x[snake->length-1] = FrameX+2*Frame_width-4; //如果蛇在左框且向左移动,穿墙 } if( ch==77 && snake->x[snake->length-1]==FrameX+2*Frame_width-2 ) { snake->x[snake->length-1] = FrameX+2; //如果蛇在右框且向右移动,穿墙 } } /******判断蛇是否死**************************************************************************/ int if_die(struct Snake *snake) {/* 当蛇头碰到自身时,蛇死 ,返回值为0 */ for(i=0;i<snake->length-1;i++) { if( snake->x[snake->length-1]==snake->x[i] && snake->y[snake->length-1]==snake->y[i] ) { return 0; } } return 1; } /******开始游戏*******************************************************************************/ void start_game() { unsigned char ch=77; //定义用于接收键盘输入的字符变量 HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); //定义显示器句柄变量 struct Snake s,*snake=&s; //定义蛇的结构体指针并指向蛇的结构体 struct Food f,*food=&f; //定义食物的结构体指针并指向食物的结构体 make_frame(); //制作游戏窗口 init_snake(snake); //初始化蛇 get_food(hOut,snake,food); //随机产生食物 while(1) { print_information(hOut,snake,food); //打印菜单信息 a[0]=snake->x[0]; //记住蛇尾的横坐标 a[1]=snake->y[0]; //记住蛇尾的竖坐标 j=0; if(kbhit()) //unknow {/* 判断是否按下键盘,如果按下,ch接收键盘输入 */ ch=getch(); if(kbhit()) {/* 如果长按键盘,则加速 */ Sleep(20); //unknow j=1; } } switch(ch) { case 72: {/* 向上移动 */ move_snake(hOut,snake); //移动蛇 snake->y[snake->length-1]-=1; //蛇头的竖坐标向上移,即减1 break; } case 80: {/* 向下移动 */ move_snake(hOut,snake); //移动蛇 snake->y[snake->length-1]+=1; //蛇头的竖坐标向下移,即加1 break; } case 75: {/* 向左移动 */ move_snake(hOut,snake); //移动蛇 snake->x[snake->length-1]-=2; //蛇头的横坐标向左移,即减2 break; } case 77: {/* 向右移动 */ move_snake(hOut,snake); //移动蛇 snake->x[snake->length-1]+=2; //蛇头的横坐标向右移,即加2 break; } } through_wall(hOut,snake,ch); //穿墙 eat_food(hOut,snake,food); //吃食物 print_snake(hOut,snake); //打印蛇 if( if_die(snake)==0 || ch==27 || snake->speed==50 ) {/* 游戏结束条件:1.蛇碰到自身 2.按Esc键 3.速度为50ms */ gotoxy(hOut,FrameX+Frame_width-2,FrameY+Frame_height/2-1); printf("Game Over"); Sleep(2000); break; } if(j==0) { Sleep(snake->speed); //延迟时间 } else { Sleep(10); } } } int main() { system("color 0D"); //设置文本为粉红色 start_game(); //开始游戏 over_game(); //结束游戏 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值