您的位置:首页 > 编程语言 > C语言/C++

C语言实现贪吃蛇(四)----游戏存档读档(文件操作)

2016-11-18 19:50 846 查看

前言:

对于C语言的初学者来说,可能会觉得文件操作是一个比较陌生的领域,但实际上无论编写怎样的程序文件操作都是必需的。在操作系统中,所有的外围设备(包括键盘和显示器)都被看作是文件系统中的文件,因此所有的输入/输出都要通过读文件/写文件完成。启动一个C语言程序时,操作环境负责打开标准输入,标准输出,标准错误3个文件,并将这3个文件的指针提供给该程序。而我们熟知的 getchar 函数 putchar 函数其实就是通过这些文件指针实现的。这样说大家应该对文件操作的重要性有了一个初步的了解,接下来我们就运用文件操作的相关知识,为贪吃蛇游戏加上存档读档功能。

如何存储画面的信息呢,在贪吃蛇游戏中,我们只需要存储蛇的坐标、食物的坐标以及蛇的前进方向。我们可以利用标准库里的fopen,fwrite,fread三个函数完成这一要求。如果大家记不住这几个函数,请自行百度。

PS:本篇博客中将要实现的游戏存档读档功能是基于 《C语言实现贪吃蛇(三)—-结构+链表实现》 这个贪吃蛇版本的。如果大家不是从那篇博客过来的话,务必回去看看。

一、游戏存档:

在编写存档函数 save() 前,我们先来看看新增的一些需要用到的变量定义:

FILE * mem;     //游戏存档文件句柄
char dir = UP;  //因为在save函数里要用到,把原来定义在主函数里的dir移出来,并初始化为向上
void save(void);    //游戏存档函数


save() 游戏存档函数:

void save(void){
snake_node * p = NULL;

//打开存档文件
if((mem = fopen("memory.txt","w+")) == NULL){
fprintf(stderr,"Can't open memory.txt!\n");
exit(1);
}

//保存蛇长度
fwrite(&len,sizeof(len),1,mem);
//保存方向
fwrite(&dir,sizeof(dir),1,mem);
//保存食物坐标(结构体是值类型)
fwrite(&food,sizeof(food),1,mem);
//保存蛇坐标
for(p = snake_head;p != NULL;p = p->next){
fwrite(&(p->cor),sizeof(p->cor),1,mem);
}
fclose(mem);
}


二、游戏读档:

编写 load() 读档函数前只需声明一下自己即可:

//读档
void load(void);


load() 游戏读档函数:

void load(void){
snake_node * p,* q;    //这里的 p、q 无非跟之前的 last、current 一样
int m = 1,i,j;

//读取游戏存档
if((mem = fopen("memory.txt","r")) == NULL){
fprintf(stderr,"No Game Cache Here!\n");
exit(2);
}
//读取蛇长度
fread(&len,sizeof(len),1,mem);
//读取方向
fread(&dir,sizeof(dir),1,mem);
//读取食物坐标(结构体是值类型)
fread(&food,sizeof(food),1,mem);
//读取蛇头坐标
snake_head = (snake_node *)malloc(sizeof(snake_node));
fread(&(snake_head->cor),sizeof(COORD),1,mem);
snake_head->next = NULL;

m ++;   //准备读取第二节

//获取蛇身体坐标
p = snake_head;
while(m <= len){
q = (snake_node *)malloc(sizeof(snake_node));
fread(&(q->cor),sizeof(COORD),1,mem);
q->next = NULL;
p->next = q;
p = q;
m ++;
}

//打印围墙
for (j = 0; j < 17; j ++) {
for (i = 0; i < 17; i ++)
if (i == 0 || i == 16 || j == 0 || j == 16)
{
putchar('#');
}
else
{
putchar(' ');
}
putchar('\n');
}

//打印蛇身
for (p = snake_head; p != NULL; p = p->next) {
gotoxy(p->cor);
putchar('*');
}

//打印食物
gotoxy(food);
putchar('$');

fclose(mem);
}


三、存档与读档:

万事俱备、只欠东风。现在我们已经有了存档读档函数了,接下来就是考虑什么时候需要存储游戏记录了。

显然,只有在游戏中途退出的时候需要存储。我们可以在游戏进行的时候通过按 ESC 键中途退出游戏,并保存游戏进度。

在此之前,我们需要对 ESC 键定义:

#define ESC 27


对 get_dir() 函数进行稍微的改进以达到该目的:

int get_dir(int old_dir){

int new_dir = old_dir;

//用kbhit()与getch()组合实现键盘响应
//kbhit() 检查当前是否有键盘输入,若有则返回一个非0值,否则返回0
//getch() 用ch=_getch();会等待你按下任意键之后,把该键字符所对应的ASCII码赋给ch,再执行下面的语句。
if(_kbhit()){

//按ESC存档并退出
if (_getch() == ESC){
save();    //存档
free_node(snake_head);
system("cls");
printf("Caching...\n");
exit(0);
};

new_dir = _getch();     //getch()函数要使用两次,原因是因为第一次返回的值指示该键扩展的字符,第二次调用才返回实际的键代码

//如果蛇身长度大于1,则不能回头,如果摁回头方向,则按原来方向走
if(len > 1 && (abs(new_dir - old_dir) == 2 || abs(new_dir - old_dir) == 8)){
new_dir = old_dir;
}
}
return new_dir;
}


注意这里ESC应该放在第一个getch()处检测。ESC不同于方向键,第一次便返回键值,方向键需要两次。

既然加入了存档与读档功能,那么就必须得有个 新建游戏 与 继续游戏 的选择界面。我们在主函数的循环开始前加入choose函数实现这个界面。

在实现 choose() 函数前,先来看看需要的变量等的声明

//新建游戏与继续游戏选项的显示位置
COORD s_option = { 5,5 },c_option = { 5,7 };
//按回车键选择
#define ENTER 13
//选择是新建游戏还是继续游戏
void choose(void);


choose() 函数代码:

void choose(void)
{
int key, option = 1;    //默认是新建游戏
gotoxy(s_option);
printf("->NEW GAME");
gotoxy(c_option);
printf("  CONTINUE");

while ((key = _getch()) != ENTER) {
if (key == UP && option > 1)
{
option --;
}
if (key == DOWN && option < 2)
{
option ++;
}
if (option == 1) {
gotoxy(s_option);
printf("->");
gotoxy(c_option);
printf("  ");
}
else {
gotoxy(c_option);
printf("->");
gotoxy(s_option);
printf("  ");
}
}

system("cls");

if (option == 1)
{
init_game();    //初始化新游戏
}
else
{
load();     //加载存档
}
}


option的值n代表第n个选项。

由于添加了存档读档功能,因此主函数也得有相应的改变,改变如下:

int main(void) {
choose();    //决定是新建游戏还是继续游戏
while(1){
dir = get_dir(dir);     //获取方向(我们摁下的方向)
move_snake(dir);        //移动蛇身
if(!isalive()){         //判断蛇的生命状态
break;
}
}
//清除屏幕
system("cls");
printf("Game Over!\n");
//释放申请的内存空间
free_node(snake_head);

return 0;
}


到了这一步我们的程序已经算是完成了。

但是在选择是新建游戏还是继续游戏那里,你会发现光标会一直在闪烁,其实这个情况跟前面我们遇到的贯标一直跟着蛇尾是一样的,前面我们的解决方法是将光标定位到左下角:

//避免光标一直跟着蛇尾(或食物 )
COORD foot = {0,17};    //将光标置于左下角
gotoxy(foot);


在这里原作者给出了一个更好的解决方法:直接将光标隐藏掉!

为了实现隐藏光标,我们需要修改 gotoxy() 函数:

void gotoxy(COORD pt)
{
//句柄
HANDLE hout;
//GetStdHandle函数获取一个指向特定标准设备的句柄,包括标准输入,标准输出和标准错误。
//STD_OUTPUT_HANDLE正是代表标准输出(也就是显示屏)的宏
hout = GetStdHandle(STD_OUTPUT_HANDLE);
//SetConsoleCursorPosition函数用于设置控制台光标的位置
SetConsoleCursorPosition(hout, pt);
//隐藏光标
CONSOLE_CURSOR_INFO cursor_info = { 1,0 };
SetConsoleCursorInfo(hout, &cursor_info);
}


本博客参考自C语言实现贪吃蛇之文件操作篇

好了,到了这里这个程序已经很不错了。但是原作者似乎还不够过瘾,毕竟命令行黑乎乎的。于是乎原作者就搞了个贪吃蛇图形界面版。下一篇博客中我将带着大家来看看图形界面版怎么实现。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: