C语言版贪吃蛇:第二部分
本章将学习以下内容
- 学习蛇的运动原理
- 利用链表存储 蛇每一节的坐标
- 根据坐标打印出蛇
- 利用随机数生成食物
- 打印食物
如何运动?
运动的原理:
类似动画的原理,蛇每移动一次,重新绘制一次图像,通过不断的重绘,形成连续的动作。
如何运动:
- 方法一:
- 用数组存储每一节蛇身的坐标
- 运动一次,蛇头变换一次坐标,后面的蛇身拷贝前一节蛇身的坐标。
方法二:
- 考虑到,每运动一次,实际上只有蛇头和蛇尾的位置有变化。
- 可以通过将蛇尾接到蛇头前面,就可以得到新的蛇位置。
比较两种方法,可以发现方法二更为简便。
基于方法二的特点,数组不能满足我们的需求
- 若蛇头放在数组的首位,蛇尾加不到蛇头的前面。
- 若蛇尾放在数组的首位,随着不断运动,数组不断扩大。
所以这里就需要介绍一种新的数据结构
也就是: 链表
蛇的存储结构——链表
这里的链表用数组实现,没有指针。
简单的说,这里的链表就是一个结构体数组,数组里的每一个元素都存有
- 这一节蛇的坐标 ( x , y )
- 下一节在数组中的位置
next
- 上一节在数组中的位置
pre
当我们需要把蛇尾接到蛇头的前面时,我们只需
- 要把蛇尾的
pre
设置为蛇头 - 把蛇头的
next
设置为蛇尾 - 将
head
指向新的蛇头 - 将
nail
指向新的蛇尾(原蛇尾的前一个)
通过这种方法,我们能够保证蛇在数组里的存储区段不变。
除此之外,再定义两个变量,存储蛇头,蛇尾在数组中的位置。
打印时,从蛇头开始,利用 next
,不断往蛇尾移即可。
蛇的打印
- 定义相关变量
// 定义一个结构体用来存储蛇的每节身体的坐标
struct snake
{
int x, y;
int next;
//保存当前节点的下一个节点的位置(数组下标)
int pre;
//保存上一个节点的位置
} S[100];
// 定义三个变量存 蛇长,蛇头,蛇尾
int lenth, head, nail;
- 定义个函数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;
}
}
- 在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();
}
- 编译运行,测试是否正常打印
食物
- 我们需要一个能够生成随机数的函数用来产生食物的坐标,
同时还要一个函数判断坐标是否合法
- 不能与围墙重合
- 不能与蛇重合
产生随机数的方法
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;
}