為什麼選 ncurses?
- 跨平台:Linux、macOS、BSD、Windows (PDCurses/MinGW) 通用。
- 無圖形介面依賴:純終端機 (SSH、Serial Console、Docker 容器) 即可運行。
- 即時鍵盤輸入:
nodelay()、keypad()、getch() 支援非阻塞、方向鍵、功能鍵。
- 螢幕佈局控制:
move()、addch()、addstr()、refresh()、clear() 精確控制游標與字元。
- 色彩/屬性:
start_color()、init_pair()、attron() 支援前景/背景色、粗體、底線。
核心資料結構
#define MAX_LEN 100
#define WIDTH 50
#define HEIGHT 20
typedef struct { int x, y; } Point;
typedef struct {
Point body[MAX_LEN];
int len;
int dir; // 0=UP, 1=DOWN, 2=LEFT, 3=RIGHT
} Snake;
typedef struct {
Point pos;
bool exists;
} Food;
Snake snake;
Food food;
int score = 0;
int speed_us = 300000; // 300ms
ncurses 初始化與清理
void InitNcurses() {
initscr(); // 初始化螢幕
cbreak(); // 關閉行緩衝,字元即時可讀
noecho(); // 不回顯輸入字元
curs_set(0); // 隱藏游標
keypad(stdscr, TRUE); // 啟用功能鍵 (KEY_UP 等)
nodelay(stdscr, TRUE); // getch() 非阻塞
start_color(); // 啟用色彩
init_pair(1, COLOR_GREEN, COLOR_BLACK); // 蛇身
init_pair(2, COLOR_RED, COLOR_BLACK); // 食物
init_pair(3, COLOR_YELLOW, COLOR_BLACK); // 牆壁/分數
}
void CleanupNcurses() {
endwin(); // 恢復終端機狀態
}
繪圖函數
void DrawBorder() {
attron(COLOR_PAIR(3));
for (int x=0; x
遊戲邏輯核心
void InitGame() {
snake.len = 3;
snake.body[0] = (Point){WIDTH/2, HEIGHT/2};
snake.body[1] = (Point){WIDTH/2-1, HEIGHT/2};
snake.body[2] = (Point){WIDTH/2-2, HEIGHT/2};
snake.dir = 3; // RIGHT
score = 0;
SpawnFood();
}
void SpawnFood() {
do {
food.pos.x = 1 + rand() % (WIDTH-2);
food.pos.y = 1 + rand() % (HEIGHT-2);
// 避免生成在蛇身上
bool on_snake = false;
for (int i=0; i= WIDTH-1 || next.y <= 0 || next.y >= HEIGHT-1)
return true;
// 撞自己
for (int i=0; i0; i--)
snake.body[i] = snake.body[i-1];
snake.body[0] = next;
// 吃食物
if (food.exists && next.x == food.pos.x && next.y == food.pos.y) {
food.exists = false;
if (snake.len < MAX_LEN) snake.len++;
score += 10;
SpawnFood();
}
}
void HandleInput() {
int ch = getch();
switch (ch) {
case KEY_UP: if (snake.dir != 1) snake.dir = 0; break;
case KEY_DOWN: if (snake.dir != 0) snake.dir = 1; break;
case KEY_LEFT: if (snake.dir != 3) snake.dir = 2; break;
case KEY_RIGHT: if (snake.dir != 2) snake.dir = 3; break;
case 'q': case 'Q': GameOver(); break;
}
}
void GameOver() {
nodelay(stdscr, FALSE); // 切回阻塞模式等待按鍵
mvprintw(HEIGHT/2, WIDTH/2-5, "GAME OVER");
mvprintw(HEIGHT/2+1, WIDTH/2-8, "Score: %d", score);
mvprintw(HEIGHT/2+2, WIDTH/2-10, "Press any key to exit");
refresh();
getch();
CleanupNcurses();
exit(0);
}
主迴圈
int main() {
srand(time(NULL));
InitNcurses();
InitGame();
while (1) {
HandleInput();
UpdateSnake();
RefreshAll();
usleep(speed_us);
// 可選:隨分數加速
if (score > 100 && speed_us > 50000) speed_us -= 10000;
}
return 0; // 不會執行到
}
編譯與執行
# Linux (Debian/Ubuntu)
sudo apt-get install libncurses5-dev libncursesw5-dev
gcc -o snake snake.c -lncursesw -Wall -O2
./snake
# macOS
brew install ncurses
gcc -o snake snake.c -lncurses -Wall -O2
# Windows (MinGW + PDCurses)
# 下載 PDCurses,連結 pdcurses.lib
gcc -o snake.exe snake.c -lpdcurses -Wall -O2
進階擴充方向
| 功能 | 實現要點 |
| 多關卡/地圖 | 讀取文字檔地圖陣列,支援牆壁、傳送門、陷阱 |
| 障礙物/毒藥 | 額外實體結構體,碰撞邏輯分類處理 |
| 高分榜持久化 | fopen 讀寫二進位/JSON,啟動時載入顯示 |
| 雙人對戰 | 兩條蛇、分屏或同屏、獨立輸入處理 (WASD vs 方向鍵) |
| AI 自動遊玩 | BFS/A* 尋路至食物,避開自身與牆壁 |
| 色彩主題 | 多組 init_pair,菜單切換 |
| 音效 (終端機蜂鳴器) | beep() 或 printf("\a") |
常見問題排查
| 現象 | 原因 | 解決 |
| 方向鍵無反應 / 亂碼 | 未呼叫 keypad(stdscr, TRUE) | 初始化時啟用 |
| 畫面閃爍嚴重 | 未用 clear() + refresh() 雙緩衝模式 | 每幀完整重繪並單次 refresh |
| 蛇身不顯示 / 位置錯誤 | 座標系混淆 (x/y 對應 col/row) | ncurses: mvaddch(row=y, col=x) |
| 程式結束後終端機異常 | 未呼叫 endwin() | 註冊 atexit(CleanupNcurses) 或信號處理 |
編譯找不到 ncurses.h | 未安裝開發套件 | apt install libncurses5-dev / brew install ncurses |
結語
用 ncurses 寫貪食蛇,不僅能練習遊戲迴圈、狀態機、碰撞檢測、即時輸入,更能體會終端機 UI 程式設計的獨特魅力:無 GPU、無視窗系統、純字元也能跑出流暢互動體驗。這正是嵌入式裝置、伺服器管理工具、CLI 遊戲、TUI 框架 (如 ftxui, ratatui, textual) 的基石。建議以此為起點,嘗試實現 2048、Tetris、Roguelike 等更複雜的 TUI 專案。