How did yours end?

Let’s see your modifications guys!

Posted it here.

Background:

I did a version of this (Bulls and Cows) about one year ago. Recently, I found I was able to read through my old source code with no troubles, but I also realized I’d have just about zero hope of re-creating it from scratch without at least a bit of hand-holding. So, I went through Section 2 again in a very cursory fashion, lending much more of myself to the final product.

Caveat: I do not profess to be particularly good at this; For example, I took what I consider an unfortunate number of stabs at my “track the submitted letters” feature to get it working as desired. I’m glad I stuck with it, though, the results have made the game much more playable for me! (I’ve never been talented with word games).

Special Features:

  • Letter-Tracker feature informs player which letters have been used each round and phase.
  • Toggle-able extra hints to reveal exactly which letters of a guess are matches.
  • (turn them off for a real challenge!)
  • Statistics and arbitrary point rewards to compare performance day to day or against friends.
  • Dictionary validation and error handling with automatic expansion or contraction:
  • reports (but gracefully avoids) challenge words which are not isograms, making it
  • easy to add (or remove!) words to the dictionary, worry-free.
  • “Learn as you play” – limited instruction, with feedback designed to reinforce acquisition of game rules.
  • Affirmative exit; no accidentally lost scores and stats.
  • More built-in challenge words than you can shake a stick at.
  • Ready to be enhanced with challenge-curve, and or easy/expert modes to increase re-playability.
  • (i.e. start with 3-letter words and get bigger words with higher scores / more wins)

The Source Code

If you care, (or dare!) to see the evolution, I’ve tried to stay on top of my commits; they’re archived on GitHub but to also capture it at this point in time I’ll include it here:

main.cpp

/*
Isogram Game
based on https://www.udemy.com/unrealcourse/ by Ben Tristem (Thanks, Ben!)
coded by Jack Draak

A rudimentary console-based application used in learning about C++. Main.cpp, which
acts as the view in an MVC pattern and is responsible for all user interaction also 
utilizes Isogramgame.cpp for game logic operations.

Built in VisualStudio 2015, ostensibly for Windows, but it should be easy to port,
assuming anyone would desire to do so.
*/

#pragma once
#include "IsogramGame.h"

enum class eGuessValidation
{
    Invalid_Status,
    Not_Alpha,
    Not_Isogram,
    Too_Long,
    Too_Short,
    Okay
};

// ----- Function prototypes ----- //

eGuessValidation ValidateGuess(FString);
bool bContinuePlaying();
bool bIsAlpha(FString);
FString sGetValidGuess();
int main();
void PlayGame();
void PrintIntro();

// instantiate objects (ActiveGame & ActiveLetterBox) for manipulation
IsogramGame ActiveGame;
LetterBox ActiveLetterBox;

int main()
{
    PrintIntro();
    do { PlayGame(); } while (bContinuePlaying());
    return 0;
}

// ----- Method implementations ----- //

void PlayGame()
{
    ActiveLetterBox.Reset();
    srand(unsigned(time(NULL)));
    int32 cMaxGuesses = ActiveGame.iGetMaxGuesses();
    FString sGuess = "";

    for (int32 i = 1; i <= cMaxGuesses; i++)
    {
        sGuess = sGetValidGuess(); 
        sGuess = ActiveGame.sStringToLower(sGuess);
        int32 guessLength = sGuess.length();

        for (int32 i = 0; i < guessLength; i++ ) { ActiveLetterBox.SubmitLetter(sGuess[i]); }
        Analysis analysis = ActiveGame.AnalyzeGuess(sGuess);
        if (ActiveGame.bGetGuessMatch()) { break; } 
        ActiveGame.IncrementGuess();

        // ----- Output phase (turn) results ----- //

        std::cout << "\nComplement of letters used this round: " << ActiveLetterBox.sGetLetters();
        std::cout << "\n...Correct letters in the wrong place(s): " << analysis.iLetterMatches;
        if (ActiveGame.bDisplayHints) 
        {
            std::random_shuffle(analysis.sLetterHint.begin(), analysis.sLetterHint.end());
            std::cout << "  [shuffled hint: '" << analysis.sLetterHint << "']";
        }
        std::cout << "\n...Correct letters in the proper position(s): " << analysis.iPositionMatches;
        if (ActiveGame.bDisplayHints) 
        {
            std::cout << "       [hint: '" << analysis.sPositionHint << "']";
        }
    }
        // ----- Output round results ----- //
        
        if (ActiveGame.bGetGuessMatch()) { std::cout << "\nCongratulations! You guessed "; }
        else { ActiveGame.IncrementLoss(); std::cout << "\nBummer! You didn't guess "; }
        std::cout<< "the secret isogram : " << ActiveGame.sGetIsogram() << ".\nIt took you ";
        if (ActiveGame.bGetGuessMatch()) { std::cout << ActiveGame.iGetCurrentGuess() << " guesses. You earned "; }
        else { std::cout << (ActiveGame.iGetCurrentGuess() -1) << " guesses. You earned "; }
        std::cout << ActiveGame.iGetScore() << " points."; 
        ActiveGame.Tally();
        std::cout << "\nTotal score: " << ActiveGame.iGetRunningScore() << " points. (win/loss ";
        std::cout << ActiveGame.iGetWinCount() << "/" << ActiveGame.iGetLossCount() << ")";
        ActiveLetterBox.Reset(); 
        return;
}

bool bContinuePlaying()
{
    bool bContinue = true;
    do {
        FString sResponce = "";
        std::cout << "\nPlease enter (P)lay again, toggle (H)ints ";
        if (ActiveGame.bDisplayHints) { std::cout << "off"; } else { std::cout << "on"; }
        std::cout << ", (R)epeat intro, or (Q)uit...";
        getline(std::cin, sResponce);

        if ((sResponce[0] == 'h') || (sResponce[0] == 'H')) { ActiveGame.bDisplayHints = !ActiveGame.bDisplayHints; }
        else if ((sResponce[0] == 'q') || (sResponce[0] == 'Q')) { bContinue = false; break; }
        else if ((sResponce[0] == 'p') || (sResponce[0] == 'P')) { ActiveGame.Reset(); break; }
        else if ((sResponce[0] == 'r') || (sResponce[0] == 'R')) { PrintIntro(); }
    } while (true);
    if (bContinue) { return true; } else { return false; }
}

void PrintIntro()
{
    std::cout << " - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -";
    std::cout << "\n      INTRO: Thank you for playing my \'Guess the Isogram\' console game!\n";
    std::cout << " - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -";
    std::cout << "\n      - what is an isogram?\n";
    std::cout << "        An isogram is a word comprised of unique letters, for example:\n";
    std::cout << "           - step: is an isogram, each letter is unique in the word\n";
    std::cout << "           - book: is NOT an isogram; it contains two 'o's\n";
    std::cout << "\n";
    std::cout << "      - how do I play?\n";
    std::cout << "        ...details, details... We'll get to that!";
    return;
}

