用C語言實現簡易貪喫蛇

#簡易貪喫蛇(c語言)

###貪喫蛇遊戲是指在規定的方框內,通過操控鍵盤上的 ↑ ↓ ← → 鍵 來進行貪喫蛇的方向控制。在方框內喫掉隨機出現的食物來延長蛇的身體。

###需要的頭文件及函數原型

    #ifndef __SNAKE_H__
    #define __SNAKE_H__
    #define _CRT_SECURE_NO_WARNINGS 1
    #include <stdio.h>
    #include <stdlib.h>
    #include <Windows.h>
    #define WALL "X"   //牆壁符號
    #define FOOD "O"   //蛇的符號
    #define INIT_X 20  //初始化蛇的位置 X座標
    #define INIT_Y 20  //初始化蛇的位置 Y座標
    typedef int SDataType;
    typedef struct SnakeNode {
    	int x;//節點的x座標
    	int y;//節點的y座標
    	struct SnakeNode * next;
    }SnakeNode ,*pSnakeNode;
    
    enum Direction {
    	UP = 1,
    	DOWN,
    	LEFT,
    	RIGHT,
    };//蛇當前運行方向狀態
    enum State
    {
    	OK,  //存活
    	NORMAL_END,  //按esc正常退出
    	KILL_BY_WALL,//撞到牆壁死亡
    	KILL_BY_SELF,//撞到蛇身死亡
    };//蛇的狀態
    typedef struct Snake
    {
    	SnakeNode *psnake;// 蛇的頭結點
    
    	int TotalScore; //總分數
    	int AddScore;	//分數增加比
    	int SleepTime;	//蛇的運行速度(電腦刷新間隔)
    	enum Direction Dir; //蛇當前所處的方向狀態
    	enum State Status;  //社當前的狀態
    	SnakeNode *pFood;   //食物所處的位置座標
     }Snake , *pSnake;
    //蛇結構體
    void GameStart(pSnake ps);//遊戲開始函數
    void InitSnake(pSnake ps);//初始化蛇函數
    void SetPos(int x, int y);//運用控制檯命令設置節點出現的位置
    void PrintMap();		  //打印地圖
    pSnakeNode BuyNode();	  //生成一個節點
    void CreateSnake(pSnake ps);//創建一個蛇
    void CreateFood(pSnake ps);//創建一個食物節點
    void GameRun(pSnake ps);  //遊戲運行函數
    void Pause();			  //遊戲暫停函數
    int HasNextNode(pSnakeNode pf, pSnakeNode pn);//判斷蛇頭所指向的下一個節點是不是食物節點
    void EatFood(pSnake ps, pSnakeNode pn);//進食食物使得整體延長
    void NoFood(pSnake ps, pSnakeNode pn); //沒有食物節點向蛇頭方向運行
    void SnakeMove(pSnake ps);//蛇的方向操作函數
    void helpinfo(pSnake ps); //當前狀態
    void KillByWall(pSnake ps);//被牆撞死
    void KillBySelf(pSnake ps);//蛇喫自己
    void GameEnd(pSnake ps); //遊戲結束釋放空間,打印結果
    #endif//__SNAKE_H__
    //test.c 
    	#include "Snake.h"
    void test()
    {
    	Snake ps;
    	GameStart(&ps);
    	GameRun(&ps);
    	GameEnd(&ps);
    }
    int main()
    {
    	test();
    	return 0;
    }

##函數實現分析

    	#include "Snake.h"
    void InitSnake(pSnake ps)
    //初始化蛇函數
    {
    	ps->AddScore = 10;
    	ps->Dir = UP;
    	ps->SleepTime = 200;
    	ps->TotalScore = 0;
    	ps->psnake = NULL;
    	ps->Status = OK;
    }
    void SetPos(int x, int y)
    //運用控制檯命令設置節點出現的位置
    {
    	COORD pos; 
    	HANDLE handle;
    	pos.X = x;
    	pos.Y = y;
    	handle = GetStdHandle(STD_OUTPUT_HANDLE);
    	SetConsoleCursorPosition(handle,pos);
    }

