Bull Cow S3-85 - Release 1

I’ve been chipping away at this course and have something I feel comfortable enough with now!

In this version, I have incorporated a couple features:

  • Narrative Flavour: Curse of the farmer - Looks like the farmers been cursed! Seems like he’s got a request to break the curse!
  • Basic level system - solve 5 word to complete the farmers request
  • Guess feedback - Show guess letter positions (B for match, C for letter in wrong place)
  • Help! - Everyone needs a reminder of the rules once in a while, ask the farmer for ‘help’ anytime for a refresher
  • Many functions exposed as public functions for potential “external” interaction of class for future revisits
  • Source word list from text file “HiddenWordList.txt” (refer to S3-70 & 80) - Checks in place to see if it exists

Thoughts on TODOs for future revisits of this project:

  • Would like to interact with environment to obtain clues or more retries from world
  • Some sort of visual feedback (other than text) when an answer is incorrect or life lost (environment reacts)

BullCowCartridge.h

// Code is  freely available to reuse - Just credit is requested :)
// Base code from GameDev

#pragma once

#include "CoreMinimal.h"
#include "Console/Cartridge.h"
#include "BullCowCartridge.generated.h"

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

UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class BULLCOWGAME_API UBullCowCartridge : public UCartridge
{
	GENERATED_BODY()

	public:
	virtual void BeginPlay() override;
	virtual void OnInput(const FString& Input) override;
	virtual void SetupGame();
	virtual void CheckGuess(const FString& Guess);
	virtual bool CheckGuessMatch(const FString& Guess) const;
	virtual bool CheckGuessIsogram(const FString& Guess) const;
	virtual bool CheckGuessLength(const FString& Guess) const;
	virtual TArray<FString> GetValidWords(const TArray<FString>& List);
	virtual void CheckLives();
	virtual void ReduceLives();
	virtual void UpdateHiddenWord();
	virtual void PrintGameHelp();
	virtual void PrintIntro();
	virtual void LoseGame();
	virtual void WinGame();
	virtual void EndGame();
	virtual bool IsGameOver() const;
	virtual bool IsGuessMatch(const FString& Guess) const;
	static bool IsIsogram(const FString& Word);
	virtual bool IsSameLength(const FString& Word, const FString& ComparisonWord) const;
	virtual bool HasLives() const;
	virtual void SetLives(const int32& Value);
	virtual void SetHiddenWord(const FString& Word);
	virtual int32 GetLives() const;
	virtual int32 GetCurrentLevel() const;
	virtual int32 GetWordLength(const FString& Word) const;
	virtual int32 GetHiddenWordLength() const;
	virtual FString GetRandomWord(const TArray<FString>& List) const;
	virtual bool HasWon() const;
	virtual void IncreaseLevel();
	virtual FBullCowCount GetBullCows(const FString& Guess) const;
	virtual FString GetBullCowPositons(const FString& Guess) const;
	
	// Your declarations go below!
	private:
	FString HiddenWord;
	TArray<FString> WordList;
	TArray<FString> IsogramList;
	int32 Lives;
	int32 MaxLevel;
	int32 CurrentLevel;
	bool bGameOver;
};

BullCowCartridge.cpp

// Code is  freely available to reuse - Just credit is requested :)
// Base code from GameDev
#include "BullCowCartridge.h"
//#include "Math/UnrealMathUtility.h" //Not needed as CoreMinimal from BullCowHeader includes

void UBullCowCartridge::BeginPlay() // When the game starts
{
    Super::BeginPlay();
    const FString WordListPath = FPaths::ProjectContentDir() / TEXT("WordLists/HiddenWordList.txt");
    //Load with Predicate
    FFileHelper::LoadFileToStringArrayWithPredicate(IsogramList, *WordListPath, [](const FString& Word)
    {
        return Word.Len() >= 4 && Word.Len() <= 8 && IsIsogram(Word);
    });
    if (IsogramList.Num() == 0) 
    {
        PrintLine("*There is no 'HiddenWordList.txt' file*");
        PrintLine("\nCannot play game without Hidden Words.");
        PrintLine("\nPlease close game and reinstall and/or\n create: HiddenWordList.txt");
        return;
    }
    SetupGame();
}

void UBullCowCartridge::OnInput(const FString& PlayerInput) // When the player hits enter
{
    if (IsGameOver())
    {
        ClearScreen();
        SetupGame();
    }
    else
    {
        CheckGuess(PlayerInput);
    }
}

