Ritika
Ritika

Reputation: 149

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);
            }
        }
    }
}

Upvotes: 13

Views: 4942

Answers (8)

David Collins
David Collins

Reputation: 3022

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)

Upvotes: 11

Kami Kaze
Kami Kaze

Reputation: 2080

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.

Upvotes: 4

user3857812
user3857812

Reputation:

Your code has two problems; one serious, one not.

The first problem, as you have found out, is that getch() is a blocking function. In other words, the function call will not return until a key had been pressed.

The second problem, although minor, is that the program only responds to input every second.

I've modified your requirements slightly, starting the initial counter at 5.

#include <windows.h>

int main(void)
{
  int Counter;
  time_t StartTime;
  DWORD EventCount;


  Counter=5;
  do
  {
    StartTime=time(NULL);
    do
    {
      Sleep(10);  /* in ms. Don't hog the CPU(s). */
      GetNumberOfConsoleInputEvents(GetStdHandle(STD_INPUT_HANDLE),&EventCount);
    }
    while( (StartTime==time(NULL)) && (EventCount==0) );  
        /* Wait for a timeout or a key press. */

    if (EventCount!=0)  /* Have a key press. */
    {
      FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE));  /* Clear the key press. */
      Counter++;
    }
    else  /* Timed out. */
      Counter--;

    printf("Counter = %d\n",Counter);
  }
  while(Counter>0);

  return(0);
}

Compiled using Microsoft Visual C++ 2015 (command line: "cl main.c").
Tested on Windows 7 and 10.

Upvotes: 0

Roger Lipscombe
Roger Lipscombe

Reputation: 91865

If you're not bothered about portability, and you'll always be using Windows, you can use PeekConsoleInput, which tells you what console input events are waiting.

You can't (easily) use ReadConsoleInput, because it blocks until there is at least one pending input event.

Upvotes: 1

Spikatrix
Spikatrix

Reputation: 20244

Here is another way that uses select to check if input exists and also to wait. Not a pretty solution but it works. Linux only though.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <sys/select.h>

#define WAIT_TIME 1000 //Delay time in milliseconds

bool inputExists(void)
{
    fd_set readfds;
    FD_ZERO(&readfds);
    FD_SET(0, &readfds);

    struct timeval tv;
    tv.tv_sec = tv.tv_usec = 0;

    if(select(1, &readfds, NULL, NULL, &tv))
        return true;
    else
        return false;
}

void wait()
{
    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = WAIT_TIME * 1000;
    select(0, NULL, NULL, NULL, &tv);
}

int main(void)
{
    system("stty raw"); /* Switch to terminal raw input mode */

    unsigned int count = 0;
    for(;;)
    {
        if(inputExists())
        {
            char input[256] = {0};
            read(0, input, 255);
            count += strlen(input);

            printf("\rCount is now %d\n", count);
        }
        else if(count > 0)
        {
            count--;
            printf("\rDecremented count to %d\n", count);
        }

        puts("\rWaiting...");
        wait();
    }
}

A better way that avoids system("stty raw") and those \rs would be to use tcgetattr and tcsetattr:

struct termios orig_attr, new_attr;

tcgetattr(STDIN_FILENO, &orig_attr);
new_attr = orig_attr;
new_attr.c_lflag &= ~(ICANON | ECHO); //Disables echoing and canonical mode
tcsetattr(STDIN_FILENO, TCSANOW, &new_attr);

//...

tcsetattr(STDIN_FILENO, TCSANOW, &old_attr);

Upvotes: 2

sundb
sundb

Reputation: 490

You need use thread, and need use __sync_add_and_fetch and __sync_sub_and_fetch to avoid concurrency problem

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <iostream>

static void* thread(void* p) {
    int* counter = (int*)p;
    while (1) {
        if (*counter > 0) {
            __sync_sub_and_fetch(counter, 1);
            printf("sub => %d\n", *counter);
        }  else {
            sleep(1);
        }
    }

    return NULL;
}

int main() {
    int counter = 0;
    char ch;

    struct termios orig_attr, new_attr;
    tcgetattr(fileno(stdin), &orig_attr);
    memcpy(&new_attr, &orig_attr, sizeof(new_attr));
    new_attr.c_lflag &= ~(ICANON | ECHO);
    tcsetattr(fileno(stdin), TCSANOW, &new_attr);

    pthread_t pid;
    if (pthread_create(&pid, NULL, thread, &counter)) {
        fprintf(stderr, "Create thread failed");
        exit(1);
    }

    while(1) {
      char c = getchar();
      __sync_add_and_fetch(&counter, 1);
      printf("add: %d\n", counter);
    }

    return 0;
}

Upvotes: 2

pmg
pmg

Reputation: 108938

Another example using ncurses and POSIX timers and signals (and global variables).

#include <ncurses.h>
#include <signal.h>
#include <time.h>

int changed, value;

void timer(union sigval t) {
        (void)t; // suppress unused warning
        changed = 1;
        value--;
}

int main(void) {
        int ch;
        timer_t tid;
        struct itimerspec its = {0};
        struct sigevent se = {0};

        se.sigev_notify = SIGEV_THREAD;
        se.sigev_notify_function = timer;
        its.it_value.tv_sec = its.it_interval.tv_sec = 1;
        timer_create(CLOCK_REALTIME, &se, &tid);
        timer_settime(tid, 0, &its, NULL);

        initscr();
        halfdelay(1); // hit Ctrl-C to exit
        noecho();
        curs_set(0);

        for (;;) {
                ch = getch();
                if (ch != ERR) {
                        changed = 1;
                        value++;
                }
                if (changed) {
                        changed = 0;
                        mvprintw(0, 0, "%d ", value);
                        refresh();
                }
        }

        endwin();
}

Upvotes: 2

matt
matt

Reputation: 12346

Here is a pthread example that works on linux. The concept is ok, but there are probably existing loops/libraries for this.

#include <stdio.h>
#include<pthread.h>


void *timer(void* arg){
    int* counter = (int*)arg;
    while(*counter > 0){
        int a = *counter;
        printf("counter: %d \n", a);
        *counter = a - 1;
        sleep(1);
    }
}

int main(int arg_c, char** args){
    int i = 100;
    pthread_t loop;

    pthread_create(&loop, NULL, timer, &i);

    while(i>0){
        i++;
        getchar();
        printf("inc counter: %d \n", i);
    }
    printf("%d after\n", i);

    pthread_join(loop, NULL);

    return 0;
}

This starts a second thread, which has the countdown on it. That decrements the counter every second. On the main thread it has a loop with getchar. They both modify i.

Upvotes: 2

Related Questions