附件1:
华南农业大学信息学院
课程设计实验
实验题目:贪吃蛇
一、分析题目要求
经过我们3人小组的讨论,最终确定了用C 语言写贪吃蛇的游戏。
首先,要想实现贪吃蛇就要有游戏界面;其次,还要有写贪吃蛇的设计思
路以及对于数据结构的操作;另外,还应该有游戏的规则实现以及界面的美化
等。因此,我们把这个实验任务分成了3个部分,xxx 具体负责的是蛇的构
造以及食物的生成,xxx 负责界面,xxx 负责游戏规则。
要实现贪吃蛇能够前进,我们需要用到链表来存取蛇的身体,以及蛇所在
的方位。一般情况下,需要用到链表节点的删除,增加,输出等操作。另外,
产生随见事物使用系统自带的Rand ,随机定位一个游戏区域里面的方块,然后
使用gotoxy 函数可以定位到这个方块的坐标,并且涂上颜色,这样就产生了肉
眼可见的食物。关于蛇体,我们将蛇体的每个方块添加到链表里面,使用的是
snake,为什么不用数组,原因是数组的长度总是有限的,你不知道蛇体里面最
终存放多少方格,而且数组比较繁琐。通过不断的生成蛇头以及删除蛇尾循环
画出列表中的每个对象,就成功的画出了蛇体。设置蛇体的初始长度只有蛇头,
并设置蛇头的初始位置为居中。
(1) 各个函数命名,包含的参数,返回值类型
定义#include 这个头文件,它包含了其他Windows 头文件,这些
头文件的某些也包含了其他头文件:
static COORD position;//显示当前指针的坐标
static COORD food_position;//显示当前食物的坐标
static HANDLE hOut;//获取标准输出的句柄。命令行的程序会把字符输出到屏幕
定义#include 头文件,是Console Input/Output(控制台输入输出)
的简写,其中定义了通过控制台进行数据输入和数据输出的函数,主要是通过
按键盘产生的对应操作,比如getch()函数,而在Fsnake.h 中定义的
#define LEFT 75 #define UP 72 #define DOWN 80 #define RIGHT 77 #define ESC 27
#define SPACE 32 #define ENTER 13 即为getch()函数从键盘接收到的各种键值。
在Fsnake.h 中定义的:
#define GREEN 0xe #define L_GREEN 0xB #define RED 0xC 是用到
Windows.h 函数中的setcolor 函数:
以下SetConsoleTextAttribute(hOut, color); //设置字体颜色
各颜色的代码如下:
10=淡绿色 0xa
3=湖蓝色 11=淡浅绿色 0xb
4=红色 12=淡红色 0xc
5=紫色 13=淡紫色 0xd
6=黄色 14=淡黄色 0xe
7=白色 15=亮白色 0xf
在Fsnake.h 中定义的:
#define WIDTH 40
#define HEIGHT 24是地图的长和宽
生成地图的函数:
void start()//游戏地图中的开始界面
void gotoxy(int x, int y, int color)//控制命令行光标位置
int 类型的横纵坐标x ,y ,应经声明的color 。
void map()//生成地图
void HideCursor()//隐藏光标
此外,还有其他的操作函数:
void gotoxy(int x, int y, int color);
void map();
void HideCursor();
void cre_food();
void cre_snake(int x, int y);
int get_key();
void start();
void wipe(int x, int y);
void move();
int is_eat();
int is_dead();
void outputsnake();
void levelscore();
void deteSnaketail();
void checkinput();
(2)、输入输出的形式与达到的功能:
1、定义蛇的结构体为
typedef struct snake
{
int x;
int y;
struct snake *pre;
struct snake *next;
} NODESNAKE; //蛇链表
2、定义运行界面的边框函数为void map();该函数没有输入,输出的
是贪吃蛇的游戏界面范围。图示如下:
3、定义void start();作为游戏开始时候的提示,该函数没有参数,
但是需要输入数据类型的只有fsnake.h 中定义的静态数值”ESC ”和
“ENTER ”;它的输出是清除游戏界面内的文字;图示如下:
4、定义函数void cre_food( );为贪吃蛇创造食物;该函数没有参数,也没
有返回值;并且该函数不需要输入,它由自己用rand( )创造伪随机数用于程
序;该函数的输出即为随机产生的食物,图示如下:
5、定义函数int is_dead( );用于判断蛇是否死亡,死亡有两种形式:一是撞
到自己,二是撞到墙壁;is_dead()的返回值为0或1用于判断蛇的状态,他
没有参数,也不需要输入输出数据。
6、定义函数void move();用于控制贪吃蛇的移动,它所调用的函数有检
测输入函数void checkinput();检测是否吃到食物函数int is_eat();以及可能
会调用删除尾节点函数void deteSnaketail();创建食物函数void cre_food();和
输出分数等级函数void levelscore();并且该函数并不需要输入变量和输出某
一个值。
7、定义函数void outputsnake();用于输出蛇的状态,并且及时输出的前
进方向,该函数没有输入但是输出蛇的身体。
8、游戏一开始按enter 键进入游戏,按↑、↓、←、→控制蛇分别向上、
下、左、右不同方向运动,按空格键暂停,按任意键继续,按Esc 退出。如
果蛇死亡了,还可以按1继续,按任意键退出游戏。程序所达到的功能: 开
始,创建食物,创建蛇,判断蛇是否吃到食物,蛇是否死亡,控制蛇移动的
方向,等级,计分,速度,是否重新开始等功能。
9、我们选的是把地图范围和开始页面设置成绿色,左边工具栏与提示栏设
置为黄色,分数与等级为红色,食物也为红色,蛇为黄色正方形。地图设置
为长为40格,宽为24格,贪吃蛇每移动一次为一格,在横坐标0-8格之内
为工具栏,9-39格是贪吃蛇游戏的地图。
void start()//游戏地图中的开始界面
void gotoxy(int x, int y, int color)//控制命令行光标位置
输入横纵坐标x ,y 把该格定义为color 颜色
void map()//生成地图
生成贪吃蛇的游戏地图,与工具栏,开始页面
void HideCursor()//隐藏光标
隐藏输入的光标
二、解题思路
(1)、游戏界面的生成
1、游戏界面生成思路:
根据codeblocks 的输出框的大小,先找出游戏方框的大小,然后输出游
戏的边框以及文字说明。制作游戏的窗体布局。其中包括窗体的大小,位置,
添加按钮组件,在窗体上面划定游戏区域等。我们需要知道的只是边框的高
度以及宽度。
2、伪代码实现:
For()/*循环次数不超过边框宽度*/
绘出第一行墙壁;
For()/*循环次数不超过边框高度-1*/
绘出中间的竖直部分墙壁;
For()/*循环次数不超过边框宽度*/
绘出最后一行墙壁;
/*通过定为坐标来定为光标*/
写出各个需要提示的游戏说明;
(2)、创建食物和蛇的身体的实验思路:
由于食物的生成需要实现随机分布,所以要用到rand();这个函数来实现 产生随机数,只不过要注意的是我们需要判断食物的产生是否在游戏区域以
及是否和之前产生的蛇是否位置重叠。伪代码实现如下:
P=头指针;
While(p !=NULL){
If (随机数=p的数据域);
则 返回”p!=NULL”之前进行循环操作,;
P=p->next;
}
如果蛇头为空,那么将坐标定位到游戏界面正中央,并且将该处的坐标值
赋给头结点;如果蛇头不为空,那么将创建一个新的蛇头并且将现在的蛇头
下一个坐标赋值给新的蛇头。并且以此循环,直到蛇死亡、游戏结束。伪代
码实现如下:
P=游戏界面坐标;
head->pre=p;
P->next=head;
P->pre=NULL;
Head=p;
(3)、检测键盘是否输入实验思路:
首先要注意的是,我们在游戏时,并不是来自键盘上所有的输入都可以实现
蛇的移动,因此我们需要判断键盘上面的输入是否是我们想要的输入。至于关
于键盘的输入由队友来写,我想说的是,通过方向键设置蛇头的移动,蛇头每
移动一次位置就会变化到另外一个方块,对应的坐标x,y 都是有相应的变化,
例如按下UP 键,坐标的变化就是,x 不变,y 减1。
(4)、删去蛇尾思路:
在这里,只需要对蛇链表进行删除操作,不过要注意的是,在删除之前要
对蛇尾的光标进行擦除操作。最后要注意释放已删除的节点;
(5)、蛇在界面上的输出思路:
我的思路是,为了避免删除蛇尾的操作对整个蛇移动界面的影响,我觉得可
以对蛇链表进行两次遍历,一次是遍历擦除界面上所有的图案,第二次是直接
遍历输出整个蛇的形态。伪代码实现为:
While(p!=NULL){
擦除p 对应坐标的图像;
P=p->next;
}
While(p!=NULL){
定为p 点对应的坐标;
画出蛇的一节;
P=p->next;
}
(6)游戏等级、分数、速度的思路:游戏一开始吃一个食物得10分,当得分足够时升级,吃一个食物得分相应增加,速度也相应加快。用switch 选择相应功能。
(7)是否吃到食物的思路:通过判断蛇头的位置是否与食物位置重合来判断。有函数int is_eat()。
(8)判断蛇是否死亡的思路:蛇的死亡分两种:咬到自己与碰到边界;通过判断蛇头是否与边界或者自己的身体重合来判断是否死亡;有函数nt is_dead()。
三、调试分析
(1)、调试过程中遇到的问题:
首先遇到的问题是关于界面的和怎么样从键盘输入的一些问题,这些函数都
与include库函数有关,经过一番查找,我们找到了关于怎么样从
键盘直接输入数据的的方法,以及在界面上定位光标。因为队友主要负责这个
所以我不在赘述。其次,问体在于怎么样实现贪吃蛇的移动,由于最开始我们
只想到通过增加蛇头以及删除蛇尾的方法来移动,却没有准确判断吃到食物后
蛇的长度变化。
另外,在游戏结束后重新开始进行第二轮游戏时,出现了蛇的长度不变,分
数出现混乱的情况,经过修改发现是链表没有初始化,分数等级也没有初始化
的,所以才会出现乱码,以及图像的扭曲。
还有就是出现了下面这种情况:
经过讨论分析,我们发现是擦除尾部节点的时候覆盖了蛇本身的不需要擦除
的节点,然后我们把输出函数改成了先全部擦除所有的蛇的节点,然后在全部
显示,只不过这样又要多一遍循环。经过修改成功的消除了bug 。
我采用的是每实现一个功能就进行测试,分模块测试时没有出错,合起来就出错
了,经过仔细检查后发现有些语句写错了导致错误,经改正后正常运行。还有一
些for 循环使用不当,造成程序运行失败,经过多次测试后找到错误原因,改进
后正常运行。在调试过程中会发现等级与得分的数字不平衡时,需要数出他们的
横纵坐标,换为一样的,然后中间的开始界面也要确定好坐标值,将第一个字母
放在整个地图横坐标的中间。然后食物的随机生成会生成在了工具栏里,所以我
把的个随机函数要规定在横坐标9-39内生成。我采用的是每实现一个功能就进
行测试,分模块测试时没有出错,合起来就出错了,经过仔细检查后发现有些语
句写错了导致错误,经改正后正常运行。还有一些for 循环使用不当,造成程序
运行失败,经过多次测试后找到错误原因,改进后正常运行。
(2)、使用的测试数据
第一次开始等级为1时:
等级为二时:
复活之后,速度等级分数正常:
(3)、算法的效率分析和改进设想
首先,该算法的效率经过计算为o(n);不算是很复杂,需要改进的地方就
是在处理输出图像的时候,可以把循环更加简化。以及我们由于时间有限我们
还可以加入许多有趣的小东西。比如说,自主选择难度,保存上次的游戏界面,
等等其他的功能。另外,可以用文件储存游戏者之前的游戏记录,在每次开始
游戏时,游戏者可选择是继续上次游戏,或者是重新开始游戏,那么游戏记录
将会清空。还可以在一次游戏结束后询问游戏者是否重新开始,或者是复活。
复活时等级不变可是得分清零。画面的话可以增大游戏地图的面积,让游戏者
根据难度等级选择不同的地图。
(4)、经验和体会
在这个游戏的编写过程中,还是有一些不懂的地方,
但经过查阅书籍和网上学习,
都一点一点克服了,达到了学习的目的;对于每一个内容,可以试着将其进一步细分,分成多个函数来写,这样既有条理,也容易调试、修改;对于每个要求,可以一个一个来实现。如果中途需要中断程序编写时,可以做个标记,方便下次快速找到编写的位置。由于这次我设计的是游戏界面,所以先要大概打一下草稿,确定好游戏的界面要怎样布局,需要有哪些数据在里面。然后先把外层的“围栏”确定好,之后再去弄里面的东西,对于数据的位置,一定要有耐心,一个一个数清楚,不然很容易会出错。
四、源程序
Main . cpp
#include "Fsnake.h"
#include
#include
#include
#include
#include
typedef struct snake
{
int x;
int y;
struct snake *pre;
struct snake *next;
} NODESNAKE; //蛇链表
static NODESNAKE *head;
static NODESNAKE *tail;
static COORD position;
static COORD food_position;
static HANDLE hOut;
int score;
int sleep_time = 500;
int level;
int direct = RIGHT;
int main()
{
int i;
system("贪吃蛇---三贱客");
while(1){
head=NULL;
system("cls");
int level = 1; //难度
score=0;
HideCursor();
map();
start();
cre_snake(24, 12); //创建蛇头
cre_food(); //生成食物
gotoxy(3, 11, RED);
printf("%d", 1);
gotoxy(2, 15, RED);
printf("%3d", 0);
do
{
move();
extern int sleep_time;
Sleep(sleep_time);
} while (!is_dead());
gotoxy(22, 11, RED);
printf("Game over!");
gotoxy(19, 12, RED);
printf("按1重新开始,按其他退出程序\n");
gotoxy(24, 13, RED);
scanf("%d",&i);
if(i != 1){
break;
}
}
/*getch();*/
return 0;
}
void start()
{
int ch;
gotoxy(19, 11, L_GREEN);
printf("Enter键开始,Esc 退出!!!");
/*system("cls");*/
while ((ch = get_key()) != ENTER && ch != ESC);
if (ch == ESC)
exit(0);
else
{
gotoxy(19, 11, L_GREEN);
printf(""); //清空开始菜单文字
}
}
void gotoxy(int x, int y, int color)
{
position.X = 2*x;
position.Y = y;
//如果hOut 为空,则将标准输出句柄赋给它,否则保持原来的值
hOut == NULL ? (hOut = GetStdHandle(STD_OUTPUT_HANDLE)) : hOut;
SetConsoleCursorPosition(hOut, position); //将光标定位到指定坐标
SetConsoleTextAttribute(hOut, color); //设置字体颜色 }
void map()
{
int i; //行坐标
int j; //列坐标
//画出第一行
for (j = 0; j
gotoxy(j, 0, L_GREEN);
printf("■");
}
//画出中间部分
for (i = 1; i
gotoxy(0, i, L_GREEN);
printf("■");
gotoxy(8, i, L_GREEN);
printf("■");
gotoxy(WIDTH - 1, i, L_GREEN);
printf("■");
}
//画出底部
for (j = 0; j
gotoxy(j, HEIGHT - 1, L_GREEN);
printf("■");
}
gotoxy(3, 1, YELLOW);
printf("贪吃蛇");
gotoxy(1, 3, YELLOW);
printf("Snake---三贱客");
gotoxy(3, 5, YELLOW);
printf("操作方法");
gotoxy(2, 7, YELLOW);
printf("← ↑ ↓ →");
gotoxy(2, 8, YELLOW);
printf("左 上 下 右");
gotoxy(3, 10, YELLOW);
printf("等 级");
gotoxy(3, 13, YELLOW);
printf("得 分");
gotoxy(2, 17, YELLOW);
printf("提示:");
gotoxy(1, 19, YELLOW);
printf("空格键 暂停");
gotoxy(1, 21, YELLOW);
printf("任意键 继续");
}
//擦除某个坐标上的图像
void wipe(int x, int y)
{
gotoxy(x, y, YELLOW);
printf("");
}
int get_key()
{
char ch1;
ch1 = getch();
if (ch1 == ENTER || ch1 == ESC || ch1 == SPACE)
return ch1;
else if (ch1 == -32)
return getch();
else
return 0;
}
//生成食物
void cre_food()
{
int x, y;
NODESNAKE *p = head;
srand((unsigned int)time(NULL));
again:
do
{
x = 9 + rand() % (WIDTH - 10);
y = rand() %(HEIGHT - 1);
} while (x
//检查食物是否与蛇冲突
while (p != NULL){
//如果冲突,则重新生成
if (x == p->x&&y == p->y)
goto again;
p = p->next;
}
food_position.X = x;
food_position.Y = y;
gotoxy(x, y, RED);
printf("●");
}
//创建蛇的一节
void cre_snake(int x,int y)
{
int i;
NODESNAKE *pNew = NULL;
if ((pNew = (NODESNAKE *)malloc(sizeof(NODESNAKE))) == NULL) {
gotoxy(18, 11, RED);
printf("分配内存失败! 按任意键退出!");
getch();
exit(0);
}
//如果是创建蛇头
if (head == NULL)
{
head = pNew;
head->x = 24;
head->y = 12;
head->next = NULL;
head->pre = NULL;
tail = head;
}
else/*如果蛇头不为空,那么生成蛇的身体*/
{
pNew->x = x;
pNew->y = y;
head->pre = pNew;
pNew->next = head;
pNew->pre = NULL;
head = pNew;
}
}
//检测当前键盘的输入值
void checkinput(){
//检测当前键盘是否有输入
if (_kbhit())
{
int key = 0;
int temp = direct;
fflush(stdin); //清空输入缓冲
key = get_key();
if ((temp == RIGHT || temp == LEFT) && (key == UP || key == DOWN))
direct = key;
else if ((temp == UP || temp == DOWN) && (key == LEFT || key == RIGHT))
direct = key;
else if (key == SPACE)
getch();
}
//画出新蛇头
switch (direct)
{
case UP:cre_snake(head->x, head->y - 1); break;/*坐标的变化是,x 不变,y 减1。下面的同理*/
case DOWN:cre_snake(head->x, head->y + 1); break;
case LEFT:cre_snake(head->x - 1, head->y); break;
case RIGHT:cre_snake(head->x + 1, head->y); break;
}
}
//判断是否吃到食物
int is_eat()
{
if (head->x == food_position.X&&head->y == food_position.Y) return 1;
else
return 0;
}
//删去蛇尾
void deteSnaketail(){
wipe(tail->x, tail->y);/*首先擦掉蛇尾*/
NODESNAKE *p = tail;
tail = tail->pre;
tail->next = NULL;
free(p);
}
//判断蛇是否死亡
int is_dead()
{
NODESNAKE *p = head->next;
//如果咬到自己则死亡
while (p != NULL)
{
if (head->x == p->x&&head->y == p->y)
return 1;
p = p->next;
}
//如果越界则死亡
if (head->x == 8 || head->y == 0 || head->x == 39 || head->y == HEIGHT-1)
return 1;
return 0;
}
//记录分数、自动升级等级
void levelscore(){
//计分, 更改循环周期,更改等级
switch (score / 100)
{
case 0:score += 10;
sleep_time = 500;
level = 1;
break;
case 1:score += 15;
sleep_time = 350;
level = 2;
break;
case 2:
case 3:score += 25;
sleep_time = 200;
level = 3;
break;
case 4:
case 5:score += 35;
sleep_time = 150;
level = 4;
break;
case 6:
case 7:
case 8:
case 9:score += 50;
sleep_time = 100;
level = 5;
break;
default:score += 80;
sleep_time = 80;
level = 6;
}
gotoxy(3, 11, RED);
printf("%d", level);
gotoxy(3, 15, RED);
printf("%3d", score);
}
/*控制蛇的移动*/
void move()
{
checkinput();
//如果没有吃到食物,就擦去蛇尾
if (!is_eat())
{
deteSnaketail();
outputsnake();
}
//否则重新生成食物,记录分数,控制等级
else
{
outputsnake();
cre_food();
levelscore();
}
}
/*输出蛇的形态*/
void outputsnake(){
NODESNAKE *p = NULL;
for(p = head;p != NULL;p = p->next){
wipe(p->x,p->y);
}
for(p = head;p != NULL;p = p->next){
gotoxy(p->x,p->y, YELLOW);
printf("■");
}
}
//隐藏光标
void HideCursor()
{
CONSOLE_CURSOR_INFO cur_info = {1,0};
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cur_info);
}
Fsnake.h
#define WIDTH 40
#define HEIGHT 24
#define LEFT 75
#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77
#define ESC 27
#define SPACE 32
#define ENTER 13
#define YELLOW 0xe #define L_GREEN 0xB #define RED 0xC
#define TRUE 1
#define FALSE 0
void gotoxy(int x, int y, int color); void map();
void HideCursor();
void cre_food();
void cre_snake(int x, int y); int get_key();
void start();
void wipe(int x, int y); void move();
int is_eat();
int is_dead();
void outputsnake();
void levelscore();
void deteSnaketail(); void checkinput();
附件1:
华南农业大学信息学院
课程设计实验
实验题目:贪吃蛇
一、分析题目要求
经过我们3人小组的讨论,最终确定了用C 语言写贪吃蛇的游戏。
首先,要想实现贪吃蛇就要有游戏界面;其次,还要有写贪吃蛇的设计思
路以及对于数据结构的操作;另外,还应该有游戏的规则实现以及界面的美化
等。因此,我们把这个实验任务分成了3个部分,xxx 具体负责的是蛇的构
造以及食物的生成,xxx 负责界面,xxx 负责游戏规则。
要实现贪吃蛇能够前进,我们需要用到链表来存取蛇的身体,以及蛇所在
的方位。一般情况下,需要用到链表节点的删除,增加,输出等操作。另外,
产生随见事物使用系统自带的Rand ,随机定位一个游戏区域里面的方块,然后
使用gotoxy 函数可以定位到这个方块的坐标,并且涂上颜色,这样就产生了肉
眼可见的食物。关于蛇体,我们将蛇体的每个方块添加到链表里面,使用的是
snake,为什么不用数组,原因是数组的长度总是有限的,你不知道蛇体里面最
终存放多少方格,而且数组比较繁琐。通过不断的生成蛇头以及删除蛇尾循环
画出列表中的每个对象,就成功的画出了蛇体。设置蛇体的初始长度只有蛇头,
并设置蛇头的初始位置为居中。
(1) 各个函数命名,包含的参数,返回值类型
定义#include 这个头文件,它包含了其他Windows 头文件,这些
头文件的某些也包含了其他头文件:
static COORD position;//显示当前指针的坐标
static COORD food_position;//显示当前食物的坐标
static HANDLE hOut;//获取标准输出的句柄。命令行的程序会把字符输出到屏幕
定义#include 头文件,是Console Input/Output(控制台输入输出)
的简写,其中定义了通过控制台进行数据输入和数据输出的函数,主要是通过
按键盘产生的对应操作,比如getch()函数,而在Fsnake.h 中定义的
#define LEFT 75 #define UP 72 #define DOWN 80 #define RIGHT 77 #define ESC 27
#define SPACE 32 #define ENTER 13 即为getch()函数从键盘接收到的各种键值。
在Fsnake.h 中定义的:
#define GREEN 0xe #define L_GREEN 0xB #define RED 0xC 是用到
Windows.h 函数中的setcolor 函数:
以下SetConsoleTextAttribute(hOut, color); //设置字体颜色
各颜色的代码如下:
10=淡绿色 0xa
3=湖蓝色 11=淡浅绿色 0xb
4=红色 12=淡红色 0xc
5=紫色 13=淡紫色 0xd
6=黄色 14=淡黄色 0xe
7=白色 15=亮白色 0xf
在Fsnake.h 中定义的:
#define WIDTH 40
#define HEIGHT 24是地图的长和宽
生成地图的函数:
void start()//游戏地图中的开始界面
void gotoxy(int x, int y, int color)//控制命令行光标位置
int 类型的横纵坐标x ,y ,应经声明的color 。
void map()//生成地图
void HideCursor()//隐藏光标
此外,还有其他的操作函数:
void gotoxy(int x, int y, int color);
void map();
void HideCursor();
void cre_food();
void cre_snake(int x, int y);
int get_key();
void start();
void wipe(int x, int y);
void move();
int is_eat();
int is_dead();
void outputsnake();
void levelscore();
void deteSnaketail();
void checkinput();
(2)、输入输出的形式与达到的功能:
1、定义蛇的结构体为
typedef struct snake
{
int x;
int y;
struct snake *pre;
struct snake *next;
} NODESNAKE; //蛇链表
2、定义运行界面的边框函数为void map();该函数没有输入,输出的
是贪吃蛇的游戏界面范围。图示如下:
3、定义void start();作为游戏开始时候的提示,该函数没有参数,
但是需要输入数据类型的只有fsnake.h 中定义的静态数值”ESC ”和
“ENTER ”;它的输出是清除游戏界面内的文字;图示如下:
4、定义函数void cre_food( );为贪吃蛇创造食物;该函数没有参数,也没
有返回值;并且该函数不需要输入,它由自己用rand( )创造伪随机数用于程
序;该函数的输出即为随机产生的食物,图示如下:
5、定义函数int is_dead( );用于判断蛇是否死亡,死亡有两种形式:一是撞
到自己,二是撞到墙壁;is_dead()的返回值为0或1用于判断蛇的状态,他
没有参数,也不需要输入输出数据。
6、定义函数void move();用于控制贪吃蛇的移动,它所调用的函数有检
测输入函数void checkinput();检测是否吃到食物函数int is_eat();以及可能
会调用删除尾节点函数void deteSnaketail();创建食物函数void cre_food();和
输出分数等级函数void levelscore();并且该函数并不需要输入变量和输出某
一个值。
7、定义函数void outputsnake();用于输出蛇的状态,并且及时输出的前
进方向,该函数没有输入但是输出蛇的身体。
8、游戏一开始按enter 键进入游戏,按↑、↓、←、→控制蛇分别向上、
下、左、右不同方向运动,按空格键暂停,按任意键继续,按Esc 退出。如
果蛇死亡了,还可以按1继续,按任意键退出游戏。程序所达到的功能: 开
始,创建食物,创建蛇,判断蛇是否吃到食物,蛇是否死亡,控制蛇移动的
方向,等级,计分,速度,是否重新开始等功能。
9、我们选的是把地图范围和开始页面设置成绿色,左边工具栏与提示栏设
置为黄色,分数与等级为红色,食物也为红色,蛇为黄色正方形。地图设置
为长为40格,宽为24格,贪吃蛇每移动一次为一格,在横坐标0-8格之内
为工具栏,9-39格是贪吃蛇游戏的地图。
void start()//游戏地图中的开始界面
void gotoxy(int x, int y, int color)//控制命令行光标位置
输入横纵坐标x ,y 把该格定义为color 颜色
void map()//生成地图
生成贪吃蛇的游戏地图,与工具栏,开始页面
void HideCursor()//隐藏光标
隐藏输入的光标
二、解题思路
(1)、游戏界面的生成
1、游戏界面生成思路:
根据codeblocks 的输出框的大小,先找出游戏方框的大小,然后输出游
戏的边框以及文字说明。制作游戏的窗体布局。其中包括窗体的大小,位置,
添加按钮组件,在窗体上面划定游戏区域等。我们需要知道的只是边框的高
度以及宽度。
2、伪代码实现:
For()/*循环次数不超过边框宽度*/
绘出第一行墙壁;
For()/*循环次数不超过边框高度-1*/
绘出中间的竖直部分墙壁;
For()/*循环次数不超过边框宽度*/
绘出最后一行墙壁;
/*通过定为坐标来定为光标*/
写出各个需要提示的游戏说明;
(2)、创建食物和蛇的身体的实验思路:
由于食物的生成需要实现随机分布,所以要用到rand();这个函数来实现 产生随机数,只不过要注意的是我们需要判断食物的产生是否在游戏区域以
及是否和之前产生的蛇是否位置重叠。伪代码实现如下:
P=头指针;
While(p !=NULL){
If (随机数=p的数据域);
则 返回”p!=NULL”之前进行循环操作,;
P=p->next;
}
如果蛇头为空,那么将坐标定位到游戏界面正中央,并且将该处的坐标值
赋给头结点;如果蛇头不为空,那么将创建一个新的蛇头并且将现在的蛇头
下一个坐标赋值给新的蛇头。并且以此循环,直到蛇死亡、游戏结束。伪代
码实现如下:
P=游戏界面坐标;
head->pre=p;
P->next=head;
P->pre=NULL;
Head=p;
(3)、检测键盘是否输入实验思路:
首先要注意的是,我们在游戏时,并不是来自键盘上所有的输入都可以实现
蛇的移动,因此我们需要判断键盘上面的输入是否是我们想要的输入。至于关
于键盘的输入由队友来写,我想说的是,通过方向键设置蛇头的移动,蛇头每
移动一次位置就会变化到另外一个方块,对应的坐标x,y 都是有相应的变化,
例如按下UP 键,坐标的变化就是,x 不变,y 减1。
(4)、删去蛇尾思路:
在这里,只需要对蛇链表进行删除操作,不过要注意的是,在删除之前要
对蛇尾的光标进行擦除操作。最后要注意释放已删除的节点;
(5)、蛇在界面上的输出思路:
我的思路是,为了避免删除蛇尾的操作对整个蛇移动界面的影响,我觉得可
以对蛇链表进行两次遍历,一次是遍历擦除界面上所有的图案,第二次是直接
遍历输出整个蛇的形态。伪代码实现为:
While(p!=NULL){
擦除p 对应坐标的图像;
P=p->next;
}
While(p!=NULL){
定为p 点对应的坐标;
画出蛇的一节;
P=p->next;
}
(6)游戏等级、分数、速度的思路:游戏一开始吃一个食物得10分,当得分足够时升级,吃一个食物得分相应增加,速度也相应加快。用switch 选择相应功能。
(7)是否吃到食物的思路:通过判断蛇头的位置是否与食物位置重合来判断。有函数int is_eat()。
(8)判断蛇是否死亡的思路:蛇的死亡分两种:咬到自己与碰到边界;通过判断蛇头是否与边界或者自己的身体重合来判断是否死亡;有函数nt is_dead()。
三、调试分析
(1)、调试过程中遇到的问题:
首先遇到的问题是关于界面的和怎么样从键盘输入的一些问题,这些函数都
与include库函数有关,经过一番查找,我们找到了关于怎么样从
键盘直接输入数据的的方法,以及在界面上定位光标。因为队友主要负责这个
所以我不在赘述。其次,问体在于怎么样实现贪吃蛇的移动,由于最开始我们
只想到通过增加蛇头以及删除蛇尾的方法来移动,却没有准确判断吃到食物后
蛇的长度变化。
另外,在游戏结束后重新开始进行第二轮游戏时,出现了蛇的长度不变,分
数出现混乱的情况,经过修改发现是链表没有初始化,分数等级也没有初始化
的,所以才会出现乱码,以及图像的扭曲。
还有就是出现了下面这种情况:
经过讨论分析,我们发现是擦除尾部节点的时候覆盖了蛇本身的不需要擦除
的节点,然后我们把输出函数改成了先全部擦除所有的蛇的节点,然后在全部
显示,只不过这样又要多一遍循环。经过修改成功的消除了bug 。
我采用的是每实现一个功能就进行测试,分模块测试时没有出错,合起来就出错
了,经过仔细检查后发现有些语句写错了导致错误,经改正后正常运行。还有一
些for 循环使用不当,造成程序运行失败,经过多次测试后找到错误原因,改进
后正常运行。在调试过程中会发现等级与得分的数字不平衡时,需要数出他们的
横纵坐标,换为一样的,然后中间的开始界面也要确定好坐标值,将第一个字母
放在整个地图横坐标的中间。然后食物的随机生成会生成在了工具栏里,所以我
把的个随机函数要规定在横坐标9-39内生成。我采用的是每实现一个功能就进
行测试,分模块测试时没有出错,合起来就出错了,经过仔细检查后发现有些语
句写错了导致错误,经改正后正常运行。还有一些for 循环使用不当,造成程序
运行失败,经过多次测试后找到错误原因,改进后正常运行。
(2)、使用的测试数据
第一次开始等级为1时:
等级为二时:
复活之后,速度等级分数正常:
(3)、算法的效率分析和改进设想
首先,该算法的效率经过计算为o(n);不算是很复杂,需要改进的地方就
是在处理输出图像的时候,可以把循环更加简化。以及我们由于时间有限我们
还可以加入许多有趣的小东西。比如说,自主选择难度,保存上次的游戏界面,
等等其他的功能。另外,可以用文件储存游戏者之前的游戏记录,在每次开始
游戏时,游戏者可选择是继续上次游戏,或者是重新开始游戏,那么游戏记录
将会清空。还可以在一次游戏结束后询问游戏者是否重新开始,或者是复活。
复活时等级不变可是得分清零。画面的话可以增大游戏地图的面积,让游戏者
根据难度等级选择不同的地图。
(4)、经验和体会
在这个游戏的编写过程中,还是有一些不懂的地方,
但经过查阅书籍和网上学习,
都一点一点克服了,达到了学习的目的;对于每一个内容,可以试着将其进一步细分,分成多个函数来写,这样既有条理,也容易调试、修改;对于每个要求,可以一个一个来实现。如果中途需要中断程序编写时,可以做个标记,方便下次快速找到编写的位置。由于这次我设计的是游戏界面,所以先要大概打一下草稿,确定好游戏的界面要怎样布局,需要有哪些数据在里面。然后先把外层的“围栏”确定好,之后再去弄里面的东西,对于数据的位置,一定要有耐心,一个一个数清楚,不然很容易会出错。
四、源程序
Main . cpp
#include "Fsnake.h"
#include
#include
#include
#include
#include
typedef struct snake
{
int x;
int y;
struct snake *pre;
struct snake *next;
} NODESNAKE; //蛇链表
static NODESNAKE *head;
static NODESNAKE *tail;
static COORD position;
static COORD food_position;
static HANDLE hOut;
int score;
int sleep_time = 500;
int level;
int direct = RIGHT;
int main()
{
int i;
system("贪吃蛇---三贱客");
while(1){
head=NULL;
system("cls");
int level = 1; //难度
score=0;
HideCursor();
map();
start();
cre_snake(24, 12); //创建蛇头
cre_food(); //生成食物
gotoxy(3, 11, RED);
printf("%d", 1);
gotoxy(2, 15, RED);
printf("%3d", 0);
do
{
move();
extern int sleep_time;
Sleep(sleep_time);
} while (!is_dead());
gotoxy(22, 11, RED);
printf("Game over!");
gotoxy(19, 12, RED);
printf("按1重新开始,按其他退出程序\n");
gotoxy(24, 13, RED);
scanf("%d",&i);
if(i != 1){
break;
}
}
/*getch();*/
return 0;
}
void start()
{
int ch;
gotoxy(19, 11, L_GREEN);
printf("Enter键开始,Esc 退出!!!");
/*system("cls");*/
while ((ch = get_key()) != ENTER && ch != ESC);
if (ch == ESC)
exit(0);
else
{
gotoxy(19, 11, L_GREEN);
printf(""); //清空开始菜单文字
}
}
void gotoxy(int x, int y, int color)
{
position.X = 2*x;
position.Y = y;
//如果hOut 为空,则将标准输出句柄赋给它,否则保持原来的值
hOut == NULL ? (hOut = GetStdHandle(STD_OUTPUT_HANDLE)) : hOut;
SetConsoleCursorPosition(hOut, position); //将光标定位到指定坐标
SetConsoleTextAttribute(hOut, color); //设置字体颜色 }
void map()
{
int i; //行坐标
int j; //列坐标
//画出第一行
for (j = 0; j
gotoxy(j, 0, L_GREEN);
printf("■");
}
//画出中间部分
for (i = 1; i
gotoxy(0, i, L_GREEN);
printf("■");
gotoxy(8, i, L_GREEN);
printf("■");
gotoxy(WIDTH - 1, i, L_GREEN);
printf("■");
}
//画出底部
for (j = 0; j
gotoxy(j, HEIGHT - 1, L_GREEN);
printf("■");
}
gotoxy(3, 1, YELLOW);
printf("贪吃蛇");
gotoxy(1, 3, YELLOW);
printf("Snake---三贱客");
gotoxy(3, 5, YELLOW);
printf("操作方法");
gotoxy(2, 7, YELLOW);
printf("← ↑ ↓ →");
gotoxy(2, 8, YELLOW);
printf("左 上 下 右");
gotoxy(3, 10, YELLOW);
printf("等 级");
gotoxy(3, 13, YELLOW);
printf("得 分");
gotoxy(2, 17, YELLOW);
printf("提示:");
gotoxy(1, 19, YELLOW);
printf("空格键 暂停");
gotoxy(1, 21, YELLOW);
printf("任意键 继续");
}
//擦除某个坐标上的图像
void wipe(int x, int y)
{
gotoxy(x, y, YELLOW);
printf("");
}
int get_key()
{
char ch1;
ch1 = getch();
if (ch1 == ENTER || ch1 == ESC || ch1 == SPACE)
return ch1;
else if (ch1 == -32)
return getch();
else
return 0;
}
//生成食物
void cre_food()
{
int x, y;
NODESNAKE *p = head;
srand((unsigned int)time(NULL));
again:
do
{
x = 9 + rand() % (WIDTH - 10);
y = rand() %(HEIGHT - 1);
} while (x
//检查食物是否与蛇冲突
while (p != NULL){
//如果冲突,则重新生成
if (x == p->x&&y == p->y)
goto again;
p = p->next;
}
food_position.X = x;
food_position.Y = y;
gotoxy(x, y, RED);
printf("●");
}
//创建蛇的一节
void cre_snake(int x,int y)
{
int i;
NODESNAKE *pNew = NULL;
if ((pNew = (NODESNAKE *)malloc(sizeof(NODESNAKE))) == NULL) {
gotoxy(18, 11, RED);
printf("分配内存失败! 按任意键退出!");
getch();
exit(0);
}
//如果是创建蛇头
if (head == NULL)
{
head = pNew;
head->x = 24;
head->y = 12;
head->next = NULL;
head->pre = NULL;
tail = head;
}
else/*如果蛇头不为空,那么生成蛇的身体*/
{
pNew->x = x;
pNew->y = y;
head->pre = pNew;
pNew->next = head;
pNew->pre = NULL;
head = pNew;
}
}
//检测当前键盘的输入值
void checkinput(){
//检测当前键盘是否有输入
if (_kbhit())
{
int key = 0;
int temp = direct;
fflush(stdin); //清空输入缓冲
key = get_key();
if ((temp == RIGHT || temp == LEFT) && (key == UP || key == DOWN))
direct = key;
else if ((temp == UP || temp == DOWN) && (key == LEFT || key == RIGHT))
direct = key;
else if (key == SPACE)
getch();
}
//画出新蛇头
switch (direct)
{
case UP:cre_snake(head->x, head->y - 1); break;/*坐标的变化是,x 不变,y 减1。下面的同理*/
case DOWN:cre_snake(head->x, head->y + 1); break;
case LEFT:cre_snake(head->x - 1, head->y); break;
case RIGHT:cre_snake(head->x + 1, head->y); break;
}
}
//判断是否吃到食物
int is_eat()
{
if (head->x == food_position.X&&head->y == food_position.Y) return 1;
else
return 0;
}
//删去蛇尾
void deteSnaketail(){
wipe(tail->x, tail->y);/*首先擦掉蛇尾*/
NODESNAKE *p = tail;
tail = tail->pre;
tail->next = NULL;
free(p);
}
//判断蛇是否死亡
int is_dead()
{
NODESNAKE *p = head->next;
//如果咬到自己则死亡
while (p != NULL)
{
if (head->x == p->x&&head->y == p->y)
return 1;
p = p->next;
}
//如果越界则死亡
if (head->x == 8 || head->y == 0 || head->x == 39 || head->y == HEIGHT-1)
return 1;
return 0;
}
//记录分数、自动升级等级
void levelscore(){
//计分, 更改循环周期,更改等级
switch (score / 100)
{
case 0:score += 10;
sleep_time = 500;
level = 1;
break;
case 1:score += 15;
sleep_time = 350;
level = 2;
break;
case 2:
case 3:score += 25;
sleep_time = 200;
level = 3;
break;
case 4:
case 5:score += 35;
sleep_time = 150;
level = 4;
break;
case 6:
case 7:
case 8:
case 9:score += 50;
sleep_time = 100;
level = 5;
break;
default:score += 80;
sleep_time = 80;
level = 6;
}
gotoxy(3, 11, RED);
printf("%d", level);
gotoxy(3, 15, RED);
printf("%3d", score);
}
/*控制蛇的移动*/
void move()
{
checkinput();
//如果没有吃到食物,就擦去蛇尾
if (!is_eat())
{
deteSnaketail();
outputsnake();
}
//否则重新生成食物,记录分数,控制等级
else
{
outputsnake();
cre_food();
levelscore();
}
}
/*输出蛇的形态*/
void outputsnake(){
NODESNAKE *p = NULL;
for(p = head;p != NULL;p = p->next){
wipe(p->x,p->y);
}
for(p = head;p != NULL;p = p->next){
gotoxy(p->x,p->y, YELLOW);
printf("■");
}
}
//隐藏光标
void HideCursor()
{
CONSOLE_CURSOR_INFO cur_info = {1,0};
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cur_info);
}
Fsnake.h
#define WIDTH 40
#define HEIGHT 24
#define LEFT 75
#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77
#define ESC 27
#define SPACE 32
#define ENTER 13
#define YELLOW 0xe #define L_GREEN 0xB #define RED 0xC
#define TRUE 1
#define FALSE 0
void gotoxy(int x, int y, int color); void map();
void HideCursor();
void cre_food();
void cre_snake(int x, int y); int get_key();
void start();
void wipe(int x, int y); void move();
int is_eat();
int is_dead();
void outputsnake();
void levelscore();
void deteSnaketail(); void checkinput();