Completed BullCow game crashes Unreal

So after I finished the Bull Cow section of the course, and I started messing with the game, I think I discovered a slight oversight. I followed the instructor pretty much to the letter, so my code is about 90% the same as the instructor, with minor changes to naming conventions.

I found that the struct FBullCowCount Score = GetBullCows(Guess); the instructor uses and calls in the void UBullCowCartridge::ProcessGuess(const FString& Guess) crashes Unreal if the “Guess” word is longer than the actual “HiddenWord”

Example: HiddenWord = Cake, Guess = Cakes
Unreal crashes. Gives error: String index out of bounds: Index 5 from a string with a length of 5

I figured from the error code that it is something along the lines of we are trying to manipulate a value outside the range/scope of the array which in this case is 4.

Snip of code:

void UBullCowCartridge::ProcessGuess(const FString& Guess) 
{

    FBullCowCount Score = GetBullCows(Guess); // This part, before we check length of word crashes Unreal

    if (Guess == "Hint" ) //|| Guess == "hint"
    {
        UI();
        ClearScreen();
        Hint(HintCount);
        // PrintLine(TEXT("The count is %i"), HintCount); // Debug Line
        return;
    }
    
    if (HiddenWord == Guess)
    {    
        ClearScreen();    
        UI();
        PrintLine(TEXT("You are correct! You Win!"));
        EndGame();
        return;
    }    

    if (Guess.Len() != HiddenWord.Len())
    {   
        ClearScreen();
        UI();
        PrintLine(TEXT("The isogram is %i characters long"), HiddenWord.Len());
        return;
    }
     //Check IF Isogram
    if (!IsIsogram(Guess))
    {
        ClearScreen();
        UI();
        PrintLine(TEXT("No repeating letters, try again"));
        PrintLine(TEXT("The isogram is %i characters long"), HiddenWord.Len());
        return;
    }  

    ClearScreen();
    --Lives;
    UI();
    PrintLine(TEXT("Wrong, lost a life"));
    
    if (Lives <= 0)
    {
        ClearScreen();
        UI();
        PrintLine(TEXT("Game Over"));
        PrintLine(TEXT("The isogram was %s."), *HiddenWord);
        EndGame(); 
        return;  
    }    
    
    PrintLine(TEXT("You have %i Bulls and %i Cows"), Score.Bulls, Score.Cows);
}

What I did to solve this was move the FBullCowCount Score = GetBullCows(Guess); struct to after we check if length of guess is != length of hidden word.

I spent about an hour trying to figure out how to fix the actual “String index out of bounds” problem but was not able to. If you know or have an idea, please let me know as I would love to know what happened.

Unreal Error Message after Crash:

Full Code:
Cartridge.cpp

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

void UBullCowCartridge::BeginPlay() // When the game starts
{
    Super::BeginPlay();
    Isograms = GetValidWords(WordList);
    int32 HintCount = 0;
   
    SetupGame(); 
}

void UBullCowCartridge::OnInput(const FString& PlayerInput) // When the player hits enter
{
     if(bGameOver)
    {  
        ClearScreen();
        SetupGame();
    }
    else // Check PlayerGuess
    {  
        ProcessGuess(PlayerInput);
    }
}

void UBullCowCartridge::SetupGame()
{
    HiddenWord = Isograms[FMath::RandRange(0, Isograms.Num() - 1)];

    Lives = HiddenWord.Len() * 2; 
    HintCount = 2;
    bGameOver = false;

    // Print Welcome Message
    UI();
    //PrintLine(TEXT("The isogram is: %s"), *HiddenWord); //Debug Line

    PrintLine(TEXT("Welcome to Bulls and Cows!"));
    PrintLine(TEXT("Try to guess the %i letter Isogram.\n"), HiddenWord.Len());
    PrintLine(TEXT("Rules:"));
    PrintLine(TEXT("Bull = Correct letter in correct place"));
    PrintLine(TEXT("Cow = Correct letter in incorrect place"));
    PrintLine(TEXT("Type 'Hint' to receive a hint."));
    PrintLine(TEXT("Type your guess and press enter"));  
}

