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

使用 ncurses 進行顏色編程 | Linux 中國

Jim 給他的終端冒險游戲添加了顏色,演示瞭如何用 curses 操縱顏色。
— Jim Hall


致謝
編譯自 | http://www.linuxjournal.com/content/programming-color-ncurses 
 作者 | Jim Hall
 譯者 | leemeans ? ? 共計翻譯:6 篇 貢獻時間:51 天

Jim 給他的終端冒險游戲添加了顏色,演示瞭如何用 curses 操縱顏色。

在我的使用 ncurses 庫進行編程的系列文章的第一篇[1]第二篇[2]中,我已經介紹了一些 curses 函式來在屏幕上作畫、從屏幕上查詢和從鍵盤讀取字符。為了搞清楚這些函式,我使用 curses 來利用簡單字符繪製游戲地圖和玩家角色,創建了一個簡單的冒險游戲。在這篇緊接著的文章里,我展示瞭如何為你的 curses 程式添加顏色。

在屏幕上繪圖一切都挺好的,但是如果只有黑底白字的文本,你的程式可能看起來很無趣。顏色可以幫助傳遞更多的信息。舉個例子,如果你的程式需要報告執行成功或者執行失敗時。在這樣的情況下你可以使用綠色或者紅色來幫助強調輸出。或者,你只是簡單地想要“潮藝”一下給你的程式來讓它看起來更美觀。

在這篇文章中,我用一個簡單的例子來展示通過 curses 函式進行顏色操作。在我先前的文章中,我寫了一個可以讓你在一個粗糙繪製的地圖上移動玩家角色的初級冒險類游戲。但是那裡面的地圖完全是白色和黑色的文本,通過形狀來表明是水()或者山(^)。所以,讓我們將游戲更新到使用顏色的版本吧。

顏色要素

在你可以使用顏色之前,你的程式需要知道它是否可以依靠終端正確地顯示顏色。在現代操作系統上,此處應該永遠為true。但是在經典的計算機上,一些終端是單色的,例如古老的 VT52 和 VT100 終端,一般它們提供黑底白色或者黑底綠色的文本。

可以使用 has_colors() 函式查詢終端的顏色功能。這個函式將會在終端可以顯示顏色的時候傳回 true,否則將會傳回 false。這個函式一般用於 if 塊的開頭,就像這樣:

  1. if (has_colors() == FALSE) {

  2.    endwin();

  3.    printf("Your terminal does not support color\n");

  4.    exit(1);

  5. }

在知道終端可以顯示顏色之後,你可以使用 start_color() 函式來設置 curses 使用顏色。現在是時候定義程式將要使用的顏色了。

在 curses 中,你應該按對定義顏色:一個前景色放在一個背景色上。這樣允許 curses 一次性設置兩個顏色屬性,這也是一般你想要使用的方式。通過 init_pair() 函式可以定義一個前景色和背景色並關聯到索引數字來設置顏色對。大致語法如下:

  1. init_pair(index, foreground, background);

控制台支持八種基礎的顏色:黑色、紅色、綠色、黃色、藍色、品紅色、青色和白色。這些顏色通過下麵的名稱為你定義好了:

◈ COLOR_BLACK
◈ COLOR_RED
◈ COLOR_GREEN
◈ COLOR_YELLOW
◈ COLOR_BLUE
◈ COLOR_MAGENTA
◈ COLOR_CYAN
◈ COLOR_WHITE

應用顏色

在我的冒險游戲中,我想要讓草地呈現綠色而玩家的足跡變成不易察覺的綠底黃色點跡。水應該是藍色,那些表示波浪的 ~ 符號應該是近似青色的。我想讓山(^)是灰色的,但是我可以用白底黑色文本做一個可用的折中方案。(LCTT 譯註:意為終端預設的顏色沒有灰色,使用白底黑色文本做一個折中方案)為了讓玩家的角色更易見,我想要使用一個刺目的品紅底紅色設計。我可以像這樣定義這些顏色對:

  1. start_color();

  2. init_pair(1, COLOR_YELLOW, COLOR_GREEN);

  3. init_pair(2, COLOR_CYAN, COLOR_BLUE);

  4. init_pair(3, COLOR_BLACK, COLOR_WHITE);

  5. init_pair(4, COLOR_RED, COLOR_MAGENTA);

