1. 程式人生 > 程式設計 >C語言單鏈表貪吃蛇小遊戲

C語言單鏈表貪吃蛇小遊戲

C語言實現單鏈表控制檯貪吃蛇小遊戲

編譯環境:vs2019

需求:

統計遊戲開始後的時間,控制貪吃蛇;吃到食物蛇身加長,得分加一;碰牆或蛇頭碰到身體減一條生命;生命消耗完則結束遊戲。

思路:

使用wasd鍵控制蛇的移動方向,蛇頭碰到食物得分加一,並在地圖上隨機產生一個食物,累加得分,碰牆或碰自己減一條生命,並初始化整條蛇,生命值為0時結束遊戲。

做法:

使用單鏈表控制貪吃蛇移動的核心思想就是:連結串列儲存貪吃蛇所有座標,每次迴圈貪吃蛇不斷向一個方向插入一個新的結點作為新的蛇頭,按下按鍵控制新蛇頭產生的位置,然後從新蛇頭處遍歷連結串列輸出蛇身到上一個蛇尾,清除上一個蛇尾的痕跡,並釋放相關結點。

每次向連結串列插入新節點後,判斷新節點的座標是否和食物的座標重合,如果重合本輪迴圈不釋放清除蛇尾結點,反之釋放清除上一個蛇尾的結點。

另外,在寫蛇生命相關程式碼的時候,還需要注意一下哪些值應該初始化,哪些值不應該初始化。
只要明白了貪吃蛇運動的核心思想,整個程式其實就不難寫出來。

難點:

wsad控制貪吃蛇上下左右移動,並清除蛇尾。

說明:

使用單鏈表實現貪吃蛇的核心思想是我一開始沒有想到的,部分相關程式碼我學習並借鑑了一些網路上搜索到的程式碼,如果有違反了相關版權協議,請告知我修改相關程式碼。

注意:

由於編譯器原因程式中_kbhit()和_getch()函式可能在其他編譯器上編譯會出現錯誤,解決辦法是去掉函式前面的“_”。

執行效果:

C語言單鏈表貪吃蛇小遊戲

程式碼實現:

#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語言新手,我對未知的知識始終抱有學習和謙卑的態度,如有貴人能夠對我的程式提出建議,我將不勝感激。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。