[net.sources.games] jumble.c

argv@sri-spam.ARPA (AAAARRRRGGGGv) (07/05/86)

*** 

The following contains the source to "jumble" -- a game I wrote
which does what you expect. All you need is a dictionary (/usr/dict/words)
and a file to write high scores in and you're set. Note at the end of
the file is a random() function for pdp's (or other 16 bit machines).
If you use this function on a sun, it'll crash when feeking (!). Most of
you will just use the normal random() anyway.

Some think that my point scoring mechanism is too easy. This may be true,
but I'm so bad at this game, I rarely get past the first level. It's
self explanitory.

Send flames, praises, suggestions, and jokes to:

argv@sri-spam.arpa (Dan Heller)
----------------------------------------------------------------------
/*
 * Jumble --  the game in which you unscramble words to compete for
 *   points on a high score list of 10 and 4 levels of
 *   difficulty to choose from. Words are found in /usr/dict/words,
 *   the basic dictionary found on all UNIX systems.
 * 
 *   This program was written by Dan Heller.
 *
 *   compile normally, but if you're on a machine with 16 bit ints, use
 *   random() at the end of this file.
 *   argv@sri-spam.ARPA
 * 
 */

/* Include files */

#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>

/*  convenience  */
#define upper(c) (isupper(c) ? c : toupper(c))
#define lower(c) (islower(c) ? c : tolower(c))
#define Upper(c) (c = upper(c))
#define Lower(c) (c = lower(c))
#define when break;case
#define otherwise break;default

/* constants */
#define time_left  (start_time + death_time - now) /* time left to guess */
#define NAMELEN		30                    /* length of name for scores */
#define WORDS_NEEDED     4            /* words needed to get on score file */
#define the_pope_is_catholic 1      /* while(1) is boring */

/* other files */
#define WORDS 		"/usr/dict/words"             /* where to get words */
#define SCOREFILE	"/usr/games/lib/jumble.scores" /* where scores live  */

char *ordinate();                   /* returns "st", "nd", etc. for numbers */
char jumble_word[40];               /* what the word we're jumbling is */
char jumbled[40];                   /* what the jumbled word is */
int debug;                          /* debug mode -- lets you cheat */
int num_of_words;                   /* how many words player chose to play */
int words_given;                    /* how many words we've given this round */
int passes;                         /* how many passes the player has used */
int total_passes;   		    /* number of passes player has */
int gotit;                          /* did player guess the jumbled word? */
int level;                          /* the difficulty of play player chose */
int length;                         /* length of the current jumbled word */
int death_time;                     /* time you have to unscramble words */
long start_time;                    /* the time of the start of each round */
long now;                           /* what time it is now */
long random();			    /* see the end of this file */
long DICTSIZE;                      /* the size of the dictionary */
FILE *dictionary;                   /* file pointer to the dictionary */

