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 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 12 #include 13 #include 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 3 #include 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