Linux Curses編程實現貪吃蛇

curses庫 簡單而言,提供UNIX中多種終端 操做光標和顯示字符 的接口。咱們常見的vi就是使用curses實現的。如今通常都用ncurses庫。html

Linux下curses函數庫    Linux curses庫使用      這兩篇文章很詳細地介紹了curses,在此就不詳細介紹了。node

1.ubuntu安裝curses函數庫 編程

$sudo apt-get install ncurses-dev ubuntu

用curses庫,編譯程序:vim

$gcc program.c -o program -lcursescentos

 

2.工做原理數組

curses工做在屏幕,窗口和子窗口之上。屏幕是設備所有可用顯示面積(對終端是該窗口內全部可用字符位置),窗口與具體例程有關。如基本的stdscr窗口等。網絡

curses使用兩個數據結構映射終端屏幕,stdscr和curscr。stdscr是「標準屏幕」(邏輯屏幕),在curses函數庫產生輸出時就刷新,是默認輸出窗口(用戶不會看到該內容)。curscr是「當前屏幕」(物理屏幕),在調用refresh函數是,函數庫會將curscr刷新爲stdscr的樣子。數據結構

 

3.經常使用函數多線程

 1 初始化相關:
 2 initscr();            //curses程序初始化必須
 3 cbreak();            //關閉字符流的緩衝
 4 nonl();              //輸入資料時, 按下 RETURN 鍵是否被對應爲 NEWLINE 字元 ( 如 \n ).
 5 noecho();             //關閉字符回顯
 6 keypad(stdscr,TRUE);      //能夠使用鍵盤上的一些特殊字元, 如上下左右
 7 refresh();            //刷新物理屏幕
 8 int endwin(void);           //關閉全部窗口
 9 
10 移動光標、輸出相關:
11 int move(int new_y, int new_x);        //移動stdcsr的光標位置
12 addch(ch);                             //顯示某個字元.
13 mvaddch(y,x,ch);                     //在(x,y) 上顯示某個字元. 
14 addstr(str);                         //顯示一串字串.
15 mvaddstr(y,x,str);                     //在(x,y) 上顯示一串字串. 
16 printw(format,str);                 //相似 printf() , 以必定的格式輸出至螢幕.
17 mvprintw(y,x,format,str);             //在(x,y) 位置上作 printw 的工做
18     
19 前綴w用於窗口(添加一個WINDOWS指針參數),mv用於光標移動(在該位置執行操做addch或printw)(添加兩個座標值參數),mvw用於在窗口(如stdscr)中移動光標。組成以下函數:
20     addch, waddch, mvaddch, mvwaddch
21     printw, wprintw, mvprintw, mvwprintw
22 
23 屏幕讀取:沒用到就不寫了
24 鍵盤輸入:
25 //與標準io庫的getchar, gets, scanf相似
26 int getch();
27 int getstr(char *string);
28 int getnstr(char *string, int number);      //建議使用
29 scanw(format,&arg1,&arg2...):                 //如同 scanf, 從鍵盤讀取一串字元.
30 清除屏幕;
31 int erase(void);                               //在屏幕的每一個位置寫上空白字符
32 int clear(void);                            //使用一個終端命令來清除整個屏幕
用到的curses函數

 


 

貪吃蛇,要點在於1.蛇的實現;2.移動和按鍵偵聽的同步進行;3.碰撞的檢測。

1.蛇的實現,聯繫到curses的特色,想到兩種辦法,一種是隊列存儲每一個節點,還有一種是用頭-尾-拐彎節點座標實現。

   想到蛇的長度不可能太長,因此用隊列(循環數組)實現,添加頭結點,刪除尾節點。爲了方便就沒有實現循環數組,採用了一個大數組。

2.兩種動做同時進行,兩種方案:1能夠用異步編程,涉及到信號,阻塞,可重入問題。2用多線程,或多進程,涉及到變量的共享,互斥鎖等。

3.碰撞檢測。爲了效率能夠用一個線程把它抽取出來,若是代碼時間複雜度不高,能夠直接寫。

用信號實現按鍵和同時移動。又有兩種方案,一種把移動放入定時信號處理函數,主程序用輸入阻塞(本文采用)。一種是把移動放入主程序循環,把輸入放入信號處理函數。

 


 

curses的物理屏幕刷新是根據邏輯屏幕的字符,邏輯屏幕那一塊沒有變更,實際屏幕也不變,因此不會像gdi編程畫面 閃動,耗費資源也少。
所以能夠想到每次刷新只讓蛇動一個格子,蛇頭,和蛇尾 局部刷新就好了。
FC遊戲的卷軸移動,也是相似的道理,內存小,長時間不動的背景就讓他不通過程序,也同時省了cpu。
卡馬克就是根據這種方法設計出當時低速PC上第一個能卷軸移動的遊戲引擎。

tanchishe.c

#include "tanchishe.h"
//2015 10 06
//#define DEBUG__
void gogo(int );
void move_here_show(int  );
int collision_detec(void);
void gameover(int );
sigjmp_buf jmpbuffer;

int main()
{
    //函數間跳躍函數,保存進程信息,設置跳回點。(信號版,跳回後自動恢復信號屏蔽mask)
    //程序首次運行略過if內代碼,遊戲結束調用siglongjmp(jmpbuffer, int)跳回
    if(sigsetjmp(jmpbuffer,1) != 0){

    }
    init_tcs();//變量的初始化,和屏幕的初始化顯示,和蛇與食物的顯示

#ifndef DEBUG__
    signal(SIGALRM,gogo);       //設置定時信號的處理函數,用於蛇的移動,調試時,手動調用
    set_ticker(ticker);         //每ticher毫秒 產生信號
#endif
    while(1){
        int i =getch();
        switch (i) {
            case _KEY_UP   :
                if(real_g_forward!=_KEY_DOWN &&real_g_forward!=_KEY_UP)       //防止回跑
                    {g_key_forward=_KEY_UP;}break;
            case _KEY_DOWN :
                if(real_g_forward!=_KEY_UP&&real_g_forward!=_KEY_DOWN )
                    {g_key_forward=_KEY_DOWN ;}break;
            case _KEY_LEFT :
                if(real_g_forward!=_KEY_RIGHT&&real_g_forward!=_KEY_LEFT)
                    {g_key_forward=_KEY_LEFT;}break;//delay_real=&delay;
            case _KEY_RIGHT:
                if(real_g_forward!=_KEY_RIGHT&&real_g_forward!=_KEY_LEFT)
                    {g_key_forward=_KEY_RIGHT;}break;
#ifdef DEBUG__
            case 'g':gogo(0);//raise(SIGALRM);
#endif
        }
    }

    endwin();
    return 0;
}

void gogo(int signum)//zouba
{
    //保存可能隨時會變的全局變量g_key_forward(每次按下的但願前進的方向)
    int temp_g_forward=g_key_forward;
    move_here_show(temp_g_forward);//按照指定方向移動
    collision_detec();//碰撞檢測
}

void move_here_show(int  forward)
{
    switch (forward) {
        case _KEY_UP: --row; break;     //蛇頭結點按照指定方向的移動
        case _KEY_DOWN : ++row; break;
        case _KEY_LEFT: --col; break;
        case _KEY_RIGHT: ++col; break;
        default:return;
    }
    //此時更新蛇真正的前進方向
    //由於此時運動才完畢,且real_g_forward隨時都在鍵盤監聽中被使用
    real_g_forward=forward;

    //mvaddchstr(snake_s1.snake_body[last_s_node].row,
    //                          snake_s1.snake_body[last_s_node].col,LSBLANK);
    //屏幕上刪除(局部擦除)蛇最後一個節點
    mvaddstr(snake_s1.snake_body[last_s_node].row,
            snake_s1.snake_body[last_s_node].col,BLANK);
    //在隊列中新增蛇移動後的頭節點 座標(蛇頭始終增長到 數組的最小未用單元 (隊列尾))
    snake_s1.snake_body[++first_s_node].row= row;
    snake_s1.snake_body[first_s_node].col = col;
    //mvaddchstr(row,col,NOB);
    mvaddstr(row,col,NODE); //在屏幕上顯示出蛇頭新的座標

#ifdef DEBUG__
    //調試用的步數,能夠走MAX_SIZE步,大約1800秒,以前確定讓他死掉,這就是命
    printw("%d",first_s_node+1-INIT_NODE_SIZE);
#endif
    move(LINES-1,0);refresh();
    last_s_node++;            //在隊列中減去蛇移動後的尾節點 座標
}
int collision_detec()
{
    int i;
    //檢測是否食物
    if(col== food_col&& row ==food_row){
        srand((unsigned)time(NULL));
        food_col = 2+rand()%(COLS-5);
        food_row = 1+rand()%(LINES-3);

test_dupe://防止新生成的食物在蛇身上……
        for(i=last_s_node;i<first_s_node;++i){
           if(food_col == snake_s1.snake_body[i].col)
               if(food_row == snake_s1.snake_body[i].row ){
                   food_col = 2+rand()%(COLS-5);
                   food_row = 1+rand()%(LINES-3);
                   goto test_dupe;
                }
        }
        //mvaddchstr(food_row,food_col,FOOD);
        //這裏不改,centos也正常顯示,爲了保險,也改爲單字節版
        mvaddstr(food_row,food_col,FOOD);//
        last_s_node--;//muhaha

        //檢測是否升級 ,右邊數組對應level(下標)升級所須要的食物
        if(++eat_num >=times_to_upgrade_each_level[level]){
            //升級後定時器減小 每級應減小的時間,更新等級
            ticker -=delay_sub_each_level[level++];//注意此處是累減,
            //更新定時器,信號函數觸發加快,移動速度加快
            set_ticker(ticker);
        }
        score++;
        max_score =max_score>score?max_score:score;
        attrset(A_REVERSE);                     //反色下面的文字
        mvprintw(0,COLS/2 -16,"Score:%d Max Score:%d Level:%d",score,max_score,level);
        attrset(A_NORMAL);
        //move(LINES-1,0);refresh();//沒有也能夠,可是level那會閃

    }
    else{
        //檢測是否撞牆
        if(col == LEFT_WALL||col == RIGHT_WALL||row == TOP_WALL||row == BUTT_WALL)
            gameover(1);
        //檢測是否自攻
        for(i=last_s_node;i<first_s_node-2;++i){
            if(col == snake_s1.snake_body[i].col)
                if(row == snake_s1.snake_body[i].row )
                    gameover(2);
        }
    }
    return 0;
}
void gameover(int type)
{
    set_ticker(0);
    mvprintw(LINES/2-2,COLS/2 -15,           "           Game Over!    ");
    char * S=                                "       Your Score is %d!";
    if(score>max_score)
        mvprintw(LINES/2+1,COLS/2 -15,       "     You Cut the Record!!!");
    else S=                                  "  Your Score is %d! Too Young!!!";
    if(score>75)S =                          "     Your Score is %d! Nice!!!";
    mvprintw(LINES/2,COLS/2 -15, S,score);
    mvprintw(LINES/2+2,COLS/2 -15,           "Press <%c> Play Again! <%c> to Quit!",AGAIN_KEY,QUIT_KEY);
    refresh();
    free(snake_s1.snake_body);
    int geta;
    while(1){
        if((geta =getch()) == AGAIN_KEY){
            erase();
            refresh();
            siglongjmp(jmpbuffer, type);//game over 帶着信息跳到main開始準備好的套子裏……
        }
        else if(geta == QUIT_KEY)
            exit(0);
    }
}

