用ncurses库写扫雷

   日期:2020-09-28     浏览:88    评论:0    
核心提示:文章目录前言一、思路二、代码实现1.头文件2.全局变量和宏定义3.main()初始化核心4.功能函数new_mine()operate()display()check_win()三、效果展示总结前言一、思路二、代码实现1.头文件#include <stdlib.h> //rand()#include <time.h> //clock()#include <ncurses.h>2.全局变量和宏定义#define M.

文章目录

  • 前言
  • 一、思路
  • 二、代码实现
    • 1.头文件
    • 2.全局变量和宏定义
    • 3.main()
      • 初始化
      • 核心
    • 4.功能函数
      • 生成雷区new_mine()
      • 游戏主要操作operate()
      • 显示雷区图像display()
      • 检查赢check_win()
  • 三、效果展示
  • 总结

前言

经过一整子的瞎折腾,我终于完成了ncurses版的扫雷。这让我进一步认识到了ncurses库的强大。这里主要的新知识点是"鼠标事件的获取和处理",可以参考这篇文章,虽然排版凑合但还是挺实用的。相比写情诗,扫雷的代码量还是比较大的,我的版本前后有100多行的样子。我用思维导图整理了我的思路,一个功能基本就一个函数。扩展功能我这里完成了前两个,但已经够用了,扫雷的基本操作测试下来很流畅。

一、思路

二、代码实现

1.头文件

#include <stdlib.h> //要用到srand()和rand()生成雷
#include <time.h> //要用到clock()生成随机数种子
#include <ncurses.h> //主角不用解释

2.全局变量和宏定义

#define MINENUM 10 //雷的总数
#define row 9 //雷区的行数
#define line 9 //雷区的列数
int a[row][line];           //0:周围没有雷 -1:此处有雷 1~8:周围雷的数量
int f[row][line];           //0:未探索 1:已探索
int win;					//用于记录输赢的信息

3.main()

初始化

    initscr();				//初始化屏幕
    curs_set(0);			//不显示光标
    noecho();				//禁止输入的字符出现在屏幕上
    keypad(stdscr, TRUE);   //因为缺少这行一直出错
    mousemask(BUTTON1_RELEASED,0);//激活鼠标事件左键释放,使得getch()能获取鼠标事件
    MEVENT event;			//创建事件变量

核心

    while(1){ 						//这个循环使游戏可以玩很多局
        new_mine(MINENUM);			//新建雷区
        display();					//显示雷区(刚开始全是不可见的)
        while(1){ 					//这个循环使游戏的操作可以持续
            switch (getch()){ 		//获取操作
                case KEY_MOUSE:		//如果是鼠标事件,进行游戏操作
                    win=operate(event);//如果踩雷operate()会返回-2
                    break;
                case 'q': win=-2;	//如果输入的是字符'q'则退出本局游戏
            }
            display();
            if(win==-2){ 			//输掉游戏(包括中途退出)
                printw("You lose!");	break;
            }
            if(check_win()){ 		//检查是否赢得游戏
                printw("You win!");		break;
            }
        }
        if(getch()=='q')    break;	//如果再次输入字符'q'则退出游戏
    }
    endwin();						//退出curses模式

4.功能函数

生成雷区new_mine()

int add_mine(int r,int l){ 	//用于累加雷区的数字
    if(r>=0&&r<row&&l>=0&&l<line&&a[r][l]!=-1)//排除在界外的情况,而且雷上面不用进行处理
        a[r][l]++;
}

int new_mine(int num){ 
    int n=0,r=0,l=0;		
    win=0;                  //千万不要忘记初始化,下一局开始时win要再次初始化为0
    for(r=0;r<row;r++){ 
        for(l=0;l<line;l++){ 
            a[r][l]=0;		//雷区的状态也要初始化
            f[r][l]=0;		//雷区的探索情况也要初始化
        }
    }						//生成雷
    srand(clock());         //用clock()生成随机种子
    while(n<num){ 
        r=rand()%row;		//用随机数获取行数
        l=rand()%line;		//用随机数获取列数
        if(a[r][l]==0){ 		//确保即使有重复位置的雷也能生成所需的数量的雷
            a[r][l]=-1;		//放置雷
            n++;
        }
    }
    for(r=0;r<row;r++)      //布置雷区数字
        for(l=0;l<line;l++)
            if(a[r][l]==-1){ //在雷的周围累加数字
                add_mine(r-1,l-1);
                add_mine(r-1,l);
                add_mine(r-1,l+1);
                add_mine(r,l-1);
                add_mine(r,l+1);
                add_mine(r+1,l-1);
                add_mine(r+1,l);
                add_mine(r+1,l+1);
            }
}

