/* -------------------------------------------------------------------------
* Title: Hangman v1.0 (bilingual)
*
* From: migf1 (mig_f1@hotmail.com)
*
* Description:
* A rather simplistic implementation of the Hangman game, written in C (C99)
* This is NOT AI! The human player makes the guesses!
*
* Notes:
* The program WILL NOT run without its accompanying world-list files:
* - en_scrabble.dic: SCRABBLE tournament Englih words (172804 words)
* - el_ispell_small.dic: small version of iSPELL Greek words (76496 words)
*
* To play with Greek words you MUST USE Windows1253 8-BIT FONTS at your terminal
* AND compile the source code with GCC 4.x
*
* Implementation:
* When the user specifies the desired length for the secret word,
* the program loads from the dictionary all the words with the specified
* length into a singly linked-list. It then picks a word randomly and
* destroys the list.
*
* At the guessing prompt, the user may enter either a single letter or
* a whole word. In the latter case, if the entered word MATCHES the
* secret word, we have a winner and the moves loop stops. Otherwse the
* 1st letter of the entered word is checked against the secret letters.
*
* The program keeps track of all the letters already typed in by the user
* and it doesn't let him re-enter them. It also keeps track of the number
* of letters remained to be found, using it as a condition to decide whether
* the user has found the secret word or not (missing == 0).
*
* In general, all input is validated in real time, keep prompting the user
* until he enters valid input.
*
* You can play an infinite number of rounds (new words of the same length)
* as long as you answer positively when you are asked for one more round.
* Each round consists of a user-defined number of moves (9 to 13)
*
* -------------------------------------------------------------------------
*/
#include <stdio.h>
#include <stdlib.h> // for atoi(), exit()
#include <string.h> // for strlen()
#include <time.h> // for time()
#include <ctype.h> // for toupper(), isblank()
#define MAXINPUT 255+1 // max # of chars read from user input
#define MINMOVES 9 // minimum allowed guesses
#define MAXMOVES 13 // maximum allowed guesses
#define DIC_FNAME_EN "en_scrabble.dic" // filename of english wordlist
#define DIC_FNAME_EL "el_ispell_small.dic" // filename of greek wordlist
#define MAX_WLEN 20+1 // maximum allowed lebgth of secret word
#define MIN_WLEN 4+1 // minimum allowed lebgth of secret word
// our alphabet of allowed letters
#define ALPHABET_UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ"
#define MIX(x,y) (x) < (y) ? (x) : (y)
#define CREDITS() printf("\n%78s", "H A N G M A N 1.0 (by migf1) \n")
#define PAUSE() \
do{ \
char myinbufr[255]=""; \
printf("\npress ENTER to continue..."); \
fgets(myinbufr, 255, stdin); \
}while(0)
typedef enum { FALSE=0, TRUE } Bool; // our custom boolean type
typedef struct node { // node struct for the words linked-list
char word[ MAX_WLEN ];
struct node *next;
} Node;
typedef struct { // struct for the words linked-list
Node *head, *tail;
long int len;
} List;
/* ------------------------------------------------------------------
* Read up to len-1 chars from stdin into string s & null terminate it
*/
char *s_get(char *s, size_t len)
{
char *cp;
for (cp
=s
; (*cp
=getc(stdin
)) != '\n' && (cp
-s
) < len
-1; cp
++ ) ; // for-loop with empty body
*cp = '\0'; // null-terminate
return s;
}
/* ------------------------------------------------------------------
* Convert char c to uppercase (explicit care is taken for Greek 8-bit chars)
*/
char to_upper( int c )
{
switch( c )
{
case 'α':
case 'ά':
c = 'Α';
break;
case 'β':
c = 'Β';
break;
case 'γ':
c = 'Γ';
break;
case 'δ':
c = 'Δ';
break;
case 'ε':
case 'έ':
c = 'Ε';
break;
case 'ζ':
c = 'Ζ';
break;
case 'η':
case 'ή':
c = 'Η';
break;
case 'θ':
c = 'Θ';
break;
case 'ι':
case 'ί':
c = 'Ι';
break;
case 'ϊ':
case 'ΐ':
c = 'Ϊ';
break;
case 'κ':
c = 'Κ';
break;
case 'λ':
c = 'Λ';
break;
case 'μ':
c = 'Μ';
break;
case 'ν':
c = 'Ν';
break;
case 'ξ':
c = 'Ξ';
break;
case 'ο':
case 'ό':
c = 'Ο';
break;
case 'π':
c = 'Π';
break;
case 'ρ':
c = 'Ρ';
break;
case 'σ':
case 'ς':
c = 'Σ';
break;
case 'τ':
c = 'Τ';
break;
case 'υ':
case 'ύ':
c = 'Υ';
break;
case 'ϋ':
case 'ΰ':
c = 'Ϋ';
break;
case 'φ':
c = 'Φ';
break;
case 'χ':
c = 'Χ';
break;
case 'ψ':
c = 'Ψ';
break;
case 'ω':
case 'ώ':
c = 'Ω';
break;
default:
break;
}
return c;
}
/* ------------------------------------------------------------------
* Convert to uppercase all chars of the null-terminated string s
*/
char *s_toupper(char *s)
{
char *ret;
for ( ret=s; (*s=to_upper(*s)); s++ )
;
return ret;
}
/* ------------------------------------------------------------------
* Copy up to n-1 chars of string src into string dst (both strings are null-terminated)
*/
char *s_ncopy( char *dst, const char *src, int n )
{
char *ret = dst;
while ( (dst-ret) < n-1 && (*dst=*src) != '\0' )
dst++, src++;
if ( *dst )
*dst = 0;
return ret;
}
/* ------------------------------------------------------------------
* Trim leading & trailing spaces from a null-termintated string s
*/
char *s_trim( char *s )
{
char *cp1; // for parsing the whole s
char *cp2; // for shifting & padding
// trim leading & shift left remaining
for (cp1
=s
; isblank(*cp1
); cp1
++ ) // skip leading spaces, via cp1 ;
for (cp2=s; *cp1; cp1++, cp2++) // shift left remaining chars, via cp2
*cp2 = *cp1;
*cp2-- = '\0'; // mark end of left trimmed s
// replace trailing spaces with '\0's
*cp2-- = '\0'; // pad with '\0'
return s;
}
/* ------------------------------------------------------------------
* Append char c into null-terminated string s (if there's no room, s is returned intact)
*/
char *s_cappend( char *s, const char c, const int slen )
{
if ( !s )
return NULL;
char *cp;
for (cp=s; *cp && (cp-s) < slen-1; cp++)
;
if ( cp-s < slen-1 ) {
*cp = c;
*++cp = '\0';
}
return s;
}
/* ------------------------------------------------------------------
* Insert data as LAST node of list
*/
Bool list_append( List *list, char *data )
{
Node
*new
= calloc(1, sizeof(Node
) ); if ( !new )
return FALSE;
s_ncopy(new->word, data, MAX_WLEN);
new->next = NULL;
if ( list->head == NULL ) {
list->head = list->tail = new;
(list->len)++;
return TRUE;
}
list->tail->next = new;
list->tail = new;
(list->len)++;
return TRUE;
}
/* ------------------------------------------------------------------
* Return a pointer to the ith node of list
*/
Node *list_getinode( List list, long int i )
{
long int j;
for (j=0; j < i && list.head; j++)
list.head = list.head->next;
return list.head;
}
/* ------------------------------------------------------------------
* Print list contents
*/
void list_print( List list )
{
while ( list.head ) {
printf("\t%s\n", list.
head->word
); list.head = list.head->next;
}
return;
}
/* ------------------------------------------------------------------
* Free memory reserved for list
*/
void list_destroy( List *list )
{
if ( list->head == NULL )
return;
Node *dummy = NULL;
while ( list->head ) {
dummy = list->head->next;
list->head = dummy;
}
return;
}
/* ------------------------------------------------------------------
* Helper function, for reading the input buffer, trimming leading & trailing blanks
* and convert it to uppercase
*/
char *read_input( char *input, const int maxinput )
{
if ( !input )
return NULL;
s_get(input, maxinput);
s_trim( s_toupper(input) );
return input;
}
/* ------------------------------------------------------------------
* Print the secret word.
*
* Already found letters must be contained inside the null-terminated
* string: found. If so, they get printed at their correct positions
* inside the secret word, otherwise a dash is printed instead of the letter.
*
* IMPORTANT:
* The function returns the number of letters that have NOT been found
* yet! We use this information to control the main loop of the game.
* Specidically, we stop the loop when this number gets equal to 0
* because that means ALL the letters of the secret word have been found!
*/
int print_secret( char *secret, char *found )
{
register int i, ret=0;
for (i=0; secret[i] != '\0'; i++)
{
if ( strchr(found
, secret
[i
]) ) { ret++;
}
else
}
return i-ret;
}
/* ------------------------------------------------------------------
* Ask the user to choose which dictionary will the program use for pickicng
* later on the secret word.
*/
void get_dic( char *dic_fname, const char *en_fname, const char *el_fname )
{
char input[MAXINPUT] = "";
CREDITS();
printf("%76s\n", "------------------------------"); printf("%70s\n", "en\tEnglish dictionary"); printf("%70s\n", "el\tΕλληνικό λεξιλόγιο"); printf("%76s\n", "------------------------------"); do {
printf("%62s","Select (en/el): "); read_input(input, MAXINPUT);
}while ( strncmp("EN", input
, MAXINPUT
) && strncmp("EL", input
, MAXINPUT
) ); if ( strncmp(input
, "EN", MAXINPUT
) == 0 ) s_ncopy(dic_fname, en_fname, MAXINPUT);
else
s_ncopy(dic_fname, el_fname, MAXINPUT);
strncmp(input
, "EN", MAXINPUT
) == 0 ? "OFFICIAL SCRABBLE WORD LIST" :
"με χρήση ΕΛΛΗΝΙΚΟΥ λεξιλογίου"
);
if ( strncmp(input
, "EL", MAXINPUT
) == 0 ) { printf("%76s\n\n", "γυρίστε το πληκτρολόγιό σας"); printf("%76s\n\n", "Win1253 (8-BIT) FONTS REQUIRED"); }
else
return;
}
/* ------------------------------------------------------------------
* Ask user for the desired length of the secret word
*/
int get_wlen( const int minwlen, const int maxwlen )
{
int wlen = 0;
char input[MAXINPUT] = "";
do {
printf("How many letters (%d - %d)? ", minwlen
-1, maxwlen
-1); read_input(input, MAXINPUT);
} while (wlen < minwlen-1 || wlen > maxwlen-1 );
return wlen;
}
/* ------------------------------------------------------------------
* Ask user for the number of moves he''l be allowed to use for guessing the secret word
*/
int get_maxmoves( const int minturns, int maxturns )
{
int maxmoves = 0;
char input[MAXINPUT] = "";
do {
printf("How many moves (%d-%d)? ", minturns
, maxturns
); read_input(input, MAXINPUT);
} while ( maxmoves < minturns || maxmoves > maxturns );
return maxmoves;
}
/* ------------------------------------------------------------------
* Open dictionary: dic_fname, load all words of length: lensecret into a
* linked list, pick randomly a word from that list and save it into the
* null-terminated string: secret
*/
void pick_word(
char *secret, const int lensecret, const char *dic_fname, const int maxwlen )
{
FILE *fp;
List wlist = { .head=NULL, .tail=NULL, .len=0L };
long int pick = 0L;
char dummy[MAXINPUT] = "";
// open the dictionary file
fp
= fopen(dic_fname
, "rb"); if ( !fp ) {
puts("\t*** error: could not read dictionary"); }
// create a list of all words of length lensecret found in the dictionary
s_toupper(dummy);
if ( strlen(dummy
) == lensecret
) list_append( &wlist, dummy );
}
// a random word fron the list
pick
= rand() % wlist.
len; Node *p = list_getinode( wlist, pick );
if ( p )
s_ncopy(secret, p->word, maxwlen);
// destroy the list
list_destroy( &wlist );
return;
}
/* ------------------------------------------------------------------
* Our main function...
* secret: null-terminated string for the secret word
* found: null-terminated string holding letters found correctly by the user
* played: null-terminated string holding all the letters typed by the user
* lensecret: length of the secret word (provided by the user)
* missing: number of letters which have not been found yet
*/
int main( void )
{
char input[ MAXINPUT ] = ""; // our input buffer
char dic_fname[ MAXINPUT ] = ""; // dictionary filename
char secret[ MAX_WLEN ] = "", found[ MAX_WLEN ] = "", played[ MAX_WLEN ] = "";
int round=1, lensecret = 0, move=0, maxmoves=0, missing=0;
srand( time(NULL
) ); // init random generator
get_dic( dic_fname, DIC_FNAME_EN, DIC_FNAME_EL ); // ask for dictionary
lensecret = get_wlen( MIN_WLEN, MAX_WLEN ); // ask for word length
maxmoves = get_maxmoves( MINMOVES, MAXMOVES ); // ask for max moves
do { // rounds loop
printf("\n____________________________________________________________________ ROUND %d\n\n", round
++); // pick the secret word
pick_word( secret, lensecret, dic_fname, MAX_WLEN );
move = 0;
do { // moves loop
missing = print_secret(secret, found); // # of missing letters
if ( missing == 0) // no missinng letters
break; // word found
printf("\t(%d missing) played: %s\n\n", missing
, played
);
do // ask user for a guess
{ // (allow letter or word)
printf("Guess #%d/%d: ", move
+1, maxmoves
); read_input(input, MAXINPUT);
// correct word entered
if ( strncmp(input
, secret
, MAX_WLEN
) == 0 ) break;
// check letter validity
} while ( *input
== '\0' || !strchr(ALPHABET_UPPER
, *input
) || strchr(played
, *input
) );
// valid letter was entered
s_cappend( played, *input, MAX_WLEN ); // append it to played
if ( strchr(secret
, *input
) ) // correct letter... s_cappend( found, *input, MAX_WLEN );// append to found
move++; // increase moves counter
} while( strncmp(input
,secret
,MAX_WLEN
) != 0 && move
< maxmoves
&& missing
> 0 );
printf("\n\tthe word was: %s\n", secret
); // reveal secret word
printf("\nplay again with another word (/n)? ");// ask for another round read_input(input, MAXINPUT);
*secret = *found = *played = '\0'; // initializations
} while ( *input != 'N' && *input != 'Ο' );
}