程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> Linux Curses編程實現貪吃蛇,curses貪吃蛇

Linux Curses編程實現貪吃蛇,curses貪吃蛇

編輯:關於C語言

Linux Curses編程實現貪吃蛇,curses貪吃蛇


curses庫 簡單而言,提供UNIX中多種終端 操作光標和顯示字符 的接口。我們常見的vi就是使用curses實現的。現在一般都用ncurses庫。

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

1.ubuntu安裝curses函數庫 

$sudo apt-get install ncurses-dev 

用curses庫,編譯程序:

$gcc program.c -o program -lcurses

 

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)

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved