目 錄 | |
一 | Curses庫簡介與基本開發方法 |
二 | 事件驅動編程:編寫一個視頻遊戲 |
三 | 彈球遊戲的實現與優化 |
四 | 簡單TUI遊戲設計實踐總結 |
curses其實是一個函數開發包,專門用來進行UNIX下終端環境下的屏幕界面處理以及I/O處理。經過這些函數庫,C和C++程序就能夠控制終端的視頻顯示以及輸入輸出。html
使用curses包中的函數,用戶能夠很是方便的建立和操做窗口,使用菜單以及表單,並且最爲重要的一點是使用curses包編寫的程序將獨立於各類具體的終端,程序具備良好的移植性。node
庫名 | 描述 | 頭文件 |
curses | 最先的curses包只包含這一部分,主要控制屏幕的輸入和輸出,光標的操做,窗口的建立和操做等。 | curses.h |
panel | 相似於窗口堆棧,不一樣的窗口能夠存放於其中,而且能夠在其中進行移動。 | panel.h |
menu | 新增的部分,主要包括建立菜單而且與之交互的函數,主要用來接受用戶的選擇。 | menu.h |
form | 包括建立表單以及與之進行交互的函數,主要用來接受用戶數據輸入。 | form.h |
curses包的這種終端獨立性歸根於終端描述數據庫terminfo和termcap。linux
終端描述數據庫(Terminal Description Databases) - terminfo(TERMinal INFOrmation database) - termcap(TERMinal CAPabilitie database)
這兩個數據庫裏存放了不一樣終端的操做控制碼和轉義序列以及其他相關信息。ios
使用每個終端:數據庫
- curses將首先在終端描述數據庫中查找是否存在該類型的終端描述信息 - 若是找到,則進行相應的處理。 - 若是數據庫中沒有相應信息,則程序沒法在該終端上運行,除非用戶本身增長新的終端描述。
安裝編程
sudo apt-get install libncurses5-dev
若發現找不到這個包,可使用命令 sudo apt-get update 更新下包源。數組
gcc program.c -o program -lcurses
程序中使用curses庫函數:引用curses庫的頭文件curses.h,即:緩存
#include <curses.h>
<stdio.h>
和<unclt.h>
一塊兒包含進來,若是對於System V系統,<terminfo.h>
也會包含進來,另外還可能包括<termios.h>
、<termio.h>
、<sgtty.h>
,具體的由系統自己決定。編譯時加上-lcurses,用來在連接的時候提示連接程序將curses庫連接進去。安全
整型常量 | 定 義 |
OK | curses函數運行成功的返回值,系統定義爲0 |
ERR | curses函數運行發生錯誤時候的返回值,系統定義爲-1 |
TRUE | 布爾值,表示爲真,系統定義爲1 |
FALSE | 布爾值,表示爲假,系統定義爲0 |
在主函數中設置了信號處理函數以後咱們就調用了initscr(),通常狀況下在其他的curses函數被調用以前咱們就必須首先調用initscr()。數據結構
initscr()對curses包進行一些初始化的工做,並且在每個程序裏面,這個函數只能調用一次。它的做用主要包括:
- 經過讀取TERM環境變量的值來決定當前使用的終端類型,開啓終端模式。 - 根據終端的具體狀況將終端的一些性能參數讀進相關變量中,完成對相關數據結構的初始化工做,例如獲取LINES和COLS的值。 - 建立和初始化標準屏幕stdscr和當前屏幕curscr,同時爲它們分配必要的存儲空間。 - 通知refresh()函數首次調用的時候可以清除屏幕。
若是程序中使用了curses庫,那麼在程序初始化的時候,系統將自動產生兩個默認屏幕。
第一個:標準屏幕,系統定義爲stdscr,表明終端的整個屏幕區域。 第二個:當前屏幕,系統定義爲curscr,表明用戶可以看到的屏幕顯示。
curses庫的屏幕刷新機制:
若是咱們對當前屏幕進行更改而尚未調用刷新函數,那麼標準屏幕就僅是一個虛擬屏幕。
程序使用initscr()進行初始化以後,程序對終端的模式進行了一些設置。終端模式其實是一系列的開關屬性,它們直接影響着終端如何處理輸入以及輸出。例如:
- 當咱們敲入字符的時候,系統將根據模式屬性設置來判斷字符是否須要當即在屏幕上回顯。 - 系統根據模式設置決定讀取字符後的處理方法,是當即一一讀取仍是暫時先存放在字符緩衝區中。 - 系統判斷是否須要將輸入Ctrl+D解釋爲文件結束符。 - 系統判斷如何處理功能鍵F一、F2或者方向鍵等等,決定是將它們做爲普通的鍵讀取仍是做爲功能鍵讀取。
用來決定用戶的輸入是否當即回顯。
當ECHO模式設置後,它使得在鍵盤上輸入的每個字符都在終端屏幕上當前光標處顯示出來,在調用某些函數如addch()
時字符顯示後光標的位置將自動的向後移動一個位置。
在非回顯模式下,字符的顯示必須由程序自己來完成,不然字符不會顯示。
注:非回顯模式下按鍵一般用來控制屏幕的操做而不是用來進行字符輸入!
echo() 設置回顯模式 noecho() 關閉回顯模式 - 成功返回OK,失敗返回ERR。 - 默認狀況下回顯模式是打開的。
字符輸入在通常的狀況下必須加回車鍵才能完成,這時候退格鍵是起做用的,輸入的字符能夠經過BackSpace鍵刪除掉。可是這種操做並不適合交互操做。
注:中斷字符和流控制字符並不受這個模式的影響。
int cbreak() 打開當即輸入模式 int nocbreak() 關閉當即輸入模式
NEWLINE模式用來決定當輸入時回車鍵是否被對應爲新行產生符。
int nl() int nonl()
int keypad(win,buf)
參數:
WINDOW *win 一個WINDOW類型的指針,它指向須要設置功能鍵模式的窗口 int buf buf爲TRUE或者FALSE,用來指定模式的開啓和關閉。
功能鍵定義 | 控制碼 | 描 述 |
KEY_MIN | 0401 | Curses中定義的最小的鍵值 |
KEY_BREAK | 0401 | Break按鍵 |
KEY_DOWN | 0402 | ↓ |
KEY_UP | 0403 | ↑ |
KEY_LEFT | 0404 | ← |
KEY_RIGHT | 0405 | → |
KEY_HOME | 0406 | Home鍵 |
KEY_BACKSPACE | 0407 | 退格鍵backspace |
KEY_F0 | 0410 | 功能鍵F0 |
KEY_F(n) | KEY_F0+n | 功能鍵Fn |
KEY_DL | 0510 | 行刪除鍵 |
KEY_IL | 0511 | 行插入鍵 |
KEY_DC | 0512 | 字符刪除鍵 |
KEY_IC | 0513 | 字符插入鍵 |
KEY_NPAGE | 0522 | 下一頁 |
KEY_PPAGE | 0523 | 上一頁 |
KEY_END | 0550 | end按鍵 |
KEY_MAX | 0777 | 最大的curses鍵值 |
<tinfo.h>
找到,一般它的目錄爲/usr/include/
目錄下。int raw(); int noraw();
延遲模式包括半延遲模式和無延遲模式。
- int halfdelay(tenth) 設置半延遲模式 - nocbreak() 能夠取消終端的半延遲模式 - int nodelay(win,bf) 函數設置終端無延遲模式。
參數:
- int tenth指定半延遲的時間間隔,單位是10毫秒。 - WINDOW *win 指向須要設置無延遲模式的窗口的指針 - bool bf 用來決定開啓或者關閉該模式。若bf爲TRUE,則設置無延遲模式。
字符和字符串操做是應用程序中使用頻率最高的,curses庫中的一些基本函數容許咱們從鍵盤接受輸入,而且將結果輸出到指定窗口上或者在指定窗口上讀寫、刪除字符和字符串、定位光標位置或者控制字符色彩等。
int addch(ch); int echochar(ch); chtype ch; - 若是函數執行成功,將返回OK常量,不然返回ERR。
參數是一個chtype類型的字符,curses中將chtype聲明爲無符號長整型,它的低位能夠包含字符自己的信息,這部分與char字符類型類似,它的高位部分存儲了字符的額外的屬性信息,好比字符是否高亮度顯示,是否反顯以及什麼色彩等等。
函數在當前光標處輸出一個字符,同時光標將向右移動一個位置。若是移動後光標將超出了當前屏幕的範圍,光標將自動換行到下一行的開始位置。
除了經常使用的字符參數之外,addch()函數還可使用的C語言中的轉義字符:
轉義字符 | 描 述 |
/n | 換行,刪除當前到行尾的全部字符,並將字符指針向下移動一行。若設newline標誌,addch將刪除光標後的全部字符並將字符指針置於下行開始處。 |
/r | 回車,將字符指針置於當前行的開始處。 |
/t | 製表符,將字符指針移動到下一個製表符處。 |
int addstr(str); char *str;
字符串的首字符將在當前光標處輸出,它的參數是一個字符指針。
若是字符串的長度超出了屏幕的大小,字符串將被截取掉。
curses中與addstr()相似的函數還包括waddstr()、mvwaddstr()、mvaddstr()。
int printw(fmt [,arg…]) char *fmt;
參數:
- fmt 是一個字符串指針,用來表示打印的格式,好比對於字符串格式爲%s,字符爲%c,十進制整數爲%d,八進制整數爲%o等。 - arg 是須要打印的值,若是給出的arg不止一個,每個都必須用逗號隔開,它們必須與fmt的格式相適應。 - 若是fmt爲%s格式,則相應的arg參數必須爲一個字符串指針, - 若是fmt爲%d格式,則相應的arg參數必須爲整數。
相似的函數有mvprintw(),mvwprintw(),wprintw()。
getch()函數能夠從終端鍵盤讀入一個字符,而且返回字符的整數值。這個函數一般用來從鍵盤接受輸入。
輸入模式決定了在程序接受字符以前內核進行處理的過程。若是終端被設置成ECHO模式,getch()接受的字符將當即在屏幕上顯示,不然屏幕將保持不變化直到刷新後才顯示出來
一般狀況下,內核會緩存輸入文本,若不須要輸入緩存,就必須設置CBRREAK或者raw模式。
- 在raw模式下,內核並不進行緩存處理。 - 在CBREAK模式下,除了^S、^Q、^C、^Y 等控制字符之外,其他的字符都原封不動的發送到系統中被處理。 - 若是終端沒有設置成RAW或者NOECHO模式,getch()將自動的將終端設置成CBREAK模式。
對於普通的字符,getch() 將返回與字符自己相同的整數值。但若是想獲取功能鍵和方向鍵等咱們則必須設置功能鍵模式。一旦進行設置,getch()將返回curses.h中定義的與這些功能鍵對應的整數。
int getstr(str) char* str
從終端鍵盤接受字符串,而且顯示在指定的位置上。
與getch()相同,若是終端模式被設置爲ECHO,getstr()將終端屏幕上當即更新顯示字符。若是終端模式沒有設置爲RAW或者NOECHO模式,函數將自動將終端設置爲CBREAK模式,並在讀入字符串之後自動恢復到之前的模式。
int scanw(fmt [,argptr…]) char * fmt;
int insch(ch) chtype ch; int delch();
字符插入之後光標將自動的向右移動一個位置,若是最右邊的字符超出了終端屏幕的範圍,它將被截取掉。
從當前位置刪除一個字符,而且將刪除字符右邊的全部字符向左移動一個位置。當前行最右邊的字符由空格代替。
int insertln(); int deleteln();
A_NORMAL:標準的顯示模式 A_BLINK:閃爍屬性 A_BOLD:加粗屬性 A_DIM: 半透明屬性 A_REVERSE:反顯屬性 A_STANDOUT:高亮度顯示屬性 A_UNDERLINE:加下劃線 A_ALTCHARSET:可代替字符集 COLOR_PAIR(n):字符的背景和前景屬性
int attron(attrs) int attrset(attrs) int attroff(attrs) - chtype attrs;
attroff()關閉某個已經存在的屬性。
attrset(0)的特殊用法關閉全部的屬性。
Curses庫中光標分爲物理光標和邏輯光標。 - 物理光標是最經常使用的光標,每一個窗體只有一個物理光標。 - 邏輯光標屬於curses窗體,每一個窗體可能有多個邏輯光標。
int move(y,x); int y,x;
屏幕的行寬和列寬在curses庫中定義爲(LINES-1,COLS-1)。
須要注意的是行和列都是從0開始計數。咱們進行的大部分操做在操做以前都須要移動光標到必定的位置,若是這樣的話咱們須要分兩步進行:移動光標,而後進行相關操做。爲了更方便,一些函數將移動光標與顯示字符結合起來執行。這種函數的格式通常以下:
mvfunc(y , x, [arg,…]) - func通常爲操做函數的名字,好比addch,addstr等等。 - y爲操做進行時候光標所在的行數,一般也是移動以後的新的光標位置。 - x爲操做進行時候光標所在的列數。
例如:須要將光標移動到(10,5)處而後輸出字符‘X’,那麼咱們就可使用move()函數與addch()函數結合造成的mvaddch()函數來實現。能夠寫爲:mvaddch(10,5,‘X’);
- 清除整個屏幕 int clear(); int erase(); - 清除指定窗口 wclear(); werase();
這四個函數在標準屏幕上使用空格來代替當前字母從而達到清屏的效果。
clear()清除屏幕上的全部的字符而且將光標移動到屏幕的原點(0,0),繼而自動調用clearok()函數,這使得屏幕在下次調用refresh()刷新的時候可以徹底被清除。所以clear()函數可以清除物理屏幕上的那些沒法識別的東西,這樣下次輸出將是基於一個徹底「乾淨」的屏幕進行的。
erase()函數一樣能夠用來清除屏幕,可是它不會自動調用clearok()函數,所以與clear()相比,它並非一種最完全的清除方式。
int clrtoeol() int clrtobot()
用空格代替當前的須要清除部分的現有字符。
無論clrtobot()仍是clrtoeol()都會改變當前光標的位置。
操做系統一樣要面臨上面4個相似的問題:
- 內核將程序載入內存空間並維護每一個程序在內存中所處的位置。 - 在內核的調度下,程序以時間片間隔的方式運行,同時,內核在特定的時間運行特定的任務。 - 內核必須在很短的時間內響應用戶和外設在任什麼時候刻的輸入。 - 同時,作幾件事須要一些技巧。內核要保證數據的有序和規整的。
遊戲的概要描述:
- 球以必定的速度和方向移動 - 球碰到牆壁或擋板會被彈回 - 用戶按按鈕來控制擋板移動
#include <stdio.h> #include <curses.h> void main() { int i; initscr(); clear(); for(i=0; i<LINES; i++ ){ move( i, i+i ); if ( i%2 == 1 ) standout(); addstr("Hello, world"); if ( i%2 == 1 ) standend(); sleep(1); refresh(); //比較不一樣,而後刷新屏幕 } endwin(); }
Hello,world
這串字符在屏幕上自上而下逐行顯示,每秒增長一行,反色和正常交替出現。
如何讓上一個例子創造移動的假象?
#include <stdio.h> #include <curses.h> void main() { int i; initscr(); clear(); for(i=0; i<LINES; i++ ){ move( i, i+i ); if ( i%2 == 1 ) standout(); addstr("Hello, world"); if ( i%2 == 1 ) standend(); refresh(); sleep(1); move(i,i+i); //將光標移動到上一條字符串的開頭 addstr(" "); //用空串覆蓋原有字符串 } endwin(); }
方法:先輸出一個字符串,而後sleep一秒,而後在原來的地方寫空字符串覆蓋掉原有字符串,在下一行輸出新的字符串。
sleep函數由3個步驟組成:
- 爲SIGALRM設置一個處理函數 - 調用alarm(num_seconds) - 調用pause
- 有更高的精度:能夠精確到微秒。 - usleep()將當前進程掛起n微秒或者直到有一個不能被忽略的信號到達。 - 每一個進程都有3個獨立的計時器 - 每一個計時器有兩個設置:初始間隔和重複間隔設置 - 支持alarm和sleep
- 真實(ITIMER_REAL):進程運行的所有時間 - 虛擬(ITIMER_VIRTUAL):進程在用戶態的時間 - 實用(ITIMER_PROF):進程在用戶態加內核態的時間
包含初始間隔和重複間隔,每一個間隔由秒數和微秒數組成。
- 初始間隔 it_value - 重複間隔 it_interval
it_interval設爲0,再也不重複這一特徵。
#include<sys/time.h> 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); } - getitimer(int which, struct itimerval *val) - setitimer(int which, const struct itimerval* newval, struct itimerval *oldval); - which指定哪一種計時器:ITMER_REAL, ITIMER_VIRTUAL, ITIMER_PROF。
struct itimerval { struct timeval it_interval; struct timeval it_value; }; struct timeval { time_t tv_sec; suseconds_t tv_usec; };
#include <curses.h> #include <sys/time.h> #include <signal.h> #include <stdlib.h>
#define LEFT 0 //當前屏幕的最左邊 #define TOP 0 //當前屏幕的最上邊 #define RIGHT COLS-1 //球所能到達的當前屏幕最大水平範圍 #define BOTTOM LINES-1 //球所能到達的當前屏幕最大垂直範圍 #define WIDE RIGHT-LEFT+1 //寬度 #define BOARD_LENGTH 10 //擋板長度
char BALL = 'O'; //球的形狀 char BLANK = ' '; //覆蓋球走過的軌跡 int hdir; //控制球水平運動的變量 int vdir; //控制球垂直運動的變量 int pos_X; //球的橫座標 int pos_Y; //球的縱座標 int left_board; //擋板左端點 int right_board; //擋板右端點 int is_lose=0; //標誌:小球是否落在擋板上 int delay=100; //設置速度 int ndelay; //速度倍數
int main() { //初始化curses initscr(); crmode(); //中斷模式 noecho(); //關閉回顯 control(); endwin(); //結束 curses return 0; }
void init() { int i,j; clear(); //初始球 pos_X =20; //球初始的橫座標 pos_Y = BOTTOM-1; //球初始的縱座標 //初始化球的運動方向,朝右上方運動 hdir=1; vdir=-1; //初始擋板 left_board=20; right_board=left_board+BOARD_LENGTH; //顯示擋板 for(i=left_board;i<=right_board;i++) { move(BOTTOM,i); addch('='); } //初始刷新時間 ndelay=2; signal(SIGALRM,moveBall); set_ticker(delay*ndelay); keypad(stdscr,TRUE); //打開 keypad 鍵盤響應 attroff(A_BLINK); //關閉 A_BLINK 屬性 is_lose=0; move(pos_Y,pos_X); addch(BALL); move(LINES-1, COLS-1); refresh(); usleep(100000); move(LINES-1,COLS-1); refresh(); }
void moveBall() { if(is_lose) return; signal(SIGALRM,moveBall); move(pos_Y,pos_X); addch(BLANK); pos_X += hdir; pos_Y += vdir; //改變方向 if(pos_X >= RIGHT) //當球橫座標大於右邊邊緣時,球反彈朝左運動 hdir = -1; if(pos_X <= LEFT) //當球橫座標大於左邊邊緣時,球反彈朝右運動 hdir = 1; if(pos_Y <= TOP) //當球縱座標大於頂部邊緣時,球反彈朝下運動 vdir = 1; //當球在底部的時候進行額外的處理 if(pos_Y >= BOTTOM-1) { if(pos_X>=left_board&&pos_X<=right_board) //球在擋板處 vdir=-1; else //球不在擋板處 { is_lose=1; move(pos_Y,pos_X); addch(BALL); move(LINES-1, COLS-1); refresh(); usleep(delay*ndelay*1000); move(pos_Y,pos_X); addch(BLANK); pos_X += hdir; pos_Y += vdir; move(pos_Y,pos_X); addch(BALL); move(LINES-1, COLS-1); refresh(); } } //不改變球的方向 move(pos_Y,pos_X); addch(BALL); move(LINES-1, COLS-1); refresh(); }
cmd=getch(); if(cmd=='q') break;//按「Q」鍵退出遊戲 //擋板左移 else if(cmd==KEY_LEFT) { if(left_board>0) { move(BOTTOM,right_board); addch(' '); right_board--; left_board--; move(BOTTOM,left_board); addch('='); move(BOTTOM,RIGHT); refresh(); } } //擋板右移 else if(cmd==KEY_RIGHT) { if(right_board<RIGHT) { move(BOTTOM,left_board); addch(' '); right_board++; left_board++; move(BOTTOM,right_board); addch('='); move(BOTTOM,RIGHT); refresh(); } }
int flag=1; char choice; move(6,10); addstr("Game over! Do you want try again?(y/n):"); refresh(); choice=getch(); while(flag) { if(choice=='y'||choice=='Y'||choice=='n'||choice=='N') flag=0; else choice=getch(); } if(choice=='y'||choice=='Y') { init(); continue; } else { if(choice=='n'||choice=='N') break; }
/* Bounceball Game Version 1.3 - 新增welcome界面 - 可設置遊戲難易程度(小球速度,擋板長度) - 記分 */ #include <curses.h> #include <sys/time.h> #include <signal.h> #include <stdlib.h> #define LEFT 0 //當前屏幕的最左邊 #define TOP 0 //當前屏幕的最上邊 #define RIGHT COLS-1 //球所能到達的當前屏幕最大水平範圍 #define BOTTOM LINES-1 //球所能到達的當前屏幕最大垂直範圍 #define WIDE RIGHT-LEFT+1 //寬度 char BALL = 'O'; //球的形狀 char BLANK = ' '; //覆蓋球走過的軌跡 int hdir; //控制球水平運動的變量 int vdir; //控制球垂直運動的變量 int pos_X; //球的橫座標 int pos_Y; //球的縱座標 int left_board; int right_board; int BOARD_LENGTH; int is_lose=0; int score=0; int delay; int ndelay; void init(); void moveBall(); void control(); int set_ticker(int n_msecs); void start(); void help(); void information(); int welcome(); int main() { //初始化curses initscr(); crmode(); //中斷模式 noecho(); //關閉回顯 welcome(); endwin(); //結束 curses return 0; } int welcome() { move(6,20); addstr("Hello! Welcome the the Bounceball Game!"); move(8,23); addstr("1.Start the Game"); move(9,23); addstr("2.Help"); move(10,23); addstr("3.About me"); move(11,23); addstr("4.Quit"); int flag = 1; char choice; move(13,23); addstr("Please choose your choices : "); refresh(); choice=getch(); while(flag) { if(choice=='1'||choice=='2'||choice=='3'||choice=='4') { flag = 0; switch(choice) { case '1': start(); break; case '2': help(); welcome(); break; case '3': information(); welcome(); break; case '4': break; } } else choice=getch(); } return 0; } void start() { clear(); move(6,20); addstr("Game Level:"); move(8,23); addstr("1.Easy"); move(9,23); addstr("2.Normal"); move(10,23); addstr("3.Hard"); score=0; int flag = 1; char choice; move(13,23); addstr("Please choose the level : "); refresh(); choice=getch(); while(flag) { if(choice=='1'||choice=='2'||choice=='3') { flag = 0; switch(choice) { case '1': delay=100; BOARD_LENGTH=10; break; case '2': delay=60; BOARD_LENGTH=8; break; case '3': BOARD_LENGTH=5; delay=40; break; } } else choice=getch(); } clear(); move(8,20); addstr("Are you ready?"); refresh(); control(); } void control() { init(); int cmd; while (1) { if(!is_lose) { cmd=getch(); if(cmd==27) break;//退出 //擋板左移 else { if(cmd==KEY_LEFT) { if(left_board>0) { move(BOTTOM,right_board); addch(' '); right_board--; left_board--; move(BOTTOM,left_board); addch('='); move(BOTTOM,RIGHT); refresh(); } } //擋板右移 else { if(cmd==KEY_RIGHT) { if(right_board<RIGHT) { move(BOTTOM,left_board); addch(' '); right_board++; left_board++; move(BOTTOM,right_board); addch('='); move(BOTTOM,RIGHT); refresh(); } } } } } else { //輸掉球后的處理 int flag=1; char choice; move(6,20); addstr("Game over!"); move(8,20); addstr("Your score : "); printw("%d",score); move(10,20); addstr("Do you want to try again?(y/n):"); refresh(); choice=getch(); while(flag) { if(choice=='y'||choice=='Y'||choice=='n'||choice=='N') flag=0; else choice=getch(); } if(choice=='y'||choice=='Y') { score=0; init(); continue; } else { if(choice=='n'||choice=='N') { clear(); refresh(); welcome(); break; } } } } } void init() { int i,j; clear(); //初始球 pos_X =20; //球初始的橫座標 pos_Y = BOTTOM-1; //球初始的縱座標 //初始化球的運動方向,朝右上方運動 hdir=1; vdir=-1; //初始擋板 left_board=20; right_board=left_board+BOARD_LENGTH; //顯示擋板 for(i=left_board;i<=right_board;i++) { move(BOTTOM,i); addch('='); } //初始刷新時間 ndelay=2; signal(SIGALRM,moveBall); set_ticker(delay*ndelay); keypad(stdscr,TRUE); //打開 keypad 鍵盤響應 attroff(A_BLINK); //關閉 A_BLINK 屬性 is_lose=0; move(pos_Y,pos_X); addch(BALL); move(LINES-1, COLS-1); refresh(); usleep(100000); move(LINES-1,COLS-1); refresh(); } void moveBall() { if(is_lose) return; signal(SIGALRM,moveBall); move(pos_Y,pos_X); addch(BLANK); pos_X += hdir; pos_Y += vdir; //改變方向 if(pos_X >= RIGHT) hdir = -1; if(pos_X <= LEFT) hdir = 1; if(pos_Y <= TOP) vdir = 1; //當球在底部的時候進行額外的處理 if(pos_Y >= BOTTOM-1){ if(pos_X>=left_board&&pos_X<=right_board) { vdir=-1; score++; } else { is_lose=1; move(pos_Y,pos_X); addch(BALL); move(LINES-1, COLS-1); refresh(); usleep(delay*ndelay*1000); move(pos_Y,pos_X); addch(BLANK); pos_X += hdir; pos_Y += vdir; move(pos_Y,pos_X); addch(BALL); move(LINES-1, COLS-1); refresh(); } } //不改變球的方向 move(pos_Y,pos_X); addch(BALL); move(LINES-1, COLS-1); refresh(); } 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); } void help() { clear(); move(6,20); addstr("Help Information"); move(8,23); addstr(" <- : Control the baffle left shift"); move(9,23); addstr(" -> : Control the baffle right shift"); move(10,23); addstr(" q : Exit the game "); move(12,40); addstr("Press any key to exit..."); refresh(); int ch=getch(); clear(); refresh(); } void information() { clear(); move(6,20); addstr("About the Game"); move(8,23); addstr("written by 20135317_Han"); move(9,23); addstr("Version 1.3"); move(11,40); addstr("Press any key to exit..."); refresh(); int ch=getch(); clear(); refresh(); }
參考資料1:Unix/Linux編程實踐教程(Understanding UNIX/LINUX Programming)
參考資料2:Unix/Linux下的Curses庫開發指南——第一章 Curses庫開發簡介
參考資料3:Unix/Linux下的Curses庫開發指南——第二章 curses庫I/O處理
參考資料4:Linux下curses函數庫安裝運行
參考資料5:linux中curses使用
參考資料6:curses編程函數1(三類輸出函數)
參考資料7:Linux的sleep()和usleep()的使用
參考資料8:《Unix-Linux編程實踐教程》讀書筆記(七)
參考資料9:第七章 事件驅動編程(curses庫,計時器,信號,異步I/O)
參考資料10:Linux進程的計時器和間隔計時器
參考資料11:時鐘編程: alarm和setitimer
參考資料12:信息安全系統設計基礎第十週學習總結——第八章 異常控制流