Influence_r
Influence_r

Reputation: 21

Is there a way to run a statement when a condition in a loop is met at least once?

I am currently doing games on my free time and am currently working on a hangman game. However, I have stumbled upon a problem and I think I could solve it if there was a way to run a statement if a condition inside a loop is met at least once, and if the condition isn't met even once, it'll do another thing. Is it possible to do? Does anyone have any ideas? I appreaciate any suggestions.

I tried doing something like this:

for (){
if (string [i] == letter that the player inputed){
// replace underscores with the letter guessed
// and also turn a bool statement true
}
else {
 // turn the bool statement false
  }
}
if (!bool variable){
 // print that the letter guessed was not in the answer
 // and substract one from the number of guesses available
}

However I noticed that it doesn't work because the loop will run and if the last letter that it checks is not in the answer, the bool will turn false, thus printing that the letter was not in the answer and substracting one from the score. (It's also my first time posting here, and I don't know if that's how I'm supposed to write a code, so I apologize beforehand if I'm not doing it correctly) `

Upvotes: 2

Views: 1357

Answers (4)

Francis Cugler
Francis Cugler

Reputation: 7905

Can you do what you are asking; possibly, however you stated you were making the game Hangman in C++ and I think you are going about this with the wrong approach, therefore, choosing or implementing the wrong algorithms. You are trying to traverse through two strings with possible different lengths from the back end which if it isn't done correctly can lead to issues, will tend to be hard to track especially if their comparisons determine loop conditions, exit or return statements.

I have implemented my version of "Hangman", now albeit the formatting isn't the prettiest, nor are the level dictionaries being generated from a large pool of random words. I express this in the comments of the code that these would generally be read in from a text file and saved into these structures. For simplicity's sake, I initialized them with random words directly in the code.

Take a look to see what I've done:


#include <string>
#include <iostream>
#include <vector>
#include <map>
#include <random>    

class Game;

int main() {
    using namespace util;

    try {
        Game game("Hangman");
        game.start();
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
} 

class Game {
private:
    std::string title_;
    bool is_running_{ false };

    std::string answer_;
    std::string guessed_;

    std::map<unsigned, std::vector<std::string>> dictionary_; // unsigned represents difficulty level of word
    unsigned choosen_difficulty_;

    std::string guessed_characters_{"\n"};

public:
    Game(const std::string& title) : title_{ title }, choosen_difficulty_{ 0 } {
        initialize();
        start_over();
    }

    void start() {
        is_running_ = true;

        // the player has as many attempts as twice the length of hidden answer's word length. 
        int number_tries = answer_.size() * 2;

        while (is_running_ || number_tries > 0) {
            displayGuessedWord();
            displayGuessedCharacters();

            // ask the player to guess a character;
            char guess;

            // get a character and make sure it is a valid alphabet character
            do {
                std::cout << "Guess a character ";
                std::cin >> guess;
                // Note: I'm using ascii characters in this case 
                // but for demonstration purposes only!
                if ((guess < 'a' && guess > 'z') ||
                    (guess < 'A' && guess > 'Z')) {
                    std::cout << "invalid entry ";
                }
            } while ( (guess < 'a' && guess > 'z') ||
                      (guess < 'A' && guess > 'Z') );

            // test character and update guessed word and number of tries.
            test_character(guess);
            update_guessed_characters(guess);
            number_tries--;

            // failed condition
            if (number_tries <= 0 && guessed_ != answer_) {
                std::cout << "\nGame Over!\n";              
                is_running_ = try_again(number_tries);
            // winning condition
            } else if (number_tries > 0 && guessed_ == answer_) {
                std::cout << "\nCongratulations!\n";
                is_running_ = try_again(number_tries);
            }

            if (!is_running_) break;                
        }
    }

private:
    void displayGuessedWord() {
        std::cout << '\n' << guessed_ << '\n';
    }
    void displayGuessedCharacters() {
        std::cout << guessed_characters_ << '\n';
    }

    void initialize() {
        // Normally this would be read in from a file upon game initialization
        // but for demonstration purpose, I'll generate a few small vectors of strings
        // and associate them to their difficulty level

        // levels are based on 3 factors, the length of the word, the repetitive occurance 
        // of common characters, and the amount of less commonly used characters.
        std::vector<std::string> level_1{ "ate", "cat", "dog", "coat", "coal", "does" };
        std::vector<std::string> level_2{ "able", "believe", "balloon", "better", "funny", "happy" };
        std::vector<std::string> level_3{ "ability", "carpenter", "dogmatic", "hilarious", "generosity", "hostility" };
        // ... etc. I'll use just these here for simplicty where each vector has six entries, however,
        // with random number generators, this can be done generically for any size 
        // or number of elements in each of the different vectors.

        // create generate the map:
        dictionary_[1] = level_1;
        dictionary_[2] = level_2;
        dictionary_[3] = level_3;
    }

    std::string getWordFromDictionary(unsigned difficulty, std::map<unsigned, std::vector<std::string>>& dict) {
        auto level = dict[difficulty]; // extract the vector based on difficulty level
        auto size = level.size();      // get the size of that vector
        std::random_device dev;        // create a random device                     
        std::mt19937 rng(dev());       // create a pseudo random generator
        // create a uniform int distribution type with the range from 0 to size-1
        std::uniform_int_distribution<std::mt19937::result_type> dist(0, size - 1);
        return level[dist(rng)]; // return a random string from this level.
    }

    void start_over() {
        system("cls"); // Note: I'm running visual studio on Windows!

        std::cout << "Welcome to " << title_ << '\n';

        // We can use a random generator to pick a word from the given difficulty
        // but first we need to get user input for the chosen level.
        do {
            std::cout << "Choose your difficulty [1-3]\n";
            std::cin >> choosen_difficulty_;

            if (choosen_difficulty_ < 1 || choosen_difficulty_ > 3) {
                std::cout << "Invalid entry:\n";
            }
        } while (choosen_difficulty_ < 1 || choosen_difficulty_ > 3);

        answer_ = getWordFromDictionary(choosen_difficulty_, dictionary_);

        // clear and resize guessed word to be that of answer_ and add bunch of hypens.
        guessed_.clear();
        guessed_.resize(answer_.size(), '-');

        // also reset the guessed_characters
        guessed_characters_ = std::string("\n");
    }

    bool try_again(int& tries) {
        std::cout << "Would you like to try again?\n";
        char c;
        std::cin >> c;
        if (c == 'y' || c == 'Y') {
            start_over();
            // don't forget to update this so that the loop can repeat
            tries = answer_.size() * 2;
            return true;
        }
        else {
            std::cout << "Thank you for playing " << title_ << '\n';
            return false;
        }
    }

    void test_character(const char c) {
        // here is where you would use the standard library for taking the character
        // passed into this function, updating the guessed_characters

        // get all indexes
        std::vector<unsigned> locations;
        for (unsigned i = 0; i < answer_.size(); i++)
            if (answer_[i] == c)
                locations.push_back(i);

        // now update the guessed word
        if ( locations.size() > 0 )
            for (size_t n = 0; n < locations.size(); n++)
            guessed_[locations[n]] = c;
    }

    void update_guessed_characters(const char c) {
        guessed_characters_.insert(0, &c); // just push to the front
    }
};

If you noticed how I structured the game class above; I am using while and do-while loops in conjunction with for-loops and if-statements and a single boolean flag to determine the state of the game. The game state is also determined from the update to the guessed characters and guessed word. Then I compare that to the answer. Depending on certain conditions the loop will continue seeking input from the user or will exit.

I am not guaranteeing that this code is 100% bug-free for I didn't do any rigorous testing or checking corner cases or special cases but the code has run without error and I've tested all primary game state cases. It appears to be working fine.

I know that there could be many improvements and simplifications made if I had chosen to use some of the standard library functions for working with strings, but I wanted to illustrate the individual steps that are involved in the design or thinking process of making a game with states and their transitions. I could of also put the game class declaration into its own header file with its implementation in a cpp file, but I left that as a single class that is shown in main.cpp for easy copy and paste and compilation.

With this particular game, I did not use a switch and case statements, I just stuck with some while and do-while loops, a few for loops, and if statements since there are only a few game states and transitions to worry about. This implementation also demonstrates the algorithms that are involved and shows how they interconnect with each other. I hope this helps to give you a better understanding of the design process.

When making a game that has different states with a bit of complexity to it, you should start by making your state table first and list all of its transitions before you even write any code. Then you should list your starting, continuing, winning, failing and exiting states or cases. Then you need to draw up how you would transition from one state to another by their required conditions. This will help you in the long run!

Once you have the game state and its transitions laid out properly, then you can start to make your required functions for those states and begin to connect them together. After that is when you would write the internal of the functions or their implementation of what they would do.

Finally, after you have that down is where you want to do some debugging and unit and case testing and if everything appears to be okay, then it would be safe to improve your current algorithms or choosing better ones for peak or most efficient performance.

Upvotes: 0

Jarod42
Jarod42

Reputation: 217275

You don't have to set false in the loop:

bool has_found = false;

for (auto& c : word_to_guess)
{
    if (input_letter == c) {
        // replace _ by current letter...
        has_found = true;
    }
}
if (!has_found){
    // print that the letter guessed was not in the answer
    // and substract one from the number of guesses available
}

But I suggest that your loop does only one thing at a time:

bool contains(const std::string& word_to_guess, char input_letter)
{
    return std::any_of(word_to_guess.begin(),
                       word_to_guess.end(),
                       [&](char c){ return input_letter == c; })
    /*
    for (auto& c : word_to_guess)
    {
        if (input_letter == c) {
            return true;
        }
    }
    return false;
    */
}


if (contains(word_to_guess, input_letter)
{
    // show current letter...
    for (std::size_t i = 0; i != hangman_word.size(); ++i) {
        if (word_to_guess[i] == input_letter) {
            hangman_word[i] = word_to_guess[i];
        }
    }
} else {
    // print that the letter guessed was not in the answer
    // and substract one from the number of guesses available
}

Upvotes: 0

vsh
vsh

Reputation: 341

You don't have to put flag guessed off if the comparation fails

string s;
bool guessed = false;
char inputted_letter; // comes from somewhere
for (size_t i = 0; i < s.size(); ++i) {
  if (s[i] == inputted_letter) {
    // replace underscores with the letter guessed
    guessed = true;
  }
}

if (!guessed) {
  // print that the letter guessed was not in the answer
  // and substract one from the number of guesses available
}

Upvotes: 1

lenik
lenik

Reputation: 23528

You should approach this problem from the different angle:

for( ... ) {
    if( your condition is met ) {
        do_whatever_you_have_to();

        break;   // <<--- exit the loop, so it's done only once
    }
}

Upvotes: 1

Related Questions