-
RPG troubles
Hey, it's me again. :) I've got a decent system going for my RPG now, but I have one major issue that ruins the whole thing. The problem comes from two little lines of code:
Code:
cout << "Choose a class: Press '1' for Warrior, '2' for Ranger, or '3' for Mage.\n";
cin >> charClass;
When choosing a class, if the user inputs a value that is greater than 2,147,483,647, the program will infinitely run:
Code:
hitPoints = rand() % 50 + 50;
attack = rand() % 15 + 5;
defense = rand() % 15 + 5;
magic = rand() % 5 + 5;
accuracy = rand() % 5 + 10;
cout << "Name: " << charName << endl;
cout << "Class: Warrior\n";
cout << "HP: " << hitPoints << endl;
cout << "ATK: " << attack << endl;
cout << "DEF: " << defense << endl;
cout << "MGK: " << magic << endl;
cout << "ACC: " << accuracy << endl;
cout << "\nIs this information correct? (y/n) ";
cin >> recreate;
if (recreate != 'n' && recreate != 'y')
{
cout << "\nYou have entered an invalid character.";
recreate = 'n';
}
Is there any way I can fix the overflow problem? Any help will be greatly appreciated as this problem keeps reoccurring in my programs.
-
When a stream like std::cin tries to read a number, and in doing so would cause overflow, it enters an error state. This is the same sort of error state entered when EOF is reached, for example, and you can check for it in the same way: by seeing if "std::cin >> number" evaluates to NULL or not. (The various operator>> overloads return the stream itself if no error occurs, so that you can chain together statements like "std::cin >> a >> b >> c"; and return a "NULL" object when an error occurs. This "NULL" object evaluates to false if you convert it to a bool, whereas the original stream, which is an instance of a stream class, evaluates to true.) Here's a quick example.
Code:
#include <iostream>
int main() {
int number;
std::cout << "Enter a number: ";
while(!(std::cin >> number)) { // while std::cin evaluates to false (an error state)
std::cout << "Invalid number, try again: ";
std::cin.clear(); // clear the error state
}
std::cout << "You entered " << number << std::endl;
return 0;
}
Input/output:
Code:
$ ./toolarge
Enter a number: 555555555555555555555
Invalid number, try again: -55555555555555555555
Invalid number, try again: 123
You entered 123
$
As you can see, that's an efficient way to check if a number entered was too large or too small (and would cause overflow or underflow). But it doesn't handle all cases: enter a letter, for example, and that program will enter an infinite loop.
The trouble is this. When you enter something like "A\n", and ask std::cin to read an int, it will enter an error state because it can't parse a number from your input, and leave the input unchanged. Then you clear the error state, and ask it to parse another int. Again it fails to parse "A\n", and so on in the infinite loop.
You can get around this in a few ways. Probably the best is to just clear the remaining input until the end of the line when an input operation fails. So if you want an int and the user enters "A\n", you clear the input buffer and then ask again. This is pretty easy to do:
Code:
#include <iostream>
#include <limits> // for std::numeric_limits
#include <ios> // for std::streamsize
int main() {
int number;
std::cout << "Enter a number: ";
while(!(std::cin >> number)) {
std::cout << "Invalid number, try again: ";
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
std::cout << "You entered " << number << std::endl;
return 0;
}
I/O:
Code:
$ ./toolarge
Enter a number: f
Invalid number, try again: why me?
Invalid number, try again: 44444444444444443333333333333
Invalid number, try again: fine, 12.
Invalid number, try again: 12
You entered 12
$
(There's a bit more in-depth explanation elsewhere, e.g.: http://cboard.cprogramming.com/cplus...tml#post614384 )
That new line looks a little scary, but here's a breakdown. If you say something like
Code:
std::cin.ignore(5, '\n');
then std::cin will ignore up to five characters or up until a newline (whichever happens first). The std::numeric_limits stuff just gets the absolute maximum value for that integer type, so that you ignore as much as you could possibly need to.
Anyway, you can wrap something like the above code into a function like get_number(), and use it wherever you need to read numbers from the user.
By the way: I've used std::cin here instead of just plain cin. You can use the latter, if you use something like "using namespace std". There's another whole story behind this, which you can ignore if you don't particularly care. :) Search the boards for "using namespace std" or the like for details.
[Last note: codeform was used to colour that code.]
-
Make you sure you use _NOMINMAX or #undef them or that code will fail under Windows.
-
Thank you very much! It worked perfectly!
P.S. I already knew about ignore, but I didn't know it could be used that way! :)