|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T t
Length: 15896 (0x3e18) Types: TextFile Names: »tt.c«
└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987 └─⟦this⟧ »EUUGD18/General/Tt/tt.c«
/***************************************************************************\ |*+-----------------------------------------------------------------------+*| |*| |*| |*| tt.c: A version of Tetris to run on ordinary terminals, |*| |*| (ie., not needing a workstation, so should available |*| |*| to peasant Newwords+ users. |*| |*| |*| |*| Author: Mike Taylor (mirk@uk.ac.warwick.cs) |*| |*| Started: Fri May 26 12:26:05 BST 1989 |*| |*| |*| |*| Oooh look, I've just invented a new, chunkier kind of |*| |*| comment-box. I can't decide yet whether I like it. |*| |*| |*| |*+-----------------------------------------------------------------------+*| \***************************************************************************/ #include <stdio.h> #include <strings.h> #include <signal.h> #include <sys/types.h> #include <fcntl.h> #include <errno.h> #include <sys/stat.h> #include <unistd.h> #include <pwd.h> #include "tt.h" #include "utils.h" #include "screen.h" #include "game.h" /*-------------------------------------------------------------------------*/ extern time_t time (); extern char *ctime (); extern char *malloc (); extern char *getenv (); extern char *getlogin (); extern char *sprintf (); /*-------------------------------------------------------------------------*/ int screen_depth; /* To be calculated by termcap(3) */ int screen_width; /* To be calculated by termcap(3) */ int so_gunk; /* To be calculated by termcap(3) */ int in_curses = 0; /* Set to 1 after initialisation */ int rotate_backwards = 0; /* If set non-zero, rotate clockwise */ int no_hiscores = 0; /* Number of hi-scores in the table */ int no_shown = NO_SHOWN; /* Number of hi-scores to list */ int game_level = 0; /* Number of free pieces */ int score; /* Accumulated game score */ int no_pieces; /* Number of pieces dropped so far */ int no_levels; /* Number of levels filled & deleted */ char prog_name[LINELEN]; /* Will be the basename of argv[0] */ char user_name[NAMELEN]; /* From environment: TTNAME or NAME */ char user_code[CODELEN]; /* From getpwuid(getuid())->pw_name */ int board[GAME_DEPTH+4][GAME_WIDTH]; struct score_ent hi_scores[NO_HISCORES]; char tc_string[LINELEN]; /* Needed as static storage for the awful */ char *so_str; /* ... tgetstr() function. so_str and ... */ char *se_str; /* ... se_str point into it. */ /***************************************************************************\ |* *| |* This function is called if a SIGHUP, SIGINT or SIGTERM is caught, *| |* and merely returns the user to standard terminal modes, (ie. exits *| |* from curses(3X) before die()ing. *| |* *| \***************************************************************************/ void signal_end () { if (in_curses) print_msg ("Aborted!"); die (LE_OK, ""); } /***************************************************************************\ |* *| |* The function get_scores() reads the contents of the global array *| |* hi_scores from the file named in the #definition of SCORE_FILE. *| |* It also sets no_hiscores to the number of scores in the table. *| |* *| \***************************************************************************/ void get_scores () { int fd; struct stat stat_buf; if ((fd = open (SCORE_FILE, O_RDONLY)) == -1) { if (errno != ENOENT) die (LE_OPEN, "couldn't open(2) high-score file for reading"); else { no_hiscores = 0; return; } } if (fstat (fd, &stat_buf) == -1) die (LE_STAT, "couldn't stat(2) high-score file"); no_hiscores = stat_buf.st_size/sizeof (struct score_ent); if (read (fd, (char*) hi_scores, (int) stat_buf.st_size) == -1) { (void) perror ("read()"); die (LE_READ, "couldn't read(2) high-score file"); } (void) close (fd); } /***************************************************************************\ |* *| |* The function print_scores() gets the table in from the disk_file, *| |* and prints it in a nice, human-readable format. *| |* *| \***************************************************************************/ void print_scores () { int i; get_scores (); if (no_hiscores == 0) (void) puts ("There are no high-scores (yet!)"); else { (void) puts ("+------+-------------------------+---------+-------+--------+--------+------+"); (void) puts ("| Rank | Name | Code | Score | Pieces | Levels | Type |"); (void) puts ("+------+-------------------------+---------+-------+--------+--------+------+"); for (i = 0; i < ((no_hiscores < no_shown) ? no_hiscores : no_shown); i++) (void) printf ("|%5d | %-*.*s| %-8.8s|%6d |%7d |%7d |%5d |\n", i+1, NAMELEN, NAMELEN, hi_scores[i].name, hi_scores[i].code, hi_scores[i].score, hi_scores[i].no_pieces, hi_scores[i].no_levels, hi_scores[i].game_level); (void) puts ("+------+-------------------------+---------+-------+--------+--------+------+"); } } /***************************************************************************\ |* *| |* The function update_file puts a lock on the high-score-file, and *| |* then (if successful), reads the high-scores, inserts the current *| |* score in the table if it's good enough, and writes it back if it *| |* has changed, finally removing the lock. It returns the player's *| |* position in the table, or 0 if he is unplaced. If we were unable *| |* to get the lock-file, it returns -1. *| |* *| |* I'm about to do some mods allowing compilation to be done with *| |* the flag -DLOCKF to do the mutual exclusion using lockf(3) instead *| |* a lock-file. This will be unneccesary on most systems, but here on *| |* the Warwick systems, some users have titchy quotas that cause them *| |* to be unable to create a lock-file, and thus to use the high-score *| |* table. I'm using lockf(3) instead of flock(2) since it works across *| |* machines. *| |* *| |* Apologies for the cruddy way I've written this function. It has had *| |* bits added onto it in a very ad-hoc way, including the #ifdef'd bits *| |* that determine what locking mechanism is used, and the result is, *| |* shall we say, sub-optimal elegance. Particularly nasty is that way *| |* that when the lockf(3)ing locking mechanism is used, we maintain *| |* two open file-descriptors at once on the same file, but it's the *| |* quickest and easiest way to use the existing get_scores() function, *| |* and it does at least work. Since I don't anticipate anything else *| |* significant being added to the function, I'm going to leave it as it *| |* is, and not tidy it up unless I feel *really* guilty in the morning. *| |* *| \***************************************************************************/ int update_file () { int i = 0, j = 0, k = 0; /* Sundry re-usable loop-indices */ int score_fd; /* The fd through which we write new file */ int lock_fd; /* If LOCKF is defined, we use this as */ /* an auxiliary fd on the SCORE_FILE, one */ /* that stays open all the time, so we can */ /* use lockf(). Otherwise, it is the fd */ /* that we open to the LOCK_FILE */ (void) umask (0000); /* 000 octal, just to make the point */ #ifdef LOCKF if ((lock_fd = open (SCORE_FILE, O_RDWR | O_CREAT, 0666)) == -1) die (LE_OPEN, "Couldn't open(2) score-file for lockf()"); while (i++ < 5) { /* Make up to five attempts */ if (lockf (lock_fd, F_TLOCK, 0) != -1) break; /* If we succeed, then carry on */ /* Otherwise, if not due to exclusivity */ if (errno != EAGAIN) { /* then die with a system error */ print_msg ("lockf(3) error!"); (void) get_key (); return (-1); } print_msg ("Hi-score access:"); sleep (1); /* Back off and wait ... */ print_msg (""); /* Then try again */ } #else /* LOCKF */ while (i++ < 5) { /* Make up to five attempts */ if ((lock_fd = open (LOCK_FILE, O_CREAT | O_EXCL, 0666)) != -1) break; /* If we succeed, then carry on */ /* Otherwise, if not due to exclusivity */ if (errno != EEXIST) { /* then die with a system error */ print_msg ("open(2) error!"); (void) get_key (); return (-1); } print_msg ("Hi-score access:"); sleep (1); /* Back off and wait ... */ print_msg (""); /* Then try again */ } #endif /* LOCKF */ if (i > 5) /* If we tried 5 times unsuccessfully, */ return (-1); /* Then give up and return -1 instead */ get_scores (); for (i = 0; i < no_hiscores; i++) { if (((!strcmp (user_code, hi_scores[i].code)) && (game_level == hi_scores[i].game_level)) && ((score < hi_scores[i].score) || ((score == hi_scores[i].score) && ((no_pieces < hi_scores[i].no_pieces) || ((no_pieces == hi_scores[i].no_pieces) && (no_levels < hi_scores[i].no_levels)))))) { i = NO_HISCORES; /* If the same user has a better score */ break; /* on the same level, then drop this one */ } if ((score > hi_scores[i].score) || ((score == hi_scores[i].score) && ((no_pieces > hi_scores[i].no_pieces) || ((no_pieces == hi_scores[i].no_pieces) && ((no_levels > hi_scores[i].no_levels) || ((no_levels == hi_scores[i].no_levels) && ((game_level >= hi_scores[i].game_level)))))))) /* Lisp :-) */ break; /* i is the new position of the player */ } if (i == NO_HISCORES) { /* If we looped off the end of the array */ (void) close (lock_fd); /* then the score isn't good enough: */ #ifndef LOCKF /* Automagically removes advisory lockf() */ (void) unlink (LOCK_FILE); #endif /* LOCKF */ return (0); } /* If there is a matching score lower down */ /* the file, set j to it, (otherwise to i) */ for (j = NO_HISCORES-1; j >= i; j--) if ((!strcmp (user_code, hi_scores[j].code)) && (game_level == hi_scores[j].game_level)) break; /* No duplicate score found, so just */ if (j < i) { /* shunt up all other scores. */ for (j = NO_HISCORES-1; j > i; j--) bcopy ((char*) &hi_scores[j-1], (char*) &hi_scores[j], sizeof (struct score_ent)); if (no_hiscores < NO_HISCORES) no_hiscores++; } else { /* j points at a duplicate score of the */ for (k = j; k > i; k--) { /* new one, so shift bits between them */ bcopy ((char*) &hi_scores[k-1], (char*) &hi_scores[k], sizeof (struct score_ent)); } } (void) strcpy (hi_scores[i].name, user_name); (void) strcpy (hi_scores[i].code, user_code); hi_scores[i].score = score; hi_scores[i].no_pieces = no_pieces; hi_scores[i].no_levels = no_levels; hi_scores[i].game_level = game_level; if ((score_fd = open (SCORE_FILE, O_WRONLY | O_CREAT, 0666)) == -1) { perror ("open"); die (LE_OPEN, "couldn't open(2) score-file for writing"); } if (write (score_fd, (char*) hi_scores, no_hiscores*sizeof (struct score_ent)) == -1) { perror ("write"); die (LE_WRITE, "couldn't write(2) to score-file"); } (void) close (score_fd); (void) close (lock_fd); #ifndef LOCKF (void) unlink (LOCK_FILE); #endif /* LOCKF */ return (i+1); } /***************************************************************************\ |* *| |* The function get_key() reads a character from the keyboard, and *| |* performs some simple processing on it. If it's an 's', it lists *| |* the high-score table. Otherwise, it returns 1 for a 'q' or 'n', *| |* and 0 for anything else. *| |* *| \***************************************************************************/ int get_key () { char ch; (void) read (0, &ch, 1); if ((ch == 's') || (ch == 'S')) { print_scores (); (void) read (0, &ch, 1); hoopy_refresh (); print_msg ("Press a key:"); (void) read (0, &ch, 1); } print_msg (""); return ((ch == 'n') || (ch == 'N') || (ch == 'q') || (ch == 'Q')); } /***************************************************************************\ |* *| |* The main() function handles initialisation, gets keys and names from *| |* the environent, parses command-line arguments and so on. It then *| |* goes into the main loop of calling play_game(), and asking if the *| |* player wants another game, and so on. *| |* *| \***************************************************************************/ main (argc, argv) int argc; char **argv; { int i; char *cp; /* Temporary pointer for getenv() */ time_t ignore_me; /* Storage for time for random seed. */ struct passwd *pw_ptr; /* Used with getuid() to find usercode */ (void) srandom ((int) time (&ignore_me)); (void) strcpy (prog_name, basename (argv[0])); if ((i = getuid ()) == -1) die (LE_GETUID, "couldn't getuid(2)"); if ((pw_ptr = getpwuid (i)) == NULL) die (LE_GETPW, "couldn't get password entry"); (void) strncpy (user_code, pw_ptr->pw_name, CODELEN-1); user_code[CODELEN-1] = '\0'; if ((cp = getenv ("TTNAME")) == NULL) if ((cp = getenv ("NAME")) == NULL) cp = user_code; (void) strncpy (user_name, cp, NAMELEN-1); user_name[NAMELEN-1] = '\0'; if ((cp = getenv ("TTKEYS")) != NULL) { if (*cp != '\0') left_key = *(cp++); if (*cp != '\0') right_key = *(cp++); if (*cp != '\0') rotate_key = *(cp++); if (*cp != '\0') drop_key = *(cp++); if (*cp != '\0') susp_key = *(cp++); if (*cp != '\0') quit_key = *(cp++); if (*cp != '\0') cheat_key = *(cp++); } for (i = 1; i < argc; i++) switch (argv[i][0]) { case '-': switch (argv[i][1]) { case 's': if (argv[i][2] != '\0') if (((no_shown = atoi (argv[i]+2)) < 1) || (no_shown > NO_HISCORES)) { static char tmp[LINELEN]; /* To stop recursive form() */ (void) sprintf (tmp, "Number of scores must be between 1 and %d", NO_HISCORES); die (LE_LEVEL, tmp); } print_scores (); /* Flag '-s': print scores, then exit. */ exit (LE_OK); /* Not in curses => No need to call die() */ case 'b': rotate_backwards = 1; break; case 'l': if (argv[i][2] == '\0') goto USAGE_ERROR; else if (((game_level = atoi (argv[i]+2)) < -10) || (game_level > 20)) die (LE_LEVEL, "Game-level must be between -10 and 20"); break; default: goto USAGE_ERROR; } break; default: USAGE_ERROR: die (LE_USAGE, form ("Usage: %s [ -s ] [ -b ] [ -l# ]", prog_name)); } get_termcap (); if (screen_depth < GAME_DEPTH+1) die (LE_SCREEN, "screen is not deep enough"); if (screen_width < (2*GAME_WIDTH)+6+STAT_WIDTH) die (LE_SCREEN, "screen is not wide enough"); if ((signal (SIGHUP, signal_end) == BADSIG) || (signal (SIGINT, signal_end) == BADSIG) || /* (signal (SIGQUIT, signal_end) == BADSIG) || */ (signal (SIGTERM, signal_end) == BADSIG)) die (LE_SIGNAL, "couldn't set up signal-handling"); setup_curses (); setup_screen (); clear_board (); print_msg ("Press a key:"); if (get_key ()) die (LE_OK, ""); while (1) { play_game (); if ((i = update_file ()) > 0) { static char tmp[LINELEN]; /* To stop recursive form() phelgming */ (void) sprintf (tmp, "Score ranks #%d", i); print_msg (tmp); } if (i < 0) print_msg ("Save-score failed!"); if (i != 0) { flush_keyboard (); if (get_key ()) break; } print_msg ("Again?"); flush_keyboard (); if (get_key ()) break; clear_area (); } die (LE_OK, ""); } /*-------------------------------------------------------------------------*/