2
\$\begingroup\$

I've been updating a little text-based game of mine to have the text roll out over a time period to give the appearance that it's being typed. I'm doing this by using non-blocking output with ncurses.

The text to be output is passed to a function called TimedPrint, along with arguments for the screen to print it on, the position on screen to print, and the time period between each printed letter. (printGridsLineTimed is basically the same thing as TimedPrint but for printing whole Grids, a class I have created representing blocks of text).

//somewhere in main.cpp

unsigned tries = 0;
unsigned maxTries = 4;
unsigned currentX = //a number;
unsigned currentY = //a number;

{

    unsigned Period = 15;
    if(TimedPrint(stdscr, "Welcome to ROBCO Industries (TM) Termlink.", 1, 1, Period)) Period = 0;
    if(TimedPrint(stdscr, "Password Required", 1, 2, Period)) Period = 0;
    if(TimedPrint(stdscr, "Attempts remaining: " + std::to_string(maxTries-tries), 1, 4, Period)) Period = 0;
    if(printGridsLineTimed<gridX, gridY>(stdscr, GridVec.cbegin(), GridVec.cend(), Period)) Period = 0;
    if(TimedPrint(stdscr, ">", currentX - 1, currentY, Period)) Period = 0;

}

//in functions.cpp 

int TimedPrint(WINDOW* w, const std::string& s, unsigned newX, unsigned newY, unsigned Period){

    unsigned x = 0, y = 0;
    getyx(w, y, x); //obtain current cursor position
    wmove(w, newY, newX);
    wclrtoeol(w);
    wtimeout(w, 0); //no delay in input. If the user hasn't added a command, wgetch(w) returns ERR.
    for(auto it = s.begin(); it != s.end(); ++it){
        if(*it == ' '){
            waddch(w, *it);
            wrefresh(w);
            continue;
        }
        waddch(w, *it);
        wrefresh(w);
        int result = wgetch(w);
        switch(result){
            case KEY_UP:
            case KEY_DOWN:
            case KEY_LEFT:
            case KEY_RIGHT:
            case 0xA: //If user gives a valid input, print the remainder, move back and return the key pressed
                while(++it != s.end()){
                    waddch(w, *it);
                }
                wrefresh(w);
                wtimeout(w, -1); //back to normal waiting
                wmove(w, y, x);
                return result;
        }
        Sleep(Period); //determines how fast the letters appear. Too slow and the game feels laggy since input isn't processed until after this.

    }
    wtimeout(w, -1); //back to normal waiting
    wmove(w, y, x);
    return 0;

}

TimedPrint detects when there is a key press and, if that key press is valid, prints the remaining text instantly and returns what key was pressed. I use this returned value to set Period to 0 which means the rest of the text is printed automatically. So the user can press enter and everything gets printed at once. What I'm curious about though is the code within the block scope seems like it should be possible to simplify, but I don't know how.

It is laborious to check whether every print returns a value and then set period to zero. Do you personally feel there could be a better approach to this? I could always set TimedPrint to take a reference to unsigned instead and then change Period in the function manually but:

  1. I want rvalues to be passed to TimedPrint.

  2. It doesn't seem intuitive or natural that it would change the value of the time period. It should only print what is given to it and return the value of any key presses. If I were to extend this function to use in other programs, the functions applicability feels limited if it changes the time period passed to it.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.