FString sGetValidGuess()
{
    eGuessValidation status = eGuessValidation::Invalid_Status;
    int32 iWordLen = ActiveGame.iGetIsogramLength();
    FString sGuess = "";

    do {
        std::cout << "\n\nCan you guess the " << iWordLen << " letter isogram that has been randomly pre-selected?";
        std::cout << "\nPlease, enter your guess (#" << ActiveGame.iGetCurrentGuess();
        std::cout << " of " << ActiveGame.iGetMaxGuesses() << ") now: ";
        getline(std::cin, sGuess);

        status = ValidateGuess(sGuess);
        switch (status)
        {
        case eGuessValidation::Not_Alpha:
            std::cout << "\nERROR: Your guess, \"" << sGuess << "\" contains non-alpha input.";
            std::cout << "\nPlease use only letters (this *is* a word-game, y'know! [think Scrabble].)";
            break;
        case eGuessValidation::Not_Isogram:
            std::cout << "\nERROR: Your guess, \"" << sGuess << "\" contains repeated characters.";
            std::cout << "\nPlease enter an isogram (a word comprised of all unique letters,";
            std::cout << "\ni.e. book:INVALID, two 'o's, but bark:GREAT!)";
            break;
        case eGuessValidation::Too_Long:
            std::cout << "\nERROR: Your guess, \"" << sGuess << "\" is too long.";
            std::cout << "\nPlease use a " << ActiveGame.iGetIsogramLength() << " - letter word.";
            break;
        case eGuessValidation::Too_Short:
            std::cout << "\nERROR: Your guess, \"" << sGuess << "\" is too short.";
            std::cout << "\nPlease use a " << ActiveGame.iGetIsogramLength() << " - letter word.";
            break;
        case eGuessValidation::Okay:
            std::cout << "\nYour guess was, \"" << sGuess << "\"";
            break;
        default:
            break;
        }
    } while (status != eGuessValidation::Okay);
    return sGuess;
}

eGuessValidation ValidateGuess(FString sGuess)
{
    int32 iIsogramLength = (ActiveGame.sGetIsogram()).length();
    int32 iGuessLength = sGuess.length();

    if      (!bIsAlpha(sGuess))                     { return eGuessValidation::Not_Alpha; }
    else if (!ActiveGame.bIsIsogram(sGuess))        { return eGuessValidation::Not_Isogram; }
    else if (iGuessLength < iIsogramLength)         { return eGuessValidation::Too_Short; }
    else if (iGuessLength > iIsogramLength)         { return eGuessValidation::Too_Long; }
    else                                              return eGuessValidation::Okay; 
}

bool bIsAlpha(FString sTestString)
{
    int32 iLength = sTestString.length();
    for (int32 i = 0; i < iLength; i++)
    {
        char thisChar = tolower(sTestString[i]);
        if (!(thisChar >= 'a' && thisChar <= 'z')) { return false; }
    }
    return true;
}

IsogramGame.h

#pragma once
#include <algorithm>
#include <iostream>
#include <map>
#include <string>
#include <time.h>
#include <vector>

// substitutions to maintain UnrealEngine compatability
using FString = std::string;
using int32 = int;

struct Analysis
{
    FString sLetterHint = "";
    FString sPositionHint = "";
    int32 iLetterMatches = 0;
    int32 iPositionMatches = 0;
};

// CLASS IsogramGame -- functions that manage the core of the Isogram Game
class IsogramGame {
public:
    IsogramGame(); // constructor 
    bool bDisplayHints;

    Analysis AnalyzeGuess(FString);
    bool bGetGuessMatch() const;
    bool bIsIsogram(FString);
    FString sGetIsogram() const;
    FString SelectIsogram();
    FString sStringToLower(FString);
    int32 iGetCurrentGuess() const;
    int32 iGetIsogramLength() const;
    int32 iGetLossCount() const;
    int32 iGetRunningScore() const;
    int32 iGetScore() const;
    int32 iGetMaxGuesses() const;
    int32 iGetWinCount() const;
    void IncrementGuess();
    void IncrementLoss();
    void Reset();
    void Tally();

    // see IsogramGame::IsogramGame() definition for initialization [found in IsogramGame.cpp]
private:
    bool bFirstGuess;
    bool bGuessMatch;
    bool bInitialized;
    bool bValidDictionary;
    FString sIsogram;
    int32 iCurrentGuess;
    int32 iLossCount;
    int32 iMaxGuesses;
    int32 iRunningScore;
    int32 iScore;
    int32 iWinCount;
};

// CLASS LetterBox -- container to store characters submitted during a round of play
class LetterBox {
public:
    FString sGetLetters() const;
    void Reset();
    void SubmitLetter(char);
private:
    FString sBoxOfLetters;
};

IsogramGame.cpp

#pragma once
#include "IsogramGame.h"

IsogramGame::IsogramGame()                          { Reset(); return; }

bool IsogramGame::bGetGuessMatch() const            { return bGuessMatch; }
FString IsogramGame::sGetIsogram() const            { return sIsogram; }
int32 IsogramGame::iGetCurrentGuess() const         { return iCurrentGuess; }
int32 IsogramGame::iGetIsogramLength() const        { return sIsogram.length(); }
int32 IsogramGame::iGetLossCount() const            { return iLossCount; }
int32 IsogramGame::iGetRunningScore() const         { return iRunningScore; }
int32 IsogramGame::iGetScore() const                { return iScore; }
int32 IsogramGame::iGetWinCount() const             { return iWinCount; }
void IsogramGame::IncrementGuess()                  { iCurrentGuess++; return; }
void IsogramGame::IncrementLoss()                   { iLossCount++; return; }

void IsogramGame::Reset()
{
    if (!bInitialized) 
    {
        bDisplayHints = true;
        bInitialized = true;
        bValidDictionary = false;
        iLossCount = 0;
        iScore = 0;
        iWinCount = 0;
    }
    bFirstGuess = true;
    iCurrentGuess = 1;
    sIsogram = SelectIsogram();
    return;
}

void IsogramGame::Tally()
{
    iRunningScore += iScore;
    iScore = 0;
    return;
}

int32 IsogramGame::iGetMaxGuesses() const 
{
    std::map <int32, int32> mapWordSizeToGuessCount { 
        { 3,4 }, { 4,6 }, { 5,8 }, { 6,9 }, 
        { 7,10 }, { 8,9 }, { 9,8 }, { 10,7 }, 
        { 11,6 }, { 12,5 }, { 13,4 }, { 14,3 } 
    };
    return mapWordSizeToGuessCount[sGetIsogram().length()];
}

Analysis IsogramGame::AnalyzeGuess(FString sGuess)
{
    Analysis analysis;

    int32 iIsogramLength = sIsogram.length();
    analysis.sPositionHint = FString(iIsogramLength, '-');
    analysis.sLetterHint = analysis.sPositionHint;
    
    for (int32 GuessLetter = 0; GuessLetter < iIsogramLength; GuessLetter++) {
        for (int32 IsogramLetter = 0; IsogramLetter < iIsogramLength; IsogramLetter++) {
            if (sGuess[GuessLetter] == sIsogram[IsogramLetter]) {
                bool bPosScore = false;
                bool bLetterScore = false;
                if (GuessLetter == IsogramLetter) 
                {
                    analysis.iPositionMatches++;
                    bPosScore = true;
                    analysis.sPositionHint[GuessLetter] = sGuess[GuessLetter];
                } else
                { 
                    analysis.iLetterMatches++;
                    bLetterScore = true;
                    analysis.sLetterHint[GuessLetter] = sGuess[GuessLetter];
                }
                if (!bLetterScore && bPosScore)         { iScore = (iScore + 3); }
                else if (bLetterScore && !bPosScore)    { iScore++; }
            }
        }
    }
    if (analysis.iPositionMatches == iIsogramLength) 
    {
        bGuessMatch = true;
        iWinCount++;
    } else {
        bGuessMatch = false;
    }
    return analysis;
}

