Computer Science II Lab 1
What a Colorful World!
Introduction
============
Welcome back! When last we met, for those of you whom I did meet,
we were studying the wonderful world of computer programming. Now
that you have a little bit of programming under your belt, we can
move into writing more interesting programs, explore exciting
structures, conjure up amazing algorithms, and gain full control of
our UNIX environment.
This first lab is intended to be a gentle prod to help you get
back up to speed with coding. I expect that if you haven't done any
programming in the last couple of months, you could be a little bit
rusty. How you do on this lab will show me where everyone is and
will have a direct impact on how our lectures proceed. (Unless of
course you're a gopher user who is not in my class. In which case,
this lab will still be a good warm-up exercise).
In this lab, we are going to explore ANSI escape sequences, which is
what will let us do some cool things with the terminal. It is
structured to include many of the control structures presented in
the previous class. In the challenge section of the lab, you'll
have an opportunity to show me that you can still craft loops,
branches, and get around in a UNIX system.
Good luck, and enjoy!
Messing with Terminals (Guided)
===============================
Several thousand years ago, mankind communicated primarily by
hitting each other with things. This proved to be inefficient, and
somewhat limiting, and so we came up with grunting at each other.
The grunts took on more and more order, and then language was born.
The trouble is, we had to remember everything. So then someone else
came up with the notion of writing. At first we wrote on caves,
trees, bathroom walls, and so on. Eventually someone invented
paper, followed by the printing press. This remained the state of
affairs for a very long time.
Paper was really a wonderful thing. You could communicate with
people, even over vast distances. Unlike word of mouth, paper would
convey information with high fidelity. However, one problem
remained. Namely, if you are standing at point A, and your
recipient is standing at point B, then you have to transport that
paper to him. If there was more than a few miles between you,
people in between would revert to more primitive forms of
communication and hit your courier with something large and your
message would be lost.
So then, a solution had to be found. The first answer was
telegraphy. Wires would stretch the vast distances, and because
wires were not human no one had any interest in hitting them. A
message was written down, and then an operator would push a button
in a rhythm which would cause the operator at the other end to write
down the message and take it to the nearby receiving party. The
trouble is, this was laborious, and mistakes could be made.
Another attempt to solve the problem came in the form of the
telephone, which allowed people to talk over vast distances using
wires. This reverted back to the original problem, in that it
depended on human memory, and so there seemed to be no way to send
an indelible message without employing someone or getting hit over
the head, and so the problem remained.
And then came a man who had been hit and telegraphed enough to do
something about it. His name was Emile Baudot. He invented a
digital encoding scheme, where each letter would be represented by 5
1's and 0's. This, when combined with the best of late 19th century
technology, allowed a machine to send and reproduce paper
documents. Because the machine did the work, and the work took
place at a distance, this was called "teleprinting". Thus was the
age of modern telecommunications born. It wasn't until the rise of
the Internet that mankind would learn how to hit each other using
the new medium. In honor of Baudot, we still refer to the rate
which we can transmit symbols as a "baud rate".
In time, the teleprinter gave rise to the teletype. By the early
1910's, teletypes were transmitting news stories across the world
as quickly as operators could type them. Stock ticker teleprinters
churned out stock quotes as rapidly as the traders could enter
them. Mankind could know what was going on and how much everything
cost at the drop of a hat, and so more and bloodier wars were fought
during this era than any other in history.
But there remained one problem. It was easy to send a string of
characters, but what about the formatting of a document? The answer
came in the form of special characters. There were characters for
line feeds, characters for carriage returns, and characters to advance
the printer backwards and forwards. The trouble is, with all these
controls, it didn't fit into a 5-bit alphabet, and so 7-bit ASCII
(American Standard Code for Information Interchange) was born, and
became the standard for all teletype devices.
Then came the rise of the computer. Teletypes where used to talk to
computers, and people were using paper at an alarming rate. Your
average programmer consumed somewhere in the neighborhood of 1
deciduous forest per year, and that was just when he wasn't playing
Star Trek! (man trek) So then people moved to a less permanent way
of painting text. Instead of paper, it was displayed on a screen,
and thus the visual terminal was born!
Now visual terminals offered a lot more control than paper did. For
instance, you could erase it. You could color it, you could save it
to disk, or send it to printers to be made permanent. So naturally,
more control codes had to be invented. The trouble is, they didn't
fit into 7 bits. Rather than extend the character set, sequences of
characters were designated for this use.
Eventually, computers became smaller and cheaper, and so the
physical terminal died and was replaced by a terminal emulator. The
terminal you are presently using is one of these, but it still
responds to the same escape sequences as the physical devices of
yesterday. Thus we came the current state of the art of teleprinter
control, the escape sequence!
*phew* Now are are you ready to code?
So why ware they called escape sequences? Well, the reason is they
begin with a character called the "escape" character. The escape
character is a non-printable ASCII character which has decimal value
27 (1B in hex, or 33 in octal). To try your hand at this, enter,
compile and execute the following program:
-- begin ansiExample.cpp --
1 #include <iostream>
2
3 using namespace std;
4
5 int main(void) {
6 cout << "\033[36m" << "Hello World" << endl;
7 }
-- end ansiExample.cpp --
That mess in line 6 is the escape sequence. The pattern is:
\033 (the escape character) followed by [36m.
All ANSI escape sequences begin with \033[. The m at the end means
this is a mode command. 36 is the mode for cyan color.
When you run this program, you'll probably notice that your terminal
remains in cyan mode. (NOTE: You can't run these things inside
Emacs shell. You'll have to leave Emacs or have another window open
in order for this to work). Terminal modes are persistent. That
is, they continue until something tells them otherwise. To clear
this mode, you have to send "\033[0m". The number 0 is the mode
command for "normal mode". go ahead and try that out. Add
"\033[0m" to the end of line 6, so it reads:
cout << "\033[36m" << "Hello World" << "\033[0m" << endl;
You'll see "hello world" in cyan, but your prompt will have returned
to the original color. So now, there a lot of mode commands. Some
of these follow:
\033[#m - the generic mode change pattern
# Meaning
-- -------
0 Normal Mode
1 Bold
4 Underline
5 Blink
7 Reverse Video
30 Set Foreground Color to Black
31 Set Foreground Color to Red
32 Set Foreground Color to Green
33 Set Foreground Color to Yellow
34 Set Foreground Color to Blue
35 Set Foreground Color to Magenta
36 Set Foreground Color to Cyan
37 Set Foreground Color to White
40 Set Background Color to Black
41 Set Background Color to Red
42 Set Background Color to Green
43 Set Background Color to Yellow
44 Set Background Color to Blue
45 Set Background Color to Magenta
46 Set Background Color to Cyan
47 Set Background Color to White
There are also several terminal commands, which cause the terminal
to do things. These follow the pattern of:
\033[*
Some values for * are:
* Meaning
---- -------
2J Clear the screen
2K Clear the current line
s Save the cursor position
u Move the cursor to the saved position
xA Where x is some number. This moves the cursor up x lines
xB Move the cursor down x lines
xC Move the cursor forward x characters
xD Move the cursor backward x characters
y;xH Move the cursor to position (x,y) (1,1 is the upper left
corner).
Go ahead and take a moment to play with these commands. To get full
credit for the guided part of the lab, your ansiExample.cpp needs to
have a few additional commands entered.
Now I know the one you all probably ran to. Blink! And you're
probably saying, "Hey, it's not blinking!" This is because most
modern terminal emulators (mercifully) do not enable that by default
because it is terribly annoying. If you really want to see it, you
will have to turn it on. In putty, you can do this as follows:
1. Hold Control and right click the window.
2. Select "Change Settings"
3. Click on Terminal
4. Check the box "Enable Blinking Text"
Also, you may see differences in the expected color output. This is
because what you are really doing is selecting from a 7 color
pallet. That pallet is decided by the terminal emulator software.
Ok, so now you can make the cursor do anything. You can overwrite
text, you can highlight stuff, you can do anything you want. The
trouble is, it's a pain! Wouldn't it be great if we could do
something like:
cout << red << "Hello world" << normal << endl;
Well it turns out with just a little bit of work, we can do that!
Let's write a header file. My complete file follows. As you type
it in, try to think about what each part of it does. If you
encounter something you have never seen before, make a note of it.
We'll have a discussion about which pieces were a mystery to you at
the next class meeting.
--begin ansi.h--
1 /*
2 * File: ansi.h
3 * Purpose: To have fun messing with people's terminals, and making
4 * lots of colorful and interesting programs.
5 *
6 * This provides a nice little interface to the ANSI escape
7 * sequence set.
8 */
9 #ifndef ANSI_H
10 #define ANSI_H
11 #include <iostream>
12 #include <string>
13 #include <sstream>
14
15 /*
16 * Graphic Mode Modifiers
17 */
18 inline std::ostream& normal(std::ostream &os) {
19 return os << "\033[0m";
20 }
21
22
23 inline std::ostream& bold(std::ostream &os) {
24 return os << "\033[1m";
25 }
26
27
28 inline std::ostream& underline(std::ostream &os) {
29 return os << "\033[4m";
30 }
31
32
33 inline std::ostream& blink(std::ostream &os) {
34 return os << "\033[5m";
35 }
36
37
38 inline std::ostream& reverseVideo(std::ostream &os) {
39 return os << "\033[7m";
40 }
41
42
43
44 /*
45 * Foreground Color Graphic Mode Modifiers
46 */
47 inline std::ostream& black(std::ostream &os) {
48 return os << "\033[30m";
49 }
50
51
52 inline std::ostream& red(std::ostream &os) {
53 return os << "\033[31m";
54 }
55
56
57 inline std::ostream& green(std::ostream &os) {
58 return os << "\033[32m";
59 }
60
61
62 inline std::ostream& yellow(std::ostream &os) {
63 return os << "\033[33m";
64 }
65
66
67 inline std::ostream& blue(std::ostream &os) {
68 return os << "\033[34m";
69 }
70
71
72 inline std::ostream& magenta(std::ostream &os) {
73 return os << "\033[35m";
74 }
75
76
77 inline std::ostream& cyan(std::ostream &os) {
78 return os << "\033[36m";
79 }
80
81
82 inline std::ostream& white(std::ostream &os) {
83 return os << "\033[37m";
84 }
85
86 /*
87 * Foreground Color Graphic Mode Modifiers
88 */
89 inline std::ostream& blackBackground(std::ostream &os) {
90 return os << "\033[40m";
91 }
92
93
94 inline std::ostream& redBackground(std::ostream &os) {
95 return os << "\033[41m";
96 }
97
98
99 inline std::ostream& greenBackground(std::ostream &os) {
100 return os << "\033[42m";
101 }
102
103
104 inline std::ostream& yellowBackground(std::ostream &os) {
105 return os << "\033[43m";
106 }
107
108
109 inline std::ostream& blueBackground(std::ostream &os) {
110 return os << "\033[44m";
111 }
112
113
114 inline std::ostream& magentaBackground(std::ostream &os) {
115 return os << "\033[45m";
116 }
117
118
119 inline std::ostream& cyanBackground(std::ostream &os) {
120 return os << "\033[46m";
121 }
122
123
124 inline std::ostream& whiteBackground(std::ostream &os) {
125 return os << "\033[47m";
126 }
127
128
129 /*
130 * Simple terminal commands
131 */
132 inline std::ostream& clearScreen(std::ostream &os) {
133 return os << "\033[2J";
134 }
135
136
137 inline std::ostream& clearLine(std::ostream &os) {
138 return os << "\033[K";
139 }
140
141
142 inline std::ostream& saveCursor(std::ostream &os) {
143 return os << "\033[s";
144 }
145
146
147 inline std::ostream& restoreCursor(std::ostream &os) {
148 return os << "\033[u";
149 }
150
151
152 /*
153 * This class is used by commands requiring arguments
154 */
155 class ArgSequence {
156 public:
157 ArgSequence(std::string seq) {
158 this->seq = seq;
159 }
160
161 std::ostream& operator()(std::ostream &os) const {
162 return os << seq;
163 }
164
165 private:
166 std::string seq;
167 };
168
169
170 //overload the << operator so ostream can use the sequence!
171 std::ostream& operator<<(std::ostream& os, const ArgSequence& aSeq) {
172 return aSeq(os);
173 }
174
175
176 /*
177 * Commands requiring arguments
178 */
179 ArgSequence cursorUp(int value) {
180 //compute the arg sequence
181 std::ostringstream os;
182 os << "\033[" << value << "A";
183
184 //return the result
185 return ArgSequence(os.str());
186 }
187
188
189 ArgSequence cursorDown(int value) {
190 //compute the arg sequence
191 std::ostringstream os;
192 os << "\033[" << value << "B";
193
194 //return the result
195 return ArgSequence(os.str());
196 }
197
198
199 ArgSequence cursorForward(int value) {
200 //compute the arg sequence
201 std::ostringstream os;
202 os << "\033[" << value << "C";
203
204 //return the result
205 return ArgSequence(os.str());
206 }
207
208
209 ArgSequence cursorBackward(int value) {
210 //compute the arg sequence
211 std::ostringstream os;
212 os << "\033[" << value << "D";
213
214 //return the result
215 return ArgSequence(os.str());
216 }
217
218
219 ArgSequence cursorPosition(int x, int y) {
220 //compute the arg sequence
221 std::ostringstream os;
222 os << "\033[" << y << ";" << x << "H";
223
224 //return the result
225 return ArgSequence(os.str());
226 }
227 #endif
--end ansi.h--
At 227 lines, it's quite the editor full! However, if you look
closely you'll note that very little changes between the functions.
So if there was ever a time to practice copy and paste in your
chosen editor, it's now!
Before I explain exactly what's going on, here's a sample program to
test out our header file. We will use this header file in future
labs, so it is very important that you get it correct now.
--begin termTest.cpp--
1 #include "ansi.h"
2 #include <iostream>
3 #include <iomanip>
4
5 using namespace std;
6
7 int main(int argc, char** argv) {
8 //start on a fresh screen
9 cout << clearScreen << cursorPosition(1,1);
10
11
12 //general modes test
13 cout << normal << "Normal" << endl;
14 cout << underline << "Underline" << normal << endl;
15 cout << bold << "Bold" << normal << endl;
16 cout << blink << "Blink" << normal << endl;
17 cout << reverseVideo << "Reverse" << normal << endl;
18 cout << black << "Black" << endl;
19 cout << red << "Red" << endl;
20 cout << green << "Green" << endl;
21 cout << yellow << "Yellow" << endl;
22 cout << blue << "Blue" << endl;
23 cout << magenta << "Magenta" << endl;
24 cout << cyan << "Cyan" << endl;
25 cout << white << "White" << endl;
26
27 //stack test
28 cout << bold << yellow << magentaBackground << "Hello, blinded world!"
29 << normal << endl;
30
31 //cursor movement test
32 cout << saveCursor << cursorPosition(10, 7) << "Hello";
33 cout << cursorDown(2) << cursorForward(1) << "World";
34 cout << restoreCursor << cursorDown(1) << normal << endl;
35
36 return 0;
37 }
--begin termTest.cpp--
Go ahead and type it all in, and then compile and run it with the
commands:
g++ termTest.cpp -o termTest
./termTest
Now, let's take a look at some of the things that may seem odd
here. First, there is the word "inline" sprinkled all over the
place. You may not have seen that before. Basically what that is
is it tells the compiler to attempt to duplicate the function
instead of calling it. See, those functions are tiny one liners and
so the overhead of invoking the code is greater than the cost of
running the function. Marking them inline gives the optimizer the
hint that it may be better to simply duplicate these functions than
it is to call them. This makes the generated executable slightly
larger, but it runs a little faster.
Another thing of note is that it appears we are printing functions!
We are not. What is happening is that ostream has an overloaded
insertion operator which accepts as its left argument an ostream and
as its right argument a pointer to a function which takes an ostream
as an argument and returns an ostream. It's not as bad as it
sounds, and we'll be seeing more of this in the future.
Note that we have a small class produced here in the .h file. This
is a necessity for providing an ostream modifier which takes an
argument. We overload the () operator on our class to allow it to
be invoked as a function, and then we provide an overloaded
insertion operator to handle the final insertions. Try thinking
through what happens when we call the functions and pay close
attention to what the functions return. If you don't get this just
yet, that's ok. It's the subject of several discussions yet to
come!
So now, we have a nifty little header file which lets us do basic
terminal manipulations. You can now make much more colorful
programs. So let's try your hand at using our creation!
Putting the Terminal to Work (Challenge Lab)
============================================
Ok, so now let's try our hand at using the library. This is just a
quick little lab to get you started and for me to see what everyone
remembers. There are two parts. A written part, and a program
part. We will do the program first.
I had a professor back in the late 90's who always got on to me for
putting exclamation points in my program's error messages. (He said
it made him feel as if the computer were shouting at him!) I
retaliated by making a program which outlined the screen in red
exclamation points on each error. I want you to recreate this.
Here's what your program should do:
- repeatedly ask the user for a number between 1 and 10
- if the user complies, thank them, and ask again.
- on error, outline the terminal screen in ! and display a big
red error message in the center. Something like:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! !!
!! !!
!! !!
!! ERROR!!! !!
!! Can't you follow instructions? !!
!! Now enter a number between 1 and 10!: !!
!! !!
!! !!
!! !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Only large enough to fill the window. For simplicity sake, you can
assume the terminal is 80x24. If you make your program particularly
fancy, you will get extra credit points.
For the written part of the assignment, I want you to review all the
code you have typed in this lab. Write down any features of the
language which feel foreign to you and then try to look up how they
work. Write down any questions this leaves you with. At the very
least, you should write down the topics in this lab which puzzle
you.
Hang on to your code and your written document (which you can type
if you prefer) until next week's meetings!
Closing Remarks
===============
In this lab, we looked at ANSI escape sequences. Because of the
vast array of terminals and terminal emulator software on the
market, this is not a universal set of controls. The ones I have
elected to present here are a small subset of terminal control.
These are the ones supported by most terminals.
In order to alleviate the difficulties in programming serious
terminal applications, several services were created on UNIX
machines. The first was TERMCAP which listed the terminal
capabilities of the presently attached tty port (which stands for
teletype, by the way). Termcap was later supplanted by a library
called curses, and a free version of curses was created called
ncurses. ncurses provides a good abstraction layer for doing more
advanced terminal work. If you are interested in what ncurses can
do, check out the ncurses programming howto at:
http://tldp.org/HOWTO/NCURSES-Programming-HOWTO/index.html