BullCowGame with alterations

I kind of deviated from the lecture along the way, so there wasn’t anything else I wanted to add.

Mostly this includes a few extra functions, some separation of roles, and a more complex end game function.

Turns out, I was really close to figuring out BullCows before I got to the lecture, I was being stubborn about something I didn’t need to be stubborn about. I’m also not from a programming world where continue and break are used too often.

I tried to add a bit of personality to this where the game is really dramatic about you dying, and is more melodramatic about you winning.

Anyway, here’s my .h file:

// Fill out your copyright notice in the Description page of Project Settings.

#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& PlayerInput) override;
	// Game Loop
	void SetUpGame();
	void WelcomePlayer() const;
	void EndGame();
	// Processing Player Guess
	void ProcessGuess(const FString& Guess);
	void PlayerDidNotWin(const FString& Guess);
	void PlayerLostLife();
	// Processing Words
	FBullCowCount GetBullCows(const FString &Guess) const;
	bool IsIsogram(const FString& Word) const;
	TArray<FString> GetValidWords(const TArray<FString>&) const;

	// Your declarations go below!
private:
	TArray<FString> Words;
	TArray<FString> Isograms;
	FString HiddenWord;
	int32 Lives;
	bool bGameOver;
};

My .cpp file

// Fill out your copyright notice in the Description page of Project Settings.
#include "BullCowCartridge.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"

void UBullCowCartridge::BeginPlay() // When the game starts
{
    Super::BeginPlay();

    // Advanced Course Lesson to reduce compile times!
    const FString WordListPath = FPaths::ProjectContentDir() / TEXT("WordLists/HiddenWordList.txt");
    FFileHelper::LoadFileToStringArray(Words, *WordListPath);

    // We only need this done once
    Isograms = GetValidWords(Words);

    SetUpGame();

    // PrintLine(TEXT("The HiddenWord is %s"), *HiddenWord);           //Debug Line
    // PrintLine(TEXT("It is %i characters Long"), HiddenWord.Len()); //Debug Line

    WelcomePlayer();
}

void UBullCowCartridge::OnInput(const FString& PlayerInput) // When the player hits enter
{
    ClearScreen();

    // If Game is over, ClearScreen() and SetupGame()
    if (bGameOver) {
        EndGame();
        return; // Using a return here to exit early to prevent Normal Game Loop
    }

    // Normal Game Loop
    // Check if Valid
    ProcessGuess(PlayerInput);
}

void UBullCowCartridge::SetUpGame()
{
    HiddenWord = Isograms[FMath::RandRange(0, Isograms.Num() - 1)];
    Lives = HiddenWord.Len();
    bGameOver = false;
}

void UBullCowCartridge::WelcomePlayer() const
{
    // Separating these may make it easier to simplify dialogue later
    PrintLine(TEXT("Welcome to Bull Cows!"));
    PrintLine(TEXT("Guess the %i letter word!"), HiddenWord.Len());
    PrintLine(TEXT("You have %i lives."), Lives);
    PrintLine(TEXT("Please enter your guess and \nPress enter to continue..."));
}

void UBullCowCartridge::ProcessGuess(const FString& Guess)
{
    // No reason to check everything else if it already works
    if (Guess == HiddenWord)
    {
        PrintLine(TEXT("The word is %s"), *HiddenWord);
        EndGame();
        return;
    }

    if (HiddenWord.Len() != Guess.Len()) // Check Character Length
    {
        // Prompt to GuessAgain
        PrintLine(TEXT("The Hidden Word is %i characters long."), HiddenWord.Len());
        PrintLine(TEXT("You still have %i lives remaining. \nTry again!"), Lives);
        return;
    }

    if (!IsIsogram(Guess)) // Check if it is an Isogram
    {
        PrintLine(TEXT("No repeating Letters!\n"));
        PrintLine(TEXT("NOT"));
        PrintLine(TEXT("AN"));
        PrintLine(TEXT("ISOGRAM!!"));
        PrintLine(TEXT("You still have %i lives remaining. \nTry again!"), Lives);
        return;
    }

    // Answer is Valid, but not a victory, so now T or C
    PlayerDidNotWin(Guess);
}

void UBullCowCartridge::PlayerDidNotWin(const FString& Guess)
{
    //Didn't win? Now the bad stuff happens!
    FBullCowCount Score = GetBullCows(Guess);

    PrintLine(TEXT("You have %i Bulls and %i Cows"), Score.Bulls, Score.Cows);

    PlayerLostLife();
}

// This is taking care of itself, because there is only one way to win.
// Bull Cows Will be calculated separately
void UBullCowCartridge::PlayerLostLife()
{
    --Lives; // Keep it here because otherwise I'll be writting it twice.
    // Ask you dead or dying?
    if (Lives <= 0)
    {
        // If no lives, inform player they have perished.
        EndGame();
        return;  
    }

    if (Lives == 1)
    {
        // Warn them about how close they are to dying
        PrintLine(TEXT("YOU HAVE ONE FINAL LIFE!"));
        PrintLine(TEXT("YOU WILL PERISH IF YOU FAIL AGAIN"));
        return;
    }

    // If your not dead, your alive!
    // Prompt user for another guess
    PrintLine(TEXT("YOU MUST TRY AGAIN OR PERISH!"));
    PrintLine(TEXT("You have %i lives left"), Lives);
}

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;
}

bool UBullCowCartridge::IsIsogram(const FString& Word) const
{
    // So used to brute forcing these, I'm rially happy making it more efficient
    // I'm checking up to the second to last because that was mentioned.
    for (int32 Index = 0; Index < Word.Len() - 1; ++Index)
    {
        for (int32 Comparison = Index + 1; Comparison < Word.Len(); ++Comparison)
        {
            if (Word[Index] == Word[Comparison])
            {
                return false;
            }
        }
    }

    return true;
}

void UBullCowCartridge::EndGame()
{
    // If They've confirmed that they want to replay the game
    // Then let them re-initialize the game
    // This goes first so I have a way to exit early
    if (bGameOver) {
        // This is here because I feel like the EndGame function,
        // Should handle the events that occur EndGame()
        SetUpGame();
        WelcomePlayer();
        return; // Using a return here to exit early
    }

    // Set GameOver here so that next time function is called, we set the game up again
    bGameOver = true;

    // Extra shame to those that have failed
    // Also show user the hidden word for some closure
    if (Lives == 0) {
        PrintLine(TEXT("THEN PERISH"));
        PrintLine(TEXT("The hidden word was %s\n"), *HiddenWord);
    }
    else
    {
        PrintLine(TEXT("Oh yay. You won."));
        PrintLine(TEXT("The hidden word was %s"), *HiddenWord);
    }

    // Play Nice now
    PrintLine(TEXT("Press enter to play again..."));
}

TArray<FString> UBullCowCartridge::GetValidWords(const TArray<FString>& WordList) const
{
    TArray<FString> ValidWords;

    for (FString Word : WordList)
    {
        if ((Word.Len() >= 4) && (Word.Len() <= 8) && IsIsogram(Word))
        {
            ValidWords.Emplace(Word);
        }
    }

    return ValidWords;
}
1 Like

Incredible job with your code!

1 Like

Privacy & Terms