~/bin/submit -c=IC210 -p=Lab11 lab11v2.cpp lab11v3.cpp lab11v4.cpp lab11v5.cpp lab11v6.cpp lab11v7.cppIf you only finished up through Step 2, submit as:
~/bin/submit -c=IC210 -p=Lab11 lab11v2.cpp~/bin/submit -c=IC210 -p=Lab11 lab11v2.cpp lab11v3.cpp~/bin/submit -c=IC210 -p=Lab11 lab11v2.cpp lab11v3.cpp lab11v4.cpp~/bin/submit -c=IC210 -p=Lab11 lab11v2.cpp lab11v3.cpp lab11v4.cpp lab11v5.cpp~/bin/submit -c=IC210 -p=Lab11 lab11v2.cpp lab11v3.cpp lab11v4.cpp lab11v5.cpp lab11v6.cpp
#include <curses.h> in your
.cpp files, and you need to compile with the -l ncurses
compiler flag, like:
g++ lab11v1.cpp -o lab11v1 -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(); cbreak(); noecho(); nodelay(W,true); curs_set(0); |
// Loop forever until user enters 'q' char c; while((c = wgetch(W)) && c != 'q'); // Clean up after ncurses endwin(); |
wmove(W,0,0); waddch(W,'1'); wrefresh(W); usleep(800000); |
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 immediately 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:
|
usleep(time_us) (defined
in unistd.h), which is like the sleep(s) function
we've used before, except with finer resolution. The argument
is the time to sleep given in micro-seconds.
"Printed a 2!" just after writing the "2". We'd
just add the line
cout << "Printed a 2!" << endl;
... immediately after the waddch(W,'2');.
Compile and run that. What you will find is that this message
pops up in the middle of our ncurses screen and messes things
up. The "3" is no longer printed immediately to the right of the
"2". It's a real problem when your debugging messages cause new
bugs! So how can we write out debugging messages in an ncurses
program?
cerr) instead of standard out
(cout) for the debugging message.
This doesn't solve the problem immediately, but it helps.
To see how ...
cerr << "Printed a 2!" << endl;
... and run your program like this:
./lab11v1 2> err
What this does is run your program as usual except that any
output written to standard error (cerr) gets "redirected" to
the file err, which is created or overwritten
each time this is run. Let your program run to completion,
then give the command cat err
to see what is written.
tail -f errGo back to your original terminal and type something like this:
echo "whoooooooo" > errYou should see the whoooooo pop up in the other terminal. Spooky, eh?
Now run lab11v1 in the original terminal like this:
./lab11v1 2> err... and watch as your ncurses window is pure and unblighted by debugging messages, but the message does pop up at the appropriate time in the second window. What's going on here is this: the
2> err is telling the shell to
redirect standard error (cerr in our program) output to the
file err. Meanwhile, tail -f is constantly
monitoring the file err, printing out any new lines that get
written to the file. You can keep rerunning, compiling and
debugging your program without ever having to rerun tail -f,
which is nice.
Write a program that reads in input from the user like this
4 a (10,15) x (14,29) k (5,5) x (18,37)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.
Just pause for a moment after erasing the last one and before exiting -
no need to wait for the user to enter a 'q' like before.
Important! I fully expect that this will be done with a good design that makes use of structs --- 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.
Now we will animate things, i.e. each character will move on the
screen.
To start things off, let's have a single character, drawn as an 'X',
and let's
have the little X start at row 15, column 30. The character will have a
direction associated with it — north, south, east or west.
Start your X moving to the right (i.e., east).
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
// redraw and sleep for 80,000 usecs. This corresponds to flipping the next "frame" in an animation.
wrefresh(W);
usleep(80000);
// use wgetch to see if the user has pressed 'q'
// move character one step in its current direction
}while('q' has not been pressed);
Important! At some point your character moves of the screen, and ncurses goes crazy --- nothing it draws makes sense anymore. Just kill the program with ctrl-c, and don't worry about it, because ...
Note: seed the random number generator with srand(time(0));
or else it will do the same thing every time — that would be boring!
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 just change direction and leave it at that. Make the character "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.
Important!
Only do wrefresh(W); once per loop iteration,
do it after all characters have been redrawn, and
do it immediately prior to the call to usleep
This is the way animations work: draw a whole new scene, and
only overlay it once when it is all complete.
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.
(Typing a 'q' should still be used to quit the game.)