C語言單鏈表貪吃蛇小遊戲
C語言實現單鏈表控制檯貪吃蛇小遊戲
編譯環境:vs2019
需求:
統計遊戲開始後的時間,控制貪吃蛇;吃到食物蛇身加長,得分加一;碰牆或蛇頭碰到身體減一條生命;生命消耗完則結束遊戲。
思路:
使用wasd鍵控制蛇的移動方向,蛇頭碰到食物得分加一,並在地圖上隨機產生一個食物,累加得分,碰牆或碰自己減一條生命,並初始化整條蛇,生命值為0時結束遊戲。
做法:
使用單鏈表控制貪吃蛇移動的核心思想就是:連結串列儲存貪吃蛇所有座標,每次迴圈貪吃蛇不斷向一個方向插入一個新的結點作為新的蛇頭,按下按鍵控制新蛇頭產生的位置,然後從新蛇頭處遍歷連結串列輸出蛇身到上一個蛇尾,清除上一個蛇尾的痕跡,並釋放相關結點。
每次向連結串列插入新節點後,判斷新節點的座標是否和食物的座標重合,如果重合本輪迴圈不釋放清除蛇尾結點,反之釋放清除上一個蛇尾的結點。
另外,在寫蛇生命相關程式碼的時候,還需要注意一下哪些值應該初始化,哪些值不應該初始化。
只要明白了貪吃蛇運動的核心思想,整個程式其實就不難寫出來。
難點:
wsad控制貪吃蛇上下左右移動,並清除蛇尾。
說明:
使用單鏈表實現貪吃蛇的核心思想是我一開始沒有想到的,部分相關程式碼我學習並借鑑了一些網路上搜索到的程式碼,如果有違反了相關版權協議,請告知我修改相關程式碼。
注意:
由於編譯器原因程式中_kbhit()和_getch()函式可能在其他編譯器上編譯會出現錯誤,解決辦法是去掉函式前面的“_”。
執行效果:
程式碼實現:
#include <stdio.h> #include <stdlib.h> #include <conio.h> #include <time.h> #include <windows.h> void HideCursor(); //游標隱藏 void gotoxy(int x,int y); //游標定位 typedef struct snake { int x; int y; struct snake* next; }snake; #define WIDTH 100 //控制檯視窗寬度 #define HEIGHT 30 //控制檯視窗高度 #define SNAKEN 4 //貪吃蛇初始長度 #define LIFE 3 //初始生命次數 #define SPEED 200 //遊戲速度、迴圈休眠時間 #define U 1 //使用巨集代替需要數字代替的蛇的行動方向 #define D 2 //巨集名含義是各方向英文單詞首字母 #define L 3 //蛇的狀態,U:上 ;D:下;L:左 R:右 #define R 4 void dtxxcsh() //輸出地圖 { for (int i = 1; i < WIDTH-1; i++) //輸出上下面牆 { gotoxy(i,26); printf("-"); gotoxy(i,0); printf("-"); } for (int i = 0; i < HEIGHT-3; i++) //輸出左右兩面牆 { gotoxy(0,i); printf("|"); gotoxy(99,i); printf("|"); } gotoxy(24,28); printf("得分: 0 生命: %d 時間: 0 ",LIFE); //xy 30,28可用得分數值 14個空格 } int foodx,foody; //食物位置座標 void sjcsswhs() //隨機產生一個食物 { srand(time(NULL)); foodx = rand() % (WIDTH - 4) + 2; //使用巨集運算隨機數最大值需要加括號 while (foodx % 2) //如果食物的x座標不是偶數,再獲取一個x座標 { foodx = rand() % (WIDTH - 4) + 2; //寬度 } foody = rand() % (HEIGHT - 7) + 3; //高度 gotoxy(foodx,foody); printf("★"); } snake* head; //蛇頭指標 void cshs() //初始化蛇的位置 { snake *tail; //蛇尾指標 int i; tail = (snake*)malloc(sizeof(snake)); tail->next = NULL; tail->x = HEIGHT-6; tail->y = 8; //貪吃蛇初始長度5 SNAKEN for (i = 1; i <= SNAKEN; i++) //在蛇尾處建立連結串列 { head = (snake*)malloc(sizeof(snake)); head->next = tail; head->x = 24 + i * 2; //head->x這個數必須為偶數,和食物座標偶數對應 head->y = 8; tail = head; //此時蛇尾指標指向蛇頭 } while (tail) { gotoxy(tail->x,tail->y); printf("■"); tail = tail->next; } } int status = R; //蛇前進狀態 snake* p = NULL; //工作指標 snake* nexthead; //下一個蛇頭 int score = 0; //得分 void snakemove() //蛇前進,上U,下D,左L,右R { nexthead = (snake*)malloc(sizeof(snake)); if (status == U) { nexthead->y = head->y - 1; //確定新蛇頭的下一個座標 x,y nexthead->x = head->x; } if (status == D) //下 { nexthead->y = head->y + 1; nexthead->x = head->x; } if (status == L) //左 { nexthead->x = head->x - 2; nexthead->y = head->y; } if (status == R) //右 { nexthead->x = head->x + 2; nexthead->y = head->y; } nexthead->next = head; head = nexthead; p = head; if (p->x == foodx && p->y == foody) //判斷蛇頭的位置是否和食物的位置重合 { while (p) //輸出尾結點 { gotoxy(p->x,p->y); if (p == head) printf("●"); else printf("■"); p = p->next; //因為每次運動都是新建立一個頭結點再刪除一個尾結點, } //所以要增加一節身體,只需要這次迴圈不釋放尾結點,並輸出一次尾結點就好了 //sjcsswhs(); //碰到食物則再產生一個食物 score++; gotoxy(32,28); printf("%d",score); } else { while (p->next->next) //不輸出尾結點 { gotoxy(p->x,p->y); if (p == head) printf("●"); else printf("■"); p = p->next; } gotoxy(p->next->x,p->next->y); printf(" "); free(p->next); p->next = NULL; } p = head; while (p) //如果食物的座標重新整理到了蛇身上則再產生一個食物 * { if (p->x == foodx && p->y == foody) sjcsswhs(); p = p->next; } } void czfxhs() //操作方向函式,接收從鍵盤輸入的按鍵,控制貪吃蛇行進方向 { char ch = _getch(); switch (ch) { case 'w': if(status != D) status = U; break; case 's': if (status != U) status = D; break; case 'a': if (status != R) status = L; break; case 'd': if (status != L) status = R; break; case ' ': _getch(); break; //空格暫停 } } int yxjstjjsmz_1() //生命掉落條件1咬自己 { snake* self = head->next; //self為蛇身上的一個結點 while (self) { if (self->x == head->x && self->y == head->y) //head和self的成員作比較,蛇頭一直存在,這裡遍歷的是蛇身 { return 1; } self = self->next; } return 0; } int yxjstjjsmz_2() //生命掉落條件2碰牆 { if (head->x <= 1 || head->x >= 98 || head->y <= 0 || head->y >= 26) return 1; return 0; } int i = LIFE - 1; //變數儲存生命次數 void qcsytmslbhs() //清除並釋放上一條蛇留下來的痕跡,更新生命資訊 { p = head; int _x_ = p->x; //用於儲存死掉的蛇的蛇頭處的位置,用於輸出被蛇頭頂掉的牆壁 int _y_ = p->y; while (head) { gotoxy(head->x,head->y); printf(" "); head = head->next; free(p); p = head; } gotoxy(52,28); //更新生命資訊 printf("%d",i); if (_y_ == 0 || _y_ == HEIGHT - 4) //用於在蛇死掉後,蛇頭的位置輸出被清除蛇頭頂替掉的牆壁 { gotoxy(_x_,_y_); printf("--"); } else if (_x_ == WIDTH - 2) { gotoxy(_x_+1,_y_); printf("|"); } else if(_x_ == 0) { gotoxy(_x_,_y_); printf("|"); } } void sbjsjmhs() //失敗結束介面 { p = head; //釋放記憶體 while (head) { head = head->next; free(p); p = head; } system("cls"); gotoxy(45,12); printf("遊戲結束!"); gotoxy(44,14); printf("最終得分:%d",score); gotoxy(0,28); } int updatetime() //獲取一次電腦現在的時間 { int now; SYSTEMTIME system_time; GetLocalTime(&system_time); now = system_time.wMinute * 60 + system_time.wSecond; return now; } int time_1 = updatetime(); //儲存遊戲剛開始的時間 void gametime() //寫在每次迴圈之內 { int time_2 = updatetime() - time_1; //更新遊戲開始後時間,用現在的時間減去剛開始的時間 gotoxy(72,28); printf("%d s",time_2); } int main()//主函式 { system("mode con cols=100 lines=30"); //設定控制檯大小 system("title 貪吃蛇遊戲"); //設定標題 HideCursor(); //隱藏游標 sjcsswhs(); //初始化隨機產生一個食物 dtxxcsh(); //初始化地圖、資訊 for (; i >= 0; i--) //生命 { cshs(); //初始化蛇的位置 status = R; //初始化運動方向 while (1) { snakemove(); //蛇行動動畫,方向被控制變數控制 if (_kbhit()) { czfxhs(); //接收鍵盤按鍵,控制控制變數 } if (yxjstjjsmz_1() || yxjstjjsmz_2())//兩個掉落生命的條件 break; gametime(); //更新遊戲時間 Sleep(SPEED); } qcsytmslbhs(); //清除上一條蛇留下來的痕跡,更新生命資訊 } sbjsjmhs(); //失敗結束介面 return 0; } void HideCursor() { CONSOLE_CURSOR_INFO cursor_info = { 1,0 }; SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cursor_info); } void gotoxy(int x,int y) { COORD pos = { x,y }; SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),pos); }
不足之處:
因為這是我第一次使用連結串列做一個完整的小程式,所以對連結串列的運用還很稚嫩,在程式碼規範和嚴謹性上面還有很多問題。
另外關於“食物如果重新整理到蛇身上則再隨機產生一個食物”(172行)相關程式碼,我不是很確定到底完不完善。
作為一名c語言新手,我對未知的知識始終抱有學習和謙卑的態度,如有貴人能夠對我的程式提出建議,我將不勝感激。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。