1

I have a question that has evolved from one of my previous (Conditionally Breaking A Long Sequence Of Inputs?), but so far, no one has been able to give me a satisfactory answer, and all my efforts have failed.

I am trying to allow the user to break early from entering input if they don't need to. i.e., for the code below:

cout << '\n' << "Monster A's name is: ";
cin >> nameA;
cout << '\n' << "Monster A rolled: ";
cin >> rollM_A;
cout << '\n' << "Monster A's Dex is: ";
cin >> DexA;
cout << '\n' << "Monster A's Mod is: ";
cin >> ModA;
cout << '\n' << "Monster A's Level is: ";
cin >> LvlA;

//etc.

Up to 12 monsters are actually supported. If the user wants to use only, say, 3-4 of them, I'd like them to be able to skip the rest and save themselves a lot of keystrokes. I've already made sure to initialize all the variables to 0, and have a function to remove 0 elements from the storage vector later. All that's missing is getting away from this input chain. I have tried various forms of a while loop wrapping, like this:

while(cin.get() != '#') {
   cout << '\n' << "Monster A's name is: ";
   cin >> nameA;
   //etc...
}

But upon entering the desired character, the code simply outputs all the prompts ("Monster A's name is:" , etc.) over and over again, without moving on or accepting further input. It's like the code is stuck in an infinite loop, even though it should be leaving the loop on # input.

Any ideas? Been really stuck on this for a while and would be very grateful if anyone could offer an alternate solution, or at least let me know the flaw in my own.

Thanks!

Community
  • 1
  • 1
Rome_Leader
  • 2,518
  • 9
  • 42
  • 73

5 Answers5

2

Your piece of code provoke an infinite loop because std::cin's operator >> does not take the "End of line" character (the enter key), so it's still in the stream.

So when you arrive to the cin.get() the second time, there is still a character in the buffer (enter from when you validated the name of the monster). cin.get() takes it, see that it is not '#', and go to the next std::cin, which do the same. You can fix this behavior by ignoring a char :

while(cin.get() != '#')
{
   cout << '\n' << "Monster A's name is: ";
   cin >> nameA;
   //etc...
   cin.ignore();
}
ChristopheLec
  • 866
  • 5
  • 13
  • This didn't work for me. The behaviour is the same, unfortunately. – Rome_Leader Jun 17 '13 at 15:50
  • Adjunct: Using cin.clear() for this purpose instead gives some interesting behaviour! Instead of scrolling endlessly, the list scrolls once, only to return to where it was broken by #, and then stops. This intrigues me. I feel like I may be close now. – Rome_Leader Jun 17 '13 at 16:01
  • Got it! cin.clear() and cin.ignore() together seems to have worked! The only issue now (very cosmetic) is that the character has to be input twice to skip. I'll be sure to let the user know that through a prompt. Thanks for your help in arriving here! :D – Rome_Leader Jun 17 '13 at 16:07
  • It is definitely strange that you have to use cin.clear, since it is only used to change error flags. Please explain if you find why you have to use it, I'm intrigued too now ! Anyway, glad to help :) – ChristopheLec Jun 17 '13 at 16:15
  • I think the error flag might have been causing the loop since the code expects an alphanumeric cin? The names are strings, and all over variables are integers. That would be my guess, but I'll debug later and step through it when I have a sec to see if I can't find the precise cause. Thanks again! – Rome_Leader Jun 17 '13 at 16:21
0

I just tested this set of code and it seems to work how you would like. Of course you will have to modify it to fit your original application.

std::string in;

while (true) {
    std::cout << "Enter a name\n";
    std::cin >> in;
    if (in == "#")
        break;
    std::cout << "\nMonster A's name is: " << in << "\n";
}

In order to incorporate the limit of the number of monsters, rather than having the true parameter passed into the while loop, simply add a counter to how many monsters are created and break on that condition:

int num_monsters = 0;
while (num_monsters <= 12) {
    ...
    num_monsters++;
}

Hope this helps.

Alex Brooks
  • 1,151
  • 1
  • 10
  • 39
  • I'll work on adapting your approach. I'd prefer minimal lines, however. My thought is that the use of an if statement would require one for just about every monster name. I'm a little confused as to your counting suggestion. If I try to leave early, # of monsters will never be 12, and then the loop will not exit? – Rome_Leader Jun 17 '13 at 15:55
  • @Rome_Leader The goal of the counter is to address your comment that "Up to 12 monsters are supported". If you don't add a counter to the code I provided, it will continue until the user inputs `#`. In other words, the user will be able to provide more than 12 monsters. If the counter is included and the user inputs `#`, the loop will still break. Also, I'm confused about your concern of using the if statements; could you elaborate? – Alex Brooks Jun 17 '13 at 16:00
  • Well, I fixed the issue now, but I see your point. It was just based off a difference between my original code using separate variable names for the Monster names, and yours compacting that into a loop. All good! – Rome_Leader Jun 17 '13 at 16:19
  • 1
    @Rome_Leader Gotcha. In that case, if you wanted to stick with compacting your code with the loop, you could always have your monster names in a vector or array. Even better, you could define a class/struct for your monster and put the information in a map. Just a thought :) – Alex Brooks Jun 17 '13 at 16:25
0

Object-orientation and the stdlib to the rescue:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Monster {
  string name;
  int rolled, dex, mod, level;

  template<class input, class output, class container>
    friend input& read_monster(input& i, output& o, container &c) {
      string n;
      o << "\nMonster's name is: " << flush;
      i >> n;
      if( !i || (n == "#") ) {
        o << "\nEnd of monster list";
        i.setstate(ios::failbit);
        return i;
      }
      Monster m;
      m.name = n;
      o << "\nMonster's rolled is " << flush;
      i >> m.rolled;
      if( !i ) return i;
      o << "\nMonster's dex is " << flush;
      i >> m.dex;
      if( !i ) return i;
      o << "\nMonster's mod is " << flush;
      i >> m.mod;
      if( !i ) return i;
      o << "\nMonster's level is " << flush;
      i >> m.level;
      if( !i ) return i;
      o << "\n";
      c.push_back(m);
      return i;
    }

  template<class output>
    friend output& operator<<(output& o, Monster&m) {
      return o << "Monster('" << m.name <<"', R/" << m.rolled
        << ", D/" << m.dex << ", M/" << m.mod << ", L/" << m.level
        << ")\n";
    }

  Monster() : name(), rolled(), dex(), mod(), level() {}
};

int main(int, char**) {
  vector<Monster> v;
  while( read_monster(cin, cout, v) && (v.size() <= 12) )
    ;

  for( auto m: v )
    cout << m;
  return 0;
}
Massa
  • 8,647
  • 2
  • 25
  • 26
  • Whoa, that's pretty involved. I thank you for all that and your effort, but of course, I have the class and vector declared and everything. I'd prefer against such an extensive rewrite. – Rome_Leader Jun 17 '13 at 15:52
  • Hey, not at all. Anyway, the `template input& read_monster(input& i, output& o, container &c)` above will probably work with few alterations (mostly WRT `class Monster` and your attribute names), so, be my guest to it! – Massa Jun 17 '13 at 16:31
0

Since you're doing line oriented input, you really should be using std::getline to read. And since the line oriented input consists of blocks of lines, each defining an object, you should create a class, and read it. (And in addition, you'll need a lot more error handling, since you need to handle errors in any of the intermediate lines.)

Obviously, each of these concerns should be separated into a separate function:

std::string
getLineFromPrompt( std::string const& prompt )
{
    std::string results;
    std::cout << prompt;
    std::getline( std::cin, results );
    return results;
}

template <typename T>
bool
tryGetValueFromPrompt( std::string const& prompt, T& value )
{
    std::string line = getLineFromPrompt( prompt );
    std::istringstream parser( line );
    return parser >> value >> std::ws && parser.get() == EOF;
}

template <typename T>
void
getValueFromPrompt( std::string const& prompt, T& value )
{
    while ( ! tryGetValueFromPrompt( prompt, value ) ) {
        std::cout << "Illegal input, try again" << std::endl;
    }
}

class Monster
{
    std::string name;
    int roll;
    int dexterity;
    int mod;
    int level;
public:
    Monster( std::string const& name )
        : name( name )
    {
        getValueFromPromt( name + " rolled:", roll );
        getValueFromPromt( name + " dexterity:", dexterity );
        getValueFromPromt( name + " mod:", mod );
        getValueFromPromt( name + " level:", level );
    }
};

bool
isEndFlag( std::string const& line )
{
    return line.empty() || line[0] == '#';
}

And finally, the loop:

std::vector <Monster> monsters;
std::string nextName = getLineFromPrompt();
while ( ! isEndFlag( nextName ) ) {
    monsters.push_back( Monster( nextName ) );
    nextName = getLineFromPrompt();
}

As you can see, factoring out each individual concern into a separate function results in far simpler code.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
-1

Can you cout the info, e.g. 'press # to skip this question'? Instead of let user guess which key to skip the question?

Tianyun Ling
  • 1,045
  • 1
  • 9
  • 24