4
\$\begingroup\$

This is my version of Hangman Game, written in C++. How could it be improved? what should I avoid using/doing in future projects?

#include<iostream>
#include<iomanip>
#include<algorithm>
#include<string>
#include<limits>
#undef max

class Hangman
{
private:
    std::string wordName{ "0" }, wordHint{ "0" }, answer{ "" }, wordType{ "0" }, hanged[5]{ "JOHN","ALEX","MAX","JIMMY","TIM" } ;// Just for fun names
     std::string keyboard = "\t\t  ___________________________________  \n"
                            "\t\t |             KEYBOARD              | \n"
                            "\t\t |-----------------------------------| \n"
                            "\t\t | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | \n"
                            "\t\t |-----------------------------------| \n"
                            "\t\t | a | b | c | d | e | f | g | h | i | \n"
                            "\t\t |-----------------------------------| \n"
                            "\t\t | j | k | l | m | n | o | p | q | r | \n"
                            "\t\t |-----------------------------------| \n"
                            "\t\t | s | t | u | v | w | x | y | z | 0 | \n"
                            "\t\t |-----------------------------------| \n"
                            "\t\t |___________________________________| \n";
    int countHang{ 0 }; // How many parts of the hanged man
    char ch{ 0 }; // Input Variable
    bool active{ 1 }, flag{ 1 }, tag{ 1 };  // check variables

public:

    // Hangman() -> contructor Main Menu to link all the functions together
    // HintWord() -> Word inputs 
    // Rules() -> pretty simple there
    // HangBoard() -> the main display board
    // HangCheck() -> Draws main-body of the Hangman and checks how many left
    // AgainMenu() -> Final display menu and variable resetting

    bool exit{ 0 };  // Exiting Variable

    Hangman() {

        // STARTING WINDOW
        active = 1; 
        while (active) {

           std::cout << "\t\t  ____________________________________  \n"
                        "\t\t |              MAIN MENU             | \n"
                        "\t\t |             -----------            | \n"
                        "\t\t |     --XX---            ---XX--     | \n"
                        "\t\t |       :      ~Hangman~     :       | \n"
                        "\t\t |       :                    :       | \n"
                        "\t\t |       O      1.  Play      O       | \n"
                        "\t\t |      /|\\                  /|\\      | \n"
                        "\t\t |      / \\     2.  Rules    / \\      | \n"
                        "\t\t |                                    | \n"
                        "\t\t |              3.  Exit              | \n"
                        "\t\t |____________________________________| \n\n\n"


                "\t\t            -->";
            std::cin >> ch;   // Choice menu with conditions incase of wrong input
            if (ch == '1') {
                active = 0;
                system("cls");
                HintWord(); 
            }
            else if (ch == '2') {
                active = 0;
                system("cls");
                Rules();
            }
            else if (ch == '3') {
                system("cls");
                active = 0;
                exit = 1;//EXIT
            }
            else {
                system("cls");
                //WRONG CHOICE
            }
        }
    };

    inline void Rules() {

        std::cout << "\n\n\t\t                                        ~WELCOME~                                            \n"
            "\n\n\t\t  ------------------------------------------------------------------------------------------ \n"
            "\n\n\t\t  (1)Choose one person to be the 'host.' This is the person that invents the puzzle          \n"
            "\t\t  for the other person to solve. They will be tasked with choosing a word that 'the players' \n"
            "\t\t  will have to solve.                                                                        \n\n"
            "\t\t  The host should be able to spell confidently or the game will be impossible to win.        \n\n"
            "\t\t  (2)If you are the host, choose a secret word. The other players will need to guess your    \n"
            "\t\t  word letter by letter, so choose a word you think will be difficult to guess. Difficult    \n"
            "\t\t  words usually have uncommon letters, like 'z,' or 'j,' and only a few vowels.              \n\n"
            "\t\t  (3)Start guessing letters if you are the player. Once the word has been chosen and the     \n"
            "\t\t  players know how many letters in the secret word, begin playing by entering which letters  \n"
            "\t\t  are in the word.                                                                           \n\n"
            "\t\t  (4)Whenever the players guess a letter that is not in the secret word they get a strike    \n"
            "\t\t  that brings them closer to losing. To show this, the game draws a simple stick figure of a \n"
            "\t\t  man being hung, adding a new part to the drawing with every wrong answer.                  \n\n\n"
            "\t\t                                    *** 1.  Go Back.                                         \n"
            "\t\t  ------------------------------------------------------------------------------------------ \n"
            "\t\t                                     -->";
        std::cin >> ch;
        if (ch == '1') {
            active = 0;
            system("cls");
            Hangman();
        }
        else {
            system("cls");
            //WRONG CHOICE
        }
    }


    inline void HintWord() {
        std::cout << "\t\t            ______                     \n"
            "\t\t           |      |                    \n"
            "\t\t           |      :                    \n"
            "\t\t           |                           \n"
            "\t\t           |          \\ O    O       \n"
            "\t\t           |           |\\   /|7       \n"
            "\t\t        ___|___       / \\   / \\      \n";

        std::cout << "\n\n\t\t *** Host, Enter secret word to be found:  \t\t (*) PLAYERS DON'T LOOK AT THIS SCREEN!!\n"

            "\t\t               --> ";
        std::cin >> wordName;
        std::transform(wordName.begin(), wordName.end(), wordName.begin(), tolower);  // Takes main word input here and converts to lower-case
        std::cout << std::endl;
        std::cout << "\t\t *** Enter word type e.g Movie,Food/Drink,Song..etc: \n"

            "\t\t               --> ";

        // Had a problem here as the getline() function wasn't accepting all of the input correctly , which numeric limit seemed to fix here 
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

        std::getline(std::cin, wordType);
        std::cout << std::endl;

        std::cout << "\t\t *** Enter hint:                           \n"

            "\t\t               --> ";

        std::getline(std::cin, wordHint);

        // Converting all of it into Underscores
        for (unsigned i = 0; i < wordName.length(); ++i)
            answer += "_";


        system("cls");
        HangBoard();  

    }



    inline void HangBoard() {

        int i = 0;
        active = 1;

        while (active) {
            HangCheck();
            std::cout << "\t\t ~TYPE~ \"" << wordType << "\"\t\t  ";
            //Displaying the word as underscores with spaces
            for (unsigned i = 0; i < wordName.length(); ++i) {

                std::cout << answer[i] << " ";
            }
            //ABC
            std::cout << "\n\n\t\t ~HINT~ \"" << wordHint << "\"\n\n";
            std::cout << "\t\t\t\t\t\t\t (*) '#' Shown on the keyboard means it's already tried.";
            std::cout << "\t\t\t\t\t\t\t\t (*)  Enter '.' to exit.";
            std::cout << "\n\n" << keyboard;

            // Just wanted to try doing a little fun trick , if they inputted "#" as their first choice of character
            // It would automatically show the first letter of the hidden word , if they use "#" as any other input of character
            // Which isnt the first choice, it wouldn't work :: will mark the trick part with //TRICK comment
            if (flag == 1) {  


                std::cout << "\t\t     ~X~ -->"; std::cin >> ch;
            }
            else {
            //TRICK
                std::cout << "\t\t ;)  ~O~ -->"; std::cin >> ch;
            }

            if (ch == '.') {
                system("cls");
                Hangman();
                active = 0;
                break;
            }
            //Converting 
            if (ch <= 90 && ch >= 65)
                ch += 32;

            // incase of input of any of those signs to keep keyboard same outline format , using # sign as letter being taken
            if (ch != '|'&& ch != '_' && ch != '-' && keyboard.find(ch) != std::string::npos)
                keyboard[keyboard.find(ch)] = '#';

            if (ch != '#')
                tag = 0;

            //TRICK
            if (ch == '#' && flag == 1 && tag == 1) {
                ch = wordName[0];
                flag = 0;
            }
            //END of KEYBOARD

            // Checking if correct input here with find function
            i = wordName.find(ch);
            while (wordName.find(ch, i) != std::string::npos) {

                answer[wordName.find(ch, i)] = ch;
                i++;
            }
            if (wordName.find(ch) == std::string::npos) {
                countHang++;
                // testing the basic alarm bell sound for wrong character input
                std::cout << "\a";
            }
            if (countHang == 6) {
                system("cls");
                AgainMenu();
            }
            else if (answer == wordName) {
                system("cls");
                AgainMenu();
            }

            system("cls");
        }

    }
    inline void AgainMenu() {
        active = 1;
        while (active) {
            if (countHang == 6) {

                std::cout << "\t\t  ____________________________________  \n"
                    "\t\t |             " << std::setw(5) << hanged[rand() % 5] << " DIED!            | \n"
                    "\t\t |             -----------            | \n"
                    "\t\t |              ( X _ X )             | \n"
                    "\t\t |                                    | \n"
                    "\t\t |                       ________     | \n"
                    "\t\t |                      / Nooo!!!\\    | \n"
                    "\t\t |       _____          \\  ______/    | \n"
                    "\t\t |      (     )     <O>  \\/           | \n"
                    "\t\t |      | RIP |      |                | \n"
                    "\t\t |      |_____|     <<                | \n"
                    "\t\t |   ------------------------------   | \n"
                    "\t\t |                                    | \n"
                    "\t\t |          1.  Play Again            | \n"
                    "\t\t |                                    | \n"
                    "\t\t |          2.  Exit                  | \n"
                    "\t\t |____________________________________| \n\n\n";
            }
            else {
                std::cout << "\t\t  ____________________________________  \n"
                    "\t\t |            " << std::setw(5) << hanged[rand() % 5] << " LIVES!            | \n"
                    "\t\t |             -----------            | \n"
                    "\t\t |           ________                 | \n"
                    "\t\t |          / I LIVE!\\                | \n"
                    "\t\t |          \\  ______/                | \n"
                    "\t\t |            \\/                      | \n"
                    "\t\t |        |O/                         | \n"
                    "\t\t |         |                          | \n"
                    "\t\t |        / >                         | \n"
                    "\t\t |      _____       \\O>   \\O/         | \n"
                    "\t\t |     /     \\       |     |          | \n"
                    "\t\t |    /       \\     < \\   / \\         | \n"
                    "\t\t |   ------------------------------   | \n"
                    "\t\t |                                    | \n"
                    "\t\t |          1.  Play Again            | \n"
                    "\t\t |                                    | \n"
                    "\t\t |          2.  Exit                  | \n"
                    "\t\t |____________________________________| \n\n\n";
            }
            std::cout << "\t\t            -->";
            std::cin >> ch;
            if (ch == '1') {
                active = 0;

                system("cls");
                // Variable resetting
                countHang = 0;
                flag = 1;
                tag = 1;
                wordName = "0";
                wordHint = "0";
                answer = "";
                wordType = "0";

                keyboard = "\t\t  ___________________________________  \n"
                    "\t\t |             KEYBOARD              | \n"
                    "\t\t |-----------------------------------| \n"
                    "\t\t | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | \n"
                    "\t\t |-----------------------------------| \n"
                    "\t\t | a | b | c | d | e | f | g | h | i | \n"
                    "\t\t |-----------------------------------| \n"
                    "\t\t | j | k | l | m | n | o | p | q | r | \n"
                    "\t\t |-----------------------------------| \n"
                    "\t\t | s | t | u | v | w | x | y | z | 0 | \n"
                    "\t\t |-----------------------------------| \n"
                    "\t\t |___________________________________| \n";
                HintWord();
            }
            else if (ch == '2') {
                system("cls");
                active = 0;
                exit = 1;//EXIT
            }
            else {
                system("cls");
                //WRONG CHOICE
            }
        }


    }

    inline void HangCheck() {

        if (countHang == 0) {
            std::cout << "\t\t     ______                     \n"
                "\t\t    |      |                    \n"
                "\t\t    |      :                    \n"
                "\t\t    |                           \n"
                "\t\t    |                           \n"
                "\t\t    |                           \n"
                "\t\t ___|___                        \n\n\n";
        }
        else if (countHang == 1) {
            std::cout << "\t\t     ______                     \n"
                "\t\t    |      |                    \n"
                "\t\t    |      :                    \n"
                "\t\t    |      O                    \n"
                "\t\t    |                           \n"
                "\t\t    |                           \n"
                "\t\t ___|___                        \n\n\n";
        }
        else if (countHang == 2) {
            std::cout << "\t\t     ______                     \n"
                "\t\t    |      |                    \n"
                "\t\t    |      :                    \n"
                "\t\t    |      O                    \n"
                "\t\t    |      |                    \n"
                "\t\t    |                           \n"
                "\t\t ___|___                        \n\n\n";
        }
        else if (countHang == 3) {
            std::cout << "\t\t     ______                     \n"
                "\t\t    |      |                    \n"
                "\t\t    |      :                    \n"
                "\t\t    |      O                    \n"
                "\t\t    |      |\\                  \n"
                "\t\t    |                           \n"
                "\t\t ___|___                        \n\n\n";
        }
        else if (countHang == 4) {
            std::cout << "\t\t     ______                     \n"
                "\t\t    |      |                    \n"
                "\t\t    |      :                    \n"
                "\t\t    |      O                    \n"
                "\t\t    |     /|\\                  \n"
                "\t\t    |                           \n"
                "\t\t ___|___                        \n\n\n";
        }
        else if (countHang == 5) {
            std::cout << "\t\t     ______                     \n"
                "\t\t    |      |                    \n"
                "\t\t    |      :                    \n"
                "\t\t    |      O                    \n"
                "\t\t    |     /|\\                  \n"
                "\t\t    |     /                     \n"
                "\t\t ___|___                        \n\n\n";
        }
        else if (countHang == 6) {
            std::cout << "\t\t     ______                     \n"
                "\t\t    |      |                    \n"
                "\t\t    |      :                    \n"
                "\t\t    |      O                    \n"
                "\t\t    |     /|\\                  \n"
                "\t\t    |     / \\                  \n"
                "\t\t ___|___                        \n\n\n";
        }


    }


};

int main() {

    std::ios::sync_with_stdio(0);

    Hangman game;
    if (game.exit == 1) return 0;

    std::cin.get();
    return 0;
}

Thanks again

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

This is a lot of code. It may take multiple edits over multiple days to provide all my feedback. (Of course, others may provide feedback in areas I haven't got to yet...)


C++11 allows "raw" string literals. Instead of:

       std::cout << "\t\t  ____________________________________  \n"
                    "\t\t |              MAIN MENU             | \n"
                    "\t\t |             -----------            | \n"
                    "\t\t |     --XX---            ---XX--     | \n"
                    "\t\t |       :      ~Hangman~     :       | \n"
                    "\t\t |       :                    :       | \n"
                    "\t\t |       O      1.  Play      O       | \n"
                    "\t\t |      /|\\                  /|\\      | \n"
                    "\t\t |      / \\     2.  Rules    / \\      | \n"
                    "\t\t |                                    | \n"
                    "\t\t |              3.  Exit              | \n"
                    "\t\t |____________________________________| \n\n\n"

You could write:

        std::cout << R"RAW(
                  ____________________________________
                 |              MAIN MENU             |
                 |             -----------            |
                 |     --XX---            ---XX--     |
                 |       :      ~Hangman~     :       |
                 |       :                    :       |
                 |       O      1.  Play      O       |
                 |      /|\                  /|\      |
                 |      / \     2.  Rules    / \      |
                 |                                    |
                 |              3.  Exit              |
                 |____________________________________|


)RAW";

Notice no need to end a line with \n" or escape the backslash \\. Just type what you want to appear in the string, including returns, tabs, backslashes, etc. The string is everything between R"RAW( and )RAW". (You can choose your own delimiter text, or none at all; it doesn't have to be RAW.


Try to write DRY (Don't Repeat Yourself) code, instead of WET (Write Everything Twice) code.

Your code starts off with

std::string keyboard = "<-- long multiline string -->";

Later, in the AgainMenu(), you reset the variable to its initial value, by repeating:

keyboard = "<-- the same long multiline string -->";

If you wanted to change that string, you have to change it in both places. Failure to change one of them would be a bug. Cut your work in half by making initialization constants into ... well ... constants.

const std::string INITIAL_KEYBOARD = "<-- long multiline string -->";
std::string keyboard = INITIAL_KEYBOARD;

And in the AgainMenu() function:

keyboard = INITIAL_KEYBOARD;

But wait! What if we wanted to reformat the keyboard? Maybe making it wider/narrower, remove the numbers, or show the letters in UPPER CASE, without damaging the "KEYBOARD" title?

Perhaps it would be better to store the valid characters in an array, and generate the keyboard display programmatically.

const std::string VALID_CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789";
const std::string KEYBOARD_INDENT = "\t\t ";
const int CHARACTERS_PER_LINE = 9;

std::string keyboard = VALID_CHARACTERS;

private void show_keyboard() {
    int width = 4*CHARACTERS_PER_LINE - 1;
    std::string divider = KEYBOARD_INDENT + "|" + std::string(width, '-') + "|\n";

    std::cout << KEYBOARD_INDENT << " " << std::string(width, '_') << "\n";
    std::cout << KEYBOARD_INDENT << "|" << std::string(width/2-4, ' ') << "KEYBOARD"
              << std::string(width-width/2-4, ' ') << "|\n";
    std::cout << divider;

    for(int i=0; i< keyboard.length(); i += CHARACTERS_PER_LINE) {
        std::cout << KEYBOARD_INDENT << "|";

        for(int j=0; j<CHARACTERS_PER_LINE; j++) {
            char ch = (i+j) < keyboard.length() ? keyboard[i+j] : ' ';
            std::cout << " " << ch << " |";
        }
        std::cout << "\n";
        std::cout << divider;
    }
    std::cout << KEYBOARD_INDENT << "|" << std::string(width, '_') << "|\n";
}

Now, instead of checking that the typed character isn't one of the formatting characters ...

if (ch != '|'&& ch != '_' && ch != '-' && keyboard.find(ch) != std::string::npos)
    keyboard[keyboard.find(ch)] = '#';

... you just check if the character is inside keyboard. More DRY techniques: instead of repeatedly finding it, just save the returned position.

size_t index = keyboard.find(ch);
if (index != std::string::npos) {
    keyboard[index] = '#';
    ... find character in wordName, update answer, or increase hang ...
} else {
    ... beep for illegal input ...
}

HangCheck(): Displaying the incremental hangman.

Again, like keyboard, this should be done programmatically. Most of the hangman graphic is the same. Just a small 3x3 area needs to be updated as the hang count increases. You could initial a string with the initial unhung graphic, and just update the appropriate location with the appropriate character. Or, you could have 7 3x3 sub images and display the appropriately indexed sub image as you output your fixed graphic.


Error checking.

What if the host player enters a word with illegal characters? For instance, CAN'T or Carrie-Anne? Since the guesser can never enter these invalid characters, they can never win. You should validate the host's word is guessable according to the rules. Or, reveal all unguessable characters before the guesser starts. For instance, the guesser would see: _____-____.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for your feedback! It has been really helpful. \$\endgroup\$ Oct 10, 2018 at 19:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.