上集實現了單方塊移動繪圖原型,本集將其進化為完整貪吃蛇:多節蛇身跟隨、軌跡清除、穿牆邏輯、食物隨機生成與防重疊、吃食增長機制、自我碰撞判定、遊戲主循環整合。
typedef struct Position { int x, y; } Pos;
Pos array; // 方向向量
Pos snake[300000]; // 蛇身結點數組(足夠大)
long len = 1; // 蛇長
Pos egg; // 食物座標
void command() {
if (_kbhit()) {
switch (_getch()) {
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;
}
}
}
關鍵:檢查 array 當前方向,僅在非反向時更新,防止蛇直接掉頭撞自己。
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; // 頭部按向量移動
snake[0].y += array.y * 10;
break_wall(); // 穿牆處理
}
核心邏輯:從尾部向前依次賦值,最後移動頭部。先擦除舊尾部,再更新座標,避免殘影。
break_wall()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;
}
超出邊界即從對面出現,增加遊戲趣味性,也簡化了邊界死亡判定。
void creat_egg() {
while (true) {
int ok = 0;
egg.x = rand() % 80 * 5 + 100; // 對齊網格(5像素)並留邊距
egg.y = rand() % 50 * 5 + 100;
for (int i = 0; i < len; i++) {
if (snake[i].x == 0 && snake[i].y == 0) continue;
if (abs(snake[i].x - egg.x) <= 10 && abs(snake[i].y - egg.y) <= 10) { ok = -1; break; }
}
if (ok == 0) return; // 位置合法
}
}
食物座標按5像素網格對齊(與蛇移動步長一致),檢查與所有蛇身結點距離>10像素,確保不重疊、不貼身。
eat_egg()void eat_egg() {
if (abs(snake[0].x - egg.x) <= 5 && abs(snake[0].y - egg.y) <= 5) {
setcolor(BLACK); circle(egg.x, egg.y, 5); // 擦除舊食物
creat_egg(); // 生成新食物
// 增長:在頭部插入新結點
len++;
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; // 新頭部向前一步
snake[0].y += array.y * 10;
}
}
增長策略:數組整體後移一位騰出 snake[0],新頭部按當前方向前進一步,視覺上蛇變長了且方向不變。
eat_self()void eat_self() {
if (len == 1) return;
for (int i = 1; i < len; i++) {
if (abs(snake[i].x - snake[0].x) <= 5 && abs(snake[i].y - snake[0].y) <= 5) {
outtextxy(250, 200, "GAME OVER!");
Sleep(3000);
closegraph();
exit(0);
}
}
}
檢查頭部與除自身外所有結點距離,<5像素即碰撞。使用範圍判定容忍座標微小誤差。
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 = (rand()%2)?1:-1; array.y = 0; // 隨機左右
creat_egg();
}
int main() {
init();
while (true) {
command();
move();
eat_egg();
draw();
eat_self();
}
return 0;
}
全文附帶完整可編譯代碼(含 graphics.h, conio.h, time.h, math.h)。已知潛在Bug:creat_egg 中 srand 重複調用可能導致隨機性下降;eat_egg 增長後未重新檢查穿牆;極長蛇身數組可能棧溢出(建議動態分鏈表)。歡迎指正優化。
從單方塊畫圖到完整貪吃蛇,核心遞進:
這是一個極佳的C語言綜合練習:指釖/數組、結構體、隨機數、圖形庫、遊戲循環、狀態機。下一步可嘗試:分數記錄、等級加速、雙人對戰、AI自動尋路……