高性能的貪喫蛇C語言實現

                                                           高性能的貪喫蛇C語言實現(西安微易碼科技暑期項目實訓課程)

        貪喫蛇是一個非常火爆的經典的小遊戲,由於其實現起來較爲簡單,而且對界面的要求程度不高,經常被初學者當做提升自身編程能力的一個例子,而且由於它帶有一定的趣味性,對於培養編程愛好者的興趣和提高自身信心有很大的幫助。但是由於它有遊戲的體驗以及帶給人很大的成就感,使得人們在編寫貪喫蛇時,經常會不顧一切的去實現貪喫蛇的功能以便於儘快滿足自身的成就感,以至於不願意踏踏實實的去研究其內部的代碼實現以及去思考如何改良自己的算法。本文會對貪喫蛇的不同的模塊進行詳細的講解,也會更加註重代碼的質量。


一、蛇的初始化問題

        要談一個軟件的運行,初始化是必不可少的,初始化是一個 軟件運行的開始,也是基礎,後續的代碼都是在初始化的數據基礎上進行的。

        要談貪喫蛇的初始化問題,就要談到貪喫蛇所需要用到的數據存儲結構,以及描述一個貪喫蛇所需要的數據,這裏我們給出存儲控制貪喫蛇的數據的總結構體:

typedef struct SNAKE {
	POINT snake[MAX_LENGTH];    //真正存儲蛇的實體空間(用循環數組存儲)
	int isAlive;                //判斷蛇是否活着(是否應該繼續移動)
	int headIndex;              //蛇頭所在的snake數組中的下標
	int tailIndex;              //蛇尾所在的snake數組中的下標
	int snakeLength;            //蛇的長度
	int level;                  //遊戲等級(控制速度)
	POINT food;                 //食物所在的點座標
}SNAKE;
        這裏的POINT snake[MAX_LENGTH]數組是用來存儲蛇的身體的實體空間(蛇的每一節的位置以及方向信息),因此我們給一個POINT結構體來表示蛇的每一節的位置信息以及方向信息(這裏的方向信息僅僅用於表示蛇的每一節的輸出方式,即頭部向右走表示爲‘>’,,向左走表示爲‘<’,向上‘^’,向下‘V’,身體向左右走表示爲‘-’,向上下走爲‘|’,具體的移動是由循環數組實現的,具體的實現方案會在後續的講解中做詳細解釋)。

        因此我們給出POINT結構體:

typedef struct POINT {
	int x;
	int y;
	int direct;
}POINT;
        有了數據存儲的基礎了,我們就可以開始編寫我們的初始化代碼了(具體的宏定義見程序源代碼):

void initSnake(SNAKE *snake) {
	int i;

	snake->isAlive = TRUE;                                                //初始化蛇的各項控制數據;
	snake->headIndex = DEFAULT_LENGTH - 1;
	snake->tailIndex = 0;
	snake->snakeLength = DEFAULT_LENGTH;
	snake->level = DEFAULT_LEVEL;

	for(i = 0; i < snake->snakeLength; i++) {                             //生成一條長度爲DEFAULT_LENGTH(默認長度)的蛇,將其存儲到循環數組裏,並顯示它
		snake->snake[i].x = i + 1;
		snake->snake[i].y = 1;
		snake->snake[i].direct = DEFAULT_DIRECT;
		DEFAULT_LENGTH - i - 1 == 0 ? showHeadPoint(snake->snake[i]) ://判斷當前要存儲的位置是否爲蛇頭,並決定按蛇頭方式輸出還是按身體方式輸出;
			showBodyPoint(snake->snake[i]);
	}
	snake->food = getFood(snake);                                         //生成第一個食物(具體過程後面會做詳細的講解);
	getchar();
}
        初始化完成了將默認蛇長數量的點按從末尾到頭部的順序依次存儲到我們的循環數組裏,並在屏幕上顯示他們。由於輸出頭部和輸出蛇的身體有區別,而且要根據方向的不同來判讀應該輸出什麼,因此我們給出兩個函數用來專門在屏幕上輸出蛇的頭或者身體。

void showHeadPoint(POINT point) {
	gotoxy(point.x, point.y);

	printf("%c", HOLE_headShow[point.direct]);
}

void showBodyPoint(POINT point) {
	gotoxy(point.x, point.y);

	printf("%c", HOLE_bodyShow[(point.direct + 1) % 2]);
}
        我們用gotoxy()函數來定位要在哪裏輸出字符;

        這裏的輸出POINT類型的數據,我們用了一個全局數組來存儲每個方向上應該輸出什麼樣的字符,而用方向的值作爲這個全局數組的下標來確定在該方向上應該輸出什麼樣的字符。

char HOLE_headShow[4] = {'^', '<', 'V', '>'};
char HOLE_bodyShow[2] = {'-', '|'};
        在這裏我們規定,對方向變量的賦值,必須保證要用下面所給的一系列的宏進行賦值,從而保證了向上的方向一定爲0,用它作爲下標對應的headShow[0]一定爲‘^’,從而保證了根據當前POINT類型變量中direct成員的值所確定的輸出字符的準確性。
#define DIRECT_UP		0
#define DIRECT_LEFT		1
#define DIRECT_DOWN		2
#define DIRECT_RIGHT		3
#define DIRECT_ERROR		-1
        這樣,我們就完成了對蛇的初始化;

        接下來,就要討論如何讓蛇向指定位置移動起來了:

        說到移動,就是要讓蛇中的點的位置不斷地變化,以達到視覺上移動的效果,那麼我們將不斷地移動分解開來看,就是由一次一次的移動一個單位組成的。而現在的問題就集中在瞭如何讓蛇在指定的方向移動一個單位。

        首先我們先給出比較容易想到,實現起來較爲簡單,但是時間複雜度很高的方案:我們以蛇身上的每一個點作爲單位,根據得到的下一次要移動的方向確定頭所在的下一個位置,並將身體的每一個點的POINT信息賦值爲前一個點的POINT信息,然後再把原來的蛇刪掉並把新得到的蛇顯示一遍。這種方案的缺點就是每一次都需要將整條蛇遍歷一遍並且輸出一遍,當蛇變得很長的時候時間複雜度會變得非常高。不過這是一種最容易想到的讓蛇移動的方法,因爲在我們的主觀想法上,一條蛇想要移動,當然是整體都要進行移動,一個食物運動起來了,當然是每一個部分的位置都要改變的。但是不同的是我們是在完成屏幕上的東西,是要讓人看的,只要看起來一樣,沒人會在意你真正是怎麼實現的。因此我們就要仔細研究一下在屏幕上顯示蛇的移動時是如何做到的:

我們可以看出,每一次的移動用不着移動全部的蛇,只要做三次操作就可以實現蛇向指定方向移動,即,對蛇頭、蛇尾、蛇頸的操作:只需要在根據蛇當前移動方向得到的頭的下一個位置處顯示頭的字符,將原來頭的位置顯示爲身體,將蛇尾的位置的字符刪掉。將這種方式與上述方法進行對比就可以發現,無論蛇有多長,每一次移動只需要執行定量的代碼,時間複雜度爲1。

        但是,當你想要這樣實現的時候你就會發現問題並沒有這麼簡單,如果只更改頭和尾的值,中間的數據一直沒有發生變化,導致只有頭和尾在動,中間點的數據一直沒有變化,如果要存儲它們的數據,又涉及到遍歷蛇的數組。因此我們給出一種較爲犀利的方法:在存儲結構上做文章,也就是之前提到過的循環數組!

        首先先拋開貪喫蛇的問題討論一下循環數組的實現:

        用循環數組存儲數據有如下幾個特點:

1、用兩個下標確定數據存儲在數組中的位置;

2、添加新的數據,原先的末尾的數據會丟失;

3、每增加一個數據,頭下標和尾下標的值都會更改,因此數組中存儲數據的部分在數組中的位置會不斷地變化。

