/********************************************************* * * hangm.c: Web-based Hangman game * * This app dynamically generates the HTML code for a web * based hangman game. The words, along with clues, are * picked randomly from a text file. * * The game works with multiple "hangee" images, * which makes it a bit more fun. All data for the state * of the game is saved in the HTML sent to the browser. * That way, the server doesn't need to remember the state * of the multiple games going on at once. Plus, it uses * only basic HTML tags (i.e., no java, etc) so it should * work on most any platform or browser. * * Written by Eric 'Pi' Minbiole * (c) 2001 * * Special thanks to Thomas Boutell for his cgic library, * which is used to parse the CGI parameters. * (Available at www.boutell.com) * **********************************************************/ #include #include #include #include #include #include #include #include "cgic.h" /* miscellaneous constants */ #define MAX_CLUE_SIZE 100 #define MAX_WORD_SIZE 100 #define MAX_CGI_PARAM_SIZE 1000 #define WORDS_FILE_NAME "hangman.txt" /* global info about current game */ char word[MAX_WORD_SIZE+1]; char clue[MAX_CLUE_SIZE+1]; char guesses[26]; /* true or false for a...z */ char guesses_string[31]; int num_guesses; int cur_word_number; int game_over; int g_imagenum = 0; /* structure for loading in word/clue pairs from file */ typedef struct word_pair { char word[MAX_WORD_SIZE+1]; char clue[MAX_CLUE_SIZE+1]; } WORD_PAIR; /* structure to store filenames, sizes, etc */ /* of the different images to be hanged */ #define MAX_IMAGES 3 typedef struct image_info { const char *filename; const char *description; const char *difficulty; int width; int height; int top_width; int top_height; int side_width; int side_height; int bot_width; int bot_height; int max_missed_guesses; } IMAGE_INFO; /* the images */ IMAGE_INFO the_images[MAX_IMAGES] = { "vitruvian", "Vitruvian Man", "Beginner", 220, 221, /* img width & height */ 216, 39, 50, 229, 252, 31, /* gallows sizes */ 10, /* max mistakes */ "david", "David", "Intermediate", 120, 300, /* img width & height */ 123, 39, 51, 301, 252, 31, /* gallows sizes */ 6, /* max mistakes */ "vdmilo", "Venus de Milo", "Advanced", 119, 300, /* img width & height */ 144, 39, 51, 301, 252, 31, /* gallows sizes */ 4, /* max mistakes */ }; /* give up and exit */ void fatal_error(char *mess) { fprintf(cgiOut, mess); fprintf(cgiOut, "\n"); exit(0); } /* this routine computes a checksum of an integer */ /* it is used to prevent people from modifying the word */ int get_checksum(unsigned int num) { int i = 0; /* the following constants are arbitrary */ int multiplier[] = { 42, 97, 3 }; int checksum = 3; num += 2356; while (num > 0) { checksum += (num % 10) * multiplier[i]; i++; if (i >= 3) i = 0; num /= 10; } /* round it to two(ish) digits */ checksum %= 93; return (checksum); } /* given a line from the words file, parse it into the WORD_PAIR structure */ int parse_word_pair(char *buffer, WORD_PAIR *word_pair) { char seps[] = "\t\n"; char *token; /* read in the word */ token = strtok( buffer, seps ); if (token == NULL) return (0); strncpy(word_pair->word, token, MAX_WORD_SIZE); word_pair->word[MAX_WORD_SIZE - 1] = '\0'; /* read in the clue */ token = strtok( NULL, seps ); if (token == NULL) return (0); strncpy(word_pair->clue, token, MAX_CLUE_SIZE); word_pair->clue[MAX_CLUE_SIZE - 1] = '\0'; return (1); } /* read in the words file, and find the appropriate word/clue */ void load_guess_file(int desired) { FILE *fp; char line_buffer[1026], *ptr; WORD_PAIR word_pair; int index = 0; int num_pairs = 0; int found = 0; double val; /* open the translation file */ fp = fopen(WORDS_FILE_NAME, "r"); if (NULL == fp) { fatal_error("Could not open file"); } /* count number of pairs in file first */ /* (or look for the word) */ while ((ptr = fgets(line_buffer, 1024, fp)) != NULL) { line_buffer[1024] = '\0'; if (parse_word_pair(line_buffer, &word_pair)) num_pairs++; } if (num_pairs <= 0) { fatal_error("No words to use"); fclose(fp); } if (desired < 0) { /* chose a word at random */ srand((int)time(NULL)); val = rand(); val /= RAND_MAX; desired = (int)(val * num_pairs); } if (desired >= num_pairs) desired = num_pairs - 1; cur_word_number = desired; rewind(fp); /* start at beginning again */ /* find correct word */ while ((ptr = fgets(line_buffer, 1024, fp)) != NULL) { if (parse_word_pair(line_buffer, &word_pair)); { if (index == desired) { strcpy(word, word_pair.word); strcpy(clue, word_pair.clue); found = 1; break; } index++; } } if (!found) { fatal_error("could not get word"); } } #define CHECKSUM_SIZE 2 #define LINE_SIZE 4 char cgiparams[MAX_CGI_PARAM_SIZE + 1]; /* format the parameters that we'll send back to ourself */ /* with the next guess. The parameters are in the formt: */ /* s=(unique session id) Used to prevent browser caching */ /* w=(word number with checksum) */ /* g=(string of guesses) */ /* i=(image index) */ void FormCgiParams(char c) { char extra_guess[2] = {0, 0}; if ((c >= 'a') || (c <= 'z')) { /* append the extra char, if necessary */ extra_guess[0] = c; } sprintf(cgiparams, "s=%04d&w=%0*d%0*d&g=%s%s&i=%d", time(NULL)%1000, LINE_SIZE, cur_word_number, CHECKSUM_SIZE, get_checksum(cur_word_number), guesses_string, extra_guess, g_imagenum); } /* format the choices, A-Z. Only make the unchosen ones clickable */ void DisplayValidChoices(void) { int i; /* nice, big, bold font */ fprintf(cgiOut, "
\n"); /* loop through all the letters */ for (i = 0; i < 26; i++) { if ((guesses[i] == 0) && (game_over == 0)) { FormCgiParams((char)(i + 'a')); fprintf(cgiOut, "%c \n", cgiparams, i + 'A'); } else { fprintf(cgiOut, "%c \n", i + 'A'); } /* start 'n' on a new line */ if (i == 12) fprintf(cgiOut, "
"); } /* close the font tag */ fprintf(cgiOut, "
\n"); } /* read the word number from the string. */ /* make sure the checksum matches */ int ExtractWordNum(char *buf) { int wordnum, checksum, i; char word_str[10]; char checksum_str[10]; /* make sure it's all numbers */ for (i = 0; i < CHECKSUM_SIZE + LINE_SIZE; i++) { if (!isdigit(buf[i])) return(-1); } /* make sure it's correct len */ if (buf[i] != '\0') { return (-1); } /* extract the checksum */ memcpy(checksum_str, buf + LINE_SIZE, CHECKSUM_SIZE); checksum_str[CHECKSUM_SIZE] = '\0'; checksum = atoi(checksum_str); /* extract the word num */ memcpy(word_str, buf, LINE_SIZE); word_str[LINE_SIZE] = '\0'; wordnum = atoi(word_str); /* debug info */ #if 0 printf("wordnum: %d, checksum: %d, correct: %d.\n", wordnum, checksum, get_checksum(wordnum)); #endif /* verify */ if (get_checksum(wordnum) != checksum) { return(-1); } return(wordnum); } int CountMissedGuesses(void) { int correct_guesses = 0; unsigned int i, j; char c; /* loop through all guesses, finding mistakes */ for (i = 0; i < strlen(guesses_string); i++) { c = guesses_string[i]; for (j = 0; j < strlen(word); j++) { if (tolower(word[j]) == c) { /* this guess was correct */ correct_guesses++; break; } } } return (num_guesses - correct_guesses); } /* if all the letters in the word were guessed, */ /* the player won */ int PlayerWon(void) { unsigned int i; char c; for (i = 0; i < strlen(word); i++) { c = tolower(word[i]); if ((c >= 'a') && (c <= 'z')) { if (guesses[c - 'a'] == 0) return(0); /* found a missing letter */ } } return (1); } /* Loads the game from the parameters passed in the URL, if possible */ /* if not possible, start a new game */ void LoadGame(IMAGE_INFO **imgptr, int *MissedGuesses) { int i; int valid_saved_game = 0; int result1, result2; unsigned int wordnum = 0; char word_string[20]; char c; /* get the image number, and default to "David" */ cgiFormIntegerBounded("i", &g_imagenum, 0, MAX_IMAGES - 1, 1); *imgptr = &the_images[g_imagenum]; result1 = cgiFormStringNoNewlines("w", word_string, 20); result2 = cgiFormStringNoNewlines("g", guesses_string, 30); if ((result1 == cgiFormSuccess) && (result2 == cgiFormSuccess)) { valid_saved_game = 1; num_guesses = strlen(guesses_string); if (num_guesses < 1) valid_saved_game = 0; wordnum = ExtractWordNum(word_string); if (wordnum < 0) valid_saved_game = 0; if (valid_saved_game) { /* try to load this word */ load_guess_file(wordnum); } /* fill out the table of guesses */ for (i = 0; (i < num_guesses) && (valid_saved_game); i++) { c = guesses_string[i]; if ((c > 'z') || (c < 'a')) { /* this is not a valid choice */ valid_saved_game = 0; break; } guesses[c - 'a'] = 1; } } if (!valid_saved_game) { /* we don't have a valid game. Start a new one */ /* get a random word from the file */ load_guess_file(-1); /* reset the guesses */ for (i = 0; i < 26; i++) guesses[i] = 0; num_guesses = 0; guesses_string[0] = 0; } /* see if we've had too many mistakes */ *MissedGuesses = CountMissedGuesses(); if (*MissedGuesses >= (*imgptr)->max_missed_guesses) game_over = 1; } void DisplayGameInfo(IMAGE_INFO *imgptr) { char c; unsigned int count = 0, j; int mistakes_left = 0; fprintf(cgiOut, "
Clue: %s
\n", clue); /* tell 'em if they won/lost */ if (PlayerWon()) { fprintf(cgiOut, "You Win!
 \n"); game_over = 1; return; } if (game_over) { fprintf(cgiOut, "Game Over
 \n"); return; } /* see if the last guess was correct */ if (strlen(guesses_string) > 0) { /* count the occurences of the last guess */ c = guesses_string[strlen(guesses_string) - 1]; for (j = 0; j < strlen(word); j++) { if (tolower(word[j]) == c) { count++; } } /* tell 'em how many were found */ c = toupper(c); if (count == 1) { fprintf(cgiOut, "Yes, there is one %c. ", c); } else if (count > 1) { fprintf(cgiOut, "Yes, there are %d %c's. ", count, c); } else { fprintf(cgiOut, "Sorry, there were no %c's. ", c); } } /* display the number of mistakes left */ mistakes_left = imgptr->max_missed_guesses - CountMissedGuesses(); fprintf(cgiOut, "You have %d mistake%s left.
 
\n", mistakes_left, mistakes_left == 1 ? "" : "s"); } void DisplayHangmanImages(IMAGE_INFO *imgptr, int MissedGuesses) { /* show the top of the gallows */ fprintf(cgiOut, "\n", imgptr->filename, imgptr->top_width, imgptr->top_height); fprintf(cgiOut, "
\n"); /* show the vertical section of the gallows */ fprintf(cgiOut, "\n", imgptr->filename, imgptr->side_width, imgptr->side_height); /* put the appropriate pic inside the gallows */ if (MissedGuesses > 0) { fprintf(cgiOut, "\n", imgptr->filename, MissedGuesses - 1, imgptr->width, imgptr->height); } fprintf(cgiOut, "
\n"); /* show the bottom of the gallows */ fprintf(cgiOut, "\n", imgptr->filename, imgptr->bot_width, imgptr->bot_height); } void DisplayNewGameForm(void) { int i; IMAGE_INFO *imgptr; /* start the form */ fprintf(cgiOut, "
\n"); /* add a select box, with a choice for each image */ fprintf(cgiOut, "
\n"); /* add a hidden field with a pseudorandom session id. This keeps browsers from caching the page */ fprintf(cgiOut, "\n", time(NULL) % 1000); /* add a "New Game" button and close the form */ fprintf(cgiOut, "
\n"); } /* format the word in its current state, */ /* using dashes for unguessed letters */ void FormatWord(void) { unsigned int i; char c; char str[100]; /* really big, bold font */ fprintf(cgiOut, "
 \n"); /* loop through the entire word */ for (i = 0; i < strlen(word); i++) { c = word[i]; c = tolower(c); if ((c >= 'a') && (c <= 'z')) { /* it's a letter: display the letter or a dash */ if ((guesses[c - 'a']) || (game_over)) sprintf(str, "%c", word[i]); else strcpy(str, "_ "); } else if (c == ' ') { /* output the space */ strcpy(str, "  "); } else { /* other character: just echo it */ sprintf(str, "%c", c); } fprintf(cgiOut, "%s", str); } /* close out the font */ fprintf(cgiOut, "
\n"); } /* main entry point */ int cgiMain() { int MissedGuesses = 0; IMAGE_INFO *imgptr; #if DEBUG /* Load a saved CGI scenario if we're debugging */ cgiReadEnvironment("capcgi.dat"); #endif cgiHeaderContentType("text/html"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "Online Hangman\n"); fprintf(cgiOut, "

Online Hangman

\n"); /* load the current game from the params */ LoadGame(&imgptr, &MissedGuesses); /* start a table: left = picture, right = game info */ fprintf(cgiOut, "
\n"); /* format the four pics that make up the hangman */ DisplayHangmanImages(imgptr, MissedGuesses); /* move to the right half of the table & display the word & game info */ fprintf(cgiOut, "\n"); FormatWord(); DisplayGameInfo(imgptr); DisplayValidChoices(); /* display the form */ DisplayNewGameForm(); /* add some additional links */ fprintf(cgiOut, "

About the game\n"); fprintf(cgiOut, "
Home
\n"); /* finish the table and html */ fprintf(cgiOut, "
\n\n"); /* exit success */ return 0; }