1. 程式人生 > 程式設計 >C語言實現貪吃蛇遊戲設計

C語言實現貪吃蛇遊戲設計

C語言實現貪吃蛇,供大家參考,具體內容如下

實驗平臺:DEV C++

 /********************************************************************************
*File name:SnakeGame3.0.c                            
*Description:貪吃蛇遊戲原始碼(C語言),採用
*寬度優先演算法,計算蛇到食物的最短路徑(時間複雜度n^3空間複雜度n^2),這個演算法遇 *
*到自身圍困情況將失效,無法計算出最短路徑                    *
*********************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
#include<time.h>
#include<math.h>
#include<conio.h>

#define SIZE 25 //定義地圖大小
#define MAX_LENGTH 19 * 19 //定義蛇的最大長度

typedef struct point //地圖上的點的節點
{
 int r;
 int c;
} point;
typedef struct queue //迭代搜尋最短路徑用到的佇列
{
 point * body[5 * SIZE]; //儲存蛇的身體的陣列(棧的深度最大為5 * SIZE)
 int num; //記錄佇列中節點數
 int first_in_pos; //第一個進入佇列的元素的索引值
} queue;
HANDLE stdOutput; //宣告windows標準輸出控制代碼

void init(int * length,point * foodAt,int * dir,point body[],char map[][SIZE]); //初始化
int getDir(int dir); //獲取蛇的行進方向
int getAIDir(int dir,int length,point foodAt); //獲取AI判斷得出的行進方向
int moveable(point moveTo,point body[]);
int move(point foodAt,int dir,point body[]); //蛇的運動
void draw(int length,point foodAt,char map[][SIZE]); //畫圖
void food(point * foodAt,char map[][SIZE]); //生成食物

//棧相關的操作
point * pop(queue *queue); //從列隊取出最先進入的點,返回取出點的指標,取出失敗返回NULL
void push(point *point,queue *queue); //推入列隊中

int main()
{
 char map[SIZE][SIZE]; //定義地圖
 point body[MAX_LENGTH],foodAt; //整個蛇身體和食物的所在點(body陣列的第一個值為蛇頭)
 int length; //蛇的實際長度
 int dir; //行進方向
 int rate = 1; //行進速率
 int result; //儲存蛇運動的結果:【死亡】、【得分】、【無】
 init(&length,&foodAt,&dir,body,map); //初始化

 while (1)
 {
 Sleep(100 / rate);
 //dir = getDir(dir); //獲取蛇的行進方向
 dir = getAIDir(dir,length,foodAt); //獲取AI判斷得出的行進方向
 result = move(foodAt,dir,body); //蛇的運動
 if (result == 1) //如果吃到食物
 {
  length++;
  rate = length / 3;
  if (length == MAX_LENGTH)
  {
  printf("您已通關!");
  break;
  }
  food(&foodAt,map); //生成食物
 }
 draw(length,foodAt,map); //畫圖
 if (result == -1) //如果死亡
 {
  break;
 }

 }
 Sleep(500); 
 printf(" 失敗,此次得分為%d             ",(length - 3) * 100);
 system("pause");
}
void init(int * length,char map[][SIZE]) //初始化
{
 memset(map,'*',SIZE * SIZE); //初始化地圖
 body[0].r = 3,body[0].c = 2; //初始化蛇的身體
 body[1].r = 2,body[1].c = 2;
 body[2].r = 1,body[2].c = 2;
 *length = 3; //初始長度為3
 *dir = 2; //初始方向向下
 food(foodAt,*length,map); //生成食物
 draw(*length,*foodAt,map); //畫圖
 printf(" 按下任意鍵開始,用ASDW控制方向,ESC暫停\n");
 _getch();
 srand((unsigned)time(NULL)); //生成隨機數種子,備用
 stdOutput = GetStdHandle(STD_OUTPUT_HANDLE); //獲取標準輸出控制代碼
 CONSOLE_CURSOR_INFO cci;
 cci.bVisible = 0;
 cci.dwSize = 1;
 SetConsoleCursorInfo(stdOutput,&cci);
 COORD coord = { 0,SIZE * 2 + 3 };
}
int getDir(int dir) //獲取蛇的行進方向,規定返回值0代表向上,1代表向右,2代表向下,3代表向左
{
 char key;
 int newDir = dir;
 if (_kbhit())
 {
 key = _getch();
 switch (key)
 {
 case 'A': case 'a': newDir = 3; break;
 case 'S': case 's': newDir = 2; break;
 case 'D': case 'd': newDir = 1; break;
 case 'W': case 'w': newDir = 0; break;
 case 27: _getch(); break;
 }
 }
 if (newDir - dir == 2 || newDir - dir == -2) //蛇不能反向
 {
 newDir = dir;
 }
 return newDir;
}
int getAIDir(int dir,point foodAt) //獲取AI判斷得出的行進方向
{
 static int *shortestPathDir,count = 0; //儲存最短路徑的方向(方向的先後順序為倒序,即排在後面的方向先走)

 if (count == 0) //如果最短路徑還沒生成,那麼重新生成
 {
 int map_of_steps[SIZE][SIZE]; //儲存到達地圖上某一點的最小步數
 queue queue = { 0,0 };
 point *last_body = (point *)malloc(length * sizeof(point)); //儲存計算過程中上次蛇的身體位置
 point *next_body; //儲存下一次蛇的身體
 point next_point;
 int i,step = 0;
 point moveTo;
 memcpy(last_body,length * sizeof(point));
 memset(map_of_steps,SIZE * SIZE * sizeof(int));

 //向佇列中放入初始body
 push(last_body,&queue);
 push(NULL,&queue); //插入NULL來標識寬度優先搜尋的某一層的結束
 step++; //用step來表示步數,也代表層數

 while (queue.num != 0)
 {
  last_body = pop(&queue);
  if (last_body == NULL) //如果某一層結束
  {
  if (queue.num != 0) //如果還有下一層的元素
  {
   step++;
   push(NULL,&queue); //插入下一層的結束標誌
   continue;
  }
  else
  {
   break;
  }

  }
  for (i = 0; i < 4; i++) //分別檢測四個方向能否移動
  {
  switch (i)
  {
  case 0: moveTo.r = last_body[0].r - 1,moveTo.c = last_body[0].c; break;
  case 1: moveTo.r = last_body[0].r,moveTo.c = last_body[0].c + 1; break;
  case 2: moveTo.r = last_body[0].r + 1,moveTo.c = last_body[0].c; break;
  case 3: moveTo.r = last_body[0].r,moveTo.c = last_body[0].c - 1; break;
  }
  if (moveable(moveTo,last_body) && map_of_steps[moveTo.r][moveTo.c] == 0) //如果移向的點之前沒有移到過
             //(即當前路徑是到該點的最短路徑),而且該點是moveable的
  {
   map_of_steps[moveTo.r][moveTo.c] = step;
   if (moveTo.r == foodAt.r && moveTo.c == foodAt.c) //如果下一步就可以到達食物所在點
   {
   //先free一些沒用的動態記憶體
   free(last_body);
   while (queue.num != 0)
   {
    free(pop(&queue));
   }
   goto outer; //跳出迴圈
   }
   //生成next_body並將其推入佇列
   next_body = (point *)malloc(length * sizeof(point));
   for (i = length - 1; i > 0; i--) //移動蛇的位置
   {
   next_body[i] = body[i - 1];
   }
   next_body[0] = moveTo; //換一個頭

   push(next_body,&queue);//推入佇列

  }
  }
  //free一些沒用的動態記憶體
  free(last_body);
 }
 outer:;
 if (map_of_steps[foodAt.r][foodAt.c] == 0) //如果無法到達食物所在點,那麼按原路走,直到死亡
 {
  return dir;
 }
 //生成shortestPath
 shortestPathDir = (int *)malloc(step * sizeof(int));
 count = step;
 next_point = foodAt;
 for (i = 0; i < step - 1; i++) //利用map_of_steps和下一個點推知到上一個點到下一個點的方向dir
 {
  if (next_point.r + 1 < SIZE && map_of_steps[next_point.r][next_point.c] ==
  map_of_steps[next_point.r + 1][next_point.c] + 1)
  {
  shortestPathDir[i] = 0;
  next_point.r += 1;
  }
  else if (next_point.c - 1 >= 0 && map_of_steps[next_point.r][next_point.c] ==
  map_of_steps[next_point.r][next_point.c - 1] + 1)
  {
  shortestPathDir[i] = 1;
  next_point.c -= 1;
  }
  else if (next_point.r - 1 >= 0 && map_of_steps[next_point.r][next_point.c] ==
  map_of_steps[next_point.r - 1][next_point.c] + 1)
  {
  shortestPathDir[i] = 2;
  next_point.r -= 1;
  }
  else
  {
  shortestPathDir[i] = 3;
  next_point.c += 1;
  }
 }
 //第一步要單獨判斷(因為map_of_steps的值為0的點可能是蛇頭,也可能是蛇身,這樣會對蛇第一步方向判斷產生干擾)
 if (body[0].r > next_point.r)
 {
  shortestPathDir[step - 1] = 0;
 }
 else if (body[0].r < next_point.r)
 {
  shortestPathDir[step - 1] = 2;
 }
 else if (body[0].c > next_point.c)
 {
  shortestPathDir[step - 1] = 3;
 }
 else
 {
  shortestPathDir[step - 1] = 1;
 }
 /*printf("\n\n\n");
 int j;
 for (i = 0; i < SIZE; i++)
 {
  for (j = 0; j < SIZE; j++)
  {
  printf("%3d",map_of_steps[i][j]);
  }
  printf("\n");
 }
  printf("\n");*/
 }
 //沿著最短路徑走
 return shortestPathDir[--count];
}
int moveable(point moveTo,point body[]) //判斷是否可以移動到moveTo點,能1,不能0
{
 int i;
 for (i = 0; i < length - 1; i++)
 {
 if (moveTo.r == body[i].r && moveTo.c == body[i].c)
 {
  return 0;
 }
 }
 if (moveTo.r < 0 || moveTo.r >= SIZE || moveTo.c < 0 || moveTo.c >= SIZE) //如果超出邊界
 {
 return 0;
 }
 return 1;
}
int move(point foodAt,point body[]) //蛇的運動,規定返回值-1代表死亡,0代表沒有吃到食物,1代表吃到食物
{
 int i,flag = 0;
 point head = body[0];
 switch (dir)
 {
 case 0: head.r -= 1; break;
 case 1: head.c += 1; break;
 case 2: head.r += 1; break;
 case 3: head.c -= 1; break;
 }
 if (head.r < 0 || head.r >= SIZE || head.c < 0 || head.c >= SIZE) //出界了死亡
 {
 return -1;
 }
 for (i = 0; i < length - 1; i++)
 {
 if (head.r == body[i].r && head.c == body[i].c) //咬到了自己死亡
 {
  return -1;
 }
 }
 if (head.r == foodAt.r && head.c == foodAt.c) //吃到了食物
 {
 length++;
 flag = 1; //標記一下,便與等下返回值為1
 }
 for (i = length - 1; i > 0; i--) //移動蛇的位置
 {
 body[i] = body[i - 1];
 }
 body[0] = head; //換一個頭
 if (flag == 1)
 {
 return 1;
 }
 return 0;
}
void draw(int length,char map[][SIZE]) //畫圖
{

 static char bitmap[SIZE + 2][SIZE + 2]; //定義一個數組,用於把地圖背景、邊界、蛇、食物都畫上去
 int i,j;
 for (i = 0; i < SIZE; i++) //背景
 {
 for (j = 0; j < SIZE; j++)
 {
  bitmap[i + 1][j + 1] = map[i][j];
 }
 }
 //邊框
 bitmap[0][0] = '0',bitmap[0][SIZE + 1] = '1';
 bitmap[SIZE + 1][0] = '2',bitmap[SIZE + 1][SIZE + 1] = '3';
 for (i = 0; i < SIZE; i++)
 {
 bitmap[0][i + 1] = '4',bitmap[SIZE + 1][i + 1] = '4';
 bitmap[i + 1][0] = '5',bitmap[i + 1][SIZE + 1] = '5';
 }
 bitmap[foodAt.r + 1][foodAt.c + 1] = 'f'; //食物
 bitmap[body[0].r + 1][body[0].c + 1] = 'h'; //蛇頭
 for (i = 1; i < length; i++) //蛇身
 {
 bitmap[body[i].r + 1][body[i].c + 1] = 'b';
 }

 COORD coord = { 0,0 }; //座標0,0
 SetConsoleCursorPosition(stdOutput,coord); //把游標設定到0,0位置
 for (i = 0; i < SIZE + 2; i++)
 {
 for (j = 0; j < SIZE + 2; j++)
 {
  switch (bitmap[i][j])
  {
  case 'f': printf("★"); break;
  case 'b': printf("●"); break;
  case 'h': printf("○"); break;
  case '0': printf("┏"); break;
  case '1': printf("━┓"); break;
  case '2': printf("┗"); break;
  case '3': printf("━┛"); break;
  case '4': printf(" ━"); break;
  case '5': printf("┃ "); break;
  default: printf(" ");
  }
 }
 printf("\n");
 }
}
void food(point * foodAt,char map[][SIZE]) //生成食物
{
 int i;
 while (1)
 {
 foodAt->r = rand() % SIZE,foodAt->c = rand() % SIZE; //隨機生成食物位置
 for (i = 0; i < length; i++)
 {
  if (foodAt->r == body[i].r && foodAt->c == body[i].c) //如果該位置在蛇的身體上
  {
  goto retry;
  }
 }
 break;
 retry:;
 }
}

//佇列相關的函式
point * pop(queue *queue) //從佇列中取出,返回取出點的指標
{
 queue->num--;
 if (queue->first_in_pos == 5 * SIZE - 1) //返回第一進入佇列的點,並將變數fist_in_pos改變
 {
 queue->first_in_pos = 0;
 return queue->body[5 * SIZE - 1];
 }
 else
 {
 return queue->body[queue->first_in_pos++];
 }
}
void push(point * body,queue *queue)
{
 if (queue->num == 0) //如果佇列已空
 {
 queue->num++;
 queue->first_in_pos = 0; //將第一進入佇列的位置設為0
 queue->body[queue->first_in_pos] = body;
 }
 else //否則插入佇列
 {
 if (queue->first_in_pos + queue->num > 5 * SIZE - 1)
 {
  queue->body[queue->first_in_pos + queue->num++ - 5 * SIZE] = body;
 }
 else
 {
  queue->body[queue->first_in_pos + queue->num++] = body;
 }
 }
}

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