main(argc, argv)
int argc;
char **argv;
{
    char c;
    struct stat buf;
    int owner, quit(), lose();
    srandom(getpid());  /* if you're on a 16 bit machine change to srand() */
    if (!(dictionary = fopen(WORDS, "r")))     /* open the dictionary */
	printf("Sorry, can't think of any words right now.\n"), quit();
    fstat(fileno(dictionary), &buf); /* get the file status of dictionary  */
    DICTSIZE = buf.st_size;          /* got the size of the dictionary     */
    signal(SIGALRM, lose);           /* tell the alarm what function to go */
				     /* to if time runs out                */
    signal(SIGINT, quit);            /* if they ctrl-c let em quit         */
    signal(SIGQUIT,quit);            /* same as above                      */
    owner = (stat(argv[0], &buf) != -1 && buf.st_uid == getuid());
			 /* is the player the owner of the program? */
    debug = (owner && argc > 1 && !strcmp(argv[1], "-x"));
	 /* if they used a -x flag and they're the owner, enter debug mode */
    if (argc > 1 && (!strcmp(argv[1], "-s") || !strcmp(argv[1], "-n")))
	high_score(1,argv[1][1] == 'n' && owner), exit(0);
	 /* if the player just wants to see the high scores or, if the owner  */
	 /* wants to see the account names of those on the high scores list   */
    printf("Do you want directions? ");
    while ((c = getchar()) == ' ' || c == '\t');
    if(c == 'y' || c == 'Y')
	help(argv); 			      /* give help if wanted */
    while(c != '\n')
	c = getchar();   		      /* flush the input line */
    get_level();                              /* find out how badly to kill */
    while(the_pope_is_catholic) {             /* let's play */
	get_num_of_words();                   /* how many words this round? */
	gotit = 1;                            /* so far they're doing fine  */
	words_given = passes = 0;             /* basic variables get values */
	if ((total_passes = (num_of_words - (-1*level + 5))) < 0)
	    total_passes = 0;
	death_time = (long)55.0 * num_of_words * (1+level / 1.5);
	time(&start_time);                    /* game starts now */
	alarm(death_time);                    /* alarm goes off in deathtime */
	printf("%d word%s coming.\n\n", 
		num_of_words, (num_of_words > 1) ? "s" : "");
	printf("You have %d pass%s.\n", 
	     total_passes, (total_passes == 1) ? "" : "es");
	while (gotit > 0 && words_given < num_of_words) {
	    find_a_word();
	    mixit();
	    words_given++;   /* we're giving another word... */
	    printit();
	    if(!letemguess()) words_given--;
		  /* letemguess returns 0 if they passed this word */
	    else if(words_given - WORDS_NEEDED > 0) {
		printf("\nExtra time for extra words: ");
		alarm(alarm(0) + extra_credit()); /* reset alarm */
	    }
	/* they got it so give them extra credit for words
					 gotten after words needed */
	}
	if (gotit != -1) while(passes < total_passes) {
	    words_given++;  /* for each pass, give away a free word */
	    printf("Bonus for unused pass #%d: ",++passes);
	    start_time += extra_credit();
	}
	time(&now); /* get current time and check to see if the current */
		    /* score made it.  This is based on time taken to   */
		    /* unscramble all the words */
	and_now();  /* we'll talk about it when we get there */
    }
}

/*
 * get the number of words they wish to unscramble.  They have to
 * choose at least WORDS_NEEDED to get on the high score file and
 * have to choose at least 1 to play (or to exit procedure)
 */
get_num_of_words()
{
    char buf[15];
    num_of_words = 0;
    printf("You need at least %d words to get on the top ten.\n", WORDS_NEEDED);
    printf("You get extra time for unscrambling each word over the %d%s word.\n"
	,WORDS_NEEDED, ordinate(WORDS_NEEDED));
    printf("How many words do you want? ");
    while(num_of_words <= 0)
	if(strlen(gets(buf)) < 1 || (num_of_words = atoi(buf)) <= 0) 
	    printf("Use numbers (greater than 0), please: ");
    putchar('\n');
}

/*
 * just a bunch of print statments describing how to play
 */
help(argv)
char **argv;
{
    strcpy(jumble_word, getlogin()); /* get their login name and scramble it */
    mixit(); /* scramble it */
    puts("\nJumble is a game in which you have to guess what the");
    puts("jumbled word is supposed to be. For example, you would");
    printf("have %d seconds to unscramble \"%s\" to \"%s\".\n",
	    strlen(jumbled) * 9, jumbled, jumble_word);
    puts("\nTyping a '?' gives your time left and reprints the jumbled word.");
    puts("Typing a 'q' forfeits that round and tell you the current word.");
    puts("Entering 'p' (pass) reveals the current word and goes to the next.");
    printf("You get one free pass for every %d%s word.\n", 
			WORDS_NEEDED, ordinate(WORDS_NEEDED));
    printf("For each word after the %d%s that you successfully unscramble,\n", 
				    WORDS_NEEDED, ordinate(WORDS_NEEDED));
    puts("you get bonus time. That time increases more for each bonus word.");
    puts("Some words can be contructed different ways. If your guess is a");
    puts("word that works with the given letters but isn't accepted,");
    puts("try a different word.");
    puts("Since you are not penalized for wrong guesses, it is advisable");
    puts("to retype words to get the letters in a different order.");
    puts("You can see the high scores without playing by typing:");
    printf("%% %s -s\n",argv[0]);
}

/*
 * get one of four differnt difficulty levels
 * level expert finds words that are long and gives you little time
 * to unscramble them and easy gives you little words with lots of
 * time to unscramble them and the other levels are somewhere in between
 * see find_a_word() for details
 */