COORD 是 api函數。它的作用是確定橫縱座標。
HANDLE 是句柄,用來函數運行時的狀態。
GetStdHandle()獲取句柄的狀態。STD_OUT_HANDLE獲取標準輸出的句柄。
SetConsoleCursorPosition(handle, pos)。將句柄當前的狀態傳入,該函數
可以確定將光標移動到 pos所指向的位置。

    void PrintMap()
    //打印地圖
    {
    	int i = 0;
    	for (i = 0; i <58; i+=2)
    	{
    		SetPos(i, 0);
    		printf(WALL);
    	}
    	for (i = 0; i <58; i+=2)
    	{
    		SetPos(i, 27);
    		printf(WALL);
    	}
    	for (i = 0; i < 27; i++)
    	{
    		SetPos(0, i);
    		printf(WALL);
    	}
    	for (i = 0; i < 27; i++)
    	{
    		SetPos(56, i);
    		printf(WALL);
    	}
    }

在給定的範圍內打印邊界。假設整個地圖的範圍是 27 * 58.

    pSnakeNode BuyNode()
    //生成一個節點
    {
    	pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));
    	if (cur == NULL)
    	{
    		printf("BuyNode :: malloc failue ! \n");
    	}
    	cur->next = NULL;
    	return cur;
    }
    void CreateSnake(pSnake ps)
    //創建一個蛇
    {
    	pSnakeNode cur = NULL;
    	pSnakeNode first = NULL;
    	first = ps->psnake;
    
    	int i = 0;
    
    	for (i = 0; i < 5; i++)
    	{
    		cur = BuyNode();
    		cur->x = 20 + 2*i;
    		cur->y = 20;
    		SetPos(cur->x, cur->y);
    		printf(FOOD);
    		cur->next = first;
    		first = cur;
    	}
    	
    	
    	ps->psnake = first;
    }

採用頭插的方式初始化蛇的樣子。假定蛇身的長度爲5。注意在X軸方向上纔有用間距爲偶數的移動方式。

    void CreateFood(pSnake ps)
    //創建一個食物節點
    {
    //對蛇的食物進行初始化
    	ps->pFood = BuyNode();
    	pSnakeNode cur = ps->psnake;
    	//食物不能出現在邊界
    	ps->pFood->y = rand() % 25 + 1;
    	do
    	{
    		ps->pFood-> x= rand() %53+2;
    	} while (ps->pFood->x % 2 != 0);
    	
    	//食物不能出現在蛇身
    	while (cur)
    	{
    		if ((cur->x == ps->pFood->x) && (cur->y == ps->pFood->y))
    		{
    			CreateFood(ps);
    			return;
    		}
    		cur = cur->next;
    	}
    
     	SetPos(ps->pFood->x, ps->pFood->y);
    	printf(FOOD);
    	//食物要隨機出現;
    
    }

創建一個食物節點。食物節點要隨機出現所以用到 rand()函數獲取隨機數。另一方面。食物不能出現在邊界,並且食物不能出現在蛇的身體裏。最後食物的X座標必須是偶數否則蛇喫不到食物。

    void welcome()
    {
    	system("mode con cols=100 lines=30");
    	//採用mode 命令來初始化黑框的大小 假定行爲30 列爲100
    	SetPos(50, 13);
    	printf("歡迎來到貪喫蛇遊戲!\n");
    	system("pause");
    	system("cls");
    	SetPos(50, 13);
    	printf("使用↑↓←→來進行操控,F1加速,F2減速。\n");
    	SetPos(50, 14);
    	printf("使用空格鍵進行暫停\n");
    	SetPos(50, 15);
    	printf("使用esc鍵進行退出操作。\n");
    	system("pause");
    	system("cls");
    }

mode命令可以確定黑框的大小。 它的使用格式是:

mode con cols=數字 lines=數字

    void GameStart(pSnake ps)
    //遊戲開始函數
    {
    	InitSnake(ps);
    	welcome();
    	PrintMap();
    	CreateSnake(ps);
    	CreateFood(ps);
    }
    
    void Pause()
    //遊戲暫停函數
    {
    	while (1)
    	{
    		Sleep(100);
    		if(GetAsyncKeyState(VK_SPACE))
    		{
    			return;
    	}
    	}
    }

