【Linux环境编程入门】五、控制终端/屏幕的操作

本系列文章系本人原创,欢迎转载,转载请注明出处

注:这篇文章所建立的所有文件(源文件,库文件,可执行文件等)均可在这里下载。下载的是chapter5.tar.gz文件,可以在Linux系统终端中用如下命令解压到当前目录:

tar zxvf chapter5.tar.gz

在Linux系统下,你可能会与终端(命令行)有很多的交互,因此,编写能够控制终端的程序也是值的探讨的。现在我们就来讲解一下Linux下控制终端的函数库curses。

一、用curses函数库在终端打印 “Hello World!”

用Hello World来作为一个语言的学习过程中的第一个程序几乎已成为一种惯例。同样的,我们用curses函数库做的第一件事,也是打印Hello World。

在此之前,因为我们操作的是终端(屏幕),所以我们先来简单的介绍下字符是如何显示在屏幕上的。

首先,curses函数库有两个数据结构来对应终端屏幕:stdscr和curscr。stdscr是curses的默认输出屏幕,curscr对应的是当前屏幕,在程序调用refresh函数之前,输出到stdscr上的内容是不会显示出来的。而refresh函数的功能就是比较stdscr和curscr的不同,然后根据结果来刷新屏幕。
既然是要将内容输出到屏幕上,那么我们就要知道内容输出到屏幕上的什么位置。在curses函数库中,用形如(行,列)的方式来表示屏幕上的位置,如 (5,2)就表示第五行第二列。坐标原点 (0,0)在终端屏幕的左上角。
好了,接下来我们就开始第一个用curses函数库在屏幕上输出的例子吧,源码如下:

#include <curses.h>

int main()
{
    //在进行屏幕的操作前需要先初始化
    initscr();
    //移到第二行,第五列
    move(2, 5);
    //在当前位置输出字符串
    printw("%s","Hello World!");
    //refresh的作用我们在上面讲过
    refresh();
    //停顿3秒
    sleep(3);
    //我们的屏幕操作结束了
    endwin();
    return 0;
}

回到终端,编译源码。由于curses函数库不是C语言标准库,在编译的时候需要指定库文件:

gcc helloWorld.c -lcurses

编译无误后,执行文件,效果如图,会在指定的位置输出 “Hello World!”,停顿3秒,然后程序退出。
这里写图片描述

二、一些关于终端屏幕的基本操作

光标的移动

这个比较简单,我们呢在上面的 “Hello World”的例子里已经见到过:

#include <curses.h>
int move(int row, int column);

执行该函数,将光标移动到到指定的(行,列)。这里再介绍两个值LINES和COLS,它们分别对应row和column能指定的最大值。

键盘相关的操作

1) 控制模式

#include <curses.h>

//开启回显
int echo(void);
//关闭回显
int noecho(void);
//开启cbreak模式,字符一经输入便立刻传给程序处理
int cbreak(void);
//关闭cbreak模式
int nocbreak(void);
//开启raw模式,字符一经输入便立刻传给程序,并且关闭特殊字符的处理
int raw(void);
//关闭raw模式
int noraw(void);

2)键盘输入

#include <curses.h>

int getch(void)
int getstr(char *string);
int getnstr(char *string, int number_of_characters);
int scanw(char *format, ...);
例子

接下来我们用一个例子来展示curses函数库。这个例子是一个简单的扫雷程序。它会在终端屏幕上打印10*10的数组,用?表示未知的格子,用F表示用户标记,用_表示空格子,用*表示雷。用户用方向键进行光标的移动,用空格来作标记,用回车来探索一个未知的格子,当用户标记了所有雷或者触发任意雷的时候游戏结束。下面是源码:

#include <curses.h>
#include <time.h>

//定义行/列
#define ROWS 10
#define COLUMNS 10

//mine数组/矩阵就代表了屏幕上显示的雷区
bool mine[ROWS][COLUMNS];

//flag数组/矩阵用来记录雷区对应格子的状态,0:未标记,1:已被用户标记,2:已探索
int flag[ROWS][COLUMNS];

//初始化mine数组/矩阵,值为true的是雷,值为false的不是雷
void init_mine()
{
    int i,j;
    srand((unsigned)time(NULL));
    for(i=0;i<ROWS;++i)
    {
        for(j=0;j<COLUMNS;++j)
        {
            int value=rand()%5;
            if(value==4)
            {
                mine[i][j]=true;
            }else{
                mine[i][j]=false;
            }
        }
    }
}

void init_flag()
{
    int i,j;
    for(i=0;i<ROWS;++i)
        for(j=0;j<COLUMNS;++j)
            flag[i][j]=0;
}   

