From: Matthew Mondor Date: Mon, 10 Apr 2023 15:34:47 +0000 (+0000) Subject: Reimplement the way the game timing is done, simplifying it and X-Git-Url: http://git.pulsar-zone.net/?a=commitdiff_plain;h=69b705f427ab7a16c377a1fe8ed174d24095c158;p=curblaster.git Reimplement the way the game timing is done, simplifying it and keeping the code more easily portable to non-unix systems. Some timing artifacts are more detectable than when using setitimer(2) but they are still minimal versus the original implementation using halfdelay(). Those were slightly less apparent when restoring the maximum speed to 4, that was previously customized. It is suspected that some other code needed to be adapted for that limit to be changed gracefully. --- diff --git a/MATT-README.txt b/MATT-README.txt index a1ffa03..1a74558 100644 --- a/MATT-README.txt +++ b/MATT-README.txt @@ -51,18 +51,15 @@ GENERAL CHANGES not all SDL utility libs were ported. This should really be redone in an autoconf friendly way. -- Increased the maximum player speed from 4 to 5 in process_direction(). - - Introduced randmac.h which now defaults to srandom(3) and random(3) rather than weaker srand(3)/rand(3). Easy to change via the definitions. - Added an endwin(3) cleanup handler as well as error check for - initscr(3). Because some systems like OSX also do not properly - restore the original terminal state at endwin(3), explicit termios - tcgetattr(2)/tcsetattr(2) and cleanup handler were added. + initscr(3). -- Disable visible cursor in main input routine stable_getch(). +- Disable the visible cursor in the main input routine, now + frame_getch(). BUGS FIXED @@ -71,9 +68,8 @@ BUGS FIXED - A bug existed producing more than one screen clear per frame. The resulting blinking was not noticed on refresh-rate controlled AnalogTerm2 that is somewhat equivalent to double-buffering, but - was more visible in XTerm and URxvt. This was fixed by using - clearok(win, FALSE) after the main clear(), so that subsequent - [w]refresh() avoid implicit extra re-clearing. + was more visible in XTerm and URxvt. This was fixed by using erase() + instead of clear() in the redrawing routine. - Objects high up on the map appeared on the floor rather than on the first map line. Line = 0 was supposed to be line = 1. @@ -85,7 +81,19 @@ BUGS FIXED last user key if any is acknowledged at the next clock tick. I had a previous successful test of such an interactive program using getch(3) for input and setitimer(2). The clock was set - for 10 FPS, which appeared to be the intened rate. + for 10 FPS, which appeared to be the intened rate. For more + safety, SIGALRM was temporarily masked during curses refresh, to + avoid potentially inadvertently interrupting necessary syscalls + it may not implicitly retry on EINTR. + + A second solution was eventually implemented that simply relies + on usleep(3) for a fixed period, approximately the time of a + frame, then using a non-blocking (timeout of 0) getch(3) call to + determine if some user input occurred meanwhile. If so, to avoid + a situation where the input queue grows delaying all user commands, + the input queue is flushed. While this is less accurate timewise, + it requires much less code and is expected to be easier to port + to non-unix systems. - ncurses and curses support the interpretation of terminal-specific arrow sequences but the original code did not use it, instead diff --git a/src/main.cpp b/src/main.cpp index 75ddbb9..05c0d2c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,17 +22,13 @@ along with this program. If not, see . #include #include #include -#include #include #include -#include #include #include -#include #include #include #include -#include #include #include #ifdef __USE_SDL__ @@ -66,67 +62,14 @@ along with this program. If not, see . #define ANIM_FPS 10 /* 10Hz ticks */ #define TEMP_SHIELD (ANIM_FPS * 2) /* 2 secs */ -static void clock_init(void); -static void sighandler(int); static void cleanup(void); +static int frame_getch(void); +static int halfdelay_getch(void); +int block_getch(void); -static bool clock_expired = false; -static sigset_t block_mask; - -static int uc = ERR; static WINDOW *win = NULL; -void -clock_init(void) -{ - struct sigaction act; - struct itimerval itv; - - /* Setup signal handler */ - act.sa_handler = sighandler; - act.sa_flags = 0; /* ~SA_RESTART to detect interruptions */ - if (sigemptyset(&act.sa_mask) != 0) - err(EXIT_FAILURE, "sigemptyset()"); - if (sigaction(SIGALRM, &act, NULL) != 0) - err(EXIT_FAILURE, "sigaction(SIGALRM)"); - if (sigaction(SIGINT, &act, NULL) != 0) - err(EXIT_FAILURE, "sigaction(SIGINT)"); - if (sigaction(SIGTERM, &act, NULL) != 0) - err(EXIT_FAILURE, "sigaction(SIGTERM)"); - if (sigaction(SIGHUP, &act, NULL) != 0) - err(EXIT_FAILURE, "sigaction(SIGHUP)"); - - /* Block mask */ - (void)sigemptyset(&block_mask); - (void)sigaddset(&block_mask, SIGALRM); - - /* Setup interval timer */ - timerclear(&itv.it_interval); - timerclear(&itv.it_value); - itv.it_interval.tv_sec = 0; - itv.it_interval.tv_usec = 1000000 / ANIM_FPS; - itv.it_value.tv_sec = itv.it_interval.tv_sec; - itv.it_value.tv_usec = itv.it_interval.tv_usec; - if (setitimer(ITIMER_REAL, &itv, NULL) != 0) - err(EXIT_FAILURE, "setitimer()"); -} - -static void -sighandler(int sig) -{ - - switch (sig) { - case SIGALRM: - clock_expired = true; - break; - case SIGINT: - case SIGTERM: /* FALLTHROUGH */ - case SIGHUP: /* FALLTHROUGH */ - exit(EXIT_SUCCESS); - break; - } -} static void cleanup(void) @@ -139,83 +82,57 @@ cleanup(void) } /* - * This getch(3) variant is designed to wait until the next clock tick and to - * return user input if any, also using the curses internal terminal sequence - * interpretation for special keys like arrows. ERR is returned if there was - * no user input. The wgetch(3) function reads in blocking mode and could - * wait forever, but is awaken by the regular interval timer that sets an - * expired flag. If it awakes because of user input before the timer expired, - * we record the key and block on read again. This ensures that unlike with - * halfdelay(3), we have a stable clock even when extensive user input occurs. + * Since the program also already uses getch(3) in other ways, this variant + * sleeps the time of a frame then verifies for user input in non-blocking + * mode. It also temporarily disables the cursor, leaving other uses to still + * have one. Since with this mode, especially when fast repeating characters + * are received, the input queue can start filling with only one character + * processed per frame, delaying actions to user commands. For this reason, + * when a successful character is received, the input queue is also cleared. + * XXX For some reason, despite using this method the game appears to + * accelerate a bit at user input, although less drasticly than it used to + * with only halfdelay(3), as originally implemented. */ static int -stable_getch(void) +frame_getch(void) { int c; (void)curs_set(0); - (void)keypad(win, TRUE); - (void)timeout(-1); - for (clock_expired = false; !clock_expired; ) { - if ((c = wgetch(win)) != ERR) - uc = c; - } + (void)usleep(1000000 / ANIM_FPS); - c = uc; - uc = ERR; - (void)curs_set(1); + (void)keypad(win, TRUE); + (void)timeout(0); + + c = wgetch(win); + if (c != ERR) + (void)flushinp(); return c; } -void -safe_refresh(void) +static int +halfdelay_getch(void) { - sigset_t set; - - /* Temporarily block the SIGALRM signal during output syscalls */ - set = block_mask; - (void)sigprocmask(SIG_BLOCK, &set, NULL); - (void)wrefresh(win); + (void)halfdelay(1); + (void)curs_set(1); - set = block_mask; - (void)sigprocmask(SIG_UNBLOCK, &set, NULL); + return wgetch(win); } - - -/* - * On some systems like OSX [n]curses endwin() is not enough to properly - * restore the original terminal state. We can do it explicitly. - */ - -static void term_save(void); -static void term_load(void); - -static struct termios old_tios; - -static void -term_save(void) +int +block_getch(void) { - if (tcgetattr(STDIN_FILENO, &old_tios) == -1) - err(EXIT_FAILURE, "tcgetattr()"); - /* Exit cleanup hook to restore normal terminal */ - (void)atexit(term_load); - -} - -static void -term_load(void) -{ + (void)timeout(-1); + (void)curs_set(1); - (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_tios); + return wgetch(win); } - static void tempshield(void); int shieldup = 0; @@ -657,9 +574,6 @@ int main(int argc, char *argv[]){ missiles[missileloop].chase = -1; }; - clock_init(); - term_save(); - if ((win = initscr()) == NULL) err(EXIT_FAILURE, "initscr()"); @@ -676,8 +590,6 @@ int main(int argc, char *argv[]){ (void)atexit(cleanup); - //main loop - int loopvar = 0; int podcount = 0; int podsin = 0; @@ -691,8 +603,8 @@ int main(int argc, char *argv[]){ int drawlaser = 0; cbreak(); - halfdelay(1); /* XXX */ noecho(); + nonl(); //print title screen @@ -851,13 +763,12 @@ int main(int argc, char *argv[]){ tempshield(); - //hold until SPACE pressed + // Intro screen, hold until SPACE pressed while blinking pause_game = 0; int counter = 0; int show_controls = 30; while(pause_game!=' '){ - //cbreak(); - pause_game = getch(); + pause_game = halfdelay_getch(); if(counter>=4){ //GO! mvprintw(18,28,"Press SPACE to start"); @@ -869,8 +780,8 @@ int main(int argc, char *argv[]){ mvprintw(18,28," "); mvprintw(23,79,"-"); }; + refresh(); counter++; - //halfdelay(1); }; #ifdef __USE_SDL__ @@ -879,14 +790,14 @@ int main(int argc, char *argv[]){ title = NULL; #endif + //main loop + while(loopvar == 0){ int input = 0; - clear(); - /* Without this the next wrefresh() clears twice producing unnecessary - * flickering on some terminals. */ - clearok(win, FALSE); + /* Prevents needing clearok() */ + erase(); // Draw board draw_board(score, lives, level, shieldsleft, missile); @@ -1171,21 +1082,20 @@ int main(int argc, char *argv[]){ //get the cursor out of the way mvprintw(23,79,"|"); - - input = stable_getch(); - + // Sleeps for a frame then checks for any input + input = frame_getch(); + //quit if(input=='q'){ char quit; - cbreak(); clear(); printw("Really quit (y/n)? "); - for (quit = ERR; quit != 'y' && quit != 'n'; quit = getch()) ; + refresh(); + for (quit = ERR; quit != 'y' && quit != 'n'; quit = block_getch()) ; if(quit=='y'){ printw("\nBye Bye!\n"); loopvar=1; }; - halfdelay(1); }; //check for/toggle shield @@ -1267,11 +1177,10 @@ int main(int argc, char *argv[]){ if(input==' '){ pause_game = 0; while(pause_game!=' '){ - cbreak(); display_controls(); mvprintw(10,25,"Paused. Press SPACE to continue.\n"); - pause_game = getch(); - halfdelay(1); + refresh(); + pause_game = block_getch(); }; }; @@ -1941,7 +1850,6 @@ int main(int argc, char *argv[]){ //start new game if applicable if(newgame==1){ - halfdelay(1); //Reset lives lives = 4; //Reset level @@ -2086,20 +1994,20 @@ int main(int argc, char *argv[]){ //Print goodies, getch pause_game = 0; while(pause_game!=' '){ - cbreak(); mvprintw(10,20,"Level %d Completed. Press SPACE to continue.\n", level); - pause_game = getch(); + refresh(); + pause_game = block_getch(); }; if(level==24){ pause_game = 'f'; while(pause_game!=' '){ mvprintw(11,21,"Victory!!!! Score: %d Press SPACE to exit\n", score); - pause_game = getch(); + refresh(); + pause_game = block_getch(); loopvar = 1; }; }; - halfdelay(1); //Advance level level++; //Drop shields XXX diff --git a/src/main.h b/src/main.h index 70d68a9..1361d1e 100644 --- a/src/main.h +++ b/src/main.h @@ -1,6 +1,6 @@ #ifndef __MAIN_H__ #define __MAIN_H__ -void safe_refresh(void); +int block_getch(void); #endif diff --git a/src/mishaps.cpp b/src/mishaps.cpp index 485399c..cb8a36c 100644 --- a/src/mishaps.cpp +++ b/src/mishaps.cpp @@ -64,7 +64,7 @@ int boom_object(int drawlocation, game_object boomstuff, game_object object){ }; //get the cursor out of the way mvprintw(23,79,"-"); - safe_refresh(); + refresh(); }; }; }; @@ -87,11 +87,10 @@ int life_loss(int lives, int score){ if(lives>=1){ pause_game = 0; while(pause_game!=' '){ - cbreak(); mvprintw(10,20,"Boom. Press SPACE to continue.\n"); - pause_game = getch(); + refresh(); + pause_game = block_getch(); }; - halfdelay(1); } else { pause_game = 0; @@ -121,12 +120,12 @@ int life_loss(int lives, int score){ }; while(pause_game!='y'&&pause_game!='n'){ - cbreak(); mvprintw(10,20,"GAME OVER. Score:%d Play again(y/n)?\n", score); if(score>hscore){ mvprintw(11,20,"High Score: %s", outstring); }; - pause_game = getch(); + refresh(); + pause_game = block_getch(); }; if(pause_game=='n'){ endwin(); diff --git a/src/motion.cpp b/src/motion.cpp index 49872e4..89d1e40 100644 --- a/src/motion.cpp +++ b/src/motion.cpp @@ -277,7 +277,7 @@ game_object process_direction(game_object object, int input){ if(input==4||input==1||input==7){object.face=0;}; if(input==6||input==3||input==9){object.face=1;}; - if(object.speed>5){object.speed=5;}; + if(object.speed>4){object.speed=4;}; return object; }