遊戲暫停函數,程序運行的時候當從鍵盤上接收到空格鍵,遊戲界面暫停。實際上是將當前程序執行到一個死循環裏邊。只有當再次按下空格鍵時跳出循環。遊戲繼續。

       void GameRun(pSnake ps)
    //遊戲運行函數
    {
    	do
    	{
    		helpinfo(ps);
    		if ((GetAsyncKeyState(VK_UP)) && (ps->Dir != DOWN))
    		{
    			ps->Dir = UP;
    		}
    		else if ((GetAsyncKeyState(VK_DOWN)) && (ps->Dir != UP))
    		{
    			ps->Dir = DOWN;
    		}
    		else if ((GetAsyncKeyState(VK_LEFT)) && (ps->Dir != RIGHT))
    		{
    			ps->Dir = RIGHT;
    		}
    		else if ((GetAsyncKeyState(VK_RIGHT)) && (ps->Dir != LEFT))
    		{
    			ps->Dir = LEFT;
    		}
    		else if (GetAsyncKeyState(VK_F1))
    		{
				//加速
    			if (ps->SleepTime > 100)
    			{
    				ps->AddScore += 2;
    				ps->SleepTime -= 20;
    			}
    		}
    		else if (GetAsyncKeyState(VK_F2))
    		{
    			//減速
    			if (ps->SleepTime < 300)
    			{
    				ps->AddScore -= 2;
    				ps->SleepTime += 20;
    			}
    		}
    		else if (GetAsyncKeyState(VK_SPACE))
    		{
    			Pause();
    		}
    		else if (GetAsyncKeyState(VK_ESCAPE))
    		{
    			//退出
    			ps->Status = NORMAL_END;
    		}
    		KillBySelf(ps);
    		KillByWall(ps);
    		SnakeMove(ps);
    		
    		Sleep(ps->SleepTime);
    	} while (ps->Status == OK);
    }

遊戲的運行函數,使用了一個do while()語句來進行運作 當ps->status蛇的狀態來判斷遊戲是否需要繼續進行。當蛇的狀態不爲 OK時跳出循環。GetAsyncKeyState()函數是判斷鍵盤上的虛擬鍵有沒有被按下。VK_UP是上鍵,依次類推獲得 ↑ ↓ ← →, F1加速,F2減速, SPACE空格鍵暫停 ,ESC退出。
值得注意的是,蛇不可能旋轉180度喫自己,所以if條件裏要判斷該種條件。
Sleep()是睡眠函數,它用來限制程序運行速度,進而限制蛇的移動速度。
當if條件滿足的時候就改變蛇的ps->Dir方向狀態。

    void SnakeMove(pSnake ps)
    //蛇的方向操作函數
    {
    
     	pSnakeNode pnext =BuyNode(); 
    	switch (ps->Dir)
    	{
    	case UP:
    		pnext->x  = ps->psnake->x;
    		pnext->y = ps->psnake->y - 1;
    		if ( HasNextNode(ps->pFood ,pnext) )
    		{
    			EatFood(ps ,pnext);
    			CreateFood(ps);
    			ps->TotalScore += ps->AddScore;
    		}
    		else
    		{
    			NoFood(ps,pnext);
    		}
    		 break;
    	case DOWN:
    		pnext->x = ps->psnake->x;
    		pnext->y = ps->psnake->y + 1;
    		if (HasNextNode(ps->pFood, pnext))
    		{
    			EatFood(ps, pnext);
    			CreateFood(ps);
    			ps->TotalScore += ps->AddScore;
    		}
    		else
    		{
    			NoFood(ps, pnext);
    		}
    		 break;
    	case LEFT:
    		pnext->x = ps->psnake->x+2;
    		pnext->y = ps->psnake->y ;
    		if (HasNextNode(ps->pFood, pnext))
    		{
    			EatFood(ps, pnext);
    			CreateFood(ps);
    			ps->TotalScore += ps->AddScore;
    		}
    		else
    		{
    			NoFood(ps, pnext);
    		}
    		break;
    	case RIGHT:
    		pnext->x = ps->psnake->x - 2;
    		pnext->y = ps->psnake->y;
    		if (HasNextNode(ps->pFood, pnext))
    		{
    			EatFood(ps, pnext);
    			CreateFood(ps);
    			ps->TotalScore += ps->AddScore;
    		}
    		else
    		{
    			NoFood(ps, pnext);
    		}
    		break;
    	}
    }

由於在GameRun()中可能改變蛇的運動狀態。所以move函數裏需要判斷下一步所需要移動的位置。如果下一步是一個食物節點,蛇需要加長。如果下一個節點沒有食物那就繼續往前走。獲取一個pnext指針,將pnext 的賦值根據情況區別開來。如果是向上走,那麼將ps->psnake蛇頭的y軸減1賦值給pnext。然後使用HasNextNode()判斷下一個節點是否爲食物節點。

    int HasNextNode(pSnakeNode pf, pSnakeNode pn)
    //判斷蛇頭所指向的下一個節點是不是食物節點
    {
    	
    	return (pf->x == pn->x) && (pf->y == pn->y);
    }