get_level()
{
    char c;
    level = 0;
    puts("\nDifficulty levels:");
    printf("(E)xpert, (H)ard, (M)oderate or (S)imple? ");
    while(!level)
	switch(c = getchar()) {
	    when ' ' : case '\t' : break;
	    when 'E' : case 'e' :  /* Expert */
		 level = 1;
	    when 'H' : case 'h' :  /* Hard */
		 level = 2;
	    when 'M' : case 'm' :  /* Moderate */
		 level = 3;
	    when 'S' : case 's' :  /* Simple */
		 level = 4;
	    otherwise : while(c != '\n') c = getchar(); 
				printf("E, H, M, or S: ");
	}
    while(getchar() != '\n');  /* flush input stream */
    putchar('\n');
}

/*
 * finds a random word in the dictionary.
 * first, go to random spot in the dictionary
 * then, read till the next word --  we will probably have landed
 * in the middle of a word so read till a carriage return.
 * Now, we either got to a new word, or the end of the dictionary, 
 * in which case we'll try again.
 * Else, we have our word in "jumble_word".
 * Next, find out how long the word is and if it's appropriate for 
 * the level of play, set local variable "Ok" to true and exit.
 * we don't want words with upper case letters or numbers.
 */
find_a_word()
{
    char c, ok;
    printf("Jumbling a word...");
    do  {
	ok = 0;
	fseek(dictionary, (random() % DICTSIZE), 0); 
	while((c = getc(dictionary)) != EOF && c != '\n') ;
	if(fscanf(dictionary, "%s", jumble_word) == EOF)
	    { clearerr(dictionary); continue; }
	if((length = strlen(jumble_word)) >= 5)
	    switch(level)  {         
		when 4 : if(length <= 6) ok = 1;
		when 3 : if(length <= 7) ok = 1;
		when 2 : if(length <= 8) ok = 1;
		when 1 : ok = 1;  /* anything goes */
	    }
    } while (isupper(jumble_word[0]) || isdigit(jumble_word[0]) || !ok);
    putchar('\n');
}

/*
 * mixit scrambles a word 
 * "tries" is static -- the function is recursive; if it can't scramble
 * a word the first time, we call it again from within itself.
 * First, convert the entire word to upper case letters, so that we know
 * which letters have been used and which haven't been used.  When
 * searching for a letter to randomly pick, find one that is upper case
 * put it in a random place in the jumbled word variable and change it
 * to lower case.
 * Sometimes, altho, not often, we randomly scramble a word 5 times and
 * each time, it comes out the same as the regular word.
 */
mixit()
{
    static int tries = 0;
    int count = 0, rnd_num;
    length = count = strlen(jumble_word);
    while( count-- )
	Upper(jumble_word[count]);
    count = 0;  /* reset count; -1 after loop */
    while( count < length ) {
	do rnd_num = random() % length;
	while(islower(jumble_word[rnd_num]));
	jumbled[count++] = Lower(jumble_word[rnd_num]);
    }
    jumbled[count] = 0; /* terminate string with a NULL character */
    if (!strcmp(jumble_word, jumbled)) tries++; /* is it different? */
    if (tries > 5) printf("I can't seem to jumble this word: %s\n",jumbled);
    else if(tries) mixit();
    tries = 0;
}

/*
 * prints info -- the word you need to unscramble, how much time left
 * before you lose, etc...
 */
printit()
{
    int count = num_of_words - words_given;
    time(&now);
    printf(" Time: %D min. %D sec.\n%d word%s left. ",
	   time_left / 60, time_left % 60, count, (count != 1) ? "s" : "");
    printf("'q' to quit");
    if(passes < total_passes)
	printf(", 'p' to pass (%d)", total_passes - passes);
    puts(".");
    printf("\n Word #%d: \"%s\".\n", words_given, jumbled);
}

/*
 * letemguess -- if time runs out, they lose. if they type a '?'
 * goto printit above. if 'q', terminate this round and tell them
 * the word. if they guess it, great.  if in debug mode, user types
 * 'x' and the jumbled word will be printed in parens.
 * if they type "pass", and they have passes, tell them word and
 * continue with game.
 */