4、由於存儲數據的部分在數組中的位置不斷地變化,因此,當某一元素到了數組的末尾的時候,應該使其重新回到數組的下標爲0的地方,從而構成循環數組。

        從圖片我們可以看出對循環數組的控制主要是對head和tail兩個記錄存儲數據的第一個值個最後一個值在循環數組中的下標值的控制,但是控制兩個變量的困難之處顯而易見,就是在head的值是數組的最後一個元素的下標的時候,下一次如何讓head回到數組的開始的地方,這裏我們通過取餘運算來更改head和tail的值:每一次讓head或tail向後移動的操作不能是簡單的head++和tail++,而是head\tail = (head\tail + 1) % (數組長度);這樣就實現了構成一個循環數組。

        在瞭解了循環數組的運作模式和實現方法後,我們再來分析其對於貪喫蛇的意義:前面我們得到了移動蛇只需要更新頭部的POINT值,並把之後的部分的蛇的身體依次更改爲其前一節身體的POINT值,而這裏循環數組就顯示出它的強大之處了,由於我們是用兩個下標進行定位的,因此我們將head的值和tail的值向下一個位置移動後,蛇的整體都向前移動了一個單位,移動前倒數第二個元素變爲了最後一個元素,倒數第三個變爲了倒數第二個元素,只需要更改兩個下標值,這中間的每一個元素就都向前移動了一個單位,這樣就達到了我們想要的效果:只更改頭和尾的下標,其他的元素就自然而然的到了它前一個位置。通過這幅圖我們可以清晰地看到蛇移動時數據在存儲角度的變化,也可以看出循環數組實現的功能和我們想要完成的貪喫蛇的數據的變化是吻合的。

        由此我們可以得出移動一次的代碼要執行的過程:

1、將當前頭的位置按照輸出身體的方式更改爲身體的字符;

2、根據此次移動方向,確定此次移動後頭的位置;

3、將循環數組中頭下標向前移動一個單位,並將新下標位置的數據存儲爲上一步得到的頭的位置信息,並將該位置按照輸出頭的方式顯示在屏幕上;

4、根據當前循環數組中尾下標處的POINT位置,在屏幕上輸出空格;

5、將尾下標向前移動一個單位;

        將蛇移動每一步具體做了些什麼討論清楚後,就可以得出如下讓蛇移動一次的代碼了:

void moveOnceOnDirect(SNAKE *snake, int direct) {
	POINT newPoint;                    

        /*
        int HOLE_direction[4][2] = {
            0, -1,        /*UP*/
            -1, 0,      /*LEFT*/
            0, 1,       /*DOWN*/
            1, 0        /*RIGHT*/
        };
        */
	newPoint.x = snake->snake[snake->headIndex].x + HOLE_direction[direct][0];    // 根據方向direct得到新的頭的位置;
	newPoint.y = snake->snake[snake->headIndex].y + HOLE_direction[direct][1];

        if (isPartOfSnake(*snake, newPoint)) {                  // 判斷要移動到的位置是否是蛇的身體的一部分,從而判斷蛇是否還活着(有沒有咬到自己);
         snake->isAlive = FALSE;
     }

	showBodyPoint(snake->snake[snake->headIndex]);          // 把原來頭的位置的字符變成身體的字符;

	snake->headIndex = (snake->headIndex + 1) % MAX_LENGTH; // 更改循環數組中頭下標的值,並將新的點存進去;
	snake->snake[snake->headIndex] = newPoint;
	snake->snake[snake->headIndex].direct = direct;

	showHeadPoint(snake->snake[snake->headIndex]);          // 將新的頭的座標處顯示頭字符;

	deletePoint(snake->snake[snake->tailIndex]);            // 將原來尾部的座標的位置變成空格(刪除尾部的點)
	snake->tailIndex = (snake->tailIndex + 1) % MAX_LENGTH; // 更改尾部座標的值;

}
        對於上面的代碼,在根據方向得到新的頭的位置的部分,用到了一個較爲複雜的技巧需要進行說明:我們給了一個全局二維數組int HOLE_direction[4][2],存儲的是四個方向的移動對應的x和y兩個值的變化,這樣,只需要直接給原來的x和y值加上根據方向值確定的全局數組的下標的值。這樣直接定位數組的下標的方式帶來的好處就是程序的耗時較少,如果理解不了也可以用if、else判斷方向,再給新的座標賦值。

        當我們完成了讓蛇在一個方向上移動一次後,蛇在屏幕上的連續的移動也就已經做好了,只要將移動一次的代碼放到循環裏並控制每次循環所暫停的時間就可以完成蛇在屏幕上連續的移動,那現在我們開始着手解決控制方向的問題:

        說到控制方向我們就不得不提到一個C語言庫函數——bioskey()函數;