void UBullCowCartridge::EndGame()
{
    // ClearScreen();
    bGameOver = true;
    
    PrintLine(TEXT("Press Enter to play again"));
}

void UBullCowCartridge::ProcessGuess(const FString& Guess) 
{

    if (Guess == "Hint" ) //|| Guess == "hint"
    {
        UI();
        ClearScreen();
        Hint(HintCount);
        // PrintLine(TEXT("The count is %i"), HintCount); // Debug Line
        return;
    }
    
    if (HiddenWord == Guess)
    {    
        ClearScreen();    
        UI();
        PrintLine(TEXT("You are correct! You Win!"));
        EndGame();
        return;
    }    

    if (Guess.Len() != HiddenWord.Len())
    {   
        ClearScreen();
        UI();
        PrintLine(TEXT("The isogram is %i characters long"), HiddenWord.Len());
        return;
    }

    FBullCowCount Score = GetBullCows(Guess);
    
     //Check IF Isogram
    if (!IsIsogram(Guess))
    {
        ClearScreen();
        UI();
        PrintLine(TEXT("No repeating letters, try again"));
        PrintLine(TEXT("The isogram is %i characters long"), HiddenWord.Len());
        return;
    }  

    ClearScreen();
    --Lives;
    UI();
    PrintLine(TEXT("Wrong, lost a life"));
    
    if (Lives <= 0)
    {
        ClearScreen();
        UI();
        PrintLine(TEXT("Game Over"));
        PrintLine(TEXT("The isogram was %s."), *HiddenWord);
        EndGame(); 
        return;  
    }    
    
    PrintLine(TEXT("You have %i Bulls and %i Cows"), Score.Bulls, Score.Cows);
}

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

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

        for (FString Word : WordList)
        {
            if (IsIsogram(Word))
            {
            ValidWords.Emplace(Word);
            }
        }
        return ValidWords;    
}

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

void UBullCowCartridge::Hint(int32& Count) const
{
    // Ask for a Hint Sytem
    //If user types "Hint", give user 1st hint
    //If user types "Hint" again, give user 2nd hint
    //If user types "Hint" again, let user know they are out of hints

    if (Count == 2)
    {   
        Count--;
        UI();
        PrintLine(TEXT("1st letter of isogram is %c"), HiddenWord[0]);
        PrintLine(TEXT("The isogram is %i characters long"), HiddenWord.Len());
        return;
    } 
    if (Count == 1)
    {
        Count--;
        UI();
        PrintLine(TEXT("Last letter of isogram is %c"), HiddenWord[HiddenWord.Len() - 1]);
        PrintLine(TEXT("The isogram is %i characters long"), HiddenWord.Len());
        return;
    } 
    if (Count == 0)
    {
        UI();
        PrintLine(TEXT("No More Hints Left"));
        PrintLine(TEXT("The isogram is %i characters long"), HiddenWord.Len());
        return;
    } 
}

void UBullCowCartridge::UI() const
{
    PrintLine(TEXT("Hints: %i                         Lives: %i\n"),HintCount, Lives);
}

cartridge.h

// 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& Input) override;
	void SetupGame();
	void EndGame();
	void ProcessGuess(const FString& Guess);
	bool IsIsogram(const FString& Word) const;
	TArray<FString> GetValidWords(const TArray<FString>& ValidWordList) const;
	FBullCowCount GetBullCows(const FString& Guess) const;
	void Hint( int32& Count) const;
	void UI() const;

	// Your declarations go below!
	private:

	FString HiddenWord;
	int32 Lives;
	bool bGameOver;
	TArray<FString> Isograms;
	int32 HintCount;
};

2 Likes

What you did is a valid solution.

for(int32 GuessIndex = 0; GuessIndex < Guess.Len(); GuessIndex ++)
{
    if (Guess[GuessIndex] == HiddenWord[GuessIndex])

This code has the assumption that HiddenWord and Guess are the same length.

The calculation of the bulls and cows are only done after the validation checks

2 Likes

Thank you for the reply! Looking over your code helped me!

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.