Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you write a C program to increment a number by keypress and auto-decrement it per second?

I'm trying to write a program in which a number starts from 0, but when you press any key, it gets incremented by 1. If nothing is pressed, it keeps on decreasing by 1 per second until it reaches 0. Every increment or decrement is displayed on the console window.

Problem with my approach is that nothing happens until I press a key (that is, it checks if anything is pressed with getch()). How do I check that nothing is pressed? And of course, !getch() doesn't work because for that to work, it'll still need to check for keypress which nullifies the purpose itself.

OS: Windows 10 Enterprise, IDE: Code::Blocks

void main()
{
    int i, counter = 0;
    for (i = 0; i < 1000; i++)
    {
        delay(1000);
        // if a key is pressed, increment it
        if (getch())
        {
            counter += 1;
            printf("\n%d", counter);
        }
        while (counter >= 1)
        {
            if (getch())
            {
                break;
            }
            else
            {
                delay(1000);
                counter--;
                printf("\n%d", counter);
            }
        }
    }
}
like image 827
Ritika Avatar asked Oct 09 '18 07:10

Ritika


2 Answers

The following short program requires neither ncurses or threads. It does, however, require changing the terminal attributes - using tcsetattr(). This will work on Linux and Unix-like systems, but not on Windows - which does not include the termios.h header file. (Perhaps see this post if you are interested in that subject.)

#include <stdio.h>
#include <string.h>
#include <termios.h>

int main(int argc, char *argv[]) {
    struct termios orig_attr, new_attr;
    int c = '\0';
    // or int n = atoi(argv[1]);
    int n = 5;

    tcgetattr(fileno(stdin), &orig_attr);
    memcpy(&new_attr, &orig_attr, sizeof(new_attr));
    new_attr.c_lflag &= ~(ICANON | ECHO);
    new_attr.c_cc[VMIN] = 0;
    // Wait up to 10 deciseconds (i.e. 1 second)
    new_attr.c_cc[VTIME] = 10; 
    tcsetattr(fileno(stdin), TCSANOW, &new_attr);

    printf("Starting with n = %d\n", n);
    do {
        c = getchar();
        if (c != EOF) {
            n++;
            printf("Key pressed!\n");
            printf("n++ => %d\n", n);
        } else {
            n--;
            printf("n-- => %d\n", n);
            if (n == 0) {
                printf("Exiting ...\n");
                break;
            }
            if (feof(stdin)) {
                //puts("\t(clearing terminal error)");
                clearerr(stdin);
            }
        }
    } while (c != 'q');

    tcsetattr(fileno(stdin), TCSANOW, &orig_attr);

    return 0;
}

The vital points are that

new_attr.c_lflag &= ~(ICANON | ECHO);

takes the terminal out of canonical mode (and disables character 'echo'),

new_attr.c_cc[VMIN] = 0;

places it in polling (rather than 'blocking') mode, and

new_attr.c_cc[VTIME] = 10;

instructs the program to wait up till 10 deciseconds for input.

Update (2019-01-13)

  • add clearerr(stdin) to clear EOF on stdin (seems to be necessary on some platforms)
like image 150
David Collins Avatar answered Sep 20 '22 22:09

David Collins


This could be done with multithreading as already suggested, but there are other possibilities.

ncurses for example has the possibility to wait for input with a timeout.

An example for ncurses (written by Constantin):

initscr();
timeout(1000);
char c = getch();
endwin();
printf("Char: %c\n", c);

I think poll could also be used on stdin to check if input is available.

And to make your program more responsive you could lower your sleep or delay to for example 100ms and only decrement if ten iterations of sleep have passed without input. This will reduce the input lag.

like image 24
Kami Kaze Avatar answered Sep 17 '22 22:09

Kami Kaze