letemguess()
{
    char guess[80];
    gotit = 0;
    printf("--->  Guess: ");
    while(!gotit && gets(guess))
	if (time_left <= 0) return 1;
	else if (!strlen(guess)) printf("Guess: ");
	else if (!strcmp(guess, "?")) printit(), printf("Guess: ");
	else if (!strcmp(guess, "q") || !strcmp(guess, "Q")) gotit = -1;
	else if(!strcmp(guess, jumble_word)) gotit = 1;
	else if(debug && !strcmp(guess,"x")) printf("(%s)\n",jumble_word);
	else if(!strcmp(guess,"p"))
	    if (passes >= total_passes)
		printf("You have no passes.\nGuess: ");
	    else {
		passes++;
		printf("Word was: \"%s\"\nYou have %d pass%s left.\n\n",
		      jumble_word, total_passes - passes, 
		      (total_passes - passes == 1) ? "" : "es");
		gotit = 1; /* gotit = 1 so as not to exit main loop */
		return 0;  /* return 0 if passed. */
	    }
	else printf("Nope. Guess: ");
    if(strcmp(guess, jumble_word))
	clearerr(stdin), gotit = -1; /* in case some jerk ^D's */
    time(&now);            /* get current time */
    if (gotit == -1) lose();
    else printf("You got it!\n");
    return 1;
}

/*
 * they lost either by no more time left, or they typed 'q' above.
 * set alarm(0) to turn off alarm clock and get the current time.
 * if the time_left is 0, time ran out...
 * tell them word.
 */
lose()
{
    alarm(0);
    time(&now);
    if (gotit == 1) return;  /* we're in limbo, between words, whatever... */
    if (time_left < 1)
	puts("Time ran out.");
    printf("The word was \"%s\".\n", jumble_word);
    if (time_left < 1)
	fprintf(stderr, "--Hit RETURN--"), gotit = -1;
}

/*
 * we're done with the last round, what does player want to do next?
 */
and_now()
{
    char c;
    if (gotit > 0) {
	int foo = (death_time - time_left);
	printf("Your score: %d word%s in %D min. %D sec.  (%2.2f words/min.)\n",
		 num_of_words, (num_of_words == 1) ? "" : "s", foo / 60,
		 foo % 60, (float)(words_given / (foo / 60.0)));
	high_score(0,0);
    }
    printf("\nRETURN to Play again, (C)hange skill level, (S)cores, (Q)uit: ");
    while(the_pope_is_catholic)
	switch(c = getchar()) {
	    case ' ' : case '\t' : break;
	    case '\n' :           /* play again, no changes */
		return;
	    case 'C' : case 'c' : /* get new level of play */
		while(getchar() != '\n'); get_level(); return;
	    case 'Q' : case 'q' : /* quit */
		quit();
	    case 'S' : case 's' : /* look at the scores */
		high_score(1,0);
	    default : while(getchar() != '\n');
		printf("<cr>, 'c', 's', or 'q': ");
	}
}

struct scores {
    char sc_name[NAMELEN];       /* this is what they put on the high scores */
    char sc_login[8];            /* this is for their login names */
    long sc_time;                /* time taken for that game      */
    int sc_words;                /* how many words did they play with */
    int sc_level;                /* the level of play for that person */
} top_ten[10];                   /* we only want 10 high scores.  */

high_score(Read, names)
short Read, names;   /* should we Read the scores, or attempt to enter one */
{
    struct scores *scp, *temp; /* scp is a score pointer, and temp is */
			       /* for temporary storage */
    FILE *scorefp; /* file descriptor of the score file */

    /*
     * fill all ten entries with nothing in case the high score file
     * isn't completely filled.
     */
    for (scp = top_ten; scp < &top_ten[10]; scp++) {
	scp->sc_name[0] = 0;
	scp->sc_login[0] = 0;
	scp->sc_time = 0;
	scp->sc_words = 0;
	scp->sc_level = 0;
    }

    /*
     * open score file for concurrent read/write access and
     * read the top ten file into the array 
     */
    if ((scorefp = fopen(SCOREFILE, "r+")) == 0)
	{ perror(SCOREFILE); return; }
    fread(top_ten, sizeof(struct scores), 10, scorefp);

    if (Read) {      /* Print the list */
	printf("Top Jumblers:\n");
	printf("Level\tWords\t\tTime\t    words/min\tName\n");
        for (scp = top_ten; scp < &top_ten[10]; scp++) 
		/* print only those scores that are valid (not 0)   */
	    if (scp->sc_words >= WORDS_NEEDED && scp->sc_login[0]) {
	        printf("%s\t%3d\t  %2D Min. %2D sec.     %2.2f\t%s",
		(scp->sc_level == 1) ? 
		    "Expert" : (scp->sc_level == 2) ?
		    "Hard"   : (scp->sc_level == 3) ?
		    "Medium" : "Easy",
		scp->sc_words, scp->sc_time / 60, scp->sc_time % 60, 
		(float)(scp->sc_words / (scp->sc_time / 60.0)), scp->sc_name);
		if (names)
		    printf("(%s)", scp->sc_login);
		putchar('\n');
	    }
	    else break;
    }
    /* check to see if current score made it */
    else if (num_of_words >= WORDS_NEEDED) {
	int count = 0;
	signal(SIGQUIT, SIG_IGN); /* we don't want them to leave just yet */
	signal(SIGINT, SIG_IGN);
	for (scp = top_ten; scp < &top_ten[10]; scp++)
	    if (++count && value_cmp(scp))   /* check function value_cmp */
		break;      /* if it returns true, current score made it */
	if (scp < &top_ten[10]) {
	    /*
	     * move all the other scores down one in the list
	     */
	    for (temp = &top_ten[9]; temp > scp; temp--)
		*temp = *(temp-1);
	    /* 
	     * enter currnet values into list
	     * and get his name
	     */
	    scp->sc_words = num_of_words;
	    scp->sc_level = level;
	    scp->sc_time = death_time - time_left;
	    strcpy(scp->sc_login, getlogin());
	    printf("You made the top ten! (#%d) Enter a name (%d chars): ",
			  count, NAMELEN);
	    fgets(scp->sc_name, NAMELEN, stdin);
	    scp->sc_name[strlen(scp->sc_name) - 1] = 0;
	    /*
	     * if they didn't type anything, don't enter the score
	     * else, rewind the file and enter the new score list
	     */
	    if (!strlen(scp->sc_name)) 
		printf("No name, no entry.\n");
	    else {
		rewind(scorefp);
		if (fwrite(top_ten, sizeof(struct scores), 10, scorefp) <= 0)
		    perror(SCOREFILE);
	    }
	}
	/*
	 * They didn't make the top ten, so apologize
	 * Reset the signal interrupt handlers and close the score file
	 */
	else printf("Good job. Sorry you didn't make the top ten.\n");
	signal(SIGQUIT, quit);
	signal(SIGINT, quit);
	fclose(scorefp);
    }
}

/*
 * used to compare the current values of the game just played with
 * whatever the values are in the current high score entry. If the
 * time for the entry is 0, return true because it's a garbage score.
 * Also, chekc for difficulty levels -- more difficult levels always
 * win over less difficult levels.
 * next, check to see if the average words per minute is better than
 * that of the current score entry.
 */
value_cmp(entry)
struct scores *entry;
{
    if(!entry->sc_time) return 1;
    if(level != entry->sc_level) return(level < entry->sc_level);
    return (num_of_words / (float)(death_time - time_left) >=
	 entry->sc_words / (float)(entry->sc_time));
}

/* quit -- ignore signals, close files, go away */
quit()
{
    signal(SIGQUIT, SIG_IGN);
    signal(SIGINT,  SIG_IGN);
    putchar('\n');
    fclose(dictionary);
    exit(0);
}

/*
 * extra crdit given to those brave enough to request more words than
 * needed as incentive to play for higher scores.  The extra credit is
 * given in time (seconds).
 * return the actual amount of time awarded
 */
extra_credit()
{
    int extra;
    if (level == 4) extra = 1 + random() % 5;
    else extra = ((words_given - WORDS_NEEDED) * ((-1 * level) + 4) * 3);
    printf("%d min. and %d sec. bonus time.\n", extra / 60, extra % 60);
    return extra;
}

/* ordinate() adds "th" etc... on the end of numbers */
char *
ordinate(num)
int num;
{
    if(num % 100 < 21 && num % 100 > 3) return "th";
    switch(num % 10) {
	case 1 : return "st";
	case 2 : return "nd";
	case 3 : return "rd";
	default : return "th";
    }
}

/*
If you're on a PDP, use this! if You're on a sun, it'll crash your system!
long random()
{
    return rand() * 65535L + rand() * 256L  + rand();
}
*/