為了讓顏色對更容易記憶,我的程式中定義了一些符號常量:

  1. #define GRASS_PAIR     1

  2. #define EMPTY_PAIR     1

  3. #define WATER_PAIR     2

  4. #define MOUNTAIN_PAIR  3

  5. #define PLAYER_PAIR    4

有了這些常量,我的顏色定義就變成了:

  1. start_color();

  2. init_pair(GRASS_PAIR, COLOR_YELLOW, COLOR_GREEN);

  3. init_pair(WATER_PAIR, COLOR_CYAN, COLOR_BLUE);

  4. init_pair(MOUNTAIN_PAIR, COLOR_BLACK, COLOR_WHITE);

  5. init_pair(PLAYER_PAIR, COLOR_RED, COLOR_MAGENTA);

在任何時候你想要使用顏色顯示文本,你只需要告訴 curses 設置哪種顏色屬性。為了更好的編程實踐,你同樣應該在你完成了顏色使用的時候告訴 curses 取消顏色組合。為了設置顏色,應該在呼叫像 mvaddch() 這樣的函式之前使用attron(),然後通過 attroff() 關閉顏色屬性。例如,在我繪製玩家角色的時候,我應該這樣做:

  1. attron(COLOR_PAIR(PLAYER_PAIR));

  2. mvaddch(y, x, PLAYER);

  3. attroff(COLOR_PAIR(PLAYER_PAIR));

記住將顏色應用到你的程式對你如何查詢屏幕有一些微妙的影響。一般來講,由 mvinch()函式傳回的值是沒有帶顏色屬性的型別 chtype,這個值基本上是一個整型值,也可以當作整型值來用。但是,由於使用顏色添加了額外的屬性到屏幕上的字符上,所以 chtype 按照擴展的位樣式攜帶了額外的顏色信息。一旦你使用 mvinch(),傳回值將會包含這些額外的顏色值。為了只提取文本值,例如在 is_move_okay() 函式中,你需要和 A_CHARTEXT 做 & 位運算:

  1. int is_move_okay(int y, int x)

  2. {

  3.    int testch;

  4.    /* return true if the space is okay to move into */

  5.    testch = mvinch(y, x);

  6.    return (((testch & A_CHARTEXT) == GRASS)

  7.            || ((testch & A_CHARTEXT) == EMPTY));

  8. }

通過這些修改,我可以用顏色更新這個冒險游戲:

  1. /* quest.c */

  2. #include <curses.h>

  3. #include <stdlib.h>

  4. #define GRASS     ' '

  5. #define EMPTY     '.'

  6. #define WATER     '~'

  7. #define MOUNTAIN  '^'

  8. #define PLAYER    '*'

  9. #define GRASS_PAIR     1

  10. #define EMPTY_PAIR     1

  11. #define WATER_PAIR     2

  12. #define MOUNTAIN_PAIR  3

  13. #define PLAYER_PAIR    4

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

  15. void draw_map(void);

  16. int main(void)

  17. {

  18.    int y, x;

  19.    int ch;

  20.    /* 初始化curses */

  21.    initscr();

  22.    keypad(stdscr, TRUE);

  23.    cbreak();

  24.    noecho();

  25.    /* 初始化顏色 */

  26.    if (has_colors() == FALSE) {

  27.        endwin();

  28.        printf("Your terminal does not support color\n");

  29.        exit(1);

  30.    }

  31.    start_color();

  32.    init_pair(GRASS_PAIR, COLOR_YELLOW, COLOR_GREEN);

  33.    init_pair(WATER_PAIR, COLOR_CYAN, COLOR_BLUE);

  34.    init_pair(MOUNTAIN_PAIR, COLOR_BLACK, COLOR_WHITE);

  35.    init_pair(PLAYER_PAIR, COLOR_RED, COLOR_MAGENTA);

  36.    clear();

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

  38.    draw_map();

  39.    /* 在左下角創建新角色 */

  40.    y = LINES - 1;

  41.    x = 0;

  42.    do {

  43.        /* 預設情況下,你獲得了一個閃爍的光標--用來指明玩家 * */

  44.        attron(COLOR_PAIR(PLAYER_PAIR));

  45.        mvaddch(y, x, PLAYER);

  46.        attroff(COLOR_PAIR(PLAYER_PAIR));

  47.        move(y, x);

  48.        refresh();

  49.        ch = getch();

  50.        /* 測試輸入鍵值並獲取方向 */

  51.        switch (ch) {

  52.        case KEY_UP:

  53.        case 'w':

  54.        case 'W':

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

  56.                attron(COLOR_PAIR(EMPTY_PAIR));

  57.                mvaddch(y, x, EMPTY);

  58.                attroff(COLOR_PAIR(EMPTY_PAIR));

  59.                y = y - 1;

  60.            }

  61.            break;

  62.        case KEY_DOWN:

  63.        case 's':

  64.        case 'S':

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

  66.                attron(COLOR_PAIR(EMPTY_PAIR));

  67.                mvaddch(y, x, EMPTY);

  68.                attroff(COLOR_PAIR(EMPTY_PAIR));

  69.                y = y + 1;

  70.            }

  71.            break;

  72.        case KEY_LEFT:

  73.        case 'a':

  74.        case 'A':

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

  76.                attron(COLOR_PAIR(EMPTY_PAIR));

  77.                mvaddch(y, x, EMPTY);

  78.                attroff(COLOR_PAIR(EMPTY_PAIR));

  79.                x = x - 1;

  80.            }

  81.            break;

  82.        case KEY_RIGHT:

  83.        case 'd':

  84.        case 'D':

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

  86.                attron(COLOR_PAIR(EMPTY_PAIR));

  87.                mvaddch(y, x, EMPTY);

  88.                attroff(COLOR_PAIR(EMPTY_PAIR));

  89.                x = x + 1;

  90.            }

  91.            break;

  92.        }

  93.    }

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

  95.    endwin();

  96.    exit(0);

  97. }

  98. int is_move_okay(int y, int x)

  99. {

  100.    int testch;

  101.    /* 當空白處可以進入的時候傳回true */

  102.    testch = mvinch(y, x);

  103.    return (((testch & A_CHARTEXT) == GRASS)

  104.            || ((testch & A_CHARTEXT) == EMPTY));

  105. }

  106. void draw_map(void)

  107. {

  108.    int y, x;

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

  110.    /* 背景 */

  111.    attron(COLOR_PAIR(GRASS_PAIR));

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

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

  114.    }

  115.    attroff(COLOR_PAIR(GRASS_PAIR));

  116.    /* 山峰和山路 */

  117.    attron(COLOR_PAIR(MOUNTAIN_PAIR));

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

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

  120.    }

  121.    attroff(COLOR_PAIR(MOUNTAIN_PAIR));

  122.    attron(COLOR_PAIR(GRASS_PAIR));

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

  124.    attroff(COLOR_PAIR(GRASS_PAIR));

  125.    /* 湖 */

  126.    attron(COLOR_PAIR(WATER_PAIR));

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

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

  129.    }

  130.    attroff(COLOR_PAIR(WATER_PAIR));

  131. }

你可能不能認出所有為了在冒險游戲裡面支持顏色需要的修改,除非你目光敏銳。diff 工具展示了所有為了支持顏色而添加的函式或者修改的代碼:

  1. $ diff quest-color/quest.c quest/quest.c

  2. 12,17d11

  3. < #define GRASS_PAIR     1

  4. < #define EMPTY_PAIR     1

  5. < #define WATER_PAIR     2

  6. < #define MOUNTAIN_PAIR  3

  7. < #define PLAYER_PAIR    4

  8. <

  9. 33,46d26

  10. <     /* initialize colors */

  11. <

  12. <     if (has_colors() == FALSE) {

  13. <    endwin();

  14. <    printf("Your terminal does not support color\n");

  15. <    exit(1);

  16. <     }

  17. <

  18. <     start_color();

  19. <     init_pair(GRASS_PAIR, COLOR_YELLOW, COLOR_GREEN);

  20. <     init_pair(WATER_PAIR, COLOR_CYAN, COLOR_BLUE);

  21. <     init_pair(MOUNTAIN_PAIR, COLOR_BLACK, COLOR_WHITE);

  22. <     init_pair(PLAYER_PAIR, COLOR_RED, COLOR_MAGENTA);

  23. <

  24. 61d40

  25. <    attron(COLOR_PAIR(PLAYER_PAIR));

  26. 63d41

  27. <    attroff(COLOR_PAIR(PLAYER_PAIR));

  28. 76d53

  29. <            attron(COLOR_PAIR(EMPTY_PAIR));

  30. 78d54

  31. <            attroff(COLOR_PAIR(EMPTY_PAIR));

  32. 86d61

  33. <            attron(COLOR_PAIR(EMPTY_PAIR));

  34. 88d62

  35. <            attroff(COLOR_PAIR(EMPTY_PAIR));

  36. 96d69

  37. <            attron(COLOR_PAIR(EMPTY_PAIR));

  38. 98d70

  39. <            attroff(COLOR_PAIR(EMPTY_PAIR));

  40. 106d77

  41. <            attron(COLOR_PAIR(EMPTY_PAIR));

  42. 108d78

  43. <            attroff(COLOR_PAIR(EMPTY_PAIR));

  44. 128,129c98

  45. <     return (((testch & A_CHARTEXT) == GRASS)

  46. <        || ((testch & A_CHARTEXT) == EMPTY));

  47. ---

  48. >     return ((testch == GRASS) || (testch == EMPTY));

  49. 140d108

  50. <     attron(COLOR_PAIR(GRASS_PAIR));

  51. 144d111

  52. <     attroff(COLOR_PAIR(GRASS_PAIR));

  53. 148d114

  54. <     attron(COLOR_PAIR(MOUNTAIN_PAIR));

  55. 152d117

  56. <     attroff(COLOR_PAIR(MOUNTAIN_PAIR));

  57. 154d118

  58. <     attron(COLOR_PAIR(GRASS_PAIR));

  59. 156d119

  60. <     attroff(COLOR_PAIR(GRASS_PAIR));

  61. 160d122

  62. <     attron(COLOR_PAIR(WATER_PAIR));

  63. 164d125

  64. <     attroff(COLOR_PAIR(WATER_PAIR));

開始玩吧--現在有顏色了

程式現在有了更舒服的顏色設計了,更匹配原來的桌游地圖,有綠色的地、藍色的湖和壯觀的灰色山峰。英雄穿著紅色的制服十分奪目。

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

圖 2. 玩家站在左下角

圖 3. 玩家可以在游戲區域移動,比如圍繞湖,通過山的通道到達未知的區域。

通過顏色,你可以更清楚地展示信息。這個例子使用顏色指出可游戲的區域(綠色)相對著不可通過的區域(藍色或者灰色)。我希望你可以使用這個示例游戲作為你自己的程式的一個起點或者參照。這取決於你需要你的程式做什麼,你可以通過 curses 做得更多。

在下一篇文章,我計劃展示 ncurses 庫的其它特性,比如怎樣創建視窗和邊框。同時,如果你對於學習 curses 有興趣,我建議你去讀位於 Linux 文件計劃[3] 的 Pradeep Padala 寫的 NCURSES Programming HOWTO[4]


via: http://www.linuxjournal.com/content/programming-color-ncurses

作者:Jim Hall[6] 譯者:leemeans 校對:wxy

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

赞(0)

分享創造快樂

© 2019 知識星球   网站地图