c++nonblockingkbhit

How to make a loop in C++ that runs a time counter and allows for user input simultaneously (non-blocking user input)


So, what I am trying to do is create a function in C++ that allows the user to input anything and everything into a string for a set amount of time (say, ten minutes (or even one)) (and KEEP all the stuff they inputted, btw. I'm trying to design a game to help NaNoWriMo participants or authors in general to push them to write, so keeping everything is essential) and then doesn't allow for anymore input to be appended. This sounded easy at first, but five hours of Googling proved otherwise.

First, I figured that I could just append repeatedly until time ran out or something. Observe:

int main() //testing
{
    string str;
    string app = " ";
    getline(cin, app);

    for (int i = 0; i <= 3; i++)
    {
        Sleep(5000);
        cout << i << endl;
        str = " ";
        getline(cin, str);
        app.append(str);
    }

    cout << app;
}

But then I realized, duh, that the loop won't check the condition again until the user presses enter. Bothersome. Same with my clever idea of a character. I needed a loop that updated with every character typed (or every second past), no matter how long the user takes to type another one.

So, then I was like, "Well, stopping at every space and updating the time should work well enough, yeah?"

This is when I began to discover that C++ does not play well with parsing strings, or reading input as it gets it. I thought about appending each character, since that's pretty easy to find a space with, but it still waits for the user.

Frustrated, I wondered if there was a way to have a program run simultaneously while getting user input so that once time ran out (using time(null), time passes, time(null) and subtracting the two variables to get the seconds elapsed).

Turns out, there is! (I knew there had to be, considering all the computer programs and games that use this principle, but I'm pretty new to programming (I learned functions this last Wednesday, to give you an idea), so I didn't know what.)

The first functions I stumbled on were curses and ncurses, but I quickly discovered that that really wasn't a viable option for what I want to do (since I hear it does rather funny things to cout).

Then I stumbled on kbhit, which recognizes when keys are pressed on the keyboard. This blog seems to be promising, and probably what I want to do, but I'm not sure what language it's written in . . . all I know is that it doesn't play well with my C++ IDE. I have no idea how to make it play nice with C++.

So, a reiteration of what I would like:

A loop that contains a simultaneous execution of a time counter and user input (to a string), but terminates (ceases to let user input further to the string) when the time runs out. (All without doing funny things to the rest of my program, like ncurses would.)

Additionally: I run Windows 8 (the bane of my existence) and I use Code::Blocks as my IDE.

Or, if you've got it, somewhere this question has already been answered. That would actually be great. It would just make me feel silly that I couldn't find it.

(Here are some places I looked at along the way:

User input while program still running

How do I read input from standard input until a space or a tab is pressed?

Capture characters from standard input without waiting for enter to be pressed )

(The game I'm trying to create, (which is totally not very far yet; I started on it last night), in case you were curious: https://github.com/Rowan-Law/Dungeon-Crawl/blob/master/Source%20Code )


Solution

  • Here's how you'd get a pretty close approximation of what you want in C++17 using threading. The only hiccup is that the user can still type after the time limit, it just won't be saved.

    #include <atomic>
    #include <chrono>
    #include <iostream>
    #include <istream>
    #include <mutex>
    #include <ostream>
    #include <string>
    #include <thread>
    
    std::atomic_bool done = false;
    std::mutex cout_mutex{};
    
    void Timing(std::chrono::seconds length) {
        auto start = std::chrono::steady_clock::now();
        auto end = start + length;
        auto now = std::chrono::steady_clock::now();
        while(now < end) {
            now = std::chrono::steady_clock::now();
            done = false;
        }
        done = true;
        {
            std::scoped_lock<std::mutex> lock(cout_mutex);
            std::cout << "\nTime's up! Any unsaved input will be lost." << std::endl;
        }
    }
    
    int main() {
        std::string str{};
        std::string app{};
    
        using namespace std::literals::chrono_literals;
        //This can be any duration you want.
        std::thread t = std::thread(Timing, 30s);
        t.detach();
        while(!done) {
            std::getline(std::cin, str);
            if(!done) {
                app.append(str + '\n');
            }
        }
        std::cout << app;
    }