FString IsogramGame::SelectIsogram()
{
    std::vector<FString> Dictionary = {
        "bye", "art", "car", "yam", "lab", "the", "cut", "lot", "lie", "par",
        "say", "pay", "may", "jam", "mit", "din", "was", "pot", "pie", "mar",
        "ray", "elf", "fly", "fit", "lit", "sin", "put", "rot", "cry", "coy",
        "sand", "pair", "raid", "care", "sock", "fair", "hair", "land", "walk", "talk", "expo", "wasp",
        "same", "dart", "this", "from", "suit", "acre", "ages", "bale", "bail", "fast",
        "felt", "fawn", "nape", "army", "navy", "sold", "soda", "soup", "wave", "yarn",
        "toads", "brick", "stick", "roads", "stand", "trick", "thick", "loads", "talks", "locks", "angel",
        "thing", "miles", "lives", "facts", "cloth", "dwarf", "empty", "trash", "envoy", "enact", 
        "faith", "farms", "farce", "fairy", "laugh", "lingo", "litre", "march", "marsh", "swift",
        "jaunts", "abound", "tricks", "bricks", "crawls", "crowns", "around", "orgasm", "bounty", "gizmos", 
        "travel", "wealth", "second", "curled", "loving", "belfry", "fables", "factor", "fairly", "famine", "bronze",
        "farces", "nailed", "nebula", "nickel", "muster", "buster", "myrtle", "nachos", "mythos", "phrase", "quartz",
        "jukebox", "ziplock", "lockjaw", "quickly", "crazily", "jaybird", "jackpot", "quicken", "quicker", "imports",
        "clothes", "polearm", "jockeys", "subject", "cliquey", "apricot", "anxiety", "domains", "dolphin", "exclaim",
        "fabrics", "factory", "haircut", "pulsing", "scourge", "schlump", "turbine", "wrongly", "wyverns", "yoghurt",
        "isogram", "mindful",
    };
    int32 iNumberOfIsograms = size(Dictionary);

    // ----- validate dictionary ONCE ONLY ----- //
    // this is somewhat anachronistic, as now the secret word is validated immediately prior to use (as well)
    // but there's no harm keeping it for extra data, i.e. in a case where new words are added to the dictionary...
    // in one run of the program they will all be immediately flagged on the console.
    if (!bValidDictionary) // TODO: invalid dictionaries re-validate between rounds... not a high priority to fix, really
    {
        bValidDictionary = true;
        FString sTestString;
        bool oops = true;

        for (int32 i = 0; i < iNumberOfIsograms; i++)
        {
            sTestString = Dictionary[i];
            if (!bIsIsogram(sTestString))
            {
                bValidDictionary = false;
                if (oops)
                {
                    std::cout << "E R R O R . V A L I D A T I N G -" << iNumberOfIsograms << "- W O R D S\n";
                    std::cout << "INTERNAL ERROR z-13: Dictionary validation failures detected:\n";
                    oops = false;
                }
                std::cout << " -- Dictionary[" << i << "] = \"" << sTestString << "\"\n";
            }
        }
        if (!oops)
        {
            std::cout << "\nWARNING: Any words listed above will be ignored as unplayable.\n\n";
        }
    }
    FString sSelection;
    int32 iSelection;
    do {
        srand(unsigned(time(NULL)));
        iSelection = rand() % iNumberOfIsograms;
        sSelection = Dictionary[iSelection];
    } while (!bIsIsogram(sSelection));
    // TODO word-length discrimination, leading to:
    // TODO challenege-level system...
    return sSelection; // Break and watch here to cheat
}

bool IsogramGame::bIsIsogram(FString testString)
{
    testString = sStringToLower(testString);
    std::map<char, bool> observedLetter;

    for (auto Letter : testString) {
        if (!observedLetter[Letter]) { observedLetter[Letter] = true; }
        else return false;
    } return true;
}

FString IsogramGame::sStringToLower(FString convertString)
{
    int32 iLength = convertString.length();
    for (int32 i = 0; i < iLength; i++) { convertString[i] = tolower(convertString[i]); }
    return convertString;
}

// ----- Letter Box Area ----- //

FString LetterBox::sGetLetters() const      { return sBoxOfLetters; }
void LetterBox::Reset()                     { sBoxOfLetters = ""; return; }

void LetterBox::SubmitLetter(char letter)
{
    if (sBoxOfLetters == "") { sBoxOfLetters += letter; }
    else
    {
        int32 boxSize = sBoxOfLetters.length();
        int32 letterMatches = 0;
        bool bNewChar = false;
        for (int32 i = 0; i < boxSize; i++) 
        {
            if (!(sBoxOfLetters[i] == letter)) { letterMatches++; bNewChar = true; }
        }
        if (bNewChar && (letterMatches == boxSize)) { 
            sBoxOfLetters += letter; 
        }
    }
    std::sort(sBoxOfLetters.begin(), sBoxOfLetters.end());
    return;
}

Summary

If you got this far, congratulations, you win a prize! Free (open) source-code.

By all means, if you see something that nags you here, please do tell me about it. As I say, I’m fairly amateur and it’s certain there are better ways to do many (or all) of the things I’ve done with this project.

1 Like

Awesome share, and a beautifully formatted post. Well done and thank you!

I will continue with this interesting game, especially coming up with a solver.

For now, I have frozen it before I refactor for my own purposes. Here is a current play of the game.

I see a little more play-tested adjustment needed. My code can be found at orcmid’s Section02 on GitHub. I think I will fork that to a better project and then deviate in other ways.

Thanks for the challenges and the working toward having plumbing that will work underneath Unreal Engine.

Thanks for being such an active community member @orcmid

First of all, I wouldn’t say this is the final game.
It is for now. Since I’m still at such a noob level in programing I feel it’s better to leave it here and move on with the course. I can always come back later to modify it. :smile: struggled for so long to get the random words to work and tried so many different approaches to it ( I know you said we wouldn’t do it but I needed it ). Then I looked at Zaptruders code and found out what I had done wrong. So, big shout out to Zaptruder, hope you don’t get mad at me for using your solution. :wink:
I have not cleaned the code after my mayhems… so it might not be the best to look at. But, it’s here anyway and I’m pumped up to move on and fill my head with syntax and information!

1 Like

I’m so excited the I completed this GAME!!! I look forward to constantly working on this game. I added a difficulty chooser, although not a elegant as Ben would:sweat: , but I shall be working hard on it. Thank you Ben and fellow class mates.
My Code is over at : Kaylen’s Code over on github :grin:

WOW! The art is amazing and I totally loved your version of the game. I’m sure I’ll learn heaps from your work. Best of luck with the rest of the course:grin:

I just finished Section 2 and added the difficulty settings as suggested by @ben.

My game looks like this:

The code is on a Gist on GitHub.

I had to do a bit of exception handling to prevent the user from entering a non-numeric value and crashing my game.

I just finished Section 2 Bull Cow Game, differently from the lectures I have added:

  • words.txt -> a dictionary of Isograms to choose from

  • difficulty settings -> when the users prompts the game, they are asked for the length of the Isogram to guess

There are still a couple features I was thinking of adding in the future such as:

  • validation of isogram length
  • colours to intro screen
  • record of wins and losses
  • word hints -> keeps track of letters guessed, gives you 1 letter in exchange for a try?

Looking forward to section 3BullCowGame.zip|attachment (3.7 KB)

Mine was more of a spin off of the game and adapted to my friends that play tested it!

  1. The Details of the changes are in this post!
    My Playtesting Results! (I added a twist to the game!)
  2. The download link to my game is here if anyone wants to try it!
    FBullCowGame.zip (4.4 KB)

Thanks for making this course possible for all of us!
I wish you all a happy week!

A finished version for my game can be found here. I also added a random word picker, I downloaded the SOWPODS scrabble word dictionary and added all the isograms contained within that to the game. As a result it’s pretty difficult to win. I aim to add a difficulty selector next and refine the number of guesses, with 50330 words to choose from it’s fairly difficult so I will also change the game a bit further with a hinting system, and to just display the location of the bull’s straight away instead of the player having to figure out which letters are in what places.

I stored the list of words in a text file but to make this work so far I have to have the absolute path to the text file in a constant string in my class. This isn’t ideal as it means the player will have to manually edit this later. My code wouldn’t run in xcode when a relative path was used, why is this and what is a good solution? It seems a bit rubbish that this has to be manually set in a code file.

I encountered a bug in adding the random word picker into my code, and wondered if anyone else also had the same problem? I noticed a problem that sometimes the introduction message would say something like “Guess the 9 letter word” and I would enter nine letters and receive the following error message “please enter a 5 letter word”. The problem is that the reset method of BCGame is called twice, once when the object is created and then again in play game. I have “resolved” this issue by moving the call to printIntro into the play game method and calling it after the reset method is called. I am not really sure however if this is the best solution? It seems kind of broken to me that the reset method will be called twice but I can’t think of a better solution.