int bioskey(int dos)函數給出了監聽鍵盤的很強大的功能,當該函數的dos參數爲0時它可以返回從鍵盤輸入的鍵的鍵值,並且可以監聽方向鍵、esc鍵、shift鍵等等,因此我們可以通過單獨的實驗來得到上下左右鍵和esc鍵的鍵值作爲以後的判斷依據。當參數dos爲1時,函數會返回當前有沒有鍵盤的按鍵被按下,如果有返回一個非零值,如果沒有返回0;這也是我們所需要的功能,當用戶沒有按下鍵盤的時候,蛇應該按照原來的方向繼續移動,也就是direct值不改變,一旦發現用戶按下鍵盤了,就用bioskey(0)進行判斷用戶按下的是什麼鍵,並根據按下的鍵值來得到下一次移動的方向。

if(bioskey(1)) {
	keyName = bioskey(0);

	if(DIRECT_ERROR != getDirectByKeyName(keyName)) {

	        direct = getDirectByKeyName(keyName);

 }}

        當有鍵盤被按下了,我們要確定按下的按鍵是什麼鍵,如果是方向鍵,我們應該更改當前的蛇的運動方向,這裏我們判斷按鍵的函數getDirectByKeyName()函數如下:

/*
#define ARROW_UP      0x4800                // 各個方向鍵的鍵值
#define ARROW_DOWN    0x5000
#define ARROW_LEFT    0x4B00
#define ARROW_RIGHT    0x4D00
#define ESC        0x011B

#define DIRECT_UP        0               // 規定的方向的值
#define DIRECT_LEFT       1
#define DIRECT_DOWN       2
#define DIRECT_RIGHT        3
#define DIRECT_ERROR        -1

int HOLE_keyName[4] = {                        //下標值爲該鍵值對應的方向值
    ARROW_UP, ARROW_LEFT, ARROW_DOWN, ARROW_RIGHT,
};
*/
int getDirectByKeyName(int keyName) {
	int i;

	for(i = 0; i < 4; i++) {
		if(keyName == HOLE_keyName[i]) {
			return i;
		}
	}

	return DIRECT_ERROR;
}

        這裏我們給了一個全局數組,用來存儲方向的鍵值,而它們在數組中的下標就是方向值。如果理解不了還是可以用if、else進行判斷來確定。

        到了現在,蛇已經可以在屏幕上隨着我們的方向鍵移動了,接下來就要處理蛇喫食物的問題了,我們首先解決蛇喫到食物後增長的問題。

        當蛇喫到食物後,我們希望將蛇增加一定的長度,比如說一次性增長三節或者五節。那麼應該如何讓蛇的長度增加呢,我們首先想到的是在視覺角度上在需要增加的時候尾部原來的位置的字符不刪除,而頭繼續向前移動,這樣蛇的長度就增長了,但是這樣處理蛇的長度一次性增加多節就有一定的困難了,這裏我麼給出一種方法:首先將尾下標處的蛇的POINT信息想之後的空間賦值n份,n的值取決於蛇要增長的長度,然後將尾下標向後移動n個空間。這樣我們的移動的代碼不用做過多的修改,依然每次移動刪除尾部的座標的字符,當需要增加的時候,由於尾部的值被複制了好幾份,使得之後的n次移動刪除的都是同一個位置的字符,也就相當於沒有刪除,這樣就達到了增長蛇的長度的效果。

        執行增長僅僅需要完成如圖所設的操作,剩下的移動部分的代碼不需要改變。


 蛇增長具體代碼如下:

void addSnakeLength(SNAKE *snake, int addLength) {
	int i;
	POINT tailIndexData;

	tailIndexData = snake->snake[snake->tailIndex];

	for(i = 1; i <= addLength; i++) {
		snake->snake[(snake->tailIndex - i + MAX_LENGTH) % MAX_LENGTH] = tailIndexData;
	}
	snake->tailIndex = (MAX_LENGTH + snake->tailIndex - addLength) % MAX_LENGTH;
	snake->snakeLength += addLength;
}

        處理完蛇身增長後,我們就要思考如何在屏幕上隨機出現食物了,這裏我們很容易想到用rand()產生隨機數,但是目前有兩個問題需要考慮:一是隨機數產生的是一個數如何生成x和y值並控制兩者的範圍,二是如何讓出現的食物的點座標不在蛇的身上。最先想到的是產生兩個隨機數,分別控制在最大行值之內和最大列值之內,然後再根據該點的座標判斷該點是不是蛇的一部分,如果是,再重新生成隨機數直到生成的點不在蛇的身上爲止。但是這種方式在概率角度分析,屏幕中的點如果大部分都是蛇,那麼生成一個不在蛇身上的點的座標所用的時間的數學期望是很大的,因此時間複雜度會隨着蛇的增長而增加。於是我們希望找到更犀利的解決方案:

        這個算法的大體思路是這樣的:首先用一維數組來記錄蛇能運動到的所有的點(圍牆內的點)的下標,我們可以用一維數組的下標以及蛇要運動的區域的列值來表示點在屏幕上的座標,計算公式:index = col * (y - 1) + x - 1;如果有不理解可以參考我的另一篇博客《任意行列二維數組C語言實現》。 我們用這樣一個一維數組來表示蛇運動範圍的所有點,然後我們將是蛇的部分的點標記出來,如果是蛇就標誌爲1,定位方法還是用上文給出的公式。然後我們再用另一個同樣長度的數組,將之前數組中值爲0的元素的下標存放在第二個數組中。然後我們用rand()方法生成一個0到全部的點數減去蛇長的一個隨機數,用這個隨機數作爲下標取得第二個數組中的值,再將這個值用之前的公式解釋爲x和y值,這樣就得到了空間中不是蛇的身體的部分的一個隨機的座標。


        具體代碼如下:

POINT getFood(SNAKE *snake) {
	char point[MAX_ROW * MAX_COL] = {0};
	int pointIndex[MAX_ROW *MAX_COL] = {0};
	int i;
	int j = 0;
	int foodIndex;
	POINT food;

	for(i = snake->headIndex; i != snake->tailIndex; i = (i - 1 + MAX_LENGTH) % MAX_LENGTH) {
		point[snake->snake[i].x + (snake->snake[i].y - 1) * MAX_COL - 1] = 1;
	}

	point[snake->snake[i].x + (snake->snake[i].y - 1) *MAX_COL - 1] = 1;

	for(i = 0; i < MAX_ROW *MAX_COL; i++) {
		if(0 == point[i]) {
			pointIndex[j++] = i;
		}
	}
	srand(time(0));
	foodIndex = pointIndex[rand() % (MAX_ROW * MAX_COL - snake->snakeLength)];
	food.x = foodIndex % MAX_COL + 1;
	food.y = foodIndex / MAX_COL + 1;

	showFoodPoint(food);

	return food;
}
        到了這裏,貪喫蛇的幾大塊核心代碼就已經全部講解完了,裏面還是包含了很多的高級的編程技巧的,理解起來也不是容易的事,但是編程就是需要這樣的簡單的思考考慮不到的思維才能夠編出更加符合實用角度的代碼,才能讓自己的程序的算法更加優化,對整體性思路以及問題本質的把握纔會更加深刻。其餘的一些較爲基礎的比如說界面問題以及與用戶的交互問題我們不做講解,本文的主旨是介紹貪喫蛇的核心代碼的優化問題,接下來給出程序的完整代碼:

