This lab is about getting some practice writing programs that use structs. We'll try to have some fun in the process, though. Folks have asked to do something game-like, and also to work on something with partners. So, we'll try to do that as well. This lab will be done with a partner, the idea being you work together designing, programming, debuggin, etc. BTW: you must take turns at the keyboard. We're going to keep everything in the terminal, since dealing with graphics libraries would take us a bit too far offtopic for one lab period. However, we will use a libarary called "curses" (actually "ncurses") to allow us to write to arbitrary positions in the terminal window and to read keystrokes in a "nonblocking" manner.

Part 1: ncurses basics (lab11v1.cpp)

The ncurses library views your terminal window as a big 2D table of single-character positions. So think of coordinates as (row,col) rather than (x,y). Thinking of it this way, (0,0) is the upper left corner of the terminal window. To use ncurses, you need to do a #include <curses.h> in your .cpp files, and you need to compile with the -l ncurses compiler flag, like g++ lab11v1.cpp -l ncurses.

Create and compile lab11v1.cpp under your account. This source code demonstrates some simple aspects of ncurses. Here's a few comments:

Initializing Ncurses Exiting Ncurses Drawing something
  WINDOW *W = initscr();  
  // Loop forever until user enters 'q'
  char c;
  while((c = wgetch(W)) && c != 'q');

  // Clean up after ncurses
This code should appear at the point in your main when you're ready to go into "ncurses mode". When this is executed, the screen goes blank and you are ready to write to arbitrary locations in the terminal window. The variable W is important. It's a pointer to a struct object that the ncurses library defines. You'll need to pass W to many ncurses functions. Ncurses (at least as we've initialized it) changes the model for how we read data. You fetch the next character with a call to wgetch(W). The funny thing is that this is "non-blocking I/O", which means that you don't hang around and wait for input to come. If you call wgetch(W) and nobody presses a key, wgetch simply returns immediate with an error code. Contrast this with cin >> c or cin.get(). Thus, this loop just spins waiting for wgetch(W) to report that the user has pressed q. The endwin() function call is required in order for ncurses to clean up after itself and restore the terminal window. The model for writing to the screen in ncurses is this:
  1. move the cursor to a position the terminal window grid
    int wmove(Window* W, int row, int col);
  2. write a character at the current cursor position (which has the side effect of incrementing the cursor's column coordinate)
    int waddch(Window* W, char c);
  3. whatever wmove's and waddch's you've done don't actually show up on the screen until you call the wrefresh function.
    int wrefresh(WINDOW *win);
Note the calls to usleep(tus) (defined in unistd.h), which is like the sleep(s) function we've used before, except with finer resolution. The argument is the tiume to sleep given in micro-seconds.

Part 2: coming and going (lab11v2.cpp)

Write a program that reads in input from the user like this
a (10,15)
x (14,29)
k (5,5)
q (18,37)
The flashing cursor can be pretty annoying. If you want to get rid of it (make the cursor invisible) use the curs_set function like this:
Note that this call must come after the call to initscr().
before going into ncurses mode and then, once in ncurses mode, draws these to the screen one at a time with pauses in between, and then erases them from the screen one at a time (erase means writing a ' ' in the appropriate position. Note: I fully expect that this will be done with a good design that makes use of classes --- probably more than one --- and functions, and that the solution would work for a wide variety of inputs, not only for the input shown here.

Part 3: You got to move it, move it (lab11v3.cpp)

Now we will animate things, i.e. each character will move on the screen. To start things off, let's have a single character, and let's have it start at row 15, column 30. The character will have a direction associated with it — north, south, east or west. Note: design this program keeping in mind that ultimately there will be many characters moving simulatneously. Your program will consist of a loop:
do {
  // draw character
  // sleep for 100,000 usecs
  // use wgetch to see if the user has pressed 'q'
  // move character one step in its current direction
}while('q' has not been pressed);
Of course at this point your character moves off the screen and things are not that interesting. So, to add a little drama let's say that at each step, right before actually moving, the character has a 1-in-10 chance of randomly turning (i.e. changing its current direction) before moving, with a 50% chance of turning 90 left and a 50% chance of turning 90 degrees to the right.

Part 4: Momma should I build a wall? (lab11v4.cpp)

Though your single animated character makes random turns, eventually it's going to make its way off the screen. It'd be much cooler if the screen acted like a walled-in space that the character cannot escape. So let's do that. The only real difficulty here is that you need to know the height and width of the terminal window (in characters). Ncurses has a functin for that:
int getmaxyx(Window *W, int &row, int & col);
So the call getmaxyx(W,height,width) sets height to the number of rows on the screen, and width to the number of columns. Note, then, that the valid positions you can wmove(W,row,col) to are when 0 ≤ row < height and 0 ≤ col < width.

Now that you can get the height and width of the terminal window, when you come to a "move" step you must check to see whether the move in question would take you off the screen. If it does, then instead of moving change direction and leave it at that. You can either randomly change direction, or "bounce" by simply reversing direction (north goes to south, east to west, etc). If you do this, your character will be walled in and will never leave the terminal window.

Part 5: Yes it's that easy (lab11v5.cpp)

Now, instead of one randomly moving character, make it 20 (or 40 or 1000 or whatever you want!). If you've done things right, you should be able to do this trivially. If you haven't made good use of structs and functions, it might be painful!

Part 6: Imperio (lab11v6.cpp)

Now, let's make one character that's different. This will be a character that the user controls. To keep it simple, we'll use the a,s,d,w keys. Immediately after the usleep(), and prior to actually moving anyone, do a
char kb = wgetch(W);
... and if kb is an 'a', change the one character's direction to west, a 'd', change it to east, an 's' change it to south, and a 'w', change it to north. Note that this one character won't be subject to the random direction changes, and we won't worry about the walls for him ... it'll be up to the user to keep the player on the board.

Part 7: And in the end ... (lab11v7.cpp)

One last thing, if the user-controlled character collides with one of the other characters, he dies and the game is over. When that happens, you might want to pause for a second or two before exiting ncurses. Oh, it'd also be a nice touch to report the number of steps (or seconds) the user stayed alive. You could add other fun things ... the tempo could increase, or more guys could enter the game or ... whatever else you can think of.