推箱子(Sokoban)是一款經典的益智遊戲,玩家需操控角色在倉庫中將箱子推至指定目標位置。本文將帶領你從零開始,使用 C 語言在終端機環境下實作一個完整的文字版推箱子遊戲,包含地圖繪製、移動邏輯、勝利判定等核心機制。
遊戲使用二維字元陣列 char a[11][20] 來儲存地圖資料,其中 # 代表牆壁、$ 代表目標位置、@ 代表箱子、^ 代表玩家、空白則為可通行路徑。透過 system("clear") 清除螢幕(Windows 環境請改用 system("cls")),配合迴圈重繪地圖,即可營造出流暢的動畫效果。
玩家輸入 w/a/s/d 對應上下左右四個方向。移動邏輯分為兩種情況:
(e,f)程式中分別以 (m,n)、(x,y) 追蹤兩個箱子位置,推箱時需同步更新地圖陣列與座標變數。
當兩個箱子分別到達兩個目標位置 (2,8) 與 (2,10) 時,遊戲判定勝利並跳出主迴圈。輸入 q 可隨時退出遊戲。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char sr; // 儲存使用者輸入指令
// 繪製地圖:#=牆壁 $=目標 @=箱子 ^=玩家
char a[11][20] = {
"###################",
"###################",
"### $ $ ###",
"### ##### ###",
"### ###",
"#### #### ### ####",
"### # @ # ###",
"### # @ # ###",
"### ^ ###",
"###################",
"###################"
};
// 定義箱子、玩家與目標座標
int i = 1, e = 8, f = 9, m = 7, n = 9, x = 6, y = 9;
while (i) {
system("clear"); // Windows 請改用 system("cls")
printf("歡迎使用推箱子\n^ 代表你,任務是把 @ 推到 $ 的位置,加油!\n");
printf("操作說明:w=上、s=下、a=左、d=右、q=退出\n");
// 判斷勝利條件
if (m == 2 && n == 8) {
if (x == 2 && y == 10) {
printf("恭喜你挑戰成功!\n");
break;
}
}
if (m == 2 && n == 10) {
if (x == 2 && y == 8) {
printf("恭喜你挑戰成功!\n");
break;
}
}
// 列印地圖
for (int b = 0; b < 11; b++) {
for (int c = 0; c < 20; c++) {
printf("%c", a[b][c]);
}
printf("\n");
}
// 清除緩衝區並讀取輸入
fpurge(stdin);
scanf("%c", &sr);
// 方向判斷與移動邏輯
switch (sr) {
case 'w': case 'W':
if (a[e-1][f] == ' ') {
a[e-1][f] = '^'; a[e][f] = ' '; e -= 1;
} else if (a[e-1][f] == '@' && a[e-2][f] != '#' && a[e-2][f] != '@') {
if (e - m == 1 && f == n) {
m -= 1; a[m][n] = '@'; a[e-1][f] = '^'; a[e][f] = ' '; e -= 1;
} else if (e - x == 1 && f == y) {
x -= 1; a[x][y] = '@'; a[e-1][f] = '^'; a[e][f] = ' '; e -= 1;
}
}
break;
case 's': case 'S':
if (a[e+1][f] == ' ') {
a[e+1][f] = '^'; a[e][f] = ' '; e += 1;
} else if (a[e+1][f] == '@' && a[e+2][f] != '#' && a[e+2][f] != '@') {
if (m - e == 1 && f == n) {
m += 1; a[m][n] = '@'; a[e+1][f] = '^'; a[e][f] = ' '; e += 1;
} else if (x - e == 1 && y == f) {
x += 1; a[x][y] = '@'; a[e+1][f] = '^'; a[e][f] = ' '; e += 1;
}
}
break;
case 'a': case 'A':
if (a[e][f-1] == ' ') {
a[e][f-1] = '^';
if ((e == 2 && f == 8) || (e == 2 && f == 10)) a[e][f] = '$';
else a[e][f] = ' ';
f -= 1;
} else if (a[e][f-1] == '$') {
a[e][f-1] = '^';
if ((e == 2 && f == 8) || (e == 2 && f == 10)) a[e][f] = '$';
else a[e][f] = ' ';
f -= 1;
} else if (a[e][f-1] == '@' && a[e][f-2] != '#' && a[e][f-2] != '@') {
if (e == m && f - n == 1) {
n -= 1; a[m][n] = '@'; a[e][f-1] = '^';
if ((e == 2 && f == 8) || (e == 2 && f == 10)) a[e][f] = '$';
else a[e][f] = ' '; f -= 1;
} else if (e == x && f - y == 1) {
y -= 1; a[x][y] = '@'; a[e][f-1] = '^';
if ((e == 2 && f == 8) || (e == 2 && f == 10)) a[e][f] = '$';
else a[e][f] = ' '; f -= 1;
}
}
break;
case 'd': case 'D':
if (a[e][f+1] == ' ') {
a[e][f+1] = '^';
if ((e == 2 && f == 8) || (e == 2 && f == 10)) a[e][f] = '$';
else a[e][f] = ' ';
f += 1;
} else if (a[e][f+1] == '$') {
a[e][f+1] = '^';
if ((e == 2 && f == 8) || (e == 2 && f == 10)) a[e][f] = '$';
else a[e][f] = ' ';
f += 1;
} else if (a[e][f+1] == '@' && a[e][f+2] != '#' && a[e][f+2] != '@') {
if (e == m && n - f == 1) {
n += 1; a[m][n] = '@'; a[e][f+1] = '^';
if ((e == 2 && f == 8) || (e == 2 && f == 10)) a[e][f] = '$';
else a[e][f] = ' '; f += 1;
} else if (e == x && y - f == 1) {
y += 1; a[x][y] = '@'; a[e][f+1] = '^';
if ((e == 2 && f == 8) || (e == 2 && f == 10)) a[e][f] = '$';
else a[e][f] = ' '; f += 1;
}
}
break;
case 'q': case 'Q':
i = 0;
break;
}
}
return 0;
}
這個基礎版本可進一步擴展:支援多關卡地圖載入、加入步數計數與最佳紀錄、實作撤銷功能、優化輸入處理(如使用 getch() 免按 Enter)、加入顏色輸出提升視覺體驗。對於初學者而言,這個專案完美展示了陣列操作、迴圈控制、條件判斷與即時互動的綜合應用。