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 --------------------+------@--------------> 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 --------------------@--------------------> 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 ------------@-------+--------------------> 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 }