linux環境下Ncurses實現貪吃蛇游戲

游戲說明: linux環境下基于Ncurses圖形庫的C語言小游戲。

Ncurses介紹:
Ncurses(new curses)是一套編程庫,它提供了一系列的函數以便使用者調用它們去生成基于文本的用戶界面。 Ncurses是一個能提供功能鍵定義(快捷鍵),屏幕繪制以及基于文本終端的圖形互動功能的動態庫。Ncurses用得最多的地方是linux內核編譯之前的內核配置,Ncurses早已淡出舞臺,甚至體驗感完爆Ncurses的C圖形庫GTK、C++圖形庫QT也區趨于落伍嵌入式設備上的Android 系統。這個游戲只是使用Ncurses并不以學習它為目的,主要還是通過這個游戲鍛煉我們C語言的能力。

準備工作:
在ubuntu上面安裝Ncurses庫,輸入以下指令:

sudo apt-get install libncurses5-dev

來編寫第一個程序看看是否能成功運行:

#include<curses.h>
int main()
{initscr();//ncurses界面初始化函數printw("This is a ncurses window.\n");//在ncurses模式下的printfgetch();//等待用戶輸入,如果沒有這句話,程序就退出了,看不到運行結果endwin();//程序退出,調用該函數來恢復shell終端的顯示,如果沒有這句話,shell終端字亂碼壞掉return 0;
}

運行上面那個程序時需要用到-lcurses:

gcc ncurses.c -lcurses -o ncurses  //因為需要用到curses庫所以要鏈入curses

以上代碼輸出界面是:
在這里插入圖片描述
Ncurses的上下左右鍵值:

#define KEY_DOWN 0402
#define KEY_UP   0403
#define KEY_LEFT 0404
#define KEY_RIGHT 0405
//這些是Ncurses的一些宏定義,表示上下左右鍵,這些數都是八進制的
/*
這些內容可以通過命令查看curses.h的文件可知
命令:cd /usr/include/curses.h
*/

代碼如下:

#include<curses.h>
int main()
{initscr();//ncurses界面初始化函數printw("This is a ncurses window.\n");//在ncurses模式下的printfkeypad(stdscr,1);//這個函數是一個函數,第一個參數是從stdscr中接受功能建,第二個是參數表示是否接收,1表示接收while(1){//char c=getch();//等待用戶輸入,如果沒有這句話,程序就退出了,看不到運行結果int key=getch();//這里要把char類型改為int類型,因為char類型最大表示是128,而這些方向鍵的值(0404等)要>128,所以用intprintw("you input :%d\n",key);}endwin();//程序退出,調用該函數來恢復shell終端的顯示,如果沒有這句話,shell終端字亂碼壞掉return 0;
}代碼運行結果如下:
This is a ncurses window.
you input :259
you input :258
you input :260
you input :261
按下上下左右鍵,分別輸出259258260261
輸出的這些數字都是十進制的,分別對應宏定義中的:0403040204040405

代碼可以進一步優化:

#include<curses.h>
int main()
{initscr();//ncurses界面初始化函數printw("This is a ncurses window.\n");//在ncurses模式下的printfkeypad(stdscr,1);//這個函數是一個函數,第一個參數是從stdscr中接受功能建,第二個是參數表示是否接收,1表示接收while(1){//char c=getch();//等待用戶輸入,如果沒有這句話,程序就退出了,看不到運行結果int key=getch();//這里要把char類型改為int類型,因為char類型最大表示是128,而這些方向鍵的值(0404等)要>128,所以用intswitch(key){case 0402:printw("DOWN");break;case 0402:printw("DOWN");break;case 0402:case 0402:printw("DOWN");break;printw("DOWN");break;}}endwin();//程序退出,調用該函數來恢復shell終端的顯示,如果沒有這句話,shell終端字亂碼壞掉return 0;
}
當然程序中的代表方向的數字也可以用宏表示。
輸入上下左右鍵,程序運行結果如下:
This is a ncurses window.
UP
DOWN
LEFT
RIGHT

地圖規劃:

兩個- 和一個 | 組成一個方格,下圖是20×20的一個地圖。
在這里插入圖片描述地圖代碼:

#include<curses.h>void initNcurses()
{initscr();keypad(stdscr,1);
}void map()
{int hang;int lie;for(hang=0;hang<20;hang++){if(hang==0){for(lie=0;lie<20;lie++){printw("--");}printw("\n");printw("");}if(hang>=0&&hang<=19){for(lie=0;lie<=20;lie++){if(lie==0||lie==20){printw("|");}else{printw("  ");}}printw("\n");}if(hang==19){for(lie=0;lie<20;lie++){printw("--");}printw("\n");printw("");}}printw("By Feng Hui Nan.");
}
int main()
{initNcurses();map();getch();endwin();//沒有這行會破壞shell終端return 0;
}

貪吃蛇身子節點構成:

struct Snake
{int hang;int lie;struct Snake*next;
};

實現蛇身子的全部顯示,并向右移動:

#include<curses.h>
#include <stdlib.h>
#include <unistd.h>
void initNcurses()
{initscr();keypad(stdscr,1);
}struct Snake //貪吃蛇身子的一個結點
{int hang;int lie;struct Snake*next;
};
struct Snake* head=NULL;//將蛇的頭節點定義為全局變量
struct Snake* tail=NULL;//將蛇的尾節點定義為全局變量,防止錯誤過多
int hasSnakeNode(int i,int j)
{struct Snake *p;p=head;while(p!=NULL){if(p->hang==i && p->lie==j){return 1;}p=p->next;}return 0;
}
void map()
{int hang;int lie;move(0,0);//在每次調用地圖的時候用move函數將光標移動到地圖的第一個方格的位置for(hang=0;hang<20;hang++){if(hang==0){for(lie=0;lie<20;lie++){printw("--");}printw("\n");printw("");}if(hang>=0&&hang<=19){for(lie=0;lie<=20;lie++){if(lie==0||lie==20){printw("|");}else if(hasSnakeNode(hang,lie)){printw("[]");}else{printw("  ");}}printw("\n");}if(hang==19){for(lie=0;lie<20;lie++){printw("--");}printw("\n");}}printw("By Feng Hui Nan.");
}
void addSnake()
{struct Snake* new=(struct Snake*)malloc(sizeof(struct Snake));new->hang=tail->hang;new->lie=tail->lie+1;new->next=NULL;tail->next=new;tail=new;
}
void initSnake()
{struct Snake*p;while(head!=NULL){p=head;head=head->next;//將鏈表的每一個節點都釋放掉,指針后移free(p);}//添加這個循環的目的是在一次游戲結束后釋放當局游戲創建的鏈表,避免內存泄露head=(struct Snake*)malloc(sizeof(struct Snake));head->hang=1;head->lie=1;head->next=NULL;tail=head;addSnake();addSnake();addSnake();
}
void deleSnake()
{struct Snake*p;p=head;head=head->next;free(p);
}
void moveSnake()
{addSnake();//在蛇的尾部添加節點并且刪除節點后然后判斷尾節點的行和列是否達到邊界deleSnake();if(tail->hang==0 || tail->lie==20 || tail->lie==0 || tail->hang==20){initSnake();}
}
int main()
{initNcurses();initSnake();map();while(1){moveSnake();map();//刪除后再次刷新地圖顯示移動后的界面refresh();//刷新界面函數//sleep(1);//每隔一秒蛇移動一下,并刷新界面,這行控制蛇的移動速率usleep(100000);//sleep以秒為單位有點慢,usleep以微秒為單位此處控制蛇的移動速度}getch();endwin();//沒有這行會破壞shell終端return 0;
}

效果圖:
在這里插入圖片描述遇到問題:
如何在響應方向鍵的同時,控制蛇的移動?好像需要兩個while循環同時執行。我想到一個方法,就是用switch判斷輸入的方向鍵,然后再封裝向上下左右移動的函數,在switch中進行調用,仔細思考以下發現不可行,因為在這個游戲中地圖是每隔100毫秒刷新一次要不斷的刷新,所以不可行。

問題解決:
用linux線程即可解決該問題,我們將一個程序里的執行路線叫做線程(thread)。更準確的定義是:線程是一個進程內部的控制序列。

線程demo:

#include<stdio.h>
#include<pthread.h>void*thread(void *arg)
{printf("this is a thread and arg=%d.\n",*(int*)arg);*(int*)arg=0;return arg;
}
int main(int argc,char *argv[])
{pthread_t th;int ret;int arg=10;int *thread_ret=NULL;ret=pthread_create(&th,NULL,thread,&arg);//pthread_create這個函數是線程的創建函數//第一個參數th是線程的描述符,就是上邊定義的pthread_t th中的th,就相當于給第三那個參數的一個ID吧//第二個參數一般就寫NULL//第三個參數是做好的準備被調用的函數,最后一個參數是函數的參數if(ret!=0){printf("Creat thread error!\n");return -1;}printf("this is the mian process.\n");pthread_join(th,(void**)&thread_ret);//int pthread_join(pthread_t thread, void **value_ptr);//thread:等待退出線程的線程號。//value_ptr:退出線程的返回值。return 0;
}

自己實現線程的創建:

#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
void* fun1()
{while(1){printf("this is func1.\n");sleep(1);}
}
void* fun2()
{while(1){printf("this is fun2.\n");sleep(1);}
}
int main()
{pthread_t th1;pthread_t th2;int ret1;int ret2;int *pthread_th1=NULL;int *pthread_th2=NULL;ret1=pthread_create(&th1,NULL,fun1,NULL);ret2=pthread_create(&th2,NULL,fun2,NULL);if(ret1!=0 || ret2!=0){printf("thread creat error.\n");perror("error");return -1;}
//      while(1);//在主線程等待其他線程運行完后再退出pthread_join(th1,(void **)&pthread_th1);pthread_join(th2,(void **)&pthread_th2);//這種方法也可以使主線程等待指定線程退出后才退出//th2是線程的ID//int pthread_join(pthread_t thread, void **value_ptr);//thread:等待退出線程的線程號。//value_ptr:退出線程的返回值。return 0;}

程序總體代碼:

#include<curses.h>
#include <stdlib.h>
#include <unistd.h>
#include<pthread.h>#define UP     1
#define DOWN  -1
#define RIGHT  2
#define LEFT  -2struct Snake //貪吃蛇身子的一個結點
{int hang;int lie;struct Snake*next;
};struct Snake food;
void initFood()
{int x=rand()%20;int y=rand()%20;//%20是為了保證使rand出現的隨機數盡量的出現在地圖范圍內food.hang=x;food.lie=y;
}
void initNcurses()
{initscr();keypad(stdscr,1);noecho();//不要把無關的東西打印在界面上 
}struct Snake* head=NULL;//將蛇的頭節點定義為全局變量
struct Snake* tail=NULL;//將蛇的尾節點定義為全局變量,防止錯誤過多
int key;
int dir;int hasFood(int i,int j)
{if(food.hang==i && food.lie==j){return 1;}return 0;
}
int hasSnakeNode(int i,int j)
{struct Snake *p;p=head;while(p!=NULL){if(p->hang==i && p->lie==j){return 1;}p=p->next;}return 0;
}
void map()
{int hang;int lie;move(0,0);//在每次調用地圖的時候用move函數將光標移動到地圖的第一個方格的位置for(hang=0;hang<20;hang++){if(hang==0){for(lie=0;lie<20;lie++){printw("--");}printw("\n");printw("");}if(hang>=0&&hang<=19){for(lie=0;lie<=20;lie++){if(lie==0||lie==20){printw("|");}else if(hasSnakeNode(hang,lie)){printw("[]");}else if(hasFood(hang,lie)){printw("**");}else{printw("  ");}}printw("\n");}if(hang==19){for(lie=0;lie<20;lie++){printw("--");}printw("\n");}}printw("By Feng Hui Nan.");
}
void addSnake()
{struct Snake* new=(struct Snake*)malloc(sizeof(struct Snake));new->next=NULL;switch(dir){case UP:new->hang=tail->hang-1;new->lie=tail->lie;break;case DOWN:new->hang=tail->hang+1;new->lie=tail->lie;break;case LEFT:new->hang=tail->hang;new->lie=tail->lie-1;break;case RIGHT:new->hang=tail->hang;new->lie=tail->lie+1;break;}tail->next=new;tail=new;
}
void initSnake()//蛇的初始化函數
{struct Snake*p;dir=RIGHT;while(head!=NULL){p=head;head=head->next;//將鏈表的每一個節點都釋放掉,指針后移free(p);}//添加這個循環的目的是在一次游戲結束后釋放當局游戲創建的鏈表,避免內存泄露initFood();head=(struct Snake*)malloc(sizeof(struct Snake));head->hang=1;head->lie=1;head->next=NULL;tail=head;addSnake();addSnake();addSnake();
}
void deleSnake()
{struct Snake*p;p=head;head=head->next;free(p);
}int ifSnakeDie()
{struct Snake*p;p=head;if(tail->hang<0 || tail->lie==20 || tail->lie==0 || tail->hang==20){return 1;}while(p->next!=NULL){if(p->hang==tail->hang && p->lie==tail->lie){return 1;}p=p->next;}return 0;}
void* moveSnake()
{addSnake();//在蛇的尾部添加節點并且刪除節點后然后判斷尾節點的行和列是否達到邊界if(hasFood(tail->hang,tail->lie)){initFood();}else{deleSnake();}if(ifSnakeDie()){initSnake();}
}
void* refreshUi()//封裝的界面刷新函數
{while(1){moveSnake();map();//刪除后再次刷新地圖顯示移動后的界面refresh();//刷新界面函數//sleep(1);//每隔一秒蛇移動一下,并刷新界面,這行控制蛇的移動速率usleep(150000);//sleep以秒為單位有點慢,usleep以微秒為單位此處控制蛇的移動速度}
}
void turn(int direction)//這個函數的作用是當蛇在豎直方向上運動時使上下鍵無效//在水平方向運動時,左右方向鍵無效
{if(abs(dir)!=abs(direction)){dir=direction;}
}
void* changeDir()
{while(1){key=getch();switch(key){case KEY_DOWN:turn(DOWN);break;case KEY_UP:turn(UP);break;case KEY_RIGHT:turn(RIGHT);break;case KEY_LEFT:turn(LEFT);break;}}}
int main()
{initNcurses();initSnake();map();pthread_t t1;pthread_t t2;int *pthread1=NULL;int *pthread2=NULL;pthread_create(&t1,NULL,changeDir,NULL);pthread_create(&t2,NULL,refreshUi,NULL);
//      pthread_join(t1,(void**)&pthread1);
//      pthread_join(t2,(void**)&pthread2);while(1);getch();endwin();//沒有這行會破壞shell終端return 0;
}

編譯的時時候記得要加-lpthread、-lcurses

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/272265.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/272265.shtml
英文地址,請注明出處:http://en.pswp.cn/news/272265.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

韓順平循序漸進學java 第13講 抽象類.接口

13.1抽象類 13.1.1 概念 當父類的一些方法不能確定時&#xff0c;可以用abstract關鍵字來修飾該方法&#xff0c;稱為抽象方法&#xff0c;用abstract來修飾該類&#xff0c;稱為抽象類。 13.1.2 抽象類-深入討論 抽象類是java中一個比較重要的類&#xff1a; 1、用abstract關鍵…

C#實現簡體繁體轉換代碼示例

//簡體轉繁體 public static string _ConvertChinTrad(string strInput) { EncodeRobert edControl new EncodeRobert(); string strResult ""; if (strInput null) return strResult; if (strInput.ToString().Length > 1) strResult edControl.SCTCConvert(…

java基礎JDK的安裝和環境變量的配置

JRE和JDK&#xff1a; JRE是java程序運行時環境&#xff0c;包含JVM&#xff08;相當于java在不同操作系統上運行時java和操作系統之間的翻譯&#xff0c;保證java程序的跨平臺&#xff09;和運行時所需要的核心庫。所以我們想要運行一個已有的java程序&#xff0c;那么只需要…

C#通過SMTP發送郵件代碼示例

1、新建SMTP.cs類庫文件 public class SMTP { /// <summary> /// SMTP服務器 /// </summary> public string smtp { get; set; } /// <summary> /// SMTP服務器端口 /// </summary> public int port { get; set; } /// <summary> /// 發件人 ///…

docker下載tomact

docker run -it -p 8080:8080 tomcat 比如下載tomcat,你現在去訪問&#xff0c;先訪問docker里面的tomcat, 左邊的8080是對外暴露的服務端口&#xff0c;對應著右邊的8080是tomact的實際端口 下載tomcat 啟動tomcat docker run -it -p 8080:8080 tomcat

Wijmo 2016年藍圖

2015年很快就過去了&#xff0c;這是 Wijmo 重要的一年&#xff0c;尤其是對 Wijmo5。脫離傳統的小部件&#xff0c;重新寫一套 JS 控件&#xff0c;現在看來這個決定是正確的。用 TypeScript 寫 Wijmo5&#xff0c;意味著我們沒有任何依賴&#xff0c;不再需要 jQuery&#xf…

IDEA安裝和運行HelloWorld

IDEA安裝&#xff1a; IDEA中Hello World步驟&#xff1a; ① ②點擊創建空項目&#xff0c;下一步 ③ ④在打開后會彈出以下界面&#xff0c;然后點擊新建模塊 ⑤點擊新建模塊后出現以下界面&#xff0c;選擇java并選擇JDK的安裝路徑。 ⑥然后修改模塊名稱&#xff0c;點擊…

C#獲取電腦IP、MAC地址示例代碼

/// <summary> /// 使用 C# 自帶的類庫實現計算機信息獲取 /// </summary> public class DefaultDeviceInfo { public virtual string GetCpuId() { try { string cpuInfo " "; ManagementClass cimobject new ManagementClass("Win32_Processor…

docker運行隨機分配端口

docker run -d -it -P tomcat -P這個是大寫的P&#xff0c;表示隨機分配端口 執行后可以看到32768為隨機分配的端口&#xff0c;8080是tomcat端口 測試成功

C# Stream 和 byte[] 之間的轉換

/// <summary> /// 將 Stream 轉成 byte[] /// </summary> public byte[] StreamToBytes(Stream stream) { byte[] bytes new byte[stream.Length]; stream.Read(bytes, 0, bytes.Length); // 設置當前流的位置為流的開始 stream.Seek(0, SeekOrigin.Begi…

docker下如何進入到容器中

1:查看容器 docker ps -a 2:進入容器 docker exec -it e3cc80415dc7 /bin/bash 退出容器 exit

java方法和 IDEA Debug調試

方法的概述&#xff1a; 方法就是將具有獨立功能的代碼塊&#xff0c;組織成為一個整體&#xff0c;使其具有特殊功能的代碼集。我感覺方法就是類里面的函數 注意&#xff1a; 方法必須先創建才可以使用&#xff0c;該過程稱為方法的定義。方法創建后并不是直接運行的&#xf…

編譯性語言、解釋性語言和腳本語言的區別

計算機是不能理解高級語言&#xff0c;當然也就不能直接執行高級語言了。計算機只能直接理解機器語言&#xff0c;所以任何語言&#xff0c;都必須將其翻譯成機器語言&#xff0c;計算機才能運行高級語言編寫的程序。 一、翻譯和解釋的不同 翻譯的方式有兩種&#xff0c;一個是…

為什么JAVA的垃圾回收機制無法避免內存泄漏

一、本文參考&#xff1a;1.《深入理解java虛擬機 JVM高級特性與最佳實踐》2.http://coderevisited.com/memory-leaks-in-java/二、對象已死的判定方法要進行JVM中對象回收首先要判斷對象是否已經死亡&#xff0c;判斷的方法有如下幾個&#xff1a;1.引用計數法給對象中添加一個…

Java里try catch的簡單用法

Java里try catch的簡單用法&#xff1a; Java里try catch的簡單用法&#xff1a; 1、trycatch 程序的流程是&#xff1a;運行到try塊中&#xff0c;如果有異常拋出&#xff0c;則轉到catch塊去處理。然后執行catch塊后面的語句 擴展部分&#xff1a; 1、trycatchfinally 程序…

【代碼筆記】iOS-點擊城市中的tableView跳轉到旅游景點的tableView,下面會有“顯示”更多。...

一&#xff0c;效果圖。 二&#xff0c;工程圖。 三&#xff0c;代碼。 RootViewController.h #import <UIKit/UIKit.h>interface RootViewController : UIViewController <UITableViewDelegate,UITableViewDataSource> {UITableView * _tableView;NSMutableArray …