游戏主要操作operate()

int show_mine(int r,int l){ 					//用于显示雷区的状态
    if(r>=0&&r<row&&l>=0&&l<line&&a[r][l]!=-1)//排除在界外的情况,而且雷不用显示
        f[r][l]=1;
}

int operate(MEVENT mouse){ 					//游戏的主要操作
    static int r=0,l=0;
    getmouse(&mouse);						//翻译获取的鼠标事件
    mouse_trafo(&mouse.y,&mouse.x,1);		//将获取的鼠标的坐标转换为对应的屏幕坐标
    if (!wenclose(stdscr,mouse.y, mouse.x))
    	return -1; 							//如果坐标不在屏幕内,直接返回
    if(mouse.bstate==BUTTON1_RELEASED){ 		//当鼠标事件为左键释放时
        l=mouse.x/3;						//将鼠标坐标转化为对应的雷区坐标
        r=mouse.y/2;
        if(a[r][l]==-1){ 					//该坐标上有雷,踩到雷了
            for(int i=0;i<=row;i++)
                for(int j=0;j<=line;j++)
                    f[i][j]=1;				//全部雷区的状态都设为可见
            return -2;						//返回-2
        }
        show_mine(r,l);						//没踩到雷,就设置该坐标的雷区状态为可见
        if(a[r][l]==0){ 						//如果该坐标周围没有雷,则设置周围的雷区状态为可见
            show_mine(r-1,l-1);
            show_mine(r-1,l);
            show_mine(r-1,l+1);
            show_mine(r,l-1);
            show_mine(r,l+1);
            show_mine(r+1,l-1);
            show_mine(r+1,l);
            show_mine(r+1,l+1);
        }
    }
}

显示雷区图像display()

int display(){ 						//用于显示雷区
    int r=0,l=0;
    clear();						//清屏
    for(r=0;r<row;r++){ 				//遍历整个雷区
        for(l=0;l<line;l++){ 
            if(f[r][l]==1)			//如果该雷区坐标状态为可见
                if(a[r][l]==-1)		//如果该坐标是雷,打印雷
                    mvaddch(r*2+1,l*3+1,'*');
                else				//不是雷就打印数字
                    mvaddch(r*2+1,l*3+1,'0'+a[r][l]);
        }
    }
    								//绘制网格,细节就不解释了
    for(r=0;r<row;r++){ 
        for(l=0;l<line;l++){ 
            mvaddch(r*2,l*3+1,'-');
            mvaddch(r*2,l*3+2,'-');
            mvaddch(r*2,l*3,'|');
            mvaddch(r*2+1,l*3,'|');
            mvaddch(row*2,l*3,'|');        
            mvaddch(row*2,l*3+1,'-');
            mvaddch(row*2,l*3+2,'-');        
        }
        mvaddch(r*2,line*3,'|');
        mvaddch(r*2+1,line*3,'|');
    }
    mvaddch(row*2,line*3,'|');
    refresh();						//将缓冲区的所有字符都打印到屏幕上
}

检查赢check_win()

int check_win(void){ 			//看看游戏有没有赢
    int count=0;				//用于统计未探索的雷区的数量
    for(int i=0;i<row;i++)
        for(int j=0;j<line;j++)
            count+=1-f[i][j];	//因为未探索是0,已探索是1,只有未探索才+1
    return count==MINENUM;		//剩下的雷区的数量就是雷的总数,说明雷全找出来了
}

三、效果展示

如果中途按q退出,会判定为输
我输的时候
当然也有赢的时候

总结

通过这次用ncurses写扫雷的尝试,除了加深对c语言和ncurses库的理解和熟练度,更让我了解到思路的重要性。c语言和ncurses库只是工具,如果思路不清晰明朗就很难完成目标,反之是总有办法实现的。所以在敲代码之前,尤其是做复杂的任务的时候,一定要先做需求分析整理思路,将需要的数据和功能都剖析出来,这样对代码的实现非常有利。养成这种良好的习惯对于提高效率和代码品质是十分有利的。

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服