#include<stdio.h>
#include<stdlib.h>
#include<dos.h>
#include<bios.h>
#include<conio.h>
#include<time.h>

#define ARROW_UP	0x4800
#define ARROW_DOWN	0x5000
#define ARROW_LEFT	0x4B00
#define ARROW_RIGHT	0x4D00
#define ESC			0x011B

#define DIRECT_UP		0
#define DIRECT_LEFT		1
#define DIRECT_DOWN		2
#define DIRECT_RIGHT	3
#define DIRECT_ERROR	-1

#define MAX_ROW		15
#define MAX_COL		20

#define MAX_LENGTH	50

#define DEFAULT_COUNT	5
#define DEFAULT_LEVEL	2
#define DEFAULT_DIRECT	DIRECT_RIGHT

char HOLE_headShow[4] = {'^', '<', 'V', '>'};
char HOLE_bodyShow[2] = {'-', '|'};

int HOLE_direction[4][2] = {
	0, -1,		/*UP*/
	-1, 0,      /*LEFT*/
	0, 1,       /*DOWN*/
	1, 0	    /*RIGHT*/
};

int HOLE_keyName[4] = {
	ARROW_UP, ARROW_LEFT, ARROW_DOWN, ARROW_RIGHT,
};

int const HOLE_delayTime[5] = {
	1000, 700, 300, 200, 100,
};

#define DEFAULT_LENGTH	4

#define TRUE	1
#define FALSE	0

typedef struct POINT {
	int x;
	int y;
	int direct;
}POINT;

typedef struct SNAKE {
	POINT snake[MAX_LENGTH];
	int isAlive;
	int headIndex;
	int tailIndex;
	int snakeLength;
	int level;
	POINT food;
}SNAKE;

void initSnake(SNAKE *snake);
void showBodyPoint(POINT point);
void showHeadPoint(POINT point);
void showFoodPoint(POINT food);
void moveSnake(SNAKE *snake);
void deletePoint(POINT point);
int getDirectByKeyName(int keyName);
void addSnakeLength(SNAKE *snake, int addLength);
POINT getFood(SNAKE *snake);
int isPartOfSnake(SNAKE snake, POINT point);
void moveOnceOnDirect(SNAKE *snake, int direct);

void moveOnceOnDirect(SNAKE *snake, int direct) {
	POINT newPoint;

	newPoint.x = snake->snake[snake->headIndex].x + HOLE_direction[direct][0];
	newPoint.y = snake->snake[snake->headIndex].y + HOLE_direction[direct][1];

	if (isPartOfSnake(*snake, newPoint)) {
		snake->isAlive = FALSE;
	}
	showBodyPoint(snake->snake[snake->headIndex]);

	snake->headIndex = (snake->headIndex + 1) % MAX_LENGTH;
	snake->snake[snake->headIndex] = newPoint;
	snake->snake[snake->headIndex].direct = direct;

	showHeadPoint(snake->snake[snake->headIndex]);

	deletePoint(snake->snake[snake->tailIndex]);
	snake->tailIndex = (snake->tailIndex + 1) % MAX_LENGTH;
}

int isPartOfSnake(SNAKE snake, POINT point) {
	int i;

	for(i = snake.headIndex; i !=  snake.tailIndex; i = (MAX_LENGTH + i - 1) % MAX_LENGTH) {
		if(point.x == snake.snake[i].x && point.y == snake.snake[i].y) {
			return TRUE;
		}
	}
	if(point.x == snake.snake[i].x && point.y == snake.snake[i].y) {
		return TRUE;
	}
	return FALSE;
}

POINT getFood(SNAKE *snake) {
	char point[MAX_ROW * MAX_COL] = {0};
	int pointIndex[MAX_ROW *MAX_COL] = {0};
	int i;
	int j = 0;
	int foodIndex;
	POINT food;

	for(i = snake->headIndex; i != snake->tailIndex; i = (i - 1 + MAX_LENGTH) % MAX_LENGTH) {
		point[snake->snake[i].x + (snake->snake[i].y - 1) * MAX_COL - 1] = 1;
	}

	point[snake->snake[i].x + (snake->snake[i].y - 1) *MAX_COL - 1] = 1;

	for(i = 0; i < MAX_ROW *MAX_COL; i++) {
		if(0 == point[i]) {
			pointIndex[j++] = i;
		}
	}
	srand(time(0));
	foodIndex = pointIndex[rand() % (MAX_ROW * MAX_COL - snake->snakeLength)];
	food.x = foodIndex % MAX_COL + 1;
	food.y = foodIndex / MAX_COL + 1;

	showFoodPoint(food);

	return food;
}

void addSnakeLength(SNAKE *snake, int addLength) {
	int i;
	POINT tailIndexData;

	tailIndexData = snake->snake[snake->tailIndex];

	for(i = 1; i <= addLength; i++) {
		snake->snake[(snake->tailIndex - i + MAX_LENGTH) % MAX_LENGTH] = tailIndexData;
	}
	snake->tailIndex = (MAX_LENGTH + snake->tailIndex - addLength) % MAX_LENGTH;
	snake->snakeLength += addLength;
}

int getDirectByKeyName(int keyName) {
	int i;

	for(i = 0; i < 4; i++) {
		if(keyName == HOLE_keyName[i]) {
			return i;
		}
	}

	return DIRECT_ERROR;
}

void deletePoint(POINT point) {
	gotoxy(point.x, point.y);

	printf(" ");
}

void moveSnake(SNAKE *snake) {
	int keyName = ARROW_RIGHT;
	int direct = DIRECT_RIGHT;
	int isEating = FALSE;

	while(keyName != ESC && snake->isAlive && snake->snakeLength < MAX_LENGTH) {
		moveOnceOnDirect(snake, direct);

		if (snake->snake[snake->headIndex].x == snake->food.x && snake->snake[snake->headIndex].y == snake->food.y) {
			isEating = TRUE;
		}

		if(TRUE == isEating) {
			snake->food = getFood(snake);
			addSnakeLength(snake, 3);
			isEating = FALSE;
		}

		delay(HOLE_delayTime[snake->level]);

		if(bioskey(1)) {
			keyName = bioskey(0);

			if (DIRECT_ERROR != getDirectByKeyName(keyName)) {
				direct = getDirectByKeyName(keyName);
			}
		}
	}
	printf("isAlive:%d, snakeLength:%d, ", snake->isAlive, snake->snakeLength);
}

void showFoodPoint(POINT food) {
	gotoxy(food.x, food.y);

	printf("@");
}

void showHeadPoint(POINT point) {
	gotoxy(point.x, point.y);

	printf("%c", HOLE_headShow[point.direct]);
}

void showBodyPoint(POINT point) {
	gotoxy(point.x, point.y);

	printf("%c", HOLE_bodyShow[(point.direct + 1) % 2]);
}

void initSnake(SNAKE *snake) {
	int i;

	snake->isAlive = TRUE;
	snake->headIndex = DEFAULT_LENGTH - 1;
	snake->tailIndex = 0;
	snake->snakeLength = DEFAULT_LENGTH;
	snake->level = DEFAULT_LEVEL;

	for(i = 0; i < snake->snakeLength; i++) {
		snake->snake[i].x = i + 1;
		snake->snake[i].y = 1;
		snake->snake[i].direct = DEFAULT_DIRECT;
		DEFAULT_LENGTH - i - 1 == 0 ? showHeadPoint(snake->snake[i]) :
			showBodyPoint(snake->snake[i]);
	}
	snake->food = getFood(snake);
	getchar();
}

void main(void) {
	SNAKE snake = {0};

	system("cls");
	initSnake(&snake);
	moveSnake(&snake);
	getchar();
}








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