//获取当前格子周围8个格子中共有多少雷
int get_mines(const int current_row, const int current_col)
{
    int offset[]={-1,0,1};
    int i,j;
    int count=0;
    for(i=0;i<3;++i)
    {
        for(j=0;j<3;++j)
        {
            if(offset[i] || offset[j])
            {
                int tmp_row=current_row+offset[i];
                int tmp_col=current_col+offset[j];
                if(tmp_row>=0 && tmp_row<ROWS && tmp_col>=0 && tmp_col<COLUMNS)
                {
                    count+=mine[tmp_row][tmp_col];
                }
            }
        }
    }
    return count;
}

//探索指定格子,如果它周围有雷,则显示周围的雷的个数,否则在它周围8个格子中进行递归的探索
void check_mines(const int current_row, const int current_col)
{
    //如果该格子已经被探索过,则不再重复探索
    if(flag[current_row][current_col]==2)
    {
        return;
    }
    flag[current_row][current_col]=2;
    int count=get_mines(current_row,current_col);
    if(count)
    {
        move(current_row,current_col);
        printw("%d",count);
    }else {
        move(current_row,current_col);
        printw("%s","_");
        int offset[]={-1,0,1};
        int i,j;
        for(i=0;i<3;++i)
        {
            for(j=0;j<3;++j)
            {
                if(offset[i] || offset[j])
                {
                    int tmp_row=current_row+offset[i];
                    int tmp_col=current_col+offset[j];
                    if(tmp_row>=0 && tmp_row<ROWS && tmp_col>=0 && tmp_col<COLUMNS)
                    {
                        check_mines(tmp_row,tmp_col);
                    }
                }
            }
        }
    }
}

//检查当前格子,如果是雷则返回2,如果所有雷都已被用户标记,则返回1,否则探索格子并返回0
int scan_mines(const int current_row, const int current_col)
{
    if(mine[current_row][current_col])
    {
        return 2;
    }
    int i,j;
    bool is_all_scanned=true;
    for(i=0;i<ROWS;++i)
    {
        for(j=0;j<COLUMNS;++j)
        {
            if(mine[i][j])
            {
                if(!flag[i][j])
                {
                    is_all_scanned=false;
                    break;
                }
            }
        }
    }
    if(is_all_scanned)
    {
        return 1;
    }
    check_mines(current_row, current_col);
    return 0;
}

int main()
{
    init_mine();
    init_flag();

    initscr();
    noecho();

    //向屏幕输出ROWS * COLUMNS的矩阵,用?表示未知区域,用_表示空区域,用*表示雷,用数组表示该位置周围有多少雷
    int i,j;
    for(i=0;i<ROWS;++i)
    {
        for(j=0;j<COLUMNS;++j)
        {
            move(i,j);
            printw("%s","?");
        }
    }
    //将光标移到矩阵的左上角
    move(0,0);
    refresh();

    //开始与用户交互,直到没有未知区域(用?表示)或用户触雷结束。
    int status=0;//0:正常游戏中,1:用户扫了所有的雷结束,2:用户触雷结束
    int current_row=0;
    int current_col=0;
    while(!status)
    {
        //获取用户击键
        int value=getch();  
        switch (value)
        {
            case 65:
                current_row=(current_row-1)<0?0:(current_row-1);
                break;
            case 66:
                current_row=(current_row+1)>(ROWS-1)?(ROWS-1):(current_row+1);
                break;
            case 67:
                current_col=(current_col+1)>(COLUMNS-1)?(COLUMNS-1):(current_col+1);
                break;
            case 68:
                current_col=(current_col-1)<0?0:(current_col-1);
                break;
            case 32:
                if(flag[current_row][current_col]==1)
                {
                    printw("%s","?");
                    flag[current_row][current_col]=0;
                }else if (flag[current_row][current_col]==0){
                    printw("%s","F");
                    flag[current_row][current_col]=1;
                }
                break;
            case 10:
                status=scan_mines(current_row,current_col);
                break;
        }
        move(current_row,current_col);
        refresh();
    }
    if(status==2)
    {
        for(i=0;i<ROWS;++i)
        {
            for(j=0;j<COLUMNS;++j)
            {
                move(i,j);
                printw("%s",mine[i][j]?"*":"-");
            }
        }
    }
    move(ROWS+1, COLUMNS+1);
    printw("%s\n",(status==2)?"Game Over.":"You won!");
    refresh();
    sleep(10);
    endwin();

    return 0;
}

效果如下:

失败的图片:
这里写图片描述

胜利的图片:
这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值