void UBullCowCartridge::SetupGame()
{
    bGameOver = false;
    MaxLevel = 5;
    CurrentLevel = 0;
    ClearScreen();
    SetLives(5);
    SetHiddenWord(GetRandomWord(IsogramList));
    PrintIntro();
}

void UBullCowCartridge::CheckGuess(const FString& Guess)
{
    PrintLine(Guess);
    if (Guess == "help")
    {
        PrintGameHelp();
        return;
    }
    if (HasWon())
    {
        WinGame();
        return;
    }
    if (CheckGuessMatch(Guess))
    {
        UpdateHiddenWord();
        SetLives(5);
        //PrintLine(TEXT("DEBUG: %s"), *HiddenWord);
        return;
    }
    if (!CheckGuessLength(Guess))
    {
        return;
    }
    if (!CheckGuessIsogram(Guess))
    {
        return;
    }
    //Assume if above condtions dont return remove lives
    ClearScreen();
    ReduceLives();
    PrintLine(TEXT("That don't sound right..."));
    PrintLine(TEXT("\nI sense that the word is %i letters long"), GetHiddenWordLength());
    PrintLine(TEXT("I'd say you got %i more guesses left..."), GetLives());
    
    FBullCowCount Score = GetBullCows(Guess);
    PrintLine(TEXT("You have %i Bulls, and %i Cows\n"), Score.Bulls, Score.Cows);
    PrintLine(TEXT("%s"), *Guess);
    PrintLine(GetBullCowPositons(Guess));
    CheckLives();
}

bool UBullCowCartridge::CheckGuessMatch(const FString& Word) const
{
    if (IsGuessMatch(Word))
    {
        ClearScreen();
        PrintLine(TEXT("%s! Thats it! Thats the word!"), *Word);
        return true;
    }
    else
    {
        return false;
    }
}

bool UBullCowCartridge::CheckGuessIsogram(const FString& Word) const
{
    if (!IsIsogram(Word))
    {
        ClearScreen();
        PrintLine(TEXT("%s...\nThat word don't sound quite right..."), *Word);
        PrintLine(TEXT("It ain't an Isogram.\nIt has repeatin' letters!"));
        return false;
    }
    else
    {
        return true;
    }
}

bool UBullCowCartridge::CheckGuessLength(const FString& Word) const
{
    if (!IsSameLength(Word, HiddenWord))
    {
        ClearScreen();
        PrintLine(TEXT("%s...\nThat word don't sound quite right..."), *Word);
        PrintLine(TEXT("It ain't the right length o' letters"));
        PrintLine(TEXT("\nI sense that the word is %i letters long"), GetHiddenWordLength());
        return false;;
    }
    return true;
}

TArray<FString> UBullCowCartridge::GetValidWords(const TArray<FString>& List)
{
    TArray<FString> ValidList;
    for (FString Word : List)
    {
        if (GetWordLength(Word) >= 4 && GetWordLength(Word) <= 8)
        {
            if (IsIsogram(Word))
            {
                ValidList.Emplace(Word);
            }
        }
    }
    return ValidList;
}

void UBullCowCartridge::CheckLives()
{
    if (!HasLives())
    {
        LoseGame();
        return;
    }
    PrintLine(TEXT("\nGo on, take another guess why don't 'cha?"));
    return;
}

void UBullCowCartridge::UpdateHiddenWord()
{
    IncreaseLevel();
    SetHiddenWord(GetRandomWord(IsogramList));
    PrintLine(TEXT("I sense the next word is %i letters long..."), GetHiddenWordLength());
}

void UBullCowCartridge::PrintGameHelp()
{
    ClearScreen();
    PrintLine(TEXT("'To break the curse guess words of five..."));
    PrintLine(TEXT("Letters more of one they cannot hide...'"));
    PrintLine(TEXT("-Guess 5 words\n -Words have no repeatin' letters"));
    PrintLine(TEXT("They said it ain't wise to guess wrong."));
    PrintLine(TEXT("Pretty sure I can sense if:"));
    PrintLine(TEXT(" - Bull: Letter in the right place"));
    PrintLine(TEXT(" - Cow:  Letter in the wrong place"));
    PrintLine(TEXT("Go on, take a guess why don't 'cha?"));
     PrintLine(TEXT("Try guessing the %i letter word?"),  HiddenWord.Len());
}

void UBullCowCartridge::PrintIntro()
{
    PrintLine(TEXT("Well hey there traveller!"));
    PrintLine(TEXT("Who am I? Well, I'm just your run o' the\nmill farmer... cursed by pixies... heh..."));
    PrintLine(TEXT("Anywhoo, hows about you help me out?\nGuess the %i secret words and I'll be free!"), MaxLevel);
    PrintLine(TEXT("Try guessing the %i letter word?"),  HiddenWord.Len());
    PrintLine(TEXT("\nAsk for 'help' anytime!\n*Press the TAB button to focus on me*"), MaxLevel);
}

