#簡易貪喫蛇(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;
}
檢查狀態打印結果 ,遊戲結束時要釋放節點。
##運行結果