int set_ticker(int n_msecs) //把 以毫秒爲單位的時間 轉換成對應的 定時器
{
    struct itimerval new_timeset;
    long n_sec,n_usecs;

    n_sec = n_msecs/1000;
    n_usecs=(n_msecs%1000)*1000L;

    new_timeset.it_interval.tv_sec=n_sec;
    new_timeset.it_interval.tv_usec=n_usecs;

    new_timeset.it_value.tv_sec = n_sec;
    new_timeset.it_value.tv_usec= n_usecs;

    return setitimer(ITIMER_REAL,&new_timeset,NULL);
}
View Code

 

init_tcs.c 初始化變量和屏幕的函數

 

#include "tanchishe.h"
int row=0; //the head
int col=0;

int g_key_forward=_KEY_RIGHT;
int real_g_forward=_KEY_RIGHT;
int food_row =0;
int food_col =0;
int eat_num=0;
int ticker = 200;//ms


struct snake_s snake_s1;
int last_s_node=0;
int first_s_node;

int score =0;
int max_score=0;//do not init in retry!
int level=0;
int delay_sub_each_level[MAX_LEVEL]={0};
int times_to_upgrade_each_level[MAX_LEVEL]={0};

void init_tcs(void)
{
    initscr();
    cbreak();       //關閉緩衝
    nonl();
    noecho();       //關閉回顯
    intrflush(stdscr,FALSE);
    keypad(stdscr,TRUE);
    curs_set(0);    //光標不可見

    row =LINES/2-1;
    col =COLS/2-1;
    eat_num=0;
    ticker = 310;//ms
    score =0;
    g_key_forward=_KEY_RIGHT;
    real_g_forward=_KEY_RIGHT;
    last_s_node=0;
    //first_s_node;
    level=0;

    //配置每升一級須要吃的食物,和每升一級蛇的快慢,也就是ticker的大小
    int sum=delay_sub_each_level[0]=8;//EVERY_LEVEL_SUB_TIME;
    int i;
    times_to_upgrade_each_level[0]=TIMES_TO_UPGRADE_EACH_LEVEL;
    for(i=1;i<MAX_LEVEL;++i){
        times_to_upgrade_each_level[i]=times_to_upgrade_each_level[i-1]+(TIMES_TO_UPGRADE_EACH_LEVEL);

        if(sum<ticker-50){
            if(i<6)
                delay_sub_each_level[i] =8;
            else if(i<12)
                delay_sub_each_level[i] =7;
            else if(i <18)
                delay_sub_each_level[i] =6;
            else
                delay_sub_each_level[i] =5;

        }
        else delay_sub_each_level[i]=delay_sub_each_level[i-1];
        sum =sum+delay_sub_each_level[i];

    }
    //繪製邊框
    attrset(A_REVERSE);
    for(i=0;i<LINES;++i){
        mvaddch(i,0,' ');
        mvaddch(i,LEFT_WALL,' ');
        mvaddch(i,RIGHT_WALL,' ');
        mvaddch(i,COLS-1,' ');
    }
    for(i=0;i<COLS;++i){
        mvaddch(0,i,' ');
        mvaddch(LINES-1,i,' ');
    }

    mvprintw(0,COLS/2 -16,"Score:%d Max Score:%d Level:%d",score,max_score,level);
    mvprintw(LINES-1,COLS/2 -18,"Eledim Walks the Earth,%c%c%c%c to Move",_KEY_UP, _KEY_DOWN, _KEY_LEFT, _KEY_RIGHT);
    refresh();
    attrset(A_NORMAL);

    //建立蛇的節點容器
    snake_s1.snake_body = malloc( SNAKE_MAX_SIZE *sizeof(struct post)) ;
    if(snake_s1.snake_body == NULL)
        mvprintw(0,0,"malloc error");
    memset(snake_s1.snake_body,0,SNAKE_MAX_SIZE*sizeof(struct post));

    srand((unsigned)time(NULL)); //no this ,rand every time return same num
#ifdef DEBUG__
    food_row = LINES/2 -1;
    food_col =COLS/2 +1;
#else
    food_row = 1+rand()%(LINES-3);
    food_col =2+rand()%(COLS-5);
#endif
    //初始化蛇在容器中的座標,和顯示
    //snake_s1.head.row=row;
    //snake_s1.head.col=col;
    snake_s1.node_num =INIT_NODE_SIZE;
    first_s_node=snake_s1.node_num-1;

    for(i=0;i<snake_s1.node_num;++i){
        snake_s1.snake_body[i].row=row;
        snake_s1.snake_body[i].col=col-snake_s1.node_num+1+i;
        //mvaddchstr(row,col-i,NOB);
        mvaddstr(row,col-i,NODE);
    }
    //show food
    //mvaddchstr(food_row,food_col,FOOD);
    mvaddstr(food_row,food_col,FOOD);
    move(LINES-1,0);refresh();
}
View Code

 

 

