My Header
// 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"
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
void BullCowsNumber(const FString& Word) const;
bool IsIsogram(const FString& Word) const;
TArray<FString> GetValidWords(const TArray<FString>&) const;
// Your declarations go below!
private:
TArray<FString> Words;
TArray<FString> WordList;
FString HiddenWord;
int32 Lives;
bool bGameOver;
};
C++ 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
WordList = 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 = WordList[FMath::RandRange(0, WordList.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 Answer 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!
BullCowsNumber(Guess);
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);
}
void UBullCowCartridge::BullCowsNumber(const FString& Word) const
{
// PlaceHolder
// We'll want to iterate through HiddenWord the player's Guess
// Then we'll want to check for if a Player Guess Letter is in the same place as hidden word
// We'll also want to check if a Player Guess contains a letter that is in HiddenWord
int32 Bull = 0;
int32 Cow = 0;
// This will only be called after the user passes a verification check
for (int32 Index = 0; Index < HiddenWord.Len(); ++Index)
{
for (int32 Comparison = Index; Comparison < Word.Len(); ++Comparison)
{
bool isCow = HiddenWord[Index] == Word[Comparison];
bool isBull = HiddenWord[Index] == Word[Index];
if (isCow)
{
++Cow;
--Bull; // Bull Will Pass the next check, otherwise we'd have too many
}
if (isBull)
{
++Bull;
}
}
// Bug fix
// Bulls should never be negative
if (Bull < 0)
{
Bull = 0;
}
}
PrintLine(TEXT("You have %i Bulls"), Bull);
PrintLine(TEXT("You have %i Cows"), Cow);
}
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;
}
I’m having a bit of an issue trying to work out the BullCowsNumber function. I’m getting a bit ahead of myself as always, but my code isn’t acting the way I want it to.
Maybe I’m doing something backwards. I’m almost there though. I’m very likely going about this in all the wrong ways.
Edit: I was on a track, but it wasn’t the right one, and I eventually did accidentally put my code backwards. Well that was fun!