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