tanchishe.h

#ifndef TANCHISHE_H
#define TANCHISHE_H

#include <stdio.h>
#include <curses.h>
#include <time.h>
#include <sys/time.h>       //timeval
#include <unistd.h>         //usleep
#include <sys/select.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>        //memset
#include <setjmp.h>

#define LSBLANK     L" "
#define BLANK       " "

#define INIT_NODE_SIZE 5
#define SNAKE_MAX_SIZE 8192
#define LEFT_WALL 1
#define RIGHT_WALL (COLS-2)
#define TOP_WALL 0
#define BUTT_WALL (LINES-1)
#define NOB         L"#"//
#define LSFOOD      L"O"
#define NODE        "#"//instead of L"#"
#define FOOD        "O"
#define MAX_LEVEL 100
#define SUB_TIME_EACH_LEVEL 6
#define TIMES_TO_UPGRADE_EACH_LEVEL 6

#define _KEY_UP     'w'
#define _KEY_DOWN   's'
#define _KEY_LEFT   'a'
#define _KEY_RIGHT  'd'
#define AGAIN_KEY   'A'
#define QUIT_KEY    'Q'
struct post{
    int row;
    int col;
};
struct snake_s{
    int node_num;
    struct post head;
    struct post* snake_body;

};

//for all
extern struct snake_s snake_s1;
extern int row;
extern int col;
//extern struct timeval delay;

extern int real_g_forward;
extern int g_key_forward;
extern int food_row;
extern int food_col ;
extern int eat_num;
extern int score ;
extern int ticker ;//ms

extern int last_s_node;
extern int first_s_node;

extern int max_score;//do not init in retry!
extern int level;
extern int delay_sub_each_level[MAX_LEVEL];
extern int times_to_upgrade_each_level[MAX_LEVEL];
//extern int ticker_for_col;
//extern int ticker_for_row;

int set_ticker(int n_msecs);
void init_tcs(void);

#endif
View Code

 

 編譯:

cc tanchishe.c  init_tcs.c -lcurses

運行:

因爲curses是面向終端的接口(虛擬終端也包含在內),與圖形庫無關,因此程序在遠程ssh工具上也能夠完美運行,就像vim同樣。

能夠考慮繼續添加的特性:多人遊戲    路障  吃字母 網絡化 存檔文件   用戶記錄    暫停(ctrl+z)

相關文章
相關標籤/搜索