djscrew
djscrew

Reputation: 57

How to only cin one input per line (C++)

I'm new to C++ and working on a simple guessing game where you get 5 tries to guess a number between 1 and 100. I'm having issues dealing with user inputs.

I've made it so that the program only accepts numbers between 1 and 100, and it ignores characters without crashing. The problem is that when I type in gibberish like 34fa1e8, the loop will run three times, using 34 the first time, 1 the second time, and 8 the last time, instead of ignoring the input like I want it to.

The code im using is here:

int check_guess() {
    int guess;
    do {
        cin >> guess;
        if (cin.fail()) {
            cin.clear();
            cin.ignore();
        }
    } while (guess < 1 || guess > 100);
    return guess;
}

How can I make the program dismiss inputs like these instead of accepting them separately?

Upvotes: 0

Views: 580

Answers (3)

RockOnRockOut
RockOnRockOut

Reputation: 761

Use ifstream::getline to store the input in a char array. Then convert it to an integer using a function like this:

int convertToInteger(char const *s){
    if ( s == NULL || *s == '\0'){
        return 0;
    }
    int result = 0, digit = 1000;
    while((*s) && (digit > 0)){
        if ( *s >= '0' && *s <= '9' ){
            result +=  digit * (*s - '0');             
        }else{
            return 0;
        }
        ++s;
        digit /= 10;
    }
    return result;
}

The reason it works is because it will return 0 in case of failure and that's something your loop's condition will not accept. You also don't have to worry about negative numbers since your loop won't accept them anyways.

Upvotes: 0

David G
David G

Reputation: 96790

I've given an answer like this before; an explanation can be found here.

You can even extended the solution to check for the specified range:

template <int min, int max>
class num_get_range : public std::num_get<char>
{
public:
    iter_type do_get( iter_type it, iter_type end, std::ios_base& str,
                      std::ios_base::iostate& err, long& v) const
    {
        auto& ctype = std::use_facet<std::ctype<char>>(str.getloc());
        it = std::num_get<char>::do_get(it, end, str, err, v);

        if (it != end && !(err & std::ios_base::failbit)
                      && ctype.is(ctype.alpha, *it))
            err |= std::ios_base::failbit;
        else if (!(min <= v && v <= max))
            err |= std::ios_base::failbit;

        return it;
    }
};

Now you can imbue the stream with the new locale and you need to restructure your loop to discard valid input. For example:

std::locale original_locale(std::cin.getloc());
std::cin.imbue(std::locale(original_locale, new num_get_range<1, 100>));

int check_guess()
{
    int guess;
    while (!(std::cin >> guess))
    {
        std::cin.clear();
        std::cin.igore(std::numeric_limits<std::streamsize>::max(), '\n');
    }
}

Upvotes: 0

Gil D.
Gil D.

Reputation: 139

You could use getline and stol.

Upvotes: 1

Related Questions