I just finished BullCowGame and learned a lot from this section. Thanks @ben :slight_smile:

I made almost no changes in the code, but I’m used to some other coding standard (variables and functions start with a small letter, and every new word starts with a capital letter. For example: bullCowGame).

I also made an array (list) with hidden words. So everytime you play, you’ll get a random word from the list. I’m now searching for a way to make my guess full lowercase by default. For example: your guess is: PLanEt, it will automatically translate to: planet. This is more user friendly than the not lowercase error.

You can find my code here.

This weekend I’m making a small C++ console project to practice a little bit with C++ and classes. Next week I start with section 3. :slight_smile:

1 Like

You’re welcome :slight_smile:

Hi,

I just finished section 2.

Everytime you start a game , it generates a random isogram from an array.

Thats all.

I’m coming section 3!

You can check my code here: https://drive.google.com/open?id=0B-7MkPbofvvoQU45N3ktMEk4cGc

Hi All!

I added a few upgrades to the BullCowGame:
-a user option for difficulty
-a random word selected from a separate .txt file
-an additional error code for spaces entered (“flower” vs. “flow er”)

The only gripe I really have is my random word selector code portion feels sloppy.

Here’s my code:
BullCowGame.zip (4.4 KB)

Thank you for the awesome lessons so far!

Hey Guys!

So this project was really fun. I really enjoyed how in-depth the Bulls and Cows series was. I never really had a chance to get to know the Visual Studio Editor until now ( I worked with the Atom Editor for a lot of my work ). So here’s what i have so far.

##Features

  • Added a feature for users to be able to choose a word length.
  • Added a feature to get a random word based on their word length.
  • Randomly adds a word if no word length is given.

##Links
Github Repository: https://github.com/Enoch2k2/bulls-and-cows

##PS
Don’t laugh too hard with my ASCII, i’m a terrible artist haha.

main.cpp

/* This file is used for the view part of the MVC structure
	See FBullCowGame.cpp for game logic.
*/
#pragma once

#include <iostream>
#include <string>
#include "FBullCowGame.h"

// Unreal standard
using FText = std::string;
using int32 = int;

void PrintIntro();
void PrintGameSummery();
void PlayGame();
FText GetValidGuess();
int32 GetNewWordLength();
bool AskToPlayAgain();

FBullCowGame BCGame; // C++ Instantiation'

//intro point for the app
int main()
{
	do
	{
		PrintIntro();
		PlayGame();
	}
	while(AskToPlayAgain());
	// will ask the user to play again and keep it in a loop if 'y'
	return 0;
}


void PrintIntro()
{
	// prints an introduction (terrible ASCII)
	std::cout << "\n\nWelcome to Bulls and Cows, a fun word game.\n";
	std::cout << std::endl;
	std::cout << "|-----, " << " ,----," << std::endl;
	std::cout << "| |__| |" << "| |---'" << std::endl;
	std::cout << "|  __  |" << "| |    " << std::endl;
	std::cout << "| |__| |" << "| |---," << std::endl;
	std::cout << "|_____/ " << " \\___/" << std::endl;
	return;
}

// Gives details of how the game ended (win / lose)
void PrintGameSummery()
{
	if (BCGame.IsGameWon()) {
		std::cout << "Congratulations, you won!\n";
	} else {
		std::cout << "Sorry, better luck yet next time!\n";
	}
	return;
}

// players a single game to completion
void PlayGame()
{
	// ask player of how long of letters, 3-7
	int32 GetWordLength = GetNewWordLength();
	// resets game back to beginning state
	BCGame.Reset(GetWordLength);
	std::cout << "Can you guess a " << BCGame.GetHiddenWordLength() << " letter word I am thinking of?" << std::endl << std::endl;
	// while the game is NOT won and there are still remaining tries
	while(!BCGame.IsGameWon() && BCGame.GetCurrentTry() <= BCGame.GetMaxTries())
	{
		FText Guess = GetValidGuess();
		FBullCowCount BullCowCount = BCGame.SubmitValidGuess(Guess);
		std::cout << "Bulls = " << BullCowCount.Bulls;
		std::cout << ". Cows = " << BullCowCount.Cows << std::endl << std::endl;
	}
	// at this point the game is over and we'll tell the user how it ended.
	PrintGameSummery();
	return;
}

// get a random number between two integers
int32 GetRandomNumber(int32 min, int32 max)
{
	int32 n = max - min + 1;
	int32 remainder = RAND_MAX % n;
	int32 x;
	do {
		x = rand();
	} while (x >= RAND_MAX - remainder);
	return min + x % n;
}

// allow player to choose length of word
int32 GetNewWordLength()
{
	std::cout << "Would you like to choose a word length?(y/n): ";
	FText Response = "";
	std::getline(std::cin, Response);
	if (Response == "y" || Response == "Y") {
		std::cout << "\n How long of word? Enter 3-7 :";
		Response = "";
		std::getline(std::cin, Response);
		if (Response == "3") {
			return 3;
		}
		else if (Response == "4") {
			return 4;
		}
		else if (Response == "5") {
			return 5;
		}
		else if (Response == "6") {
			return 6;
		}
		else if (Response == "7") {
			return 7;
		}
		else {
			std::cout << "Invalid input\n";
			GetNewWordLength();
		}
	}
	else {
		return GetRandomNumber(3, 7);
	}
}


FText GetValidGuess()
{
	EGuessStatus Status = EGuessStatus::Invalid_Status;
	FText Guess = "";
	do {
		// get guess
		std::cout << "Try " << BCGame.GetCurrentTry() << " of " << BCGame.GetMaxTries() << ". Enter Guess: ";
		std::getline(std::cin, Guess);

		// check guess validity
		Status = BCGame.CheckGuessValidity(Guess);
		
		// check for guess errors
		switch (Status)
		{
		case EGuessStatus::Wrong_Length:
			std::cout << "Please enter a " << BCGame.GetHiddenWordLength() << " letter word.\n\n";
			break;
		case EGuessStatus::Not_Isogram:
			std::cout << "Please enter a word without repeating letters.\n\n";
			break;
		case EGuessStatus::Not_Lowercase:
			std::cout << "Please use lowercase characters.\n\n";
			break;
		default:
			// assume the guess is valid
			break;
		}
	} while (Status != EGuessStatus::OK);
	// if the Guess is not valid then restart try
	return Guess;
}

bool AskToPlayAgain()
{
	std::cout << "Do you want to play again? (y/n): ";
	FText Response = "";
	std::getline(std::cin, Response);

	return (Response[0] == 'y' || Response[0] == 'Y');
}

BullCowGame.h

/*
	Header file for the FBullCowGame, this holds the
	definitions of the game logic to be used in our
	FBullCowGame class. See FBullCowGame.cpp for game
	logic.
*/
#pragma once

#include <string>

// unreal standard
using FString = std::string;
using int32 = int;

// we set up a simple struct to keep track of bulls and cows
struct FBullCowCount {
	int32 Bulls = 0;
	int32 Cows = 0;
};

// All of our guess status
enum class EGuessStatus
{
	Invalid_Status,
	OK,
	Not_Isogram,
	Wrong_Length,
	Not_Lowercase
};

// our predefined list of public and private methods / variables
class FBullCowGame {
public:
	FBullCowGame(); // constructor

	int32 GetMaxTries() const;
	int32 GetCurrentTry() const;
	int32 GetHiddenWordLength() const;
	FString GetWord(int32) const;
	FString GetRandomWord(FString LetterArray[]) const;
	// TODO Create Function to get random word based on length chosen;
	bool IsGameWon() const;

	EGuessStatus CheckGuessValidity(FString) const;

