Search code examples
c++xcodeloopsenter

Identify "Enter"


I want my program to recognize when the user presses "enter" and keep looping. But I cant figure out how to make the program identify "enter". Tried two ways:

string enter;
string ent = "\n";
dice d1;

cout << "To start - Press enter" << endl;
getline (cin, enter);

while (enter == ent)
{
    d1.throw_dice();
    d1.draw_dice();
    cout << "Try again, press enter" << endl;
    getline (cin, enter);
}

cout << "Thank you for playing"<< endl;

And this one:

string enter;
dice d1;

cout << "To start - Press any key and enter" << endl;
getline (cin, enter);

while (enter == "\n")
{
    d1.throw_dice();
    d1.draw_dice();
    cout << "Try again, press enter" << endl;
    getline (cin, enter);
}

cout << "Thank you for playing"<< endl;

I know that string throws the "\n" away, but cant really find a way to work around it.

PS:

I found a solution. But I still feel like there should be a better option.

        while (cin.get() == '\n')
    {
        d1.throw_dice();
        d1.draw_dice();
        cout << "Try again, press enter! Or press any other key and enter" << endl;
        if (cin.get() != '\n')
            break;
    }

    cout << "Thank you for playing"<< endl;

    return 0;
}

Solution

  • std::getline

    In the first two cases, there are two issues with using getline for the input if you're trying to detect an enter press (although what you're really detecting is the newline, and pressing enter isn't the only way that can be generated, although it's typically the most straightforward). First, as you said, whatever getline uses as a delimiter it will leave out of the input it reads, so you won't really be able to get the newlines included using that without things getting a fair bit more complicated. Second, however, is that any characters entered before the newline will also be included in the line getline retrieved, so simply comparing against the string "\n" wouldn't tell you if a newline was read even were getline to keep the delimiter, which it doesn't.

    As another issue, of course, is that ideally you'd want to check to ensure that the input stream cin isn't in an error state when you read from it. Provided that isn't the case, getline should only proceed once it has received a newline in input.

    In principle this suggests that (provided you don't mind non-enter-key ways of providing newlines as input being taken as enter key presses) if you perform proper error checking you wouldn't need to check the string itself at all to know the enter key was pressed - if getline stopped blocking for input and didn't encounter an error (I'm including end-of-file in "error" here as that is in the stream state flags) it read the delimiter, which in your case is the newline character. So you could leverage the blocking behavior of getline(cin, enter) to direct your control flow, though to exit the loop you'd still need to check to see if something other than newline was entered (or if end-of-file was entered or an error occurred, by checking the state flags on cin).

    std::istream::get()

    With the cin.get() method you're using in your third case, you can actually retrieve the newline characters, unlike with getline - although the code you provide requires two enter presses per loop iteration after the first, rather than one (which I doubt was your intent). The separate conditional break case shouldn't be necessary, and the loop condition should suffice (in the failure case no-args std::istream::get() returns the EOF value, which won't equal \n so your loop exits in that case). In other words,

    while (cin.get() == '\n') {
        d1.throw_dice();
        d1.draw_dice();
        cout << "Try again, press enter! Or press any other key and enter" << endl;
    }
    

    Should provide the behavior of pressing enter after one loop iteration leading directly to the next, instead of needing to press it twice.

    You're still left with the somewhat awkward syntax of "enter some other character then enter to exit", but that partly is a result of the limitations of line-buffered input (such as the typical command-line terminal). With the above loop you could also exit immediately (without pressing the enter key) by entering your platform's key combination for end-of-file (varies but on UNIX-based systems is typically CTRL+D), but if you wanted to progress much further than this towards general interactive keyboard input (have things happen directly by pressing keys as opposed to entering line-by-line text) in a command-line environment, that can be a good deal more complicated than the scope of this program seems to be and typically requires external libraries and/or platform-specific support. If you do want to delve into that for C++ you might have a look at some of the answers to this question.

    In other words, there are in general better options for detecting key presses for an interactive command-line program than cin.get(), but they can become significantly more involved if you want to detect the pressing of keys other than enter and have an immediate response (instead of waiting for another enter). There are other input-handling options other than cin.get() and getline() available in basic, platform-independent C++, but as far as I am aware they are all going to run into this sort of issue - the fact that input to cin doesn't become available on the input stream until the end of a line - without going into platform-specific code (or external libraries that contain the platform-specific code) to change that fact or otherwise work around it.