Submit up to and including the furthest step you have working properly. For full credit on this lab, submit Parts 1,2,3:
~/bin/submit -c=SI204 -p=Lab10 p2.cpp p3.cppIf you finished more, submit as (up to whatever you finished):
~/bin/submit -c=SI204 -p=Lab10 p2.cpp p3.cpp p4.cpp p5.cpp p6.cpp p7.cpp
#include "easycurses.h" in your
.cpp files, and you need to compile with the -l ncurses
compiler flag, like:
g++ p1.cpp -l ncurses
Create and compile p1.cpp under your account. This source code demonstrates some simple aspects of ncurses. Here's a few comments:
startCurses();
This line of 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.
drawChar('B', 20, 30);
This draws the char 'B' at position 20,30 in the terminal. How about a pause after you draw? The following sleeps your program for 0.8 seconds:
usleep(800000); // need: #include <unistd.h>
// Loop forever until user enters 'q'
char c;
while( (c = inputChar()) && c != 'q' ) {
// do nothing
}
// Close ncurses
endCurses();
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 inputChar().
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 inputChar() and
nobody presses a key, inputChar simply returns immediately with an error code.
Contrast this with cin >> c or cin.get().
Thus, this loop just spins waiting for inputChar() 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.
What if we need to use cout for debugging information? Of course we do!
Well this is slightly problematic because ncurses now controls the screen.
Suppose in the Part1 code we wanted to print out a debugging
message "Printed a B!" just after writing the "B". We'd
just add the line:
cout << "Printed a B!" << endl;
... immediately after the drawChar('B',20,30).
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 "C" is no longer printed immediately to the right of the
"B". 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 B!" << endl;
... and run your program like this:
~/$ ./a.out 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?
~/$ ./a.out 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.
2> err is telling the shell to
redirect standard error (cerr in our program) output to the
file err.
tail -f is constantly
monitoring the file err, printing out any new lines that get
written to the file.
Download our rundebug script here.
This automates Part 1b above. The script will open a second debugging terminal for you automatically and then run your a.out program. Just download it, and turn its executable bit on:
chmod 755 rundebug
Now instead of running ./a.out you just:
./rundebug a.out
4 a (10,15) x (14,29) k (5,5) x (18,37)
' ' in the appropriate position.
Important! We 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, we want each character to have a direction associated with it — north, south, east or west. Make sure you store that in your struct! Start all your characters from Part 2 moving to the right (i.e., east).
Your program will consist of a loop:
do {
// Move all characters one step:
// 1. erase them in the old positions
// 2. draw them in the new positions
usleep(80000); // sleep for 80,000 usecs. This corresponds to flipping the next "frame" in an animation.
// use inputChar() to see if the user has pressed 'q'
} while('q' has not been pressed);
Important! At some point your characters move off 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.
You may want to consider a couple functions which takes your struct type and modifies its position and/or direction variable, such as:
__??__ move(__??__)
__??__ turn(__??__)
Note: seed the random number generator with
srand(time(0)); // need: #include <cstdlib>
// #include <ctime>
or else it will do the same thing every time — that would be boring!
Recall: The following code will print "yes" with probabilty 1/10:
if( rand() % 10 == 0 )
cout << "yes" << endl;
Though your single animated character makes random turns, eventually it's going to make its way off the screen and strange things will happen. 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). Our easycurses function for that:
void getWindowDimensions(int& row, int& col);
So the call getWindowDimensions(h,w) sets h to
the number of rows on the screen, and w to the number of
columns. Note, then, that the valid positions you can
drawChar(ch,row,col) to are when 0 ≤ row < h
and 0 ≤ col < w.
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.
Now, instead of a few moving characters, 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!
Important!
Every time you call drawChar(), it refreshes the screen.
Now that we have so many chars, we don't want to refresh every time.
Instead call the function drawCharNoRefresh() with the same parameters.
The chars won't immediately show up, though, so you'll later need to
call the function refresh(); to have them all appear.
Do this once per loop iteration
after all characters have been drawn, 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 = inputChar();... 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.)