	void Reset(int32);
	FBullCowCount SubmitValidGuess(FString);

private:
	int32 MyCurrentTry;
	FString MyHiddenWord;
	bool bGameWon;
	bool IsIsogram(FString) const;
	bool IsLowerCase(FString) const;
};

BullCowGame.cpp

/*
	This file contains the logic of the game itself, it
	is the game controller. See the header file for a
	list of game definitions, or the main.cpp file for
	the view logic.
*/
#pragma once

#include "FBullCowGame.h"
#include <map>
#define TMap std::map

// unreal standard
using FString = std::string;
using int32 = int;

// instantiates the game with a fresh state
FBullCowGame::FBullCowGame(){ Reset(3); }

// getters
int32 FBullCowGame::GetCurrentTry() const { return MyCurrentTry; }
int32 FBullCowGame::GetHiddenWordLength() const { return MyHiddenWord.length(); }
bool FBullCowGame::IsGameWon() const { return bGameWon; }

// get random word to be the hidden word randomly
FString FBullCowGame::GetWord(int32 WordLength) const
{
	// create arrays of words based on length
	FString FiveLetterWords[3] = { "plane", "smile", "giant" };
	FString ThreeLetterWords[3] = { "and", "get", "sea" };
	FString FourLetterWords[3] = { "love", "grow", "tire" };
	FString SexLetterWords[3] = { "beauty", "flower", "string" };
	FString SevenLetterWords[3] = { "alchemy", "machine", "sardine" };
	if (WordLength == 3) {
		return GetRandomWord(ThreeLetterWords);
	}
	else if (WordLength == 4) {
		return GetRandomWord(FourLetterWords);
	}
	else if (WordLength == 5) {
		return GetRandomWord(FiveLetterWords);
	}
	else if (WordLength == 6) {
		return GetRandomWord(SexLetterWords);
	}
	else {
		return GetRandomWord(SevenLetterWords);
	}
}

// used to get a random word out of an array with a length of 3 elements
FString FBullCowGame::GetRandomWord(FString LetterArray[]) const
{
	int32 n = 2 - 0 + 1;
	int32 remainder = RAND_MAX % n;
	int32 x;
	do {
		x = rand();
	} while (x >= RAND_MAX - remainder);
	return LetterArray[0 + x % n];	
}

int32 FBullCowGame::GetMaxTries() const 
{
	// creates a difficulty - first difficulty in the TMap is 3 length word, 4 tries. ect...
	TMap<int32, int32> WordLengthToMaxTries{ {3,4}, {4,7}, {5, 10}, {6,15}, {7, 20} };
	return WordLengthToMaxTries[MyHiddenWord.length()];
}

// resets the game to beginning state
void FBullCowGame::Reset(int32 WordLength)
{
	const FString HIDDEN_WORD = GetWord(WordLength);
	MyHiddenWord = HIDDEN_WORD;
	MyCurrentTry = 1;
	bGameWon = false;
	return;
}

// validates the guess
FBullCowCount FBullCowGame::SubmitValidGuess(FString Guess)
{
	MyCurrentTry++;
	FBullCowCount BullCowCount;
	int32 WordLength = MyHiddenWord.length();
	for (int32 i = 0; i < WordLength; i++) {
		for (int32 j = 0; j < WordLength; j++)	{
			if (MyHiddenWord[i] == Guess[j]) {
				if (i == j) {
					BullCowCount.Bulls++;
				}
				else {
					BullCowCount.Cows++;
				}
			}
		}
	}
	// if they get the word right, set the bGameWon flag to true
	// indicating that they have won
	if (BullCowCount.Bulls == WordLength) {
		bGameWon = true;
	}
	else {
		bGameWon = false;
	}
	return BullCowCount;
}

EGuessStatus FBullCowGame::CheckGuessValidity(FString Guess) const
{
	if (!IsIsogram(Guess))// if the guess isn't an isogram
	{
		return EGuessStatus::Not_Isogram;
	}
	else if (!IsLowerCase(Guess)) // if the guess isn't all lowercase
	{
		return EGuessStatus::Not_Lowercase;
	}
	else if (Guess.length() != GetHiddenWordLength())// if the guess length is wrong
	{
		return EGuessStatus::Wrong_Length;
	}
	else
	{
		return EGuessStatus::OK;
	}
}

bool FBullCowGame::IsIsogram(FString Word) const
{
	if (Word.length() <= 1){ return true; }

	TMap<char, bool> LetterSeen;
	// check each letter if the letter has been seen.
	for (auto Letter : Word)
	{
		Letter = tolower(Letter);
		// if the letter has been seen, then it's not an Isogram
		if (LetterSeen[Letter]) {
			return false;
		}
		else {
			// indicates the letter has been seen for the first time.
			LetterSeen[Letter] = true;
		}
	}	
	return true;
}

// this method checks the string if all the characters are lowercased.
bool FBullCowGame::IsLowerCase(FString Word) const
{
	if (Word.length() <= 1) { return true; }
	// checks each letter if the letter is lowercased.
	for (auto Letter : Word)
	{
		if (!islower(Letter)) {
			return false;
		}
	}
	return true;
}

Hello,

Here’s my final code for Bulls and Cows.
It took way longer than I expected to add the changes I wanted but a least I’m satisfied with it which has to count for something.

I added a bank of isograms from three to six letters picked from this website: http://www.morewords.com/unique-letters/

In an effort to narrow down the process of figuring out the word I’ve only chosen words that are common nouns ( so something “easy to think of” like a cat, a house, a thing ). Pluralization is also excluded because that just gets unnecessarily confusing.

The player can also pick the length of the secret word or have it chosen at random.
You also get a brief explanation of the game and the amount of tries you have initially.

Here’s the code:

main.cpp

#include <iostream>
#include <string>
#include <ctime>
#include "FBullCowGame.h"

/* This is the exe that makes use of the BullCow class.
This acts as a view in a MVC pattern, and is responsible for all user interaction. For game logic see BullCow class.

*/

using FText = std::string;
using int32 = int;

// Functions
void PrintIntro();
void PlayGame();
void ChooseSecretWordLength();
FText GetValidGuess();
bool AskToPlayAgain();
void PrintGameSummary();

FBullCowGame BCGame;	// Create an instance of the Game Class

int main()				// entry point of the game
{
	PrintIntro();
	do
	{
		PlayGame();
	} 
	while (AskToPlayAgain() == true);
	return 0;			// exits the application
}

void PrintIntro()
{
	std::cout << "\n\n";
	std::cout << "   /(           )\\Welcome to  _________    " << std::endl;
	std::cout << "   \\ \\__-----__/ / Bulls     /         \\   " << std::endl;
	std::cout << "     /_       _\\      &     / _       _ \\  " << std::endl;
	std::cout << "    /- \\     \\ -\\    Cows! /-|         |-\\ " << std::endl;
	std::cout << "       \\\\_____\\/             | _____ /      " << std::endl;
	std::cout << "        (o_____o)           (o_____o)      " << std::endl;
	std::cout << " \n\n";
	std::cout << " Guess the secret isogram!\n";
	std::cout << " A Cow means you've found one of the secret word's letters in the wrong place. \n";
	std::cout << " A Bull means you've found a letter and it is in the right place.";
	std::cout << std::endl;
	return;
}

void PlayGame()
{
	BCGame.Reset();
	ChooseSecretWordLength();
	std::cout << "\n The secret isogram is " << BCGame.GetHiddenWordLength() << " letters long. \n It is a common noun and cannot be in its plural form. ";
	std::cout << "\n You have " << BCGame.GetMaxTries() << " tries.";
	int MaxTries = BCGame.GetMaxTries();							//pick the ammount of tries based on the words length
	while (!BCGame.IsGameWon() && BCGame.GetCurrentTry() <= MaxTries)//loop until the game is won for the ammount of turns the player can guess
	{
		FText Guess = GetValidGuess();
		FBullCowCount BullCowCount = BCGame.SubmitValidGuess(Guess);// Submit valid guess to the game
		std::cout << " Bulls = " << BullCowCount.Bulls;				// print number of bulls and cows
		std::cout << "  " << " Cows = " << BullCowCount.Cows << "  ";
		std::cout << "Your guess was " << Guess << ". \n";			// repeats player's guess
	}
	PrintGameSummary();
	return;
}

