貪吃蛇脫韁自動向右走:脫韁的野蛇
#include <curses.h>
#include <stdlib.h>
struct snake{
????????int hang;
????????int lie;
????????struct snake *next;
};
struct snake *head;
struct snake *tail;
void initNcurse()
{
????????initscr();
????????keypad(stdscr,1);
}
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 gamepic()
{
????????int hang;
????????int lie;
????????move(0,0);
????????for(hang=0;hang<20;hang++){
????????????????if(hang == 0){
????????????????????????for(lie=0;lie<20;lie++){
????????????????????????????????printw("--");
????????????????????????}
?????????????????printw("\n");
????????????????}
????????????????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 shijintao");
??????????printw("\n");
???}
void addNode()
{
????????struct snake *new;
????????new =(struct snake *)malloc(sizeof(struct snake));
????????new->hang=tail->hang;
????????new->lie=tail->lie+1;
????????tail->next = new;
????????tail = new;
????????new->next = NULL;
}
void ?initSnake()
{
????????struct snake *p;
????????while(head != NULL){
?????????????p=head;
?????????????head=head->next;
?????????????free(p);
????????}
????????head = (struct snake *)malloc(sizeof(struct snake));
????????head->hang=2;
????????head->lie=2;
????????head->next=NULL;
????????tail = head;
????????addNode();
????????addNode();
}
void deleteNode()
{
????????struct snake *p;
????????p = head;
????????head = head->next;
????????free(p);
}
void moveSnake()
{
????????addNode();
????????deleteNode();
????????if(tail->hang==0||tail->hang==20||tail->lie==20||tail->lie==0){
????????????????initSnake();
????????}
}
int main()
{
????????????????int con;
????????????????initNcurse();
????????????????initSnake();
????????????????gamepic();
????????????????while(1){
???????????????????????????moveSnake();
???????????????????????????gamepic();
???????????????????????????refresh();
???????????????????????????usleep(100000);
????????????????}
????????????????getch();
????????????????endwin();
????????????????return 0;
}
這邊最主要改動的代碼地方在于main函數里面的?
????????主要是我們要先想一下如果只是一味的想讓這個蛇自己往右邊走(這個右邊就是代碼效果,也沒有設置方向,就是新節點的插入導致一個向右邊走的效果),肯定要把監測右方向鍵給去掉,然后死循環就可以實現l,不斷的moveSnake(不斷的addNode,deleteNode)
while(1){
???????????????????????????moveSnake();
???????????????????????????gamepic();
???????????????????????????refresh();
???????????????????????????usleep(100000);
????????????????}
原始代碼中,getch()?函數的調用確實會導致屏幕刷新。這是因為?getch()?在等待用戶輸入時,會隱式觸發一次屏幕刷新。
1.?getch()?的隱式刷新機制
在 ncurses 中,getch()?不僅用于獲取用戶輸入,還會執行以下操作:
- 刷新屏幕:在等待按鍵時,ncurses 會自動將當前緩沖區內容渲染到屏幕上。
- 處理輸入事件:捕獲用戶按下的鍵并返回其 ASCII 碼(或特殊鍵的編碼,如方向鍵)。
因此,當你在循環中調用?getch()?時,即使沒有顯式調用?refresh(),屏幕也會被刷新。這解釋了為什么你之前的代碼能正常顯示畫面。
2. 為什么現在需要顯式調用?refresh()?
在你的新代碼中,循環結構發生了變化:
while(1){
????moveSnake();??????// 更新蛇位置
????gamepic();????????// 繪制畫面
????refresh();????????// 顯式刷新屏幕
????usleep(100000);???// 延時控制速度}
這里的關鍵區別是:蛇的移動不再依賴用戶輸入,而是通過定時器自動更新(usleep())。因此:
- 如果沒有?refresh(),繪制操作(gamepic())的結果只會停留在緩沖區,無法顯示。
- usleep()?是純延時函數,不會觸發屏幕刷新,必須手動調用?refresh()。
3. 兩種實現方式的對比
舊代碼(依賴?getch()) | 新代碼(自動移動) |
蛇僅在用戶按鍵時移動 | 蛇自動持續移動 |
getch()?隱式刷新屏幕 | 必須顯式調用?refresh() |
游戲節奏由用戶控制 | 游戲節奏由?usleep()?控制 |
4. 最佳實踐建議
- 始終顯式調用?refresh():即使在有?getch()?的場景中,顯式刷新能讓代碼邏輯更清晰,避免依賴隱式機制。
- 分離輸入處理和畫面刷新:在自動移動的游戲中,用戶輸入(如方向鍵)和屏幕刷新是獨立的操作,需要分別處理。
總結
原始代碼之所以能正常顯示,是因為?getch()?在等待輸入時隱式刷新了屏幕。而在新的自動移動實現中,由于沒有依賴?getch(),必須通過?refresh()?手動刷新屏幕才能看到蛇的移動效果。這是 ncurses 編程中的一個常見細節,理解它有助于寫出更健壯的代碼!
貪吃蛇風騷走位之支持方向變換
????????那么問題來了,方向的改變要監測鍵盤的按鍵需要一個while(1)循環(不斷的檢測用戶的輸入)? ?還有,蛇的脫韁的走也需要一個while(1)循環支持,就是這兩個while(1)需要同時跑,那么我們現在單線程,就是目前無法一心二用,所以我們還做不了,需要學習多線程。
Linux多線程概念引入及編碼實現
我們做貪吃蛇的時候不是要需要控制按方向鍵和moveSnake(脫韁的野蛇)且 要同時運行且都是要用while死循環去進行接收嗎,那么我們單線程就無法做到了,這個時候要學習多線程,這樣我們就是可以同時執行兩個死循環。
所以我們先學習一下多線程的基本原理
想讓我們先看個單線程的例子
我想通過這個單線程的返回結果,和等下多線程的返回結果做對比就很容易理解多線程的意思了;
????????因為在單線程環境下,函數是順序執行的。當調用 FUNC1 時,即使里面有sleep
操作,它也只是讓執行流程暫停一下,并不會切換到其他函數執行,所以會先輸出this is FUNC1
,等待sleep
時間過后再繼續輸出this is FUNC1
。
假如現在這段代碼變成了多線程
在多線程環境下,當一個線程執行到類似sleep
操作時,它會暫停執行一段時間,這時操作系統會調度其他可運行的線程。
假設存在FUNC1
和FUNC2
兩個函數分別在不同線程中執行:
- 當
FUNC1
所在線程執行到sleep
時,該線程會進入休眠狀態,此時操作系統會尋找其他可運行的線程,很可能就會找到FUNC2
所在線程(如果FUNC2
線程處于可運行狀態),然后開始執行FUNC2
。 - 當
FUNC2
執行到類似sleep
操作后,它也會暫停,操作系統又會重新調度,此時FUNC1
線程休眠時間結束的話,可能會繼續執行FUNC1
;如果FUNC1
還沒結束休眠,且還有其他可運行線程,就可能會執行其他線程。
這種線程間的切換和執行順序,取決于操作系統的線程調度機制,沒有辦法精確地預測哪個線程會在特定時刻執行,除非使用線程同步機制(如鎖、條件變量、信號量等)來控制線程的執行順序。例如在 Java 中,可以使用join
方法讓一個線程等待另一個線程執行完畢;或者使用同步塊和wait
、notify
方法來控制線程間的協作和執行順序。
通過下面的例子學習一下
運行后是這樣的 ?一直循環這個兩句話,就是說不一定每次調用的都是一樣的(在多核是同時運行,在單核是他們去爭奪cpu資源),但是可以保證他們可以同時運行的(這就夠了)
- #include <pthread.h>?:引入 POSIX 線程庫的頭文件,該庫用于在 C 語言中實現多線程編程,后續用到的?pthread_t?類型以及?pthread_create?等函數聲明都在這個頭文件中。
- func1?和?func2?是兩個線程函數。它們的返回類型為?void *?,這是 POSIX 線程庫中線程函數的標準返回類型要求。
- 每個函數內部都有一個無限循環?while(1)?。在循環中,先通過?printf?輸出相應的提示信息("this is func1\n"?或?"this is func2\n"?),然后調用?sleep(1)?讓線程暫停 1 秒鐘。這使得線線程變量聲明:pthread_t th1;?和?pthread_t th2;?聲明了兩個?pthread_t?類型的變量,pthread_t?用于標識線程,th1?和?th2?分別用來標識即將創建的兩個不同線程。
- 線程創建:
- pthread_create(&th1,NULL,func1,NULL);?調用?pthread_create?函數創建了一個新線程,該線程的標識符存儲在?th1?中。第二個參數?NULL?表示使用默認的線程屬性;第三個參數?func1?指明這個新線程要執行的函數是?func1?;第四個參數?NULL?表示不向?func1?函數傳遞額外參數 。
- 同理,pthread_create(&th2,NULL,func2,NULL);?創建了另一個線程,其標識符為?th2?,執行函數為?func2?。
- 無限循環與程序結束:while(1);?是一個無限循環,這里的作用是防止?main?函數執行結束。因為在多線程程序中,如果主線程(main?函數所在線程)執行完畢退出,那么其他子線程也會被操作系統強制終止。通過這個無限循環,讓主線程一直保持運行狀態,從而使得創建的?th1?和?th2?線程能持續執行它們各自的任務。return 0;?理論上不會執行到,不過按照?main?函數的規范,需要有返回值聲明。
整體功能
這段代碼實現了一個簡單的多線程程序,創建了兩個線程分別執行?func1?和?func2?函數,這兩個線程會并發地不斷打印各自的提示信息并間隔 1 秒。但代碼存在一些不足,比如沒有對線程的返回值進行處理,也沒有合理的方式來終止線程或主線程,實際應用中可以進一步完善,比如添加信號處理機制來結束程序等 。
- 程會不斷重復打印信息并間隔 1 秒。