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
- 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.
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
#include <ncurses.h>
#include <cstring>
#include <cstdlib>
-#include <time.h>
#include <cstdio>
#include <math.h>
-#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
-#include <signal.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string>
-#include <termios.h>
#include <unistd.h>
#include <vector>
#ifdef __USE_SDL__
#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)
}
/*
- * 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;
missiles[missileloop].chase = -1;
};
- clock_init();
- term_save();
-
if ((win = initscr()) == NULL)
err(EXIT_FAILURE, "initscr()");
(void)atexit(cleanup);
- //main loop
-
int loopvar = 0;
int podcount = 0;
int podsin = 0;
int drawlaser = 0;
cbreak();
- halfdelay(1); /* XXX */
noecho();
+ nonl();
//print title screen
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");
mvprintw(18,28," ");
mvprintw(23,79,"-");
};
+ refresh();
counter++;
- //halfdelay(1);
};
#ifdef __USE_SDL__
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);
//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
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();
};
};
//start new game if applicable
if(newgame==1){
- halfdelay(1);
//Reset lives
lives = 4;
//Reset level
//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