void ChooseSecretWordLength()
{
	bool LengthChosen = false;
	FText WordLengthInput = "";
	std::cout << " \n To choose the length of the secret word, \n please enter a number between 3 and 6 or 'R' to pick at random: ";
	while (!LengthChosen)												// loop until we've decided how long the secret word is
	{
		std::getline(std::cin, WordLengthInput);
		FText DesiredWordLength = WordLengthInput;						// redeclaring the word length to make the input c-string into a std::string/FText

		if (DesiredWordLength == "R" || DesiredWordLength == "r")
		{
			srand(time(NULL));
			BCGame.ChooseSecretWord(std::rand() % 3 + 3);
			LengthChosen = true;
		}
		else if (DesiredWordLength == "3" || DesiredWordLength == "4" || DesiredWordLength == "5" || DesiredWordLength == "6")
		{
			int32 WordLength = std::stoi(DesiredWordLength);			// make the deisred word length FText into an int32
			BCGame.ChooseSecretWord(WordLength);
			LengthChosen = true;
		}
		else
		{
			std::cout << " Please enter a number from 3 to 6 or 'R' for random: ";
		}
	}
}

FText GetValidGuess()
{
	FText Guess = "";
	EGuessStatus Status = EGuessStatus::Invalid_Status;
	do
	{
		std::cout << "\n \n Guess #" << BCGame.GetCurrentTry() << ". ";// get a guess from the player
		std::cout << "Enter your guess: ";
		std::getline(std::cin, Guess);
		Status = BCGame.CheckGuessValidity(Guess);					// check the guess validity

		switch (Status)												// give the player the apropriate feedback
		{
		case EGuessStatus::Wrong_Length:
			std::cout << "Please enter a " << BCGame.GetHiddenWordLength() << " letter word.";
			break;

		case EGuessStatus::Not_Lowercase:
			std::cout << "Please enter your guess in all lower case ";
			break;

		case EGuessStatus::Not_Isogram:
			std::cout << "Please enter an isogram, a word with no repeating letters.";
			break;

		default:
			break;

		}
	} while (Status != EGuessStatus::OK);							// loop until the player inputs a valid guess
	return Guess;
}

bool AskToPlayAgain()
{
	std::cout << "Do you wish to continue? Y/N: ";
	FText Response = "";
	std::getline(std::cin, Response);
		
	if (Response[0] == 'y' || Response[0] == 'Y')
	{
		std::cout << "\n";
		return true;
	}
	else if (Response[0] == 'n' || Response[0] == 'N')
	{
		std::cout << "\n";
		return false;
	}
	AskToPlayAgain();
	return true;
}

void PrintGameSummary()
{
	if (BCGame.IsGameWon() == true)
	{
		std::cout << " \n Congratulations! The word was " << BCGame.GetMyHiddenWord() << "! ";
	}
	else
	{
		std::cout << " \n The word was: " << BCGame.GetMyHiddenWord() << ". Better luck next time! ";
	}
}

FBullCowGame.h

#pragma once
#include <string>

using FString = std::string;
using int32 = int;

struct FBullCowCount 
{
	int32 Bulls = 0;
	int32 Cows = 0;
};

enum class EGuessStatus
{
	OK,
	Not_Isogram,
	Wrong_Length,
	Not_Lowercase,
	Invalid_Status
};

class FBullCowGame 
{
public:

	FBullCowGame(); // constructor

	int32 GetCurrentTry() const;
	int32 GetHiddenWordLength() const;
	bool IsGameWon() const;
	FString GetMyHiddenWord() const;
	void Reset();
	FString ChooseSecretWord(int32 NumberOfLetters);
	int32 GetMaxTries() const;
	EGuessStatus CheckGuessValidity(FString Guess) const;
	FBullCowCount SubmitValidGuess(FString);

private: // see constructor for initialisation

	int32 MyCurrentTry;
	FString MyHiddenWord;
	bool bIsGameWon;

	bool IsIsogram(FString) const;
	bool IsLowercase(FString) const;
};

FBullCowGame.cpp

#include "FBullCowGame.h"
#include <map>
#include <ctime>
#define TMap std::map

using int32 = int;

// Default constructor
FBullCowGame::FBullCowGame() { Reset(); }

// Getter functions
int32 FBullCowGame::GetCurrentTry() const { return MyCurrentTry; }
int32 FBullCowGame::GetHiddenWordLength() const { return MyHiddenWord.length(); }
bool FBullCowGame::IsGameWon() const { return bIsGameWon; }
FString FBullCowGame::GetMyHiddenWord() const { return MyHiddenWord; }

// Functions
void FBullCowGame::Reset()
{
	bIsGameWon = false;
	MyCurrentTry = 1;
	return;
}

