歡迎光臨
每天分享高質量文章

通過 ncurses 在終端創建一個冒險游戲 | Linux 中國

怎樣使用 curses 函式讀取鍵盤並操作屏幕。
— Jim Hall


致謝
編譯自 | http://www.linuxjournal.com/content/creating-adventure-game-terminal-ncurses 
 作者 | Jim Hall
 譯者 | leemeans ? ? 共計翻譯:5 篇 貢獻時間:22 天

怎樣使用 curses 函式讀取鍵盤並操作屏幕。

之前的文章[1]介紹了 ncurses 庫,並提供了一個簡單的程式展示了一些將文本放到屏幕上的 curses 函式。在接下來的文章中,我將介紹如何使用其它的 curses 函式。

探險

當我逐漸長大,家裡有了一臺蘋果 II 電腦。我和我兄弟正是在這臺電腦上自學瞭如何用 AppleSoft BASIC 寫程式。我在寫了一些數學智力游戲之後,繼續創造游戲。作為 80 年代的人,我已經是龍與地下城桌游的粉絲,在游戲中角色扮演一個追求打敗怪物併在陌生土地上搶掠的戰士或者男巫,所以我創建一個基本的冒險游戲也在情理之中。

AppleSoft BASIC 支持一種簡潔的特性:在標準解析度圖形樣式(GR 樣式)下,你可以檢測屏幕上特定點的顏色。這為創建一個冒險游戲提供了捷徑。比起創建並更新周期性傳送到屏幕的記憶體地圖,我現在可以依賴 GR 樣式為我維護地圖,我的程式還可以在玩家的角色(LCTT 譯註:此處 character 雙關一個代表玩家的角色,同時也是一個字符)在屏幕四處移動的時候查詢屏幕。通過這種方式,我讓電腦完成了大部分艱難的工作。因此,我的自頂向下的冒險游戲使用了塊狀的 GR 樣式圖形來展示我的游戲地圖。

我的冒險游戲使用了一張簡單的地圖,上面有一大片綠地伴著山脈從中間蔓延向下和一個在左上方的大湖。我要粗略地為桌游戰役繪製這個地圖,其中包含一個允許玩家穿過到遠處的狹窄通道。

圖 1. 一個有湖和山的簡單桌游地圖

你可以用 curses 繪製這個地圖,並用字符代表草地、山脈和水。接下來,我描述怎樣使用 curses 那樣做,以及如何在 Linux 終端創建和進行類似的一個冒險游戲。

構建程式

在我的上一篇文章,我提到了大多數 curses 程式以相同的一組指令獲取終端型別和設置 curses 環境:

  1. initscr();

  2. cbreak();

  3. noecho();

在這個程式,我添加了另外的陳述句:

  1. keypad(stdscr, TRUE);

這裡的 TRUE 標誌允許 curses 從用戶終端讀取小鍵盤和功能鍵。如果你想要在你的程式中使用上下左右方向鍵,你需要使用這裡的 keypad(stdscr, TRUE)

這樣做了之後,你現在可以開始在終端屏幕上繪圖了。curses 函式包括了一系列在屏幕上繪製文本的方法。在我之前的文章中,我展示了 addch() 和 addstr() 函式以及在添加文本之前先移動到指定屏幕位置的對應函式 mvaddch() 和 mvaddstr()。為了在終端上創建這個冒險游戲的地圖,你可以使用另外一組函式:vline() 和 hline(),以及它們對應的函式 mvvline() 和 mvhline()。這些 mv 函式接受屏幕坐標、一個要繪製的字符和要重覆此字符的次數的引數。例如,mvhline(1, 2, '-', 20) 將會繪製一條開始於第一行第二列並由 20 個橫線組成的線段。

為了以編程方式繪製地圖到終端屏幕上,讓我們先定義這個 draw_map() 函式:

  1. #define GRASS     ' '

  2. #define EMPTY     '.'

  3. #define WATER     '~'

  4. #define MOUNTAIN  '^'

  5. #define PLAYER    '*'

  6. void draw_map(void)

  7. {

  8.    int y, x;

  9.    /* 繪製探索地圖 */

  10.    /* 背景 */

  11.    for (y = 0; y < LINES; y++) {

  12.        mvhline(y, 0, GRASS, COLS);

  13.    }

  14.    /* 山和山道 */

  15.    for (x = COLS / 2; x < COLS * 3 / 4; x++) {

  16.        mvvline(0, x, MOUNTAIN, LINES);

  17.    }

  18.    mvhline(LINES / 4, 0, GRASS, COLS);

  19.    /* 湖 */

  20.    for (y = 1; y < LINES / 2; y++) {

  21.        mvhline(y, 1, WATER, COLS / 3);

  22.    }

  23. }

在繪製這副地圖時,記住填充大塊字符到屏幕所使用的 mvvline() 和 mvhline() 函式。我繪製從 0 列開始的字符水平線(mvhline)以創建草地區域,直到占滿整個屏幕的高度和寬度。我繪製從 0 行開始的多條垂直線(mvvline)在此上添加了山脈,繪製單行水平線添加了一條山道(mvhline)。並且,我通過繪製一系列短水平線(mvhline)創建了湖。這種繪製重疊方塊的方式看起來似乎並沒有效率,但是記住在我們呼叫 refresh() 函式之前 curses 並不會真正更新屏幕。

繪製完地圖,創建游戲就還剩下進入迴圈讓程式等待用戶按下上下左右方向鍵中的一個然後讓玩家圖標正確移動了。如果玩家想要移動的地方是空的,就應該允許玩家到那裡。

你可以把 curses 當做捷徑使用。比起在程式中實體化一個版本的地圖並複製到屏幕這麼複雜,你可以讓屏幕為你跟蹤所有東西。inch() 函式和相關聯的 mvinch() 函式允許你探測屏幕的內容。這讓你可以查詢 curses 以瞭解玩家想要移動到的位置是否被水填滿或者被山阻擋。這樣做你需要一個之後會用到的一個幫助函式:

  1. int is_move_okay(int y, int x)

  2. {

  3.    int testch;

  4.    /* 如果要進入的位置可以進入,傳回 true */

  5.    testch = mvinch(y, x);

  6.    return ((testch == GRASS) || (testch == EMPTY));

  7. }

如你所見,這個函式探測行 x、列 y 併在空間未被占據的時候傳回 true,否則傳回 false

這樣我們寫移動迴圈就很容易了:從鍵盤獲取一個鍵值然後根據是上下左右鍵移動用戶字符。這裡是一個這種迴圈的簡單版本:

  1.    do {

  2.        ch = getch();

  3.        /* 測試輸入的值並獲取方向 */

  4.        switch (ch) {

  5.        case KEY_UP:

  6.            if ((y > 0) && is_move_okay(y - 1, x)) {

  7.                y = y - 1;

  8.            }

  9.            break;

  10.        case KEY_DOWN:

  11.            if ((y < LINES - 1) && is_move_okay(y + 1, x)) {

  12.                y = y + 1;

  13.            }

  14.            break;

  15.        case KEY_LEFT:

  16.            if ((x > 0) && is_move_okay(y, x - 1)) {

  17.                x = x - 1;

  18.            }

  19.            break;

  20.        case KEY_RIGHT

  21.            if ((x < COLS - 1) && is_move_okay(y, x + 1)) {

  22.                x = x + 1;

  23.            }

  24.            break;

  25.        }

  26.    }

  27.    while (1);

為了在游戲中使用這個迴圈,你需要在迴圈裡添加一些代碼來啟用其它的鍵(例如傳統的移動鍵 WASD),以提供讓用戶退出游戲和在屏幕上四處移動的方法。這裡是完整的程式:

  1. /* quest.c */

  2. #include

  3. #include

  4. #define GRASS     ' '

  5. #define EMPTY     '.'

  6. #define WATER     '~'

  7. #define MOUNTAIN  '^'

  8. #define PLAYER    '*'

  9. int is_move_okay(int y, int x);

  10. void draw_map(void);

  11. int main(void)

  12. {

  13.    int y, x;

  14.    int ch;

  15.    /* 初始化curses */

  16.    initscr();

  17.    keypad(stdscr, TRUE);

  18.    cbreak();

  19.    noecho();

  20.    clear();

  21.    /* 初始化探索地圖 */

  22.    draw_map();

  23.    /* 在左下角初始化玩家 */

  24.    y = LINES - 1;

  25.    x = 0;

  26.    do {

  27.    /* 預設獲得一個閃爍的光標--表示玩家字符 */

  28.    mvaddch(y, x, PLAYER);

  29.    move(y, x);

  30.    refresh();

  31.    ch = getch();

  32.    /* 測試輸入的鍵並獲取方向 */

  33.    switch (ch) {

  34.    case KEY_UP:

  35.    case 'w':

  36.    case 'W':

  37.        if ((y > 0) && is_move_okay(y - 1, x)) {

  38.        mvaddch(y, x, EMPTY);

  39.        y = y - 1;

  40.        }

  41.        break;

  42.    case KEY_DOWN:

  43.    case 's':

  44.    case 'S':

  45.        if ((y < LINES - 1) && is_move_okay(y + 1, x)) {

  46.        mvaddch(y, x, EMPTY);

  47.        y = y + 1;

  48.        }

  49.        break;

  50.    case KEY_LEFT:

  51.    case 'a':

  52.    case 'A':

  53.        if ((x > 0) && is_move_okay(y, x - 1)) {

  54.        mvaddch(y, x, EMPTY);

  55.        x = x - 1;

  56.        }

  57.        break;

  58.    case KEY_RIGHT:

  59.    case 'd':

  60.    case 'D':

  61.        if ((x < COLS - 1) && is_move_okay(y, x + 1)) {

  62.        mvaddch(y, x, EMPTY);

  63.        x = x + 1;

  64.        }

  65.        break;

  66.    }

  67.    }

  68.    while ((ch != 'q') && (ch != 'Q'));

  69.    endwin();

  70.    exit(0);

  71. }

  72. int is_move_okay(int y, int x)

  73. {

  74.    int testch;

  75.    /* 當空間可以進入時傳回true */

  76.    testch = mvinch(y, x);

  77.    return ((testch == GRASS) || (testch == EMPTY));

  78. }

  79. void draw_map(void)

  80. {

  81.    int y, x;

  82.    /* 繪製探索地圖 */

  83.    /* 背景 */

  84.    for (y = 0; y < LINES; y++) {

  85.    mvhline(y, 0, GRASS, COLS);

  86.    }

  87.    /* 山脈和山道 */

  88.    for (x = COLS / 2; x < COLS * 3 / 4; x++) {

  89.    mvvline(0, x, MOUNTAIN, LINES);

  90.    }

  91.    mvhline(LINES / 4, 0, GRASS, COLS);

  92.    /* 湖 */

  93.    for (y = 1; y < LINES / 2; y++) {

  94.    mvhline(y, 1, WATER, COLS / 3);

  95.    }

  96. }

在完整的程式清單中,你可以看見使用 curses 函式創建游戲的完整佈置:

☉ 初始化 curses 環境。
☉ 繪製地圖。
☉ 初始化玩家坐標(左下角)
☉ 迴圈:

◈ 繪製玩家的角色。
◈ 從鍵盤獲取鍵值。
◈ 對應地上下左右調整玩家坐標。
◈ 重覆。
☉ 完成時關閉curses環境並退出。

開始玩

當你運行游戲時,玩家的字符在左下角初始化。當玩家在游戲區域四處移動的時候,程式創建了“一串”點。這樣可以展示玩家經過了的點,讓玩家避免經過不必要的路徑。

圖 2. 初始化在左下角的玩家

圖 3. 玩家可以在游戲區域四處移動,例如湖周圍和山的通道

為了創建上面這樣的完整冒險游戲,你可能需要在他/她的角色在游戲區域四處移動的時候隨機創建不同的怪物。你也可以創建玩家可以發現在打敗敵人後可以掠奪的特殊道具,這些道具應能提高玩家的能力。

但是作為起點,這是一個展示如何使用 curses 函式讀取鍵盤和操縱屏幕的好程式。

下一步

這是一個如何使用 curses 函式更新和讀取屏幕和鍵盤的簡單例子。按照你的程式需要做什麼,curses 可以做得更多。在下一篇文章中,我計劃展示如何更新這個簡單程式以使用顏色。同時,如果你想要學習更多 curses,我鼓勵你去讀位於 Linux 文件計劃的 Pradeep Padala 寫的如何使用 NCURSES 編程[2]


via: http://www.linuxjournal.com/content/creating-adventure-game-terminal-ncurses

作者:Jim Hall[4] 譯者:Leemeans 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出

赞(0)

分享創造快樂

© 2021 知識星球   网站地图