上節我們用方向控制函數寫了個小畫圖程序,它雖然簡單好玩,但我們不應該止步於此。革命尚未成功,同志還需努力。
先復習一下貪吃蛇的結構:
開始實現之前,我們先理清一下思路。和前面畫圖程序不同,貪吃蛇可以有很多節,可以用一個足夠大的結構體數組來儲存它。 還需要一個食物坐標。定義如下:
typedef struct Position //坐標結構 { int x; int y; }Pos; Pos array; //移動方向向量 Pos snake[300000] = {}; //蛇的結構體數組,誰能夠無聊到吃299999個食物~_~
long len=1; //蛇的長度
Pos egg; //食物坐標
之前的畫圖程序是四個方向都可以走,可蛇是不能倒著走的,所以方向控制函數要改成這樣:
void command() //獲取鍵盤命令 { if (_kbhit()) //如果有鍵盤消息 switch (_getch()) /*這裡不能用getchar()*/ { case 'a': if (array.x != 1 || array.y != 0) {//如果命令不是倒著走,就修正方向向量,否則不做改變,下同。 array.x = -1; array.y = 0; } break; case 'd': if (array.x != -1 || array.y != 0) { array.x = 1; array.y = 0; } break; case 'w': if (array.x != 0 || array.y != 1) { array.x = 0; array.y = -1; } break; case 's': if (array.x != 0 || array.y != -1) { array.x = 0; array.y = 1; } break; } }
蛇可能不止一節,所以移動函數需要做出改變。仔細一想就知道,除了頭結點外,每個節點的下一個坐標為它前一個結點當前的坐標,而頭節點的坐標等於它本身坐標加上移動向量(這裡是 方向向量*10)
還有個問題是蛇走過的痕跡需要擦除,每走一步,它留下的痕跡應該是走這一步之前蛇的最末一個結點的坐標,我們需要擦除掉它。
結果如下:
void move() //修改各節點坐標以達到移動的目的 { setcolor(BLACK); //覆蓋尾部走過的痕跡 rectangle(snake[len-1].x - 5, snake[len-1].y - 5, snake[len-1].x + 5, snake[len-1].y + 5); for (int i = len-1; i >0; i--) //除了頭結點外,每個節點的下一個坐標為它前一個結點當前的坐標 { snake[i].x = snake[i - 1].x; snake[i].y = snake[i - 1].y; } snake[0].x += array.x*10; //頭節點的坐標等於它本身坐標加上移動向量(這裡是 方向向量*10) snake[0].y += array.y*10; }
另外,我們的蛇是有穿牆術的~~~它的實現方法非常簡單:
void break_wall() { if (snake[0].x >= 640) //如果越界,從另一邊出來 snake[0].x = 0; else if (snake[0].x <= 0) snake[0].x = 640; else if (snake[0].y >= 480) snake[0].y = 0; else if (snake[0].y <= 0) snake[0].y = 480; }
接下來是食物相關函數,這個算是重點。
1. 食物生成
我們希望食物每次出現的位置都是隨機的, 可以這樣實現。
1 srand((unsigned)time(NULL)); 2 egg.x = rand() % 80 * 5 + 100; //頭節點位置隨機化 3 egg.y = rand() % 50 * 5 + 100;
而且食物不能與蛇重合,最好也不要離蛇太近。綜合起來就是這樣:(srand在初始化中會被調用,所以這裡略去了)
void creat_egg() { while (true) { int ok = 0; //這是個標記,用於判斷函數是否進入了某一分支 egg.x = rand() % 80 * 5 + 100; //頭節點位置隨機化 egg.y = rand() % 50 * 5 + 100; for (int i = 0; i < len; i++) //判斷是否離蛇太近 { if (snake[i].x == 0 && snake[i].y == 0) continue; if (fabs(snake[i].x - egg.x) <= 10 && fabs(snake[i].y - egg.y) <= 10) ok = -1; //如果,進入此分支,改變標記 break; } if (ok == 0) //如果不重合了,跳出函數 return; } }
2. 吃到食物
如果吃到食物,那麼需要消除被吃掉的食物,生成新食物,蛇也要增長一節。
我覺得這裡最麻煩的就是蛇變長的實現:是在蛇頭添加一節,還是在蛇尾?添加在蛇頭(尾)的上下左右哪一邊?
想來想去,只有在蛇頭位置,我們可以根據當前方向向量,在移動方向上新添一節。這對應的代碼如下:
//add snake node len += 1; for (int i = len - 1; i >0; i--) //所有數據後移一個單位,騰出snake[0]給新添的一節 { snake[i].x = snake[i - 1].x; snake[i].y = snake[i - 1].y; } snake[0].x += array.x * 10; //這就是新添的這一節的位置 snake[0].y += array.y * 10;
吃到食物的完整代碼如下:
void eat_egg() { if (fabs(snake[0].x - egg.x)<=5 && fabs(snake[0].y - egg.y)<=5) //判斷是否吃到食物,因為食物位置有點小偏差,只好使用范圍判定~~ { setcolor(BLACK); //hide old egg circle(egg.x, egg.y, 5);
creat_egg(); //create new egg //add snake node len += 1; for (int i = len - 1; i >0; i--) { snake[i].x = snake[i - 1].x; snake[i].y = snake[i - 1].y; } snake[0].x += array.x * 10; //每次移動10pix snake[0].y += array.y * 10; } }
游戲結束判定
最後,我們還差一個死亡判定,因為自帶穿牆術,所以實際的死亡判定只有一個,就是咬到自己,代碼如下:
void eat_self() { if (len == 1) //只有一節當然吃不到自己~~ return; for (int i = 1; i < len; i++) if (fabs(snake[i].x - snake[0].x) <= 5 && fabs(snake[i].y - snake[0].y) <= 5) //如果咬到自己(為了不出bug,使用了范圍判定) { outtextxy(250, 200, "GAME OVER!"); //你的蛇死了~ Sleep(3000); //3s時間讓你看看你的死相~~ closegraph(); exit(0); //退出 } }
當然,你也可以直接丟掉這個函數,然後開心地狂咬自己—_—||
最後:畫圖函數
畫出食物和蛇,其實蛇沒必要全部畫出來,只要畫蛇頭就可以了,但這之中有些小問題,誰有興趣可以自己玩玩,我是懶得動了~
void draw() //畫出蛇和食物 { setcolor(BLUE); for (int i = 0; i < len; i++) { rectangle(snake[i].x - 5, snake[i].y - 5, snake[i].x + 5, snake[i].y + 5); } setcolor(RED); //畫蛋(怎麼感覺怪怪的~) circle(egg.x, egg.y, 5); Sleep(100); }
到這裡,游戲大功告成~~ 什麼?你說運行不起來?那是因為少了初始化函數,和游戲循環啦~~這幾個都比較簡單,就直接放下面了:
void init() //初始化 { initgraph(640, 480); //初始化圖形界面 srand((unsigned)time(NULL)); //初始化隨機函數 snake[0].x = rand() % 80 * 5 + 100; //頭節點位置隨機化 snake[0].y = rand() % 50 * 5 + 100; array.x = pow(-1,rand()); //初始化方向向量,左或者右 array.y = 0; creat_egg(); } int main() { init(); while (true) { command(); //獲取鍵盤消息 move(); //修改頭節點坐標-蛇的移動 eat_egg(); draw(); //作圖 eat_self(); } return 0; }
好了,這是真的大功告成了。給你們看看死亡方式之自盡:
完整代碼如下:
1 #include<graphics.h> 2 #include<conio.h> 3 #include<time.h> 4 #include<math.h> 5 6 typedef struct Position //坐標結構 7 { 8 int x; 9 int y; 10 }Pos; 11 12 Pos snake[300000] = {}; 13 Pos array; 14 Pos egg; 15 long len=1; 16 17 void creat_egg() 18 { 19 while (true) 20 { 21 int ok = 0; 22 srand((unsigned)time(NULL)); //初始化隨機函數 23 egg.x = rand() % 80 * 5 + 100; //頭節點位置隨機化 24 egg.y = rand() % 50 * 5 + 100; 25 for (int i = 0; i < len; i++) 26 { 27 if (snake[i].x == 0 && snake[i].y == 0) 28 continue; 29 if (fabs(snake[i].x - egg.x) <= 10 && fabs(snake[i].y - egg.y) <= 10) 30 ok = -1; 31 break; 32 } 33 if (ok == 0) 34 return; 35 } 36 } 37 38 void init() //初始化 39 { 40 initgraph(640, 480); //初始化圖形界面 41 srand((unsigned)time(NULL)); //初始化隨機函數 42 snake[0].x = rand() % 80 * 5 + 100; //頭節點位置隨機化 43 snake[0].y = rand() % 50 * 5 + 100; 44 array.x = pow(-1,rand()); //初始化方向向量 45 array.y = 0; 46 creat_egg(); 47 } 48 49 void command() //獲取鍵盤命令 50 { 51 if (_kbhit()) //如果有鍵盤消息 52 switch (_getch()/*這裡不能用getchar()*/) 53 { 54 case 'a': 55 if (array.x != 1 || array.y != 0) {//如果不是反方向 56 array.x = -1; 57 array.y = 0; 58 } 59 break; 60 case 'd': 61 if (array.x != -1 || array.y != 0) { 62 array.x = 1; 63 array.y = 0; 64 } 65 break; 66 case 'w': 67 if (array.x != 0 || array.y != 1) { 68 array.x = 0; 69 array.y = -1; 70 } 71 break; 72 case 's': 73 if (array.x != 0 || array.y != -1) { 74 array.x = 0; 75 array.y = 1; 76 } 77 break; 78 } 79 } 80 81 void move() //修改各節點坐標以達到移動的目的 82 { 83 setcolor(BLACK); //覆蓋尾部走過的痕跡 84 rectangle(snake[len-1].x - 5, snake[len-1].y - 5, snake[len-1].x + 5, snake[len-1].y + 5); 85 86 for (int i = len-1; i >0; i--) 87 { 88 snake[i].x = snake[i - 1].x; 89 snake[i].y = snake[i - 1].y; 90 } 91 snake[0].x += array.x*10; //每次移動10pix 92 snake[0].y += array.y*10; 93 94 if (snake[0].x >= 640) //如果越界,從另一邊出來 95 snake[0].x = 0; 96 else if (snake[0].x <= 0) 97 snake[0].x = 640; 98 else if (snake[0].y >= 480) 99 snake[0].y = 0; 100 else if (snake[0].y <= 0) 101 snake[0].y = 480; 102 } 103 104 void eat_egg() 105 { 106 if (fabs(snake[0].x - egg.x)<=5 && fabs(snake[0].y - egg.y)<=5) 107 { 108 setcolor(BLACK); //shade old egg 109 circle(egg.x, egg.y, 5); 110 creat_egg(); 111 //add snake node 112 len += 1; 113 for (int i = len - 1; i >0; i--) 114 { 115 snake[i].x = snake[i - 1].x; 116 snake[i].y = snake[i - 1].y; 117 } 118 snake[0].x += array.x * 10; //每次移動10pix 119 snake[0].y += array.y * 10; 120 } 121 } 122 123 void draw() //畫出蛇和食物 124 { 125 setcolor(BLUE); 126 for (int i = 0; i < len; i++) 127 { 128 rectangle(snake[i].x - 5, snake[i].y - 5, snake[i].x + 5, snake[i].y + 5); 129 } 130 setcolor(RED); 131 circle(egg.x, egg.y, 5); 132 Sleep(100); 133 } 134 135 void eat_self() 136 { 137 if (len == 1) 138 return; 139 for (int i = 1; i < len; i++) 140 if (fabs(snake[i].x - snake[0].x) <= 5 && fabs(snake[i].y - snake[0].y) <= 5) 141 { 142 Sleep(1000); 143 outtextxy(250, 200, "GAME OVER!"); 144 Sleep(3000); 145 closegraph(); 146 exit(0); 147 } 148 } 149 150 int main() 151 { 152 init(); 153 while (true) 154 { 155 command(); //獲取鍵盤消息 156 move(); //修改頭節點坐標-蛇的移動 157 eat_egg(); 158 draw(); //作圖 159 eat_self(); 160 } 161 162 return 0; 163 } snakey可能還有若干bug留存,歡迎大家指正~~
甲鐵城鎮~