/*****************************************************************************************************************************
貪吃蛇實現原理: *
貪吃蛇游戲在理論上是可以無限的進行下去的(除了撞牆和咬到自己),那麼游戲主體就一定是個循環。 *
蛇是如何動起來的?在這裡就是通過不斷改變蛇的坐標,然後根據蛇的坐標不斷刷新屏幕在視覺上形成蛇的移動效果。*
食物出現在隨機位置(當然不能出現在障礙物和蛇身上)。 *
蛇能吃到食物其實就是蛇頭的坐標與食物的坐標重合時。 *
當蛇咬到自己或者撞到牆的時候游戲結束(坐標判斷) *
******************************************************************************************************************************/
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <windows.h>
#include <time.h>
//72,80,75,77是方向鍵對應的鍵值
#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77
#define SNAKE 1 //蛇的坐標標識
#define FOOD 2 //食物的坐標標識
#define BAR 3 //牆的坐標標識
//初始化地圖 17*17
char map[17][17] = {0};
//初始化蛇頭坐標
unsigned char snake[50] = {77};
//初始化食物坐標
unsigned char food = 68;
//蛇長
char len = 1;
//存儲坐標數字與x、y的轉換函數
void tran(unsigned char num,unsigned char * x,unsigned char * y);
//打印游戲
void print_game(void);
//獲取方向函數(注意當蛇身長度超過一節時不能回頭)
int get_dir(int old_dir);
//移動蛇身函數(游戲大部分內容在其中)
void move_snake(int dir);
//生產食物的函數
unsigned char generate_food(void);
//判斷蛇死活的函數(判斷了蛇是否撞到邊界或者自食)
int isalive(void);
int main(void){
int dir = UP; //初始方向默認向上,UP是我們定義的宏
//按道理該游戲是可以無限繼續下去的,因此是個循環
while(1){
print_game(); //打印游戲
dir = get_dir(dir); //獲取方向(我們摁下的方向)
move_snake(dir); //移動蛇身
if(!isalive()){ //判斷蛇的生命狀態
break;
}
}
printf("Game Over!\n");
return 0;
}
//
void tran(unsigned char num,unsigned char * x,unsigned char * y){
*x = num >> 4;
*y = (unsigned char)(num << 4) >> 4; //注意這裡要做個強制類型轉換
//根據匯編,如果不做強制轉換,y的值與num的值相同
}
void print_game(void){
int i,j;
//根據地圖上每點的情況繪制游戲( i 表示 x 軸,j 表示 y 軸),按行打印,j表示行,i表示列
for(j = 0;j < 17;j ++){
for(i = 0;i < 17;i ++){
//空白地方
if(map[i][j] == 0){
putchar(' ');
}
//蛇身
else if(map[i][j] == SNAKE){
putchar('*');
}
//圍欄
else if(map[i][j] == BAR){
putchar('#');
}
//食物
else if(map[i][j] == FOOD){
putchar('$');
}
}
putchar('\n');
}
Sleep(500); //休眠函數 將進程掛起500ms,包含在window.h(在linux下用 sleep(),#include <unistd.h>)
system("cls"); //清屏函數 配合下一次 print_game() 起到刷新作用,包含在stdlib.h中
}
int get_dir(int old_dir){
int new_dir = old_dir;
//用kbhit()與getch()組合實現鍵盤響應
//kbhit() 檢查當前是否有鍵盤輸入,若有則返回一個非0值,否則返回0
//getch() 用ch=_getch();會等待你按下任意鍵之後,把該鍵字符所對應的ASCII碼賦給ch,再執行下面的語句。
if(_kbhit()){
_getch(); //第一次輸出的方向鍵的擴展值,第二次是方向鍵的實際值,只有方向鍵上下左右這樣
new_dir = _getch(); //getch()函數要使用兩次,原因是因為第一次返回的值指示該鍵擴展的字符,第二次調用才返回實際的鍵代碼
//如果蛇身長度大於1,則不能回頭,如果摁回頭方向,則按原來方向走
//abs(new_dir - old_dir) == 2 表示 |LEFT-RIGHT|
//abs(new_dir - old_dir) == 8 表示 |UP-DOWN|
if(len > 1 && (abs(new_dir - old_dir) == 2 || abs(new_dir - old_dir) == 8)){
new_dir = old_dir;
}
}
return new_dir;
}
void move_snake(int dir){
int last = snake[0],current; //last與current用於之後蛇坐標的更新
int i,j;
int grow=0; //判斷是否要長身體
unsigned char x, y,fx,fy; //蛇坐標與食物坐標
tran(food, &fx, &fy); //食物坐標
tran(snake[0], &x, &y); //蛇頭坐標
switch (dir){ //更新蛇頭坐標(坐標原點是左上角)
case UP:
y--;
break;
case DOWN:
y++;
break;
case LEFT:
x--;
break;
case RIGHT:
x++;
break;
}
//按位抑或(妙!)
//http://www.bianceng.cn
snake[0] = ((x ^ 0) << 4) ^ y; //將x,y換回一個數
//x與0抑或保留原值
//將x與y重新合成一個值
//蛇吃到了食物
if (snake[0] == food) {
grow = 1;
food = generate_food(); //產生新食物
}
/*******************************************************************************************************************************/
for (i = 0; i<len; i++) { //蛇移動的關鍵,通過將蛇頭原來的坐標賦給第二節,原來的第二節賦給第三節,依次下去,完成蛇坐標的更新
if (i == 0) //如果只有頭,跳過,因為前面已更新蛇頭坐標
continue;
current = snake[i]; //將當前操作的蛇節坐標存儲到current裡
snake[i] = last; //完成當前操作蛇節坐標的更新
last = current; //last記錄的是上一次操作蛇節的坐標,這次操作已經結束,故把current賦給last
}
/*******************************************************************************************************************************/
//如果蛇邊長了
if (grow) {
snake[len] = last;
len++;
}
for (j = 0; j < 17; j ++){ //將邊界與食物加到地圖裡去(i,j 對應 x軸和y軸)
for (i = 0; i < 17; i ++){
if (i == 0 || i == 16 || j == 0 || j == 16){
map[i][j] = BAR;
}
else if (i == fx&&j == fy){
map[i][j] = FOOD;
}
else{
map[i][j] = 0;
}
}
for (i = 0; i < len; i++) { //將蛇加到地圖裡去
tran(snake[i], &x, &y);
if (snake[i] > 0){
map[x][y] = SNAKE;
}
}
}
}
unsigned char generate_food(void)
{
unsigned char food_,fx,fy;
int in_snake=0,i;
//以當前時間為參數提供種子供rand()函數生成更為隨機的數
srand((unsigned int)time(NULL));
//循環產生在邊界內且不在蛇身上的食物
do {
food_ = rand() % 255;//產生一個0--255的隨機數
tran(food_, &fx, &fy);
for (i = 0; i < len; i++){
if (food_ == snake[i]){
//在不在蛇身上
in_snake = 1;
}
}
} while (fx == 0 || fx == 16 || fy == 0 || fy == 16 || in_snake);
return food_;
}
int isalive(void)
{
int self_eat = 0;
int i;
unsigned char x, y;
tran(snake[0], &x, &y);
for (i = 1; i < len; i++){
if (snake[0] == snake[i]){
self_eat = 1;
}
}
//蛇頭撞邊界或者吃到自己 ,則死掉
return (x == 0 || x == 16 || y == 0 || y >= 16 || self_eat) ? 0 : 1;
}