Having got to the end of the updated Bulls and Cows module, as is currently available, I decided to have a go today and try and finish the original outline for the game despite the course not having quite got that far yet.
I’ve completed adding checks for ISOGRAM conformance, calculating the Bull and Cow counts, all the associated messages to the user and I’ve implemented a simple string array to hold ten words which the program uses to randomly choose, one at a time. I default the Lives to 4 but increase those on any word lengths of 4 or over to the length + 1. So, for example a 7 letter word will get 8 lives. Restarting the game and exiting have been catered for too.
Functionally I think I’ve been successful, it’s probably not very elegant but it appears to work. Thanks Michael for your work on this so far, I have found it interesting and it’s been a fun project to work on.
I will go on to look at the original course version of this as I may pick up further useful knowledge.
OK, so for what it is worth here is my code in it’s entirety:
BullCowCartridge.h
#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& Input) override;
// Your declarations go below!
private:
FString HiddenWord;
FString CurrentWordGuess;
bool bHelpCalled;
bool bNewWord;
int32 WordLength;
bool bHasGuessed;
bool bStartGame;
bool bContinuePlay;
int32 LivesLeft;
TArray<FString> WordArray;
void DisplayWelcome();
void DisplayHelp();
void InitGame();
void PopulateHiddenWordArray();
FString GetRandomWord();
void ProcessUserInput(const FString& Input);
void DisplayInstruction();
void DisplayGuess(const FString& Guess);
void DisplayLivesRemaining(int32 Lives);
void DisplayContinueGamePrompt();
};
and BullCowCartridge.cpp
#include "BullCowCartridge.h"
void UBullCowCartridge::BeginPlay() // Called by Unreal Engine when the game starts
{
Super::BeginPlay();
InitGame();
}
void UBullCowCartridge::OnInput(const FString& Input) // When the player hits enter
{
ClearScreen();
ProcessUserInput(Input);
}
void UBullCowCartridge::InitGame()
{
// Initialise variables
bHelpCalled = false; // Flag to indicate if DisplayHelp had previously been called
bNewWord = false; // Flag to indicate its a new word
bHasGuessed = false; // Flag to indicate the user has provided a guess
bStartGame = false; // Flag to indicate start of a new game
bContinuePlay = true; // Flag to indicate play should continue
LivesLeft = 4; // Defaults to a minimum of 4 lives
// Seed the random number generator
srand(FDateTime::Now().GetMillisecond());
// Build a string array of words
PopulateHiddenWordArray();
// Get a random hidden word from the string array
HiddenWord = GetRandomWord();
//PrintLine(HiddenWord); // For debug purposes, comment out for release build
WordLength = HiddenWord.Len();
// Increase the lives if the word length is greater than 4
if (WordLength > 3)
{
LivesLeft = WordLength + 1;
}
DisplayWelcome();
}
void UBullCowCartridge::PopulateHiddenWordArray()
{
WordArray.Emplace(TEXT("tin")); // Always encose literal strings in a TEXT macro to ensure native Unicode encoding.
WordArray.Emplace(TEXT("world"));
WordArray.Emplace(TEXT("melt"));
WordArray.Emplace(TEXT("window"));
WordArray.Emplace(TEXT("plate"));
WordArray.Emplace(TEXT("biscuit"));
WordArray.Emplace(TEXT("man"));
WordArray.Emplace(TEXT("metal"));
WordArray.Emplace(TEXT("sun"));
WordArray.Emplace(TEXT("rain"));
}
FString UBullCowCartridge::GetRandomWord()
{
int32 RandomArrayIndex;
// Get a random number between 0 up to the number of elements in the array
RandomArrayIndex = (rand() % WordArray.Num());
return HiddenWord = WordArray[RandomArrayIndex];
}
void UBullCowCartridge::ProcessUserInput(const FString& Input)
{
int32 NumOfBulls = 0;
int32 NumOfCows = 0;
FString UCGuessedWord;
FString UCHiddenWord;
// Start of game so just display Instruction and ignore any other input
if (!bStartGame)
{
bStartGame = true;
DisplayInstruction();
return;
}
// Handle the ContinueGamePrompt user input
if (!bContinuePlay)
{
if (Input == TEXT("r"))
{
InitGame();
bContinuePlay = true;
return;
}
else if (Input == TEXT("q"))
{
// Quit game
GIsRequestingExit = true;
}
else
{
// Non valid input from user so just display prompt again
DisplayContinueGamePrompt();
return;
}
}
DisplayInstruction();
// If user previously called help then display the previously stored word guess
if (bHelpCalled)
{
if (CurrentWordGuess != "")
{
DisplayGuess(CurrentWordGuess);
DisplayLivesRemaining(LivesLeft);
}
// Reset our help called flag
bHelpCalled = false;
return;
}
// So long as something has been typed
if (Input != "")
{
// If user requested help
if (Input == TEXT("help"))
{
DisplayHelp();
// Set our help called flag
bHelpCalled = true;
}
else
{
// Store the current word guess incase the user subsequently requests help or just hits enter and we need to restore the guess
CurrentWordGuess = Input;
bHasGuessed = true;
DisplayGuess(Input);
// Test to see if the guess is correct or not
if (Input == HiddenWord)
{
// Display the users latest guess
PrintLine(TEXT("Correct. You Win!"));
DisplayContinueGamePrompt();
bContinuePlay = false;
CurrentWordGuess = "";
bNewWord = true;
bHasGuessed = false;
}
else
{
// Is the length correct
if (Input.Len() != WordLength)
{
PrintLine(TEXT("Incorrect. The word has %i characters"), WordLength);
PrintLine(TEXT("Please try again."));
return;
}
// Convert and store both words converted to Upper Case so we can do effective character comparisons
UCGuessedWord = Input.ToUpper();
UCHiddenWord = HiddenWord.ToUpper();
// Check the word is an ISOGRAM (no repeated characters in the word)
for (int32 i = 0; i < WordLength; i++)
{
for (int32 n = 0; n < WordLength; n++)
{
// Avoid checking the same position for a matching character
if (n == i) continue;
if (UCGuessedWord.Mid(i, 1) == UCGuessedWord.Mid(n, 1))
{
PrintLine(TEXT("Incorrect. The word is not an ISOGRAM"));
PrintLine(TEXT("Please try again."));
return;
}
}
}
// Decrement the number of lives left
--LivesLeft;
// Check for bulls and cows
for (int32 i = 0; i < WordLength; i++)
{
// If characters match in the same position in the strings then increment the Bulls count
if (UCGuessedWord.Mid(i, 1) == UCHiddenWord.Mid(i, 1))
{
++NumOfBulls;
}
else
{
// If characters match in a different position in the strings then increment the Cows count
for (int32 n = 0; n < WordLength; n++)
{
// Avoid checking the same position as we know already they don't match
if (n == i) continue;
if (UCGuessedWord.Mid(i, 1) == UCHiddenWord.Mid(n, 1))
{
NumOfCows++;
}
}
}
}
// Display the results
PrintLine(TEXT("Incorrect. You lose a life"));
PrintLine(TEXT("You scored Bulls: %i, Cows: %i"), NumOfBulls, NumOfCows);
DisplayLivesRemaining(LivesLeft);
PrintLine(TEXT("Please try again"));
// Handle user out of lives
if (LivesLeft == 0)
{
PrintLine(TEXT("You have no lives left"));
DisplayContinueGamePrompt();
bContinuePlay = false;
}
}
}
}
else if (bHasGuessed)
{
// If the user has previously guessed but has just pressed Enter then print previous guess
DisplayGuess(CurrentWordGuess);
DisplayLivesRemaining(LivesLeft);
}
}
void UBullCowCartridge::DisplayWelcome()
{
PrintLine(TEXT("Welcome to the Bulls and Cows game\n"));
PrintLine(TEXT("Press Enter to continue"));
}
void UBullCowCartridge::DisplayHelp()
{
ClearScreen();
PrintLine(TEXT("The game is a guess the hidden word game."));
PrintLine(TEXT("The hidden word will always be an ISOGRAM,"));
PrintLine(TEXT("that is a word with no repeating letters."));
PrintLine(TEXT("Any correct letters in the right position"));
PrintLine(TEXT("will score you BULLS, any correct letters"));
PrintLine(TEXT("in the wrong position will score you COWS"));
PrintLine(TEXT("and any wrong letters score nothing."));
PrintLine(TEXT("You will have so many tries in order to "));
PrintLine(TEXT("guess the word correctly and win the game."));
PrintLine(TEXT("Press Enter to return to the game."));
}
void UBullCowCartridge::DisplayInstruction()
{
PrintLine(TEXT("Type help and press Enter for a basic"));
PrintLine(TEXT("description of the game and rules.\n"));
PrintLine(TEXT("Otherwise guess the %i letter word ...\n"), WordLength);
}
void UBullCowCartridge::DisplayGuess(const FString& Guess)
{
PrintLine("You guessed: " + Guess);
}
void UBullCowCartridge::DisplayLivesRemaining(int32 Lives)
{
PrintLine(TEXT("You have %i lives remaining"), LivesLeft);
}
void UBullCowCartridge::DisplayContinueGamePrompt()
{
PrintLine(TEXT("To restart type 'r' and Enter"));
PrintLine(TEXT("To quit type 'q' and Enter"));
}