1. 程式人生 > >C語言與VT100控制碼程式設計

C語言與VT100控制碼程式設計


                            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 }