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

前言:

对于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语言实现贪吃蛇之文件操作篇

好了,到了这里这个程序已经很不错了。但是原作者似乎还不够过瘾,毕竟命令行黑乎乎的。于是乎原作者就搞了个贪吃蛇图形界面版。下一篇博客中我将带着大家来看看图形界面版怎么实现。

  • 47
    点赞
  • 251
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值