Reputation: 149
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
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)
clearerr(stdin)
to clear EOF
on stdin
(seems to be necessary on some platforms)Upvotes: 11
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
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
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
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 \r
s 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
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
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
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