FString FBullCowGame::ChooseSecretWord(int32 NumberOfLetters) // chooses a secret word from a collection of isograms based on the player's choice of word length
{
	srand(time(NULL));

	FString ThreeLetterWords[110] =
	{
		"ace", "air", "ash", "alp", "awe", "axe", "bag", "bed", "boy", "bus",
		"cab", "cam", "car", "cat", "cel", "chi", "cob", "cod", "cup", "cue",
		"dam", "dot", "dye", "ear", "eat", "ego", "elf", "elk", "fan", "fat",
		"fog", "fox", "fur", "gap", "gas", "gym", "ham", "hat", "ice", "ink",
		"ion", "ivy", "jab", "jag", "jam", "jaw", "jay", "jet", "job", "jog",
		"key", "lab", "lad", "lam", "lap", "led", "lei", "leg", "lip", "log",
		"lux", "mac", "man", "map", "mat", "maw", "may", "mud", "mug", "nap",
		"net", "nog", "nut", "oaf", "oak", "oar", "oat", "ohm", "oil", "ore",
		"owl", "pad", "pan", "paw", "pay", "pea", "pen", "pet", "pie", "pig",
		"pin", "pit", "ply", "rat", "ray", "rom", "sac", "spa", "spy", "sun",
		"toy", "tux", "urn", "van", "wax" ,"wok" ,"yak" , "yam", "yew", "yen",
	};

	FString FourLetterWords[136] =
	{
		"ache", "acid", "aloe", "alto", "ankh", "ante", "apex", "arch", "aunt", "auto",
		"axel", "bail", "bait", "band", "bang", "bank", "bard", "barn", "base", "bath",
		"bead", "beak", "beam", "bean", "bear", "bend", "bias", "bind", "bite", "bump",
		"cape", "clue", "cola", "deal", "dive", "exam", "face", "fact", "feat", "fern",
		"feta", "feud", "film", "firm", "fish", "fist", "game", "gear", "girl", "glow",
		"gnat", "gold", "golf", "hack", "haze", "head", "heat", "help", "home", "horn",
		"icon", "idea", "iron", "jail", "jean", "jolt", "jump", "kart", "kelp", "kilo",
		"lace", "lash", "lawn", "leaf", "leap", "lion", "lock", "lure", "lute", "mace",
		"melt", "milk", "mint", "mold", "name", "newt", "norm", "oath", "pace", "pack",
		"pawn", "pelt", "pile", "pine", "quay", "ramp", "rice", "rope", "sack", "sage",
		"sand", "shop", "show", "sign", "silk" ,"silo" ,"skin" , "sled", "soap", "soda",
		"soup", "star", "suit", "tail", "taxi", "toad", "tomb", "tram", "trap", "unit",
		"user", "vale", "veil", "vest", "vice", "wait", "walk", "wash", "wind", "wing",
		"wolf", "worm", "yarn", "yeti", "zeal" ,"zinc"
	};

	FString FiveLetterWords[143] =
	{
		"acorn", "adult", "alder", "angle", "ankle", "audit", "bacon", "badge", "bagel", "beach",
		"bonus", "brain", "break", "broth", "brute", "cabin", "cable", "cigar", "claim", "clerk",
		"cloud", "coble", "comet", "dance", "depot", "drain", "extra", "fable", "facet", "faith",
		"feast", "felon", "femur", "fiber", "fight", "flock", "flour", "forum", "gecko", "ghost",
		"ghoul", "giant", "gland", "glove", "gnome", "growl", "habit", "haiku", "haven", "house",
		"human", "image", "ivory", "kanji", "labor", "lapis", "laser", "lotus", "lumen", "lunge",
		"lymph", "march", "marsh", "mocha", "molar", "month", "motel", "nacho", "niche", "noble",
		"noise", "nomad", "ocean", "olive", "opera", "ounce", "padle", "paint", "patch", "phase",
		"pilot", "plain", "plume", "punch", "purse", "quack", "quake", "quilt", "quirk", "radio",
		"ramen", "realm", "recap", "rhino", "rivet", "roman", "saber", "sauce", "sault", "scalp",
		"scare", "scarf", "scrap", "screw", "shirt" ,"shock" ,"slant" , "slate", "slide", "slime",
		"slope", "sloth", "smirk", "smile", "straw", "stray", "study", "table", "talon", "tango",
		"thumb", "token", "topic", "torch", "tramp", "tribe", "tunic", "valet", "valse", "vinyl",
		"viper", "vista", "visor", "voice", "voter" ,"wafer", "wagon", "water", "wheat", "witch",
		"yacht", "yield", "zebra"
	};

	FString SixLetterWords[129] =
	{
		"action", "agency", "almond", "answer", "anthem", "ascent", "aspect", "author", "badger", "bakery",
		"bleach", "blight", "breath", "bridge", "bronze", "brunch", "bushel", "butler", "camper", "carpet",
		"cashew", "casino", "castle", "cavern", "chapel", "choker", "chorus", "cinder", "clause", "client",
		"climax", "combat", "curfew", "denial", "design", "domain", "dynamo", "eclair", "falcon", "family",
		"fedora", "felony", "fiance", "fiasco", "fidget", "flambe", "folder", "forest", "format", "garden",
		"glider", "goblet", "goblin", "hacker", "hockey", "hornet", "icebox", "income", "incult", "jacket",
		"jingle", "judoka", "keypad", "knight", "larynx", "latino", "lawyer", "legion", "locust", "magnet",
		"magpie", "maiden", "mantis", "matrix", "matron", "minute", "minuet", "motive", "muscle", "nectar",
		"object", "orchid", "orphan", "parcel", "pardon", "parent", "parody", "patrol", "patron", "pencil",
		"phrase", "pigeon", "pirate", "plague", "planet", "praise", "quartz", "refund", "region", "relish",
		"retina", "rocket", "rodent", "rumble", "sample" ,"scream" ,"sculpt" , "slicer", "sliver", "sludge",
		"smoker", "string", "stripe", "sundae", "symbol", "tavern", "teabox", "teacup", "trophy", "upload",
		"utopia", "vendor", "virtue", "walnut", "whisky", "wrench", "zealot", "zenith", "zombie"
	};
	
	if (NumberOfLetters == 3)
	{
		int32 RandIndex = rand() % 110 + 1;
		FString SecretWord = ThreeLetterWords[RandIndex];
		MyHiddenWord = SecretWord;
		return SecretWord;
	}

	else if (NumberOfLetters == 4)
	{
		int32 RandIndex = rand() % 136 + 1;
		FString SecretWord = FourLetterWords[RandIndex];
		MyHiddenWord = SecretWord;
		return SecretWord;
	}

	else if (NumberOfLetters == 5)
	{
		int32 RandIndex = rand() % 143 + 1;
		FString SecretWord = FiveLetterWords[RandIndex];
		MyHiddenWord = SecretWord;
		return SecretWord;
	}

	else if (NumberOfLetters == 6)
	{
		int32 RandIndex = rand() % 129 + 1;
		FString SecretWord = SixLetterWords[RandIndex];
		MyHiddenWord = SecretWord;
		return SecretWord;
	} 
}
int32 FBullCowGame::GetMaxTries() const
{
	TMap<int32, int32> WordLengthToMaxTries{ { 3, 6 },{ 4, 9 },{ 5, 10 },{ 6, 16 },{ 7, 20 } };
	return WordLengthToMaxTries[MyHiddenWord.length()];
}

EGuessStatus FBullCowGame::CheckGuessValidity(FString Guess) const
{
	if (!IsIsogram(Guess)) 
	{
		return EGuessStatus::Not_Isogram;
	}
	else if (!IsLowercase(Guess))  
	{
		return EGuessStatus::Not_Lowercase;
	}
	else if (Guess.length() != GetHiddenWordLength()) 
	{
		return EGuessStatus::Wrong_Length;
	}
	else
	{
		return EGuessStatus::OK;
	}
}

FBullCowCount FBullCowGame::SubmitValidGuess(FString Guess)			// receives a valid guess and increments Tries
{
	MyCurrentTry++;

	FBullCowCount BullCowCount;

	int32 HiddenWordLength = MyHiddenWord.length();
	for (int32 HWChar = 0; HWChar < HiddenWordLength; HWChar++)		// loop through all letters in the hidden word
	{
		for (int32 GChar = 0; GChar < HiddenWordLength; GChar++)	// for each of the hidden words letters, loop through all of the guesses letters
		{
			if (Guess[GChar] == MyHiddenWord[HWChar])				// and compare them
			{
				if (HWChar == GChar)								// if they match and are in the same position, they are Bulls
				{
					BullCowCount.Bulls++;
				}
				else												// if they just match, they are Cows
				{
					BullCowCount.Cows++;
				}
			}
		}
	}
	if (BullCowCount.Bulls == HiddenWordLength)						// if the player gets all Bulls, the game is won
	{
		bIsGameWon = true;
	}

	else 
	{
		bIsGameWon = false;
	}
	return 	BullCowCount;
}

bool FBullCowGame::IsIsogram(FString Guess) const
{
	if (Guess.length() <= 1) { return true; }						// treat 0 and 1 letter words as isograms

	TMap<char, bool> LetterSeen;									// TMap setup

	for (auto Letter : Guess)										// loop through all the letters of the guess
	{
		Letter = tolower(Letter);									//make the letter lower case and the function case insensitive by extension
		if (LetterSeen[Letter])										// if the letter has been added to the map, it's value will have been set to "true"
		{
			return false;											// we do NOT have an isogram
		}
		else
		{
			LetterSeen[Letter] = true;								// add the letter to the map, set it's value to "true"
		}
	}
	return true;													// The guess is a verified isogram
}

bool FBullCowGame::IsLowercase(FString Word) const
{
	if (Word.length() < 1) { return false; }
	for (auto Letter : Word)										// loop through the letters of the word
	{
		if (!islower(Letter))										// check if the letter is lowercase
		{
			return false;											// returns false if the lowercase check is failed by any of the letters
		}
	}
	return true;													// returns true if none of the letters are upper case
}

Hi Guys!
I just finished section 2.

I Added two small features:

  • chance to select the length of the word
  • The word is randomly chosen by an array based on the length of the word

This is my code:

