作者 | Jim Hall
譯者 | leemeans ? ? 共計翻譯:6 篇 貢獻時間:51 天
Jim 給他的終端冒險遊戲添加了顏色,演示瞭如何用 curses 操縱顏色。
在我的使用 ncurses 庫進行程式設計的系列文章的第一篇[1]和第二篇[2]中,我已經介紹了一些 curses 函式來在螢幕上作畫、從螢幕上查詢和從鍵盤讀取字元。為了搞清楚這些函式,我使用 curses 來利用簡單字元繪製遊戲地圖和玩家角色,建立了一個簡單的冒險遊戲。在這篇緊接著的文章裡,我展示瞭如何為你的 curses 程式新增顏色。
在螢幕上繪圖一切都挺好的,但是如果只有黑底白字的文字,你的程式可能看起來很無趣。顏色可以幫助傳遞更多的資訊。舉個例子,如果你的程式需要報告執行成功或者執行失敗時。在這樣的情況下你可以使用綠色或者紅色來幫助強調輸出。或者,你只是簡單地想要“潮藝”一下給你的程式來讓它看起來更美觀。
在這篇文章中,我用一個簡單的例子來展示透過 curses 函式進行顏色操作。在我先前的文章中,我寫了一個可以讓你在一個粗糙繪製的地圖上移動玩家角色的初級冒險類遊戲。但是那裡面的地圖完全是白色和黑色的文字,透過形狀來表明是水(~
)或者山(^
)。所以,讓我們將遊戲更新到使用顏色的版本吧。
顏色要素
在你可以使用顏色之前,你的程式需要知道它是否可以依靠終端正確地顯示顏色。在現代作業系統上,此處應該永遠為true。但是在經典的計算機上,一些終端是單色的,例如古老的 VT52 和 VT100 終端,一般它們提供黑底白色或者黑底綠色的文字。
可以使用 has_colors()
函式查詢終端的顏色功能。這個函式將會在終端可以顯示顏色的時候傳回 true
,否則將會傳回 false
。這個函式一般用於 if
塊的開頭,就像這樣:
if (has_colors() == FALSE) {
endwin();
printf("Your terminal does not support color\n");
exit(1);
}
在知道終端可以顯示顏色之後,你可以使用 start_color()
函式來設定 curses 使用顏色。現在是時候定義程式將要使用的顏色了。
在 curses 中,你應該按對定義顏色:一個前景色放在一個背景色上。這樣允許 curses 一次性設定兩個顏色屬性,這也是一般你想要使用的方式。透過 init_pair()
函式可以定義一個前景色和背景色並關聯到索引數字來設定顏色對。大致語法如下:
init_pair(index, foreground, background);
控制檯支援八種基礎的顏色:黑色、紅色、綠色、黃色、藍色、品紅色、青色和白色。這些顏色透過下麵的名稱為你定義好了:
COLOR_BLACK
COLOR_RED
COLOR_GREEN
COLOR_YELLOW
COLOR_BLUE
COLOR_MAGENTA
COLOR_CYAN
COLOR_WHITE
應用顏色
在我的冒險遊戲中,我想要讓草地呈現綠色而玩家的足跡變成不易察覺的綠底黃色點跡。水應該是藍色,那些表示波浪的 ~
符號應該是近似青色的。我想讓山(^
)是灰色的,但是我可以用白底黑色文字做一個可用的折中方案。(LCTT 譯註:意為終端預設的顏色沒有灰色,使用白底黑色文字做一個折中方案)為了讓玩家的角色更易見,我想要使用一個刺目的品紅底紅色設計。我可以像這樣定義這些顏色對:
start_color();
init_pair(1, COLOR_YELLOW, COLOR_GREEN);
init_pair(2, COLOR_CYAN, COLOR_BLUE);
init_pair(3, COLOR_BLACK, COLOR_WHITE);
init_pair(4, COLOR_RED, COLOR_MAGENTA);
為了讓顏色對更容易記憶,我的程式中定義了一些符號常量:
#define GRASS_PAIR 1
#define EMPTY_PAIR 1
#define WATER_PAIR 2
#define MOUNTAIN_PAIR 3
#define PLAYER_PAIR 4
有了這些常量,我的顏色定義就變成了:
start_color();
init_pair(GRASS_PAIR, COLOR_YELLOW, COLOR_GREEN);
init_pair(WATER_PAIR, COLOR_CYAN, COLOR_BLUE);
init_pair(MOUNTAIN_PAIR, COLOR_BLACK, COLOR_WHITE);
init_pair(PLAYER_PAIR, COLOR_RED, COLOR_MAGENTA);
在任何時候你想要使用顏色顯示文字,你只需要告訴 curses 設定哪種顏色屬性。為了更好的程式設計實踐,你同樣應該在你完成了顏色使用的時候告訴 curses 取消顏色組合。為了設定顏色,應該在呼叫像 mvaddch()
這樣的函式之前使用attron()
,然後透過 attroff()
關閉顏色屬性。例如,在我繪製玩家角色的時候,我應該這樣做:
attron(COLOR_PAIR(PLAYER_PAIR));
mvaddch(y, x, PLAYER);
attroff(COLOR_PAIR(PLAYER_PAIR));
記住將顏色應用到你的程式對你如何查詢螢幕有一些微妙的影響。一般來講,由 mvinch()
函式傳回的值是沒有帶顏色屬性的型別 chtype
,這個值基本上是一個整型值,也可以當作整型值來用。但是,由於使用顏色添加了額外的屬性到螢幕上的字元上,所以 chtype
按照擴充套件的位樣式攜帶了額外的顏色資訊。一旦你使用 mvinch()
,傳回值將會包含這些額外的顏色值。為了只提取文字值,例如在 is_move_okay()
函式中,你需要和 A_CHARTEXT
做 &
位運算:
int is_move_okay(int y, int x)
{
int testch;
/* return true if the space is okay to move into */
testch = mvinch(y, x);
return (((testch & A_CHARTEXT) == GRASS)
|| ((testch & A_CHARTEXT) == EMPTY));
}
透過這些修改,我可以用顏色更新這個冒險遊戲:
/* quest.c */
#include <curses.h>
#include <stdlib.h>
#define GRASS ' '
#define EMPTY '.'
#define WATER '~'
#define MOUNTAIN '^'
#define PLAYER '*'
#define GRASS_PAIR 1
#define EMPTY_PAIR 1
#define WATER_PAIR 2
#define MOUNTAIN_PAIR 3
#define PLAYER_PAIR 4
int is_move_okay(int y, int x);
void draw_map(void);
int main(void)
{
int y, x;
int ch;
/* 初始化curses */
initscr();
keypad(stdscr, TRUE);
cbreak();
noecho();
/* 初始化顏色 */
if (has_colors() == FALSE) {
endwin();
printf("Your terminal does not support color\n");
exit(1);
}
start_color();
init_pair(GRASS_PAIR, COLOR_YELLOW, COLOR_GREEN);
init_pair(WATER_PAIR, COLOR_CYAN, COLOR_BLUE);
init_pair(MOUNTAIN_PAIR, COLOR_BLACK, COLOR_WHITE);
init_pair(PLAYER_PAIR, COLOR_RED, COLOR_MAGENTA);
clear();
/* 初始化探索地圖 */
draw_map();
/* 在左下角建立新角色 */
y = LINES - 1;
x = 0;
do {
/* 預設情況下,你獲得了一個閃爍的游標--用來指明玩家 * */
attron(COLOR_PAIR(PLAYER_PAIR));
mvaddch(y, x, PLAYER);
attroff(COLOR_PAIR(PLAYER_PAIR));
move(y, x);
refresh();
ch = getch();
/* 測試輸入鍵值並獲取方向 */
switch (ch) {
case KEY_UP:
case 'w':
case 'W':
if ((y > 0) && is_move_okay(y - 1, x)) {
attron(COLOR_PAIR(EMPTY_PAIR));
mvaddch(y, x, EMPTY);
attroff(COLOR_PAIR(EMPTY_PAIR));
y = y - 1;
}
break;
case KEY_DOWN:
case 's':
case 'S':
if ((y < LINES - 1) && is_move_okay(y + 1, x)) {
attron(COLOR_PAIR(EMPTY_PAIR));
mvaddch(y, x, EMPTY);
attroff(COLOR_PAIR(EMPTY_PAIR));
y = y + 1;
}
break;
case KEY_LEFT:
case 'a':
case 'A':
if ((x > 0) && is_move_okay(y, x - 1)) {
attron(COLOR_PAIR(EMPTY_PAIR));
mvaddch(y, x, EMPTY);
attroff(COLOR_PAIR(EMPTY_PAIR));
x = x - 1;
}
break;
case KEY_RIGHT:
case 'd':
case 'D':
if ((x < COLS - 1) && is_move_okay(y, x + 1)) {
attron(COLOR_PAIR(EMPTY_PAIR));
mvaddch(y, x, EMPTY);
attroff(COLOR_PAIR(EMPTY_PAIR));
x = x + 1;
}
break;
}
}
while ((ch != 'q') && (ch != 'Q'));
endwin();
exit(0);
}
int is_move_okay(int y, int x)
{
int testch;
/* 當空白處可以進入的時候傳回true */
testch = mvinch(y, x);
return (((testch & A_CHARTEXT) == GRASS)
|| ((testch & A_CHARTEXT) == EMPTY));
}
void draw_map(void)
{
int y, x;
/* 繪製探索地圖 */
/* 背景 */
attron(COLOR_PAIR(GRASS_PAIR));
for (y = 0; y < LINES; y++) {
mvhline(y, 0, GRASS, COLS);
}
attroff(COLOR_PAIR(GRASS_PAIR));
/* 山峰和山路 */
attron(COLOR_PAIR(MOUNTAIN_PAIR));
for (x = COLS / 2; x < COLS * 3 / 4; x++) {
mvvline(0, x, MOUNTAIN, LINES);
}
attroff(COLOR_PAIR(MOUNTAIN_PAIR));
attron(COLOR_PAIR(GRASS_PAIR));
mvhline(LINES / 4, 0, GRASS, COLS);
attroff(COLOR_PAIR(GRASS_PAIR));
/* 湖 */
attron(COLOR_PAIR(WATER_PAIR));
for (y = 1; y < LINES / 2; y++) {
mvhline(y, 1, WATER, COLS / 3);
}
attroff(COLOR_PAIR(WATER_PAIR));
}
你可能不能認出所有為了在冒險遊戲裡面支援顏色需要的修改,除非你目光敏銳。diff
工具展示了所有為了支援顏色而新增的函式或者修改的程式碼:
$ diff quest-color/quest.c quest/quest.c
12,17d11
< #define GRASS_PAIR 1
< #define EMPTY_PAIR 1
< #define WATER_PAIR 2
< #define MOUNTAIN_PAIR 3
< #define PLAYER_PAIR 4
<
33,46d26
< /* initialize colors */
<
< if (has_colors() == FALSE) {
< endwin();
< printf("Your terminal does not support color\n");
< exit(1);
< }
<
< start_color();
< init_pair(GRASS_PAIR, COLOR_YELLOW, COLOR_GREEN);
< init_pair(WATER_PAIR, COLOR_CYAN, COLOR_BLUE);
< init_pair(MOUNTAIN_PAIR, COLOR_BLACK, COLOR_WHITE);
< init_pair(PLAYER_PAIR, COLOR_RED, COLOR_MAGENTA);
<
61d40
< attron(COLOR_PAIR(PLAYER_PAIR));
63d41
< attroff(COLOR_PAIR(PLAYER_PAIR));
76d53
< attron(COLOR_PAIR(EMPTY_PAIR));
78d54
< attroff(COLOR_PAIR(EMPTY_PAIR));
86d61
< attron(COLOR_PAIR(EMPTY_PAIR));
88d62
< attroff(COLOR_PAIR(EMPTY_PAIR));
96d69
< attron(COLOR_PAIR(EMPTY_PAIR));
98d70
< attroff(COLOR_PAIR(EMPTY_PAIR));
106d77
< attron(COLOR_PAIR(EMPTY_PAIR));
108d78
< attroff(COLOR_PAIR(EMPTY_PAIR));
128,129c98
< return (((testch & A_CHARTEXT) == GRASS)
< || ((testch & A_CHARTEXT) == EMPTY));
---
> return ((testch == GRASS) || (testch == EMPTY));
140d108
< attron(COLOR_PAIR(GRASS_PAIR));
144d111
< attroff(COLOR_PAIR(GRASS_PAIR));
148d114
< attron(COLOR_PAIR(MOUNTAIN_PAIR));
152d117
< attroff(COLOR_PAIR(MOUNTAIN_PAIR));
154d118
< attron(COLOR_PAIR(GRASS_PAIR));
156d119
< attroff(COLOR_PAIR(GRASS_PAIR));
160d122
< attron(COLOR_PAIR(WATER_PAIR));
164d125
< 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中國 榮譽推出