判斷pnext的x .y座標是否與ps->pfood相等,如果相等代表着下一個節點就是食物。

    void EatFood(pSnake ps,pSnakeNode pn)
    //進食食物使得整體延長
    {
    	pn->next = ps->psnake;
    	ps->psnake = pn;
    	pSnakeNode cur = ps->psnake;
    	SetPos(ps->psnake->x, ps->psnake->y);
    	printf(FOOD);
    
    }
    void NoFood(pSnake ps, pSnakeNode pn)
    //沒有食物節點向蛇頭方向運行
    {
    	pn->next = ps->psnake;
    	ps->psnake  = pn;
    	pSnakeNode cur = ps->psnake ;
    	while (cur->next->next != NULL)
    	{
    		SetPos(cur->x, cur->y);
    		printf(FOOD);
    		cur = cur->next;
    	}
    	SetPos(cur->next->x, cur->next->y);
    	printf(" ");
    	free(cur->next);
    	cur->next = NULL;
    }

EatFood()函數只需要將pnext頭插入蛇身體裏,然後再次打印蛇頭就行了。
NoFood()函數。需要將pnext頭插入蛇身體 (現在 5+1)。然後遍歷打印蛇,且遍歷的條件是
cur->next->next != NULL。(打印了 5)最後一個(剩下的1)置爲空格 就不改變蛇的身體完成移動。

    void helpinfo(pSnake ps)
    //當前狀態
    {
    	SetPos(62, 12);
    	printf("Sleeptime : %d \n", ps->SleepTime);
    	SetPos(63, 13);
    	printf("AddScore : %d \n", ps->AddScore );
    	SetPos(63, 14);
    	printf("TotalScore : %d \n", ps->TotalScore);
    }
    void KillByWall(pSnake ps)
    //被牆撞死
    {
    	if (ps->psnake->y == 0 || ps->psnake->y == 27 || ps->psnake->x == 57 || ps->psnake->x == 0)
    	{
    		ps->Status = KILL_BY_WALL;
    	}
    }
    void KillBySelf(pSnake ps)
    //蛇喫自己
    {
    	pSnakeNode cur = ps->psnake;
    	cur = cur->next;
    	while (cur)
    	{
    		if ((ps->psnake->x == cur->x) && (ps->psnake->y == cur->y))
    		{
    			ps->Status = KILL_BY_SELF;
    			break;
    		}
    		cur = cur->next;
    	}
    }

蛇被牆撞死要看當前狀態是不是在邊界上,如果在邊界上那麼就要改變Status爲KILL_BY_WALL。

蛇自殺,判斷蛇的頭結點在不在蛇的身體裏。通過while遍歷整個蛇的身體只要發現條件(ps->snake->x == cur->x && ps->snake->y == cur->y)就自殺。

    void GameEnd(pSnake ps)
    //遊戲結束釋放空間,打印結果
    {
    	if (ps->Status == KILL_BY_SELF)
    	{
    		system("cls");
    		printf("有時候餓的連自己都想喫······ \n");
    		printf("你獲得的總分 : %d \n", ps->TotalScore);
    		system("pause");
    	}
    	if (ps->Status == KILL_BY_WALL)
    	{
    		system("cls");
    		printf("撞了南牆不回頭 ! \n");
    		printf("你獲得的總分 : %d \n", ps->TotalScore);
    		system("pause");
    	}
    	if (ps->Status == NORMAL_END)
    	{
    		system("cls");
    		printf("平穩着陸 ! 穩 ! \n");
    		printf("你獲得的總分 : %d \n", ps->TotalScore);
    		system("pause");
    	}
    
    	pSnakeNode cur = ps->psnake;
    	pSnakeNode del = NULL;
    	while (cur)
    	{
    		del = cur;
    		cur = cur->next;
    		free(del);
    		del = NULL;
    	}
    	ps->psnake = NULL;
    	cur = ps->pFood;
    	free(cur);
    	ps->pFood = NULL;
    
    }

檢查狀態打印結果 ,遊戲結束時要釋放節點。

##運行結果

這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章