main.cpp

     #include <iostream>
    #include <string>
    #include "FBullCowGame.h"
    using FText = std::string;
    using int32 = int;

    void PrintIntro();
    void PlayGame();
    bool AskToPlayAgain();
    FText GetValidGuess();

    FBullCowGame BCGame;
    int main()
    {
    	do
    	{
    		BCGame.Reset();
    		PrintIntro();
    		PlayGame();
    		BCGame.PrintGameSummary();
    	} while (AskToPlayAgain());
    	return 0;
    }

    //introduce to the game
    void PrintIntro()
    {
    	std::cout << "Welcome to Bulls and Cows, a fun word game.\n\n";
    	FString WordLenght = "";
    	int32 n;
    	while (true)
    	{
    		WordLenght = "";
    		n = 0;
    		std::cout << "Select word lenght (3-7): ";
    		std::getline(std::cin, WordLenght);
    		std::cout << "\n";
    		try
    		{
    			n = std::stoi(WordLenght);
    		}
    		catch (std::invalid_argument)
    		{
    			std::cout << "Please enter a number!\n\n";
    			continue;
    		}
    		catch (std::out_of_range)
    		{
    			std::cout << "Please enter a number between 3 and 7!\n\n";
    			continue;
    		}

    		if (n < 3 || n>7)
    		{
    			std::cout << "Please enter a number between 3 and 7!\n\n";
    			continue;
    		}
    		BCGame.SetWord(n);
    		break;
    	}
    }

    //Loop continually until the user gives a valid guess 
    FText GetValidGuess()
    {
    	FText Guess = "";
    	EGuessStatus status = EGuessStatus::Invalid_Status;
    	do
    	{
    		//get a guess from the player
    		std::cout << "Try: " << BCGame.GetCurrentTry() << " of " << BCGame.GetMaxTries() << ". Enter your guess: ";
    		Guess = "";
    		std::getline(std::cin, Guess);

    		//check status and give feedback
    		status = BCGame.CheckGuessValidity(Guess);
    		switch (status)
    		{
    		case EGuessStatus::Wrong_lenght:
    			std::cout << "Please enter a " << BCGame.GetGuessWordLenght() << " letter word\n\n";
    			break;
    		case EGuessStatus::Not_Isogram:
    			std::cout << "Please enter a word without repeating letters\n\n";
    			break;
    		case EGuessStatus::Not_Lowercase:
    			std::cout << "Please enter all lowercase letters\n\n";
    			break;
    		default:
    			break;	//assuming the guess is valid
    		}
    	} while (status != EGuessStatus::OK);	//keep looping until we get no errors
    	return Guess;
    }
    void PlayGame()
    {
    	int32 maxTry = BCGame.GetMaxTries();
    	while (!BCGame.IsGameWon() && BCGame.GetCurrentTry() <= maxTry)
    	{
    		FText Guess = GetValidGuess();




    		//submit a valid guess to the game, and receive counts
    		FBullCowCount BullCowCount = BCGame.SubmitValidGuess(Guess);

    		std::cout << "Bulls: " << BullCowCount.Bulls;
    		std::cout << " Cows: " << BullCowCount.Cows << "\n\n";
    	}
    }

    bool AskToPlayAgain()
    {
    	std::cout << "Do you want to play again? (y/n) ";
    	FText response = "";
    	std::getline(std::cin, response);
    	std::cout << "\n";
    	return (response[0] == 'y' || response[0] == 'Y');
    }`

FBullCowGame.h

#pragma once
#include <string>
#include <map>
using int32 = int;
using FString = std::string;
#define TMap std::map


struct FBullCowCount
{
	int32 Bulls = 0;
	int32 Cows = 0;
};
struct Words
{
	FString WordsToGuess[10];
};
enum EGuessStatus
{
	Invalid_Status,
	OK,
	Not_Isogram,
	Not_Lowercase,
	Wrong_lenght
};
class FBullCowGame
{
public:
	FBullCowGame();
	void Reset();
	void PrintGameSummary();
	void SetWord(int32 n);
	int32 GetGuessWordLenght() const;
	int32 GetMaxTries() const;
	int32 GetCurrentTry() const;
	bool IsGameWon() const;
	EGuessStatus CheckGuessValidity(FString);
	FBullCowCount SubmitValidGuess(FString);

private:
	int32 MyCurrentTry;
	FString MyHiddenWord;
	bool bGameWon;
	bool IsIsogram(FString) const;
	bool IsLowerCase(FString) const;
	TMap<int32, Words>WordsMap;

};

FBullCowGame.cpp

#include "FBullCowGame.h"
#include <iostream>
#include <map>
#define TMap std::map
using int32 = int;
using FString = std::string;
int32 FBullCowGame::GetMaxTries() const 
{ 
	TMap<int32, int32>WordLenghtToMaxTries = { {3,4},{4,7},{5,10},{6,16},{7,20} };
	return WordLenghtToMaxTries[MyHiddenWord.length()];
}

int32 FBullCowGame::GetCurrentTry() const { return MyCurrentTry; }

FBullCowGame::FBullCowGame()
{
	Reset();
	WordsMap.insert(std::pair<int32, Words>(3, { "man","job","mix","box","cup","one","gym","big","boy","day" }));
	WordsMap.insert(std::pair<int32, Words>(4, { "rock","quiz","wolf","jump","base","true","blue","burn","busy","work" }));
	WordsMap.insert(std::pair<int32, Words>(5, { "angle","angry","mouse","alone","actor","audio","blind","sword","brain","broad" }));
	WordsMap.insert(std::pair<int32, Words>(6, { "planet","action","agency","source","answer","active","advise","bridge","branch","custom" }));
	WordsMap.insert(std::pair<int32, Words>(7, { "mistake","bearing","careful","century","convert","explain","dynamic","improve","include","kitchen" }));
}

void FBullCowGame::Reset()
{
	MyCurrentTry = 1;
	bGameWon = false;
}

void FBullCowGame::PrintGameSummary()
{
	if (bGameWon)
	{
		std::cout << "Well Done! You Win!\n";
		return;
	}
	std::cout << "You Lose. Better luck next time!\n";
}

void FBullCowGame::SetWord(int32 n)
{
	
	int random = std::rand() % 11;
	MyHiddenWord = WordsMap[n].WordsToGuess[random];
}

int32 FBullCowGame::GetGuessWordLenght() const
{
	return MyHiddenWord.length();
}

bool FBullCowGame::IsGameWon() const
{
	return bGameWon;
}

EGuessStatus FBullCowGame::CheckGuessValidity(FString Guess)
{
	if (!IsIsogram(Guess))	//if the guess is not an isogram
	{
		return EGuessStatus::Not_Isogram;
	}
	else if (!IsLowerCase(Guess))	//if the guess is not all lowercase
	{
		return EGuessStatus::Not_Lowercase;
	}
	else if (GetGuessWordLenght() != Guess.length())	//if the guess lenght is wrong
	{
		return EGuessStatus::Wrong_lenght;
	}
	else
	{
		return EGuessStatus::OK;
	}
}
//receives a valid guess, increments turn, and return count
FBullCowCount FBullCowGame::SubmitValidGuess(FString Guess)
{
	MyCurrentTry++;
	FBullCowCount BullCowCount;
	int32 WordLenght = MyHiddenWord.length();	//assuming same lenght as guess

	//loop through all letters in the guess
	for (int32 i = 0; i < WordLenght; i++)
	{//compare letters against the hidden word
		for (int32 j = 0; j < WordLenght; j++)
		{
			if (MyHiddenWord[i] == Guess[j]) // if they match then
			{

				if (i == j)//if they are in the same place
				{
					BullCowCount.Bulls++; //increment bulls
				}
				else
				{
					BullCowCount.Cows++; //increment cows
				}
			}
		}


	}
	if (BullCowCount.Bulls == WordLenght)
		bGameWon = true;
	return BullCowCount;
}

bool FBullCowGame::IsIsogram(FString Word) const
{
	if (Word.length() < 2)
		return false;

	TMap<char, bool> letterSeen;
	for (char letter : Word)
	{
		letter = tolower(letter);
		if (letterSeen[letter])
			return false;
		letterSeen[letter] = true;
	}
	return true;
}

bool FBullCowGame::IsLowerCase(FString Word) const
{
	for (auto letter : Word)
	{
		if (!islower(letter))
			return false;
	}
	return true;
}

Privacy & Terms