Home > Back-end >  How to update console output instantly in an infinite loop on key press C
How to update console output instantly in an infinite loop on key press C

Time:12-07

I am trying to code the famous snake game, traditionally found on old school Nokia phones, I do have the head at the moment and simply working on changing its direction, and it does work, but only when I don't use the sleep function on the while loop.

This here will delay in updating the direction of the snake

while (true) {
    // some code to print the board here
    // logic to move on the board
    // logic to change direction
    sleep(1); // sleep one second to simulate snake moving on cell per second
    system("cls");    
}

This here will work fine

while (true) {
    // some code to print the board here
    // logic to move on the board
    // logic to change direction
    // no sleep function
    system("cls"); 
}

Here is my full code:

#include <iomanip>
#include <iostream>
#include <stdlib.h>
#include <string>
#include <windows.h>
#include <unistd.h>

const int BOARD_HEIGHT = 10;
const int BOARD_WIDTH  = 10;
char board[BOARD_HEIGHT][BOARD_WIDTH];

void clearScreen();
void createBoard();
void printBoard();
void move(int &, bool negative_direction);

int main() {

    createBoard();
    
    int x_pos = 4, y_pos = 4;
    int direction = 1;  
    char ch = '>';
    
    board[x_pos][y_pos] = ch;


    system("cls");  
    
    while (true) {
        
        printBoard();
        
        board[y_pos][x_pos] = '_';
        
        switch (direction) {
            case 0: // NORTH
                move(y_pos, true); // true: moving towards negative numbers
                break;
            case 1: // EAST
                move(x_pos, false); // false: moving towards positive numbers
                break;
            case 2: // SOUTH
                move(y_pos, false); 
                break;
            case 3: // WEST
                move(x_pos, true);
                break;
            default:
                std::cout << "Direction unknown..." << std::endl;
                exit(EXIT_FAILURE);
        }
        
        // board[y_pos][x_pos] = ch;
        
        if(GetAsyncKeyState(VK_UP) & 0x8000 && direction != 2) {
            direction = 0;
            ch = '^';
        } 
        else if (GetAsyncKeyState(VK_RIGHT) & 0x8000 && direction != 3) {
            direction = 1;
            ch = '>';
        } 
        else if (GetAsyncKeyState(VK_DOWN) & 0x8000 && direction != 0) {
            direction = 2;
            ch = 'v';
        }
        else if (GetAsyncKeyState(VK_LEFT) & 0x8000 && direction != 1) {
            direction = 3;
            ch = '<';
        }
        
        if (GetAsyncKeyState('X') & 0x8000)
            break;
        
        board[y_pos][x_pos] = ch;
        
        clearScreen();
    }
    
    return 0;
}

void clearScreen() {    
    COORD cursorPosition;
    cursorPosition.X = 0;   
    cursorPosition.Y = 0;   
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), cursorPosition);
}

void createBoard() {
    for (int row = 0; row < BOARD_WIDTH; row  ) {
        for (int col = 0; col < BOARD_HEIGHT; col  ) {
            board[row][col] = '_';
        }
    }
}

void move(int &dir, bool negative_direction) {
    if (negative_direction) 
         (dir == 0) ? dir = 9: dir--;
    else 
        (dir == 9) ? dir = 0: dir  ;
}

void printBoard() {
    for (int row = 0; row < BOARD_WIDTH; row  ) {
        // std::cout << std::setw(2) << (row   1) << " ";
        for (int col = 0; col < BOARD_HEIGHT; col  ) {
            std::cout << board[row][col] << " ";
        }
        std::cout << std::endl;
    }
    sleep(1);
}

I used GetAsyncKeyState() instead of GetKeyState() thinking it would solve my problem, as I mentioned above, it changes direction well when I don't use the sleep() function, but I kinda have to use it so as to simulate one cell movement per second

CodePudding user response:

The problem with using the Sleep or sleep function together with GetAsyncKeyState is that you are only checking for keypresses while not sleeping, but you also want to be notified of keypresses that occur while sleeping.

Therefore, what you need is a function which will wait until either of the following events occur:

  1. The user presses a key, or
  2. a timeout occurs, which means that it is time to move the snake again.

The function WaitForSingleObject can do this.