void UBullCowCartridge::LoseGame()
{
    ClearScreen();
    PrintLine(TEXT("Well, sorry partner, *heheheh*, looks like you'll be taking my place!"));
    EndGame();
}

void UBullCowCartridge::WinGame()
{
    ClearScreen();
    PrintLine(TEXT("Yeeeh haw! Ye did it! I'm Free from mah\ncurse!"));
    PrintLine(TEXT("Them pixies will have ta' return me to\nnormal now!"));
    EndGame();
}

void UBullCowCartridge::EndGame()
{
    bGameOver = true;
    PrintLine(TEXT("\nPress the ENTER button to continue..."));
}

void UBullCowCartridge::ReduceLives()
{
    --Lives;
}

bool UBullCowCartridge::IsGameOver() const
{
    return bGameOver;
}

bool UBullCowCartridge::IsGuessMatch(const FString& Guess) const
{
    return Guess == HiddenWord;
}

bool UBullCowCartridge::IsIsogram(const FString& Word)
{
    int32 WordLength = Word.Len();
    for (int32 Index = 0; Index < WordLength; Index++)
    {
        for (int32 Comparison = Index + 1; Comparison < WordLength; Comparison++)
        {
            if (Word[Index] == Word[Comparison])
            {
                return false;
            }
        }
    }
    return true;
}

bool UBullCowCartridge::IsSameLength(const FString& Word, const FString& ComparisonWord) const
{
    return Word.Len() == ComparisonWord.Len();
}

bool UBullCowCartridge::HasLives() const
{
    return Lives > 0;
}

void UBullCowCartridge::SetLives(const int32& Value)
{
    Lives = Value;
}

void UBullCowCartridge::SetHiddenWord(const FString& Word)
{
    HiddenWord = Word;
}

int32 UBullCowCartridge::GetLives() const
{
    return Lives;
}

int32 UBullCowCartridge::GetCurrentLevel() const
{
    return CurrentLevel;
}

int32 UBullCowCartridge::GetWordLength(const FString& Word) const
{
    return Word.Len();
}

int32 UBullCowCartridge::GetHiddenWordLength() const
{
    return HiddenWord.Len();
}

FString UBullCowCartridge::GetRandomWord(const TArray<FString>& List) const
{
    if (List.Num() > 0)
    {
        return List[FMath::RandRange(0, List.Num() - 1)];
    }
    return "";
}

FBullCowCount UBullCowCartridge::GetBullCows(const FString& Guess) const
{
    FBullCowCount Count;
    for (int32 GuessIndex = 0; GuessIndex < Guess.Len(); GuessIndex++)
    {
        if (Guess[GuessIndex] == HiddenWord[GuessIndex])
        {
            Count.Bulls++;
            continue;
        }
        for (int32 HiddenIndex = 0; HiddenIndex < HiddenWord.Len(); HiddenIndex++)
        {
            if (Guess[GuessIndex] == HiddenWord[HiddenIndex])
            {
                Count.Cows++;
                break;
            }
        }
    }
    return Count;
}

FString UBullCowCartridge::GetBullCowPositons(const FString& Guess) const
{
    FString BullCowPositions = TEXT("");
    bool NotInString = true;
    for (int32 GuessIndex = 0; GuessIndex < Guess.Len(); GuessIndex++)
    {
        NotInString = true;
        if (Guess[GuessIndex] == HiddenWord[GuessIndex])
        {
            BullCowPositions+="B ";
            NotInString = false;
            continue;
        }
        for (int32 HiddenIndex = 0; HiddenIndex < HiddenWord.Len(); HiddenIndex++)
        {
            if (Guess[GuessIndex] == HiddenWord[HiddenIndex])
            {
                BullCowPositions+="C ";
                NotInString = false;
                break;
            }
        }
        if (NotInString) 
        {
            BullCowPositions+="- ";
        }
    }
    return BullCowPositions.TrimEnd();
}

bool UBullCowCartridge::HasWon() const
{
    if (CurrentLevel == MaxLevel - 1)
    {
        return true;
    }
    return false;
}

void UBullCowCartridge::IncreaseLevel()
{
    if (CurrentLevel < MaxLevel - 1)
    {
        ++CurrentLevel;
    }
}

Screenshots!

Intro

Help

Guess Feedback/Hints

Privacy & Terms