|
General
C/C++ coding techniques
A friend
needed some C and C++ tutoring. Her school, which I'll not name,
decided that they could teach their students C and C++ as a single
course! Excuse me, but how in the world is someone going to handle
C++ without the basic C foundation?
In my not so humble
opinion, one should first teach students assembler language, followed by a
higher level language (C, for example), and then teach them the object
oriented techniques and constructs (C++, for example).
Another
friend, who is an experienced programmer instructor was impressed enough
with my tutoring, that he asked my permission to use my hangman example in
his C/C++ classes. I granted him permission.
Anyway, I've chosen
to place the tutoring homework that I provided to my friend here, so that
you may examine some C and C++ coding techniques. The homework
assignment was to write a simple hangman program in C/C++ that reads a
random word from a text file for the user to guess. For simplicity,
it has no separate header file.
The program will
run in a DOS box under Windows 9x, NT, 2000 or XP (It's a 32 bit console
application, so it needs to
run under a Windows DOS box), or under UNIX and Linux.
The Windows
download is a zip file containing the hangman source, executable, sample
text file with words and a makefile if you want to compile it yourself.
The makefile was generated with Microsoft Visual C++ 5.0.
To compile the
Windows console version yourself, you need a compiler and linkage editor
as well as some flavor of make. The following works for me:
pkunzip hangman.zip
nmake /f hangman.mak
The UNIX/Linux
download is a "tar ball" containing the same.
To extract the "tar ball" change to your directory of choice and
use:
gunzip -c
hangman.tar.gz | tar -xvf -
(Note the trailing
hyphen)
The UNIX/Linux
"tar ball" contains a GLIBC 2.0 executable, so you might need to
recompile hangman before you can use it. To do this, switch to the
directory where you extracted the "tar ball" files and type:
make
The source code is identical for
both the Windows and the UNIX/Linux versions.
Download
Hangman |
Windows
Version
|
|
File
date: |
Thursday, 02-Dec-2004 11:56:10 EST |
File
size: |
91,898
bytes |
|
UNIX/Linux
Version
|
|
File
date: |
Thursday, 02-Dec-2004 11:54:07 EST |
File
size: |
25,153
bytes |
|
Here is the
"Hangman" source code.
/*----------------------------------------------------------------------------------------------*/
/* Homework - Hangman in C++. */
/* -------------------------- */
/* */
/* This program will open a file of words, randomly select one of the words in the file and */
/* provide the terminal operator an opportunity to guess the word. It displays underscores */
/* to indicate the number of characters in the word and fills each underscore with a letter */
/* for each correct guess. */
/* */
/* After 10 incorrect guesses, the operator is notified that he or she is hanged and the word */
/* is displayed. If the operator solves the word, a congratulation message is displayed. */
/*----------------------------------------------------------------------------------------------*/
#include <stdlib.h> // This is where standard c functions are defined.
#include <stdio.h> // This is where fopen, fclose, etc., are defined.
#ifdef DEPRECATED // Older (DEPRECATED) compiler support.
#include <iostream.h> // This is where cout is defined.
#include <fstream.h> // This is where ifstream is defined.
#else // New (not DEPRECATED) compiler support.
#include <iostream> // This is where cout is defined.
#include <fstream> // This is where ifstream is defined.
#endif // End of DEPRECATED/Not-DEPRECATED support.
#ifdef UNIX // UNIX/LINUX only.
#include <unistd.h> // This is where read is defined.
#include <termios.h> // This is where the termios functions are defined.
#include <ctype.h> // This is where the type conversions are defined.
#endif // End of UNIX/LINUX only.
#ifndef UNIX // Windows (95/98/NT/2000) only.
#include <conio.h> // This is where getch is defined.
#endif // End of Windows (95/98/NT/2000) only.
#include <malloc.h> // This is where malloc and memset are defined.
#include <time.h> // This is where the time functions are defined.
#include <string.h> // This is where the string handling functions
// are defined.
#define MAXWORDSIZE 256 // Arbitrarily define the maximum word size.
#ifdef UNIX // UNIX/LINUX only.
char getch(void); // Declare the getch function for UNIX.
#endif // End of UNIX/LINUX only.
#ifndef DEPRECATED // New (not DEPRECATED) compiler support.
using namespace std; // This tells the compiler to use the standard
// namespace - e.g., cout is 'std::cout'.
#endif // End of New (not DEPRECATED) compiler support.
class hangman // Beginning of Hangman class definition.
{
public: // Public hangman functions and data.
hangman(void); // Constructor creates the hangman object.
bool guessword(char *); // Member function stores the word.
bool guess(char); // Member function check the letter.
bool isused(char); // Member function to check if letter is used.
bool hanged(void); // Member function to check if you're hanged.
int solved(void); // Member function to check if you've solved the word.
int remaining(void); // Member function to return guesses remaining.
char *theword(void); // Member function to return a pointer to the word.
void display(void); // member function displays correct letters.
~hangman(void); // Destructor destroys a hangman object.
protected: // Protected hangman functions and data.
// There aren't any.
private: // Private hangman functions and data...
// Only the hangman object can directly access these.
char *word; // This is the word to solve.
char *letters; // This contains our correct guesses so far.
int guesses; // This is the number of guesses remaining.
char *used; // This contains the letters we've tried so far.
};
/*----------------------------------------------------------------------------------------------*/
/* This is the hangman class constructor. */
/* -------------------------------------- */
/* */
/* It initializes the private hangman member variables. */
/* */
/* Call with: void - Nothing. */
/* */
/* Returns: - Nothing (Constructors aren't allowed to return anything. */
/*----------------------------------------------------------------------------------------------*/
hangman::hangman(void)
{
word = 0;
letters = 0;
guesses = 0;
used = 0;
}
/*----------------------------------------------------------------------------------------------*/
/* This is the function to store the word to solve. */
/* ------------------------------------------------ */
/* */
/* It allocates real system storage to store the word to solve, the current word, the letters */
/* guessed so far and the maximum number of guesses allowed before you're hanged. */
/* */
/* Call with: char * - a pointer to a "C" type string array. */
/* */
/* Returns: bool - True (1) if successful. */
/* False (0) if unsuccessful. */
/*----------------------------------------------------------------------------------------------*/
bool hangman::guessword(char *newword) // Function stores the word to solve.
{
int i; // General purpose integer for the word length.
i = strlen(newword); // Get the length of the word character array.
if (!(word = new char[i + 1])) // Ensure there's enough system memory to store the
return false; // word to solve. If not, return false.
word[0] = '\0'; // Terminate the "C" string.
strcpy(word, newword); // Copy the string into the array
if (!(letters = new char[i + 1])) // Ensure there's enough system memory to store the
return false; // correctly guessed letters. If not, return false.
memset(letters, '_', i); // Initialize the array to underscores.
letters[i] = '\0'; // Terminate the "C" string.
if (!(used = new char[i + 11])) // Ensure there's enough system memory to store the
return 0; // letters guessed so far. If not, return false.
used[0] = '\0'; // Terminate the "C" string.
guesses = 10; // Initialize the maximum number of incorrect guesses.
return true; // Everything's ok, so return true.
};
/*----------------------------------------------------------------------------------------------*/
/* This is the function to test whether a letter has already been tried. */
/* --------------------------------------------------------------------- */
/* */
/* Call with: char - a character integer containing the letter to guess. */
/* */
/* Returns: bool - True (1) if we've already used this character. */
/* False (0) if we haven't used this character yet. */
/*----------------------------------------------------------------------------------------------*/
bool hangman::isused(char c) // Function tests to see whether the letter has
{ // already been tried.
int x = strlen(used); // Get the length of the "used letters" string.
while (x) // Try every letter in the string array.
{ // Remember arrays are relative to zero (thus - 1).
// We don't care about case, thus the tolower's.
if (tolower(c) == tolower(used[x - 1]))
return true; // If we find a match, return true.
else // Otherwise,
x--; // decrement the array subscript until it's zero.
}
return false; // No match, so return false.
}
/*----------------------------------------------------------------------------------------------*/
/* This is the function that tries a new letter and stores it in the used letter array. */
/* ------------------------------------------------------------------------------------ */
/* */
/* This function also decrements the remaining guesses counter if the character doesn't exist */
/* in the word to solve. */
/* */
/* Call with: char - a character integer containing the letter to guess. */
/* */
/* Returns: bool - True (1) if the character appears one or more times in the word. */
/* False (0) if the character doesn't appear in the word. */
/*----------------------------------------------------------------------------------------------*/
bool hangman::guess(char c) // Function tests whether a letter is in the word.
{
unsigned
int x; // General purpose unsigned integer.
bool f = false; // Boolean return flag initialized to false.
x = strlen(used); // Get the length of the used letters array.
used[x] = c; // Store this letter at the end of the used letter
used[x + 1] = '\0'; // array and terminate the "C" string.
x = 0; // Re-initialize x to zero.
while (x < strlen(word)) // Test all the characters in the word.
{ // We don't care about case (thus the tolower's).
if (tolower(c) == tolower(word[x]))
{ // If it's a match, store it in the correct position
letters[x] = word[x]; // in the correct letters array.
f = true; // Set the return flag to true.
}
x++; // Increment the word subscript to try the next letter.
}
if (!f) // If the flag has not been set to true, the guess
if (guesses) // was incorrect, so decrement the remaining guesses
guesses--; // allowed counter.
return f; // Return a boolean true of false.
}
/*----------------------------------------------------------------------------------------------*/
/* This function returns the number of guesses remaining. */
/* ------------------------------------------------------ */
/* */
/* Call with: void - nothing. */
/* */
/* Returns: int - an integer reflecting the number of guesses remaining. */
/*----------------------------------------------------------------------------------------------*/
int hangman::remaining(void)
{
return guesses; // Return the number of guesses remaining.
}
/*----------------------------------------------------------------------------------------------*/
/* This function returns whether you're hanged or not. */
/* --------------------------------------------------- */
/* */
/* Call with: void - nothing. */
/* */
/* Returns: bool - a boolean true or false. */
/* False (0) if you still have guesses remaining. */
/* True (1) if the guesses counter has been decremented to zero. */
/*----------------------------------------------------------------------------------------------*/
bool hangman::hanged(void)
{
if (guesses) // Is guesses non-zero?
return false; // Return false.
return true; // Otherwise, return true.
}
/*----------------------------------------------------------------------------------------------*/
/* This function returns whether you've solved the word or not. */
/* ------------------------------------------------------------ */
/* */
/* Call with: void - nothing. */
/* */
/* Returns: int - an integer. */
/* non-zero if you've correctly guessed the word. */
/* Zero if you haven't guessed the word yet. */
/*----------------------------------------------------------------------------------------------*/
int hangman::solved(void)
{
return !strcmp(letters, word); // Return the compliment of the string compare.
}
/*----------------------------------------------------------------------------------------------*/
/* This function displays the correctly guessed letters so far. */
/* ------------------------------------------------------------ */
/* */
/* Call with: void - nothing. */
/* */
/* Returns: void - nothing. */
/*----------------------------------------------------------------------------------------------*/
void hangman::display(void)
{
cout << letters << endl << endl; // Display the "letters" character string.
}
/*----------------------------------------------------------------------------------------------*/
/* This function returns a pointer to the word to solve character array. */
/* --------------------------------------------------------------------- */
/* */
/* Call with: void - nothing. */
/* */
/* Returns: char * - A pointer the the character array containing the word to solve. */
/*----------------------------------------------------------------------------------------------*/
char *hangman::theword(void)
{
return word; // Return the pointer to the word character array.
}
/*----------------------------------------------------------------------------------------------*/
/* This is the hangman object destructor. */
/* -------------------------------------- */
/* */
/* It returns all of the resources that the hangman object allocated back to the operating */
/* system. */
/* */
/* Call with: void - nothing. */
/* */
/* Returns: void - nothing. */
/*----------------------------------------------------------------------------------------------*/
hangman::~hangman(void)
{
if (word) // If we allocated memory for the word array,
delete [] word; // release it.
if (letters) // If we allocated memory for the letters array,
delete [] letters; // release it.
if (used) // If we allocated memory for the used array,
delete [] used; // release it.
} // End of Hangman class definition.
/*----------------------------------------------------------------------------------------------*/
class wordfile // Beginning of wordfile class definition.
{
public: // Public wordfile functions and data.
wordfile(void); // Constructor creates the wordfile object.
char *getword(char *); // Member function to read a word from a file.
~wordfile(void); // Destructor destroys a wordfile object.
protected: // Protected wordfile functions and data.
// Only the wordfile object and its subclasses
// can use these.
bool openfile(char *); // Member function to open a wordfile file.
void closefile(void); // Member function to close a wordfile file.
int countwords(void); // Member function to count the words in a file.
int randomword(void); // Member function to generate a random word number.
private: // Private wordfile functions and data.
// Only the wordfile object can access these.
ifstream // Input file stream:
*fstr; // Pointer to and ifstream ios derived object.
char word[MAXWORDSIZE]; // Character array that contains the random word.
int nwords; // Integer that contains the number of words in the
}; // file.
/*----------------------------------------------------------------------------------------------*/
/* This is the wordfile class constructor. */
/* -------------------------------------- */
/* */
/* It initializes the private wordfile member variables. */
/* */
/* Call with: void - Nothing. */
/* */
/* Returns: - Nothing (Constructors aren't allowed to return anything. */
/*----------------------------------------------------------------------------------------------*/
wordfile::wordfile(void)
{
fstr = 0; // Set the ifstream pointer to zero.
word[0] = '\0'; // Initialize the word array to a null "C" string.
nwords = 0; // Initialize the word counter to zero.
}
/*----------------------------------------------------------------------------------------------*/
/* This function opens a file containing a list of words to guess. */
/* --------------------------------------------------------------- */
/* */
/* Call with: char * - A pointer to a character array containing the file name to open. */
/* */
/* Returns: bool - True (1) if the input file stream was successfully opened. */
/* False (0) if there was a problem opening the input file stream. */
/*----------------------------------------------------------------------------------------------*/
bool wordfile::openfile(char *f)
{
fstr = new ifstream(f, // Construct a new input file stream object and
ios::in); // try to open it for reading an existing file;
if (fstr->is_open()) // Test whether the ifstream opened properly.
return true; // If it did, return true.
else
return false; // Otherwise, return false.
}
/*----------------------------------------------------------------------------------------------*/
/* This function closes a file containing a list of words to guess. */
/* ---------------------------------------------------------------- */
/* */
/* Call with: void - Nothing. */
/* */
/* Returns: void - Nothing. */
/*----------------------------------------------------------------------------------------------*/
void wordfile::closefile(void)
{
if (fstr) // If the input file stream pointer is not zero,
{ // and if the input file stream is still open,
if (fstr->is_open()) // destroy the object (which closes the file and
fstr->~ifstream(); // releases its internal buffer) - a simple close
// does not release the internal buffer.
fstr = 0; // Set the input file stream pointer to zero.
}
}
/*----------------------------------------------------------------------------------------------*/
/* This function counts the number of words in the file. */
/* ----------------------------------------------------- */
/* */
/* Call with: void - Nothing. */
/* */
/* Returns: int - An integer containing the number of words found in the file. */
/*----------------------------------------------------------------------------------------------*/
int wordfile::countwords(void)
{
while (!fstr->eof()) // Read the file until we reach end of file (EOF).
{
fstr->getline(word, MAXWORDSIZE); // Read a word character array up to MAXWORDSIZE in
// length.
int x = fstr->rdstate(); // Get the I/O status for the previous read.
if ((x == ios::goodbit) || // If there were no errors (EOF is ok),
(x == ios::eofbit)) // increment the word counter.
nwords++;
else
return 0; // Otherwise return zero.
}
fstr->seekg(0); // Set the word file position back to the beginning
fstr->clear(ios::goodbit); // of the file and clear the end of file (EOF) flag.
return nwords; // Return the number of words counted.
}
/*----------------------------------------------------------------------------------------------*/
/* This function tries to generate a random number to randomly select a word. */
/* -------------------------------------------------------------------------- */
/* */
/* Note: Most C Runtime psuedo random number generators are really flakey. */
/* */
/* Call with: void - Nothing. */
/* */
/* Returns: int - An integer containing the random word number to read. */
/*----------------------------------------------------------------------------------------------*/
int wordfile::randomword(void)
{ // Display what we're doing.
cout << "Selecting a random word out of a list of "
<< nwords << " words." << endl;
srand(time(NULL)); // Seed the random number generator with the number
// of seconds elapsed since midnight, Jan 1, 1970.
int i, x; // Declare some general purpose integers.
for (x = 0; x < 7; x++) // Use a prime number (7) and get the seventh
i = rand(); // number from the random number generator.
while (i > nwords) // Make the random number fall within the number of
i = i/10; // words in the file by dividing by 10 until the
// number is within our range.
// Display the word number we picked.
cout << "Selected word number "
<< i << endl << endl;
return i; // Return the word number.
}
/*----------------------------------------------------------------------------------------------*/
/* This function returns a randomly selected word from the wordfile. */
/* ----------------------------------------------------------------- */
/* */
/* Call with: char * - A pointer to a character array file name. */
/* */
/* Returns: char * - A pointer to a character array containing the random word. */
/*----------------------------------------------------------------------------------------------*/
char *wordfile::getword(char *f)
{
int i; // Declare a general purpose integer.
if (!openfile(f)) // Open the word list file.
return 0; // Return a zero pointer if there was a problem.
if (!countwords()) // Count the number of words in the file.
return 0; // Return a zero pointer if there was a problem.
i = randomword(); // Get a random word number.
for (int x = 0; x < i; x++) // Find the word within the file by reading each
{ // word starting from the first one, until we
fstr->getline(word, MAXWORDSIZE); // reach the word number that was picked by the
// random number generator.
int e = fstr->rdstate(); // Get the status of the last read I/O.
if ((e == ios::failbit) || // If there were any I/O errors while reading the
(e == ios::badbit)) // file, return a zero pointer.
return 0;
}
if ((i = strlen(word)) < 2) // Make sure our word is at least 2 bytes in length.
return 0; // Return a zero pointer if it's less.
closefile(); // We're done with the file, so close it.
return word; // Return the pointer to the word we found.
}
/*----------------------------------------------------------------------------------------------*/
/* This is the wordfile object destructor. */
/* --------------------------------------- */
/* */
/* It returns all of the resources that the wordfile object allocated back to the operating */
/* system. */
/* */
/* Call with: void - nothing. */
/* */
/* Returns: void - nothing. */
/*----------------------------------------------------------------------------------------------*/
wordfile::~wordfile(void)
{
closefile(); // Close the file if someone inadvertently left it
} // open.
// End of wordfile class definition.
/*----------------------------------------------------------------------------------------------*/
/* This is the main entry point of the hangman program. */
/* ---------------------------------------------------- */
/* */
/* Call with: void - Nothing. */
/* */
/* Returns: int - An integer return code. */
/* 0 if everything went ok. */
/* 8 if the word file could not be opened. */
/* 12 if the operating system had insufficient real memory resources */
/* to allocate the neccessary arrays. */
/*----------------------------------------------------------------------------------------------*/
int main (void) // Begining of program (entry point).
{
char *w;
cout << "Hangman" << endl // Display a title on the screen.
<< "Tutored to Laurie Beatty by Marc Niegowski"
<< endl << endl;
wordfile wf; // Build a wordfile object.
if (!(w = wf.getword("hangman.txt"))) // Read a random word from the "hangman.txt" file.
{
cout << "Error reading the hangman.txt data file containing"
<< endl << "a list of words to guess."
<< endl << endl;
return 8; // Return to the operating system with condition
} // code 8 if there was a problem opening the file.
hangman hm; // Build a hangman object.
if (!hm.guessword(w)) // Store a word in the hangman object.
{ // If we couldn't allocate memory for the arrays...
cout << "There isn't enough system memory to run this program."
<< endl;
return 12; // Return to the operating system with condition
} // code 12.
while (!hm.hanged()) // While we're not hanged...
{
char c; // General purpose character integer.
// Display the number of guesses remaining.
// We get this number from the hangman object.
cout << hm.remaining()
<< " Guesses remaining"
<< endl << endl;
hm.display(); // The hangman object will display the guesses so far.
cout << "Guess a letter: " // Prompt the user for a letter and
<< flush; // flush the buffer (no new line manipulator).
while (true) // Start an indefinite loop.
{
c = getch(); // Get a character from the keyboard.
if (((c >= 'A') && (c <= 'Z')) ||
((c >= 'a') && (c <= 'z')) ||
(c == ' ') || (c == '-')) // We only want A-Z or a-z or a space or hyphen.
break; // Get out if we have a valid character.
}
cout << c // Display the letter the user chose
<< endl << endl; // and send out a couple of new lines.
if (hm.isused(c)) // The hangman object will test if the letter is
// already used.
cout << "You've already tried " // If it has been, display a message and ask for
<< c // another letter.
<< ", try another letter."
<< endl << endl;
else // Otherwise, the hangman object will test the
hm.guess(c); // letter for one or more matches in the word.
if (hm.solved()) // The hangman object will test to see whether
break; // the word has been solved. If it has, break
} // out of the "while not hanged" loop.
if (hm.solved()) // If it's solved, display the solved word and
{ // a congrats. message.
cout << "Correct '"
<< hm.theword()
<< "'." << endl;
cout << "Congratulations, you solved the word!"
<< endl << endl;
}
else // Otherwise, display a hanged message
{ // followed by the word to solve.
cout << "Sorry, you're hanged!"
<< endl;
cout << "The word was '"
<< hm.theword()
<< "'." << endl << endl;
}
return 0; // Return to the operating system with return code 0.
}
#ifdef UNIX // UNIX/LINUX only.
char getch() // Function to read a raw character from the keyboard.
{
fd_set kb; // Declare a file descriptor for the keyboard.
char ch = '\0'; // Declare a character and initialize it to null.
termios newtty, // Declare a termios structure for the new TTY
oldtty; // settings and one to save the current settings.
if (tcgetattr(0, &oldtty) == -1) // Get the current TTY attributes.
{
cout << "Unable to save the TTY attributes."
<< endl << endl;
exit(8); // Exit to the operating system with a condition code
} // 8 if we weren't able to save the current settings.
newtty = oldtty; // Start out with the same TTY attributes.
newtty.c_lflag &= ~ICANON; // Turn of the wait for end of line (EOL).
newtty.c_lflag &= ~ECHO; // Don't echo any characters to the display.
newtty.c_cc[VMIN] = 1; // Set the minimum character buffer.
newtty.c_cc[VTIME] = 0; // Set the minimum wait time.
if (tcsetattr(0, TCSAFLUSH, &newtty) == -1)
{ // Set our new tty attributes.
cout << "Unable to set the TTY attributes."
<< endl << endl;
exit(8); // If it fails, return to the operating system
} // with a condition code of 8.
FD_ZERO(&kb); // Clear out the file descriptor set.
FD_SET(0, &kb); // Add STDIN (fd 0) to the file descriptor set.
select(1, &kb, NULL, NULL, NULL); // Wait for a character from STDIN.
read(1, &ch, 1); // Read one byte from STDIN.
if (tcsetattr(0, TCSAFLUSH, &oldtty) == -1)
{ // Restore the original TTY settings.
cout << "Unable to restore the TTY attributes."
<< endl << endl;
exit(8); // Exit to the operating system with a condition
} // code 8 if it fails.
return ch; // Return the character we read.
}
#endif // End of UNIX/LINUX only.
|
I will continue to add
tips and techniques as time permits.
Please direct any
inquiries or problems regarding this web to webmaster@marcsweb.com
Page design, composition and HTML by Marc Niegowski
Copyright © 1998-2012, Marc Niegowski - Connectivity, Inc., All Rights Reserved
23 W. Fourth Street • Media • Pennsylvania • 19063-2805 • USA
Phone: 610-566-0227 • Fax: 610-566-0641 • Email: Marc@Tech-Center.com
Revision Date:
Wednesday, November 15, 2006 10:02:03 AM
|