Also, you should be using the function ReadConsoleInput instead of GetAsyncKeyState, because the function GetAsyncKeyState will only tell you whether a key is currently down1, whereas the function ReadConsoleInput uses an event queue which specifies whether a new keypress event has occurred.

Here is an example of a game loop you could use in your snake game:

#define WIN32_LEAN_AND_MEAN

#include <Windows.h>
#include <iostream>

//the snake should move once per second, which is once
//every 1000 milliseconds
constexpr DWORD dwMillisecondsPerMove = 1000;

int game_loop()
{
    INPUT_RECORD ir;
    HANDLE hConInput = GetStdHandle(STD_INPUT_HANDLE);
    DWORD dwReadCount;
    DWORD dwStartTimeOfMove = GetTickCount();
    bool quit = false;

    enum class Direction { north, east, south, west };
    Direction snake_dir = Direction::east;
    const char *direction_strings[] = { "north", "east", "south", "west" };

    while ( !quit )
    {
        DWORD dwTimeRemaining = dwMillisecondsPerMove - ( GetTickCount() - dwStartTimeOfMove );

        switch ( WaitForSingleObject( hConInput, dwTimeRemaining ) )
        {
        case WAIT_OBJECT_0:

            //there is a new input event, possibly a keyboard event

            //attempt to read input
            if ( !ReadConsoleInput( hConInput, &ir, 1, &dwReadCount ) || dwReadCount != 1 )
                throw std::runtime_error( "Unexpected input error!\n" );

            //only handle key-down events
            if ( ir.EventType != KEY_EVENT || !ir.Event.KeyEvent.bKeyDown )
                break;

            switch ( ir.Event.KeyEvent.wVirtualKeyCode )
            {
            case VK_UP:
                snake_dir = Direction::north;
                std::cout << "Changing snake direction to NORTH.\n";
                break;
            case VK_RIGHT:
                snake_dir = Direction::east;
                std::cout << "Changing snake direction to EAST.\n";
                break;
            case VK_DOWN:
                snake_dir = Direction::south;
                std::cout << "Changing snake direction to SOUTH.\n";
                break;
            case VK_LEFT:
                snake_dir = Direction::west;
                std::cout << "Changing snake direction to WEST.\n";
                break;
            case 0x51: //Q key
                quit = true;
                break;
            }
            break;

        case WAIT_TIMEOUT:
            std::cout << "Time to move the snake " << direction_strings[static_cast<int>(snake_dir)] << ".\n";
            dwStartTimeOfMove = GetTickCount();
            break;

        default:
            throw std::runtime_error( "unexpected error!" );
        }

        //make sure that the output buffer is flushed before
        //waiting again
        std::cout << std::flush;
    }

    return 0;
}

int main()
{
    try
    {
        game_loop();
    }
    catch ( std::runtime_error &e )
    {
        std::cerr << e.what() << std::endl;
    }
}

This program has the following output:

Time to move the snake east.
Time to move the snake east.
Changing snake direction to NORTH.
Time to move the snake north.
Time to move the snake north.
Time to move the snake north.
Changing snake direction to SOUTH.
Time to move the snake south.
Time to move the snake south.
Time to move the snake south.
Changing snake direction to WEST.
Time to move the snake west.
Time to move the snake west.

The output "Changing snake direction to" appears when I press an arrow key, and the other lines appear when the timeout expires, which is once per second.

To quit the program, you can press the Q key.


Footnote:

1 One of the bits of the return value of the function GetAsyncKeyState actually does indicate whether a key has been pressed. However, according to the official Microsoft documentation of that function, this information is unreliable and is only provided for backward-compatibility, so it should not be used.

CodePudding user response:

You need perform the cell movement once per second, but you do not need to delay for one second. For example:

for(;;)
{
    // some code to print the board here
    // logic to move on the board
    // logic to change direction

    static clock_t tref = clock() ;
    clock_t now = clock() ;
    if( now - tref >= CLOCKS_PER_SEC )
    {
        // Every second update here
    }
    
    system("cls");    
}

You might want a WinAPI Sleep(1) - nominally 1 ms delay (note the case, not POSIX sleep()) in each iteration just to prevent the process hogging the CPU.

  • Related