C語言與VT100控制碼程式設計
阿新 • • 發佈:2018-12-25
C語言與VT100控制碼程式設計 宣告: 1. 如果您打算閱讀本文,希望您已經瞭解過C語言的基本語法,本文不對C語言的基本語法進行說明,因為那些東西幾乎唾手可得; 2. 本文在vim中編輯,請儘量是用vim進行閱讀,因為有不對齊的現象; 3. 本人強烈建議您先編譯,執行本文最後提供的sinDemo原始碼,再看本文的正文,因為您看了執行效果,您就知道本人為什麼要寫這篇文章; \\\\\\\\\\\\--*目錄*--/////////// | 一. 需求背景 | | 二. VT100控制碼是什麼 | | 三. sin函式動態圖sinDemo示例 | \\\\\\\\\\\\\\\\\//////////////// 一. 需求背景 以前很長時間裡,開啟Ubuntu的終端,使用會產生動態效果的shell命令(如top)讓我覺得不可思議,於是很多時候也希望自己的程式也能那樣動起來,但由於自己的知識面的原因,不知道的東西太多: 1. 如何改變字元輸出的位置? 2. 如何改變前景色,背景色? 3. 最重要的是,查資料時用什麼關鍵字查也不知道? 當然,也許有人會說,去問人,可問題是我也不知道怎麼去描述我的需求,更不知道誰知道這玩意.那時候覺這是一件挺難的事,於是我開始憑著感覺是用不同的關鍵字百度,最後是通過python找到tput,然後通過tput找到VT100碼,因為時間已經過去挺久了,具體的經過也基本上忘記了. :) 在使用了一段時間的VT100碼以後,我發現,我們學C語言的時候,就應該學會配合使用VT100碼,因為這樣你可以在黑白的終端世界裡做出很酷的東西,比如俄羅斯方塊,貪吃蛇等等,還有其他的一些經典的動畫效果,個人覺得早期開發遊戲的那些人,也許就是這麼幹的,本文的Demo提供了一個生成sin函式的動畫效果. 本人也使用VT100碼實現了在終端繪製方框,填充方框,使用不同的字元繪製直線,等等內容,並且把這些做成了一個庫供自己使用.其實shell命令裡的tput也是這麼做,Ncurses的底層也是這麼幹,只不過貌似他們做得比我好,考慮得更周到. :) 二. VT100控制碼是什麼 VT100是一個終端型別定義,VT100控制碼是用來在終端擴充套件顯示的程式碼.比如在終端上任意座標用不同的顏色顯示字元.所有的控制符是'\033'(033是八進位制的數,十進位制對應的是27,即ESC的ASCII碼,如果需要檢視,可以使用shell命令:man ascii)開頭.用輸出字元語句來輸出,在C程式中用printf來輸出VT100的控制字元. 1. VT100 控制碼歸類如下。 \033[0m 取消之前所有屬性 \033[1m 設定高亮度 \033[4m 下劃線 \033[5m 閃爍 \033[7m 反顯 \033[8m 消隱 \033[30m -- \033[37m 設定前景色 |------------+ \033[40m -- \033[47m 設定背景色 |------------+ \033[nA 游標上移 n 行 | \033[nB 游標下移 n 行 | \033[nC 游標右移 n 行 | \033[nD 游標左移 n 行 | \033[y;xH 設定游標位置 | \033[2J 清屏 | \033[K 清除從游標到行尾的內容 | \033[s 儲存游標位置 | \033[u 恢復游標位置 | \033[?25l 隱藏游標 | \033[?25h 顯示游標 | V +------<----------------------------<--+ | V 2. VT100 的顏色輸出分為,前景色和背景色可以分別輸出,如果不需要之前所有的設定可以用\033[0m取消。 1. 字背景顏色範圍:40----49 40:黑 41:深紅 42:綠 43:黃色 44:藍色 45:紫色 46:深綠 47:白色 2. 字前景顏色範圍:30----39 30:黑 31:紅 32:綠 33:黃 34:藍色 35:紫色 36:深綠 37:白色 3. 輸出一個字串( something here )有前景色和背景色程式碼如下: printf("\033[41;36m something here \033[0m"); 三. sin函式動態圖sinDemo示例 1. 示例終端輸出一屏影象: | SinDemo | 0123456789012345678901234567890123456789 0000 [email protected]> Y 0001 |----------* 0002 |---------------* 0003 |------------------* 0004 |------------------* 0005 |------------------* 0006 |---------------* 0007 |----------* 0008 |-----* 0009 * 0010 *-----| 0011 *----------| 0012 *---------------| 0013 *------------------| 0014 *------------------| 0015 *------------------| 0016 *---------------| 0017 *----------| 0018 *-----| 0019 * 0020 V X 2. sinDemo原始碼:
1 /****************************************************************** 2 * sinDemo 3 * 4 * 1. 本Demo主要目標是為了實現在終端下實現sin函式的動態效果; 5 * 2. 本Demo之前是使用shell tput和C語言實現的,這次將其改為 6 * C語言+VT100控制碼的形式; 7 * 8 * 2015-3-27 陰 深圳 曾劍峰 9 * 10 *****************************************************************/ 11 #include <stdio.h> 12 #include <math.h> 13 #include <unistd.h> 14 15 /** 16 * 定義圓周率的值 17 */ 18 #define PI 3.14 19 /** 20 * 本Demo中假設sin曲線週期為20,幅值也是20,幅值分正負幅值, 21 * 所以後面的很多地方有SIN_AMPLITUDE*2,也就是Y軸方向上的值. 22 */ 23 #define SIN_AMPLITUDE 20 24 /** 25 * 定義每次重新整理圖形時間間隔為100ms 26 */ 27 #define DELAY_TIME 100000 28 /** 29 * 定義圓的一週角度為360度 30 */ 31 #define TRIANGLE 360.0 32 /** 33 * 輸出的時候,數字行放在哪一行,也就是輸出圖形中的這行數字: 34 * 0123456789012345678901234567890123456789 35 * 本Demo中把上面這行數字放在介面的第3行 36 */ 37 #define Y_NUMBER_BEGIN_LINE 3 38 /** 39 * 在本Demo中,圖形就在上面數字行的下一行,也就是輸出圖形中如下面的內容: 40 * 0000 [email protected]> Y 41 * 0001 |-----* 42 * 0002 |----------* 43 * 0003 |---------------* 44 * 0004 |------------------* 45 * 0005 |------------------* 46 * 0006 |------------------* 47 * 0007 |---------------* 48 * 0008 |----------* 49 * 0009 |-----* 50 * 0010 * 51 * 0011 *-----| 52 * 0012 *----------| 53 * 0013 *---------------| 54 * 0014 *------------------| 55 * 0015 *------------------| 56 * 0016 *------------------| 57 * 0017 *---------------| 58 * 0018 *----------| 59 * 0019 *-----| 60 * 0020 V X 61 */ 62 #define SIN_GRAPH_BEGIN_LINE (Y_NUMBER_BEGIN_LINE+1) 63 64 int main(int argc, char* argv[]){ 65 66 /** 67 * 區域性變數說明: 68 * 1. i : 主要用於迴圈計算; 69 * 2. lineNumber : 用於儲存行號; 70 * 3. offsetCenter : 用於儲存sin曲線上的點的相對於中心軸的偏移; 71 * 4. nextInitAngle : 儲存下一屏要輸出圖形的初始角度制角度(如30度); 72 * 5. currentInitAngle : 當前一屏要輸出的圖形的初始角度制角度(如30度); 73 * 6. currentInitradian : 當前一屏要輸出的圖形的初始弧度制弧度(如PI/6) 74 * 根據currentInitAngle換算而來,因為sin函式需要 75 * 角度制進行求值; 76 * 77 */ 78 int i = 0; 79 int lineNumber = 0; 80 int offsetCenter = 0; 81 int nextInitAngle = 0; 82 double currentInitAngle = 0; 83 double currentInitradian = 0; 84 85 //軟體開始執行,清一次屏,保證螢幕上沒有無關內容 86 printf("\033[2J"); 87 88 //輸出標題,因為這個軟體名字叫: SinDemo 89 printf("\033[1;1H | SinDemo |\t"); 90 91 /** 92 * 這裡主要是完成那一行重複的0-9,SIN_AMPLITUDE*2是因為sin曲線的 93 * 最高點和最低點是2倍的幅值 94 */ 95 printf("\033[%d;1H\t", Y_NUMBER_BEGIN_LINE); 96 for (i = 0; i < SIN_AMPLITUDE*2; i++) 97 printf("%d", i%10); 98 printf("\n"); 99 100 /** 101 * while迴圈主要完成內容: 102 * 1. 每次迴圈對區域性變數重新初始化; 103 * 2. 將下一屏圖形的初始角度賦值給當前的圖形初始角; 104 * 3. 將下一屏圖形的初始角度加上間隔角度(TRIANGLE/SIN_AMPLITUDE), 105 * TRIANGLE/SIN_AMPLITUDE在本Demo中是360/20=18度,就相當於X軸 106 * 每格代表18度 107 * 2. 調整游標到固定的位置; 108 * 3. 重新繪製整屏圖形; 109 */ 110 while(1){ 111 112 //重新初始化區域性變數,因為每一屏圖形都像一個新的開始 113 i = 0; 114 offsetCenter = 0; 115 lineNumber = 0; 116 currentInitradian = 0; 117 118 //從nextInitAngle中獲取當前的初始化角度 119 currentInitAngle = nextInitAngle; 120 121 //為下一次迴圈提供下一次的初始化角度 122 nextInitAngle += TRIANGLE/SIN_AMPLITUDE; 123 124 //將游標移動到開始繪圖的位置去 125 printf("\033[%d;1H", SIN_GRAPH_BEGIN_LINE); 126 127 /** 128 * 根據不同的情況繪製圖形, 每一次迴圈,就是繪製了圖形中的一行 129 */ 130 while(1){ 131 //判斷是不是最後一行,lineNumber起始行是從0開始 132 if(lineNumber == SIN_AMPLITUDE){ 133 //列印最後一行前面的數字行號 134 printf("\033[%d;1H%04d\t", lineNumber+SIN_GRAPH_BEGIN_LINE, lineNumber); 135 for (i = 0; i < SIN_AMPLITUDE*2; i++) 136 /** 137 * 判斷是否到達中間位置,因為中間位置要放V的箭頭,同時在旁邊輸出一個X, 138 * 代表這是X軸方向. 139 */ 140 i == SIN_AMPLITUDE ? printf("V X") : printf(" "); 141 break; 142 } 143 144 145 /** 146 * 對currentInitAngle角度進行修整,比如370度和10度是對應相同的sin值 147 * 其實這一步可以不用,但是這裡保留了,後面是將currentInitAngle角度制的值 148 * 換算成對應的弧度制的值,便於sin求值. 149 */ 150 currentInitAngle = ((int)currentInitAngle)%((int)TRIANGLE); 151 currentInitradian = currentInitAngle/(TRIANGLE/2)*PI; 152 153 /** 154 * 算出當前次currentInitradian對應的sin值,並乘以幅值SIN_AMPLITUDE,獲取sin曲線 155 * 在Y軸上相對於中心軸的偏移offsetCenter,offsetCenter可能是正值,也可能是負值, 156 * 因為中心軸在中間. 157 */ 158 offsetCenter = (int)(sin(currentInitradian)*SIN_AMPLITUDE); 159 160 /** 161 * 在正確的地方輸出正確的行號 :) 162 */ 163 printf("\033[%d;1H%04d", lineNumber+SIN_GRAPH_BEGIN_LINE, lineNumber); 164 165 //用一個製表符,給出行號與圖形的空間距離 166 printf("\t"); 167 168 /** 169 * 第一行,和其他的行不一樣,有區別,輸出結果如下: 170 * 0000 [email protected]+--------------------> Y 171 */ 172 if(lineNumber == 0){ 173 for (i = 0; i < SIN_AMPLITUDE*2; i++){ 174 /** 175 * 判斷當前輸出的字元位置是否是X,Y軸交叉的位置,如果是就輸出'+', 176 * 不是就輸出'-' 177 */ 178 i == SIN_AMPLITUDE ? printf("+") : printf("-"); 179 /** 180 * 判斷當前輸出的字元位置是否是sin曲線上的點對應的位置, 181 * 如果是就輸出'@' 182 */ 183 if(i == offsetCenter+SIN_AMPLITUDE) 184 printf("@"); 185 } 186 //代表這個方向是Y軸 187 printf("-> Y\n"); 188 } else { 189 for (i = 0; i < SIN_AMPLITUDE*2; i++){ 190 //判斷當前輸出的字元位置是否是sin曲線上的點對應的位置,如果是就輸出'*' 191 if(i == (offsetCenter+SIN_AMPLITUDE)){ 192 printf("*"); 193 //判斷當前輸出的字元位置是否是X軸上對應的位置,如果是就輸出'|' 194 }else if(i == SIN_AMPLITUDE){ 195 printf("|"); 196 }else{ 197 /** 198 * 這裡主要是要處理一行裡面除了畫'*'、'|'、之外的'-'、' ' 199 * 其中的SIN_AMPLITUDE到SIN_AMPLITUDE+offsetCenter正好就是需要輸出'-'的地方 200 * 其他的地方輸出' ' 201 */ 202 (((i > SIN_AMPLITUDE) && (i < SIN_AMPLITUDE+offsetCenter)) || \ 203 ((i < SIN_AMPLITUDE) && (i > SIN_AMPLITUDE+offsetCenter))) \ 204 ? printf("-") : printf(" "); 205 } 206 //行尾,輸出換行符 207 if(i == (SIN_AMPLITUDE*2-1)) 208 printf("\n"); 209 } 210 } 211 212 /** 213 * 一行輸出完成,為下一行輸出作準備,下一行比上一行在角度上多加TRIANGLE/SIN_AMPLITUDE, 214 * 在本Demo中相當於360/20=18,也就是加18度. 215 */ 216 currentInitAngle += TRIANGLE/SIN_AMPLITUDE; 217 218 //行號加1 219 lineNumber++; 220 } 221 /** 222 * 一屏影象輸出完畢,最後輸出一個換行符,並且延時一段時間再開始繪製下一屏圖形 223 */ 224 printf("\n"); 225 usleep(DELAY_TIME); 226 } 227 228 return 0; 229 }