Done with Section 2!
Added a few of my own extra features to the game.
Relies on Windows functionality for console color changes (apologies to mac users).
Has a 60 word list divided into 3 difficulties.
Gives the player additional color coded based hints for Bulls and Cows.
Has a win screen with a score and a loss screen that lets the user know what the word was.
Also has replaces the ascii Bull and Cow with a large ascii cow face.
main.cpp
/* This is the console executable that makes use of the FBullCowGame class.
This acts as the view in a MVC pattern, and is responsible for all user interactions.
For game logic see the FBullCowGame class.
-Zaptruder
*/
#include <iostream>
#include <string>
#include "FBullCowGame.h"
#include <windows.h>
// to make syntax Unreal Engine 4 friendly
using FText = std::string;
using int32 = int;
// Prototyping above main to make all functions available.
void PrintIntro();
void PlayGame();
FText GetValidGuess();
bool AskToPlayAgain();
void GameSummary();
void GetGameDifficulty();
void SetColor(int32);
void BullCowPositionHint(FText);
//instantiate (make instance of) FBullCowGame
FBullCowGame BCGame;
//entry point for our application
int main ()
{
bool bPlayAgain = false;
do {
PrintIntro();
GetGameDifficulty();
PlayGame();
bPlayAgain = AskToPlayAgain();
} while (bPlayAgain == true);
return 0; // exit application
}
// Introduce the game
void PrintIntro()
{
std::cout << "\nWelcome to 'BULLS AND COWS' - A Fun Word Game\n";
std::cout << "This is a game about guessing isograms, which are words without repeating letters.\n";
std::cout << std::endl;
SetColor(240);
std::cout << " " << std::endl;
std::cout << " /~~~~~\\ /~~~~~\\ " << std::endl;
std::cout << " | (~' ~~~) | " << std::endl;
std::cout << " \\ \__________/ / " << std::endl;
std::cout << " /~:::::::: ~\\ " << std::endl;
std::cout << " /~~~~~~~-_| :::::::: |_-~~~~~~~\\ " << std::endl;
std::cout << " \\ ======= /| ::A::; A :|\\ ====== / " << std::endl;
std::cout << " ~-_____-~ | _----------------_::| ~-____-~ " << std::endl;
std::cout << " |/~ ~\\| " << std::endl;
std::cout << " / \\ " << std::endl;
std::cout << " ( () () ) " << std::endl;
std::cout << " `\\ ./' " << std::endl;
std::cout << " ~-_______________-~ " << std::endl;
std::cout << " /~~~~\\ " << std::endl;
std::cout << " | | " << std::endl;
std::cout << " | | " << std::endl;
std::cout << " (________) " << std::endl;
std::cout << " () " << std::endl;
std::cout << " " << std::endl;
SetColor(7);
return;
}
//Call the necessary functions to get the player guessing.
void PlayGame()
{
BCGame.Reset(); //reset to initialize with requested game difficulty.
// The isogram that you're guessing has [ X letters ] in it. Good Luck! (where [ X letters ] has a white background)
std::cout << "\nThe isogram that you're guessing has ";
SetColor(240);
std::cout << " " << BCGame.GetWordLength() << " letters ";
SetColor(7);
std::cout << " in it. Good luck!\n";
int32 MaxTries = BCGame.GetMaxTries();
FBullCowCount BullCowCount;
// loop to allow the player to take a number of guesses
while(!BCGame.IsGameWon() && BCGame.GetCurrentTry() <= MaxTries)
{
FText Guess = GetValidGuess();
BullCowCount = BCGame.SubmitValidGuess(Guess);
std::cout << " ";
//Show the player the Bull and Cow positions via background color changes.
BullCowPositionHint(Guess);
SetColor(160);
std::cout << "Bulls = " << BullCowCount.Bulls << " (CORRECT letters in CORRECT places). \n";
SetColor(79);
std::cout << "Cows = " << BullCowCount.Cows << " (CORRECT letters in WRONG places). \n";
SetColor(7);
std::cout << "Misses = " << Guess.length() - BullCowCount.Bulls - BullCowCount.Cows << " (WRONG letters). \n\n";
}
//once game finishes, summarize the game.
GameSummary();
return;
}
//designed to set the game difficulty, which determines which isogram list the game chooses.
void GetGameDifficulty()
{
FText PlayerDifficulty ="";
int32 GameDifficulty;
//Prompt for difficulty 1=Easy, 2=Medium, 3=Hard
do {
std::cout << "\nSet the difficulty of the game.\n";
//EASY
std::cout << "1 = ";
SetColor(160);
std::cout << "Easy (5 to 6 letter words)\n";
SetColor(7);
//MEDIUM
std::cout << "2 = ";
SetColor(224);
std::cout << "Medium (7 to 8 letter words)\n";
SetColor(7);
//HARD
std::cout << "3 = ";
SetColor(192);
std::cout << "Hard (9 to 12 letter words)\n";
SetColor(7);
std::cout << "\n";
//get and consume input and set difficulty to input.
std::getline(std::cin, PlayerDifficulty); //Get console input for difficulty.
GameDifficulty = std::stoi(PlayerDifficulty); //turn console input string into a sanitized integer
} while (GameDifficulty < 1 || GameDifficulty > 3); //while first character value != 1, 2, 3
BCGame.SetDifficulty((int)GameDifficulty); //set difficulty to requested.
}
//Prompts the user for a Guess and returns that FText
FText GetValidGuess()
{
EGuessStatus Status = EGuessStatus::Invalid;
FText GuessInput;
do {
// get a guess from the player
std::cout << "\nATTEMPT " << BCGame.GetCurrentTry() << "/" << BCGame.GetMaxTries() << ".\nPlease enter a guess: "; // "ATTEMPT #/#.\nPlease enter a guess: "
std::getline(std::cin, GuessInput);
//Ensure guess is valid
Status = BCGame.CheckGuessValid(GuessInput);
switch (Status)
{
case EGuessStatus::Not_Isogram:
std::cout << "Please enter an isogram (a word with all different letters).\n";
break;
case EGuessStatus::Incorrect_Length:
std::cout << "Please enter a " << BCGame.GetWordLength() << " letter word.\n";
break;
case EGuessStatus::Not_Alphabetical:
std::cout << "Please enter a word with only alphabetical characters.\n";
break;
case EGuessStatus::Already_Attempted:
std::cout << "This guess has already been attempted. Try a different word.\n";
break;
default:
break;
}
std::cout << std::endl;
} while (Status != EGuessStatus::OK);
return GuessInput;
}
// Asks the player to play again.
bool AskToPlayAgain()
{
FText PlayAgainAnswer;
std::cout << "Do you want to play again? Y/N: ";
std::getline(std::cin, PlayAgainAnswer);
std::cout << std::endl;
return (PlayAgainAnswer[0] == 'y') || (PlayAgainAnswer[0] == 'Y');
}
//Summarizes the game, giving the player win/lose messages.
void GameSummary()
{
if (BCGame.IsGameWon()) { //On Win: Display Message and Give score.
SetColor(47);
std::cout << "CONGRATULATIONS! You've guessed correctly and WON THE GAME!\n";
std::cout << " " << std::endl;
std::cout << " /~~~~~\\ /~~~~~\\ " << std::endl;
std::cout << " | (~' ~~~) | " << std::endl;
std::cout << " \\ \__________/ / " << std::endl;
std::cout << " /~:::::::: ~\\ " << std::endl;
std::cout << " /~~~~~~~-_| :::::::: |_-~~~~~~~\\ " << std::endl;
std::cout << " \\ ======= /| ::A::; A :|\\ ====== / " << std::endl;
std::cout << " ~-_____-~ | _----------------_::| ~-____-~ " << std::endl;
std::cout << " |/~ ~\\| " << std::endl;
std::cout << " / \\ " << std::endl;
std::cout << " ( () () ) " << std::endl;
std::cout << " `\\ ./' " << std::endl;
std::cout << " ~-_______________-~ " << std::endl;
std::cout << " /~~~~\\ " << std::endl;
std::cout << " | | " << std::endl;
std::cout << " | | " << std::endl;
std::cout << " (________) " << std::endl;
std::cout << " () " << std::endl;
std::cout << " " << std::endl;
SetColor(7);
std::cout << "You won in ";
std::cout << BCGame.GetCurrentTry()-1;
std::cout << " out of ";
std::cout << BCGame.GetMaxTries();
std::cout << " Attempts! Your final score is: ";
std::cout << ((BCGame.GetGameDifficulty() * (BCGame.GetMaxTries() - BCGame.GetCurrentTry() + 2)) * 10);
std::cout << " " << std::endl;
std::cout << "\n";
}
else { //On Loss: Commiserate the player and reveal isogram
std::cout << "Unfortunately you haven't guessed in the required number of attempts.\nThe isogram was \"";
SetColor(160); //set Isogram reveal to bright green
std::cout << BCGame.GetHiddenWord();
SetColor(7); //set back to default colors
std::cout << ".\"\nBetter luck next time!\n\n";
}
}
//Return the player guess with color coded position hints for Bulls and Cows
void BullCowPositionHint(FText Guess){
for (int32 i = 0; i < (int32)Guess.length(); i++)
{ // Bright Green for Bulls
if (BCGame.BullCowPosition[i] == 1) {
SetColor(160);
char c = toupper(Guess[i]);
std::cout << c;
} // Red for Cows
else if (BCGame.BullCowPosition[i] == 2) {
SetColor(79);
char c = toupper(Guess[i]);
std::cout << c;
} // Black for Misses
else {
SetColor(7);
char c = toupper(Guess[i]);
std::cout << c;
}
}
std::cout << "\n\n";
}
// Sets console color
void SetColor(int32 value) {
/*
0: Black
1: Blue
2: Green
3: Cyan
4: Red
5: Purple
6: Yellow (Dark)
7: Default white
8: Gray/Grey
9: Bright blue
10: Brigth green
11: Bright cyan
12: Bright red
13: Pink/Magenta
14: Yellow
15: Bright white
+
16: blue background
32: green background
48: Cyan
64: Red
80: Purple
96: Yellow (Dark)
112: Default white
128: Gray/Grey
144: Bright blue
160: Brigth green
176: Bright cyan
192: Bright red
208: Pink/Magenta
224: Yellow
240: Bright white
*/
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), value);
}
FBullCowGame.cpp
#include "FBullCowGame.h"
#include <time.h>
FBullCowGame::FBullCowGame() {Reset();} //see rest for constructor initialization values
int32 FBullCowGame::GetCurrentTry() const {return MyCurrentTry;}
int32 FBullCowGame::GetWordLength() const { return MyHiddenWord.length(); }
int32 FBullCowGame::GetGameDifficulty() const { return GameDifficulty; }
bool FBullCowGame::IsGameWon() const {return bGameWon;}
FString FBullCowGame::GetHiddenWord() const { return MyHiddenWord; }
int32 FBullCowGame::GetMaxTries() const { return MyMaxTries; }
//Set the difficulty value
void FBullCowGame::SetDifficulty(int Difficulty)
{
GameDifficulty = Difficulty;
}
// Returns a random isogram from a list, accounting for difficulty choice.
FString FBullCowGame::GetNewHiddenWord()
{
//Get random integer based on time
srand(time(NULL));
int32 Random = std::rand() % 20;
// Give word based on difficulty.
if (GameDifficulty == 1) { // Easy
MyMaxTries = 10;
return EasyIsogramList[Random];
} else if (GameDifficulty == 2) { // Medium
MyMaxTries = 15;
return MediumIsogramList[Random];
} else { // Hard
MyMaxTries = 20;
return HardIsogramList[Random];
}
}
// Checks to ensure that the player's guess input can be used.
EGuessStatus FBullCowGame::CheckGuessValid(FString Guess)
{
if (Guess.length() != GetWordLength()) {// if the guess isn't an isogram
return EGuessStatus::Incorrect_Length;
} else if (!IsAlphabetical(Guess)) {//// if the guess has non-alphabet characters
return EGuessStatus::Not_Alphabetical;
} else if (!IsIsogram(Guess)) { // if the guess is shorter than word length
return EGuessStatus::Not_Isogram;
} else if (AlreadyAttempted(Guess)) { // if the guess has already been attempted
return EGuessStatus::Already_Attempted;
} else {
return EGuessStatus::OK;
}
}
bool FBullCowGame::IsIsogram(FString Guess)
{
// treat 0 and 1 letter words as isograms
if (Guess.length() <= 1) { return true; }
/*Take each character of 'Guess' and add them to the map table.
Before doing so, check to see if this value is already in place. Return false if so.*/
TMap <char, int32> LetterSeen; //setup the map
for (auto Letter : Guess) //For each Guess character
{
Letter = tolower(Letter);
if (LetterSeen[Letter] == 0) //if the guess character count is 0
{
//add guess character to map
LetterSeen[Letter] = 1;
}
else { return false; }
}
return true;
}
bool FBullCowGame::IsAlphabetical(FString Guess)
{
for (int32 GuessCNo = 0; GuessCNo < (int32)Guess.length(); GuessCNo++)
{ //Alphabetical if guess is upper case A to Z or lower case A to Z.
if ((Guess[GuessCNo] >= 'A' && Guess[GuessCNo] <= 'Z') || (Guess[GuessCNo] >= 'a' && Guess[GuessCNo] <= 'z')) {
return true;
}
}
return false;
}
bool FBullCowGame::IsLowerCase(FString Guess)
{
if (Guess.length() <= 1) { return true; }
for (auto Letter : Guess)
{
if (!islower(Letter)) { return false; }
}
return true;
}
bool FBullCowGame::AlreadyAttempted(FString Attempt)
{
for (int i = 0; i < GetMaxTries(); i++) {
// Check to see if the attempt is already on the list
if (Attempt == AlreadyAttemptedList[i]) return true;
// Otherwise add the attempt to the list.
else AlreadyAttemptedList[i] = Attempt;
}
return false;
}
// initialize all the values in the game.
void FBullCowGame::Reset()
{
MyCurrentTry = 1;
MyHiddenWord = GetNewHiddenWord(); //Accept Difficulty change
bGameWon = false;
std::vector<int32> bullcowpositioninit(GetWordLength(), 0);
BullCowPosition = bullcowpositioninit;
std::vector<FString> attemptlistinit(GetMaxTries(), "");
AlreadyAttemptedList = attemptlistinit;
return;
}
//receives a VALID guess, increments turn and returns count
//Enter each letter into a BullCowPosition as Guess is read, and marks them as 0(nothing) 1(bull), 2(cow).
FBullCowCount FBullCowGame::SubmitValidGuess(FString Guess)
{
MyCurrentTry++;
FBullCowCount BullCowCount;
int32 WordLength = MyHiddenWord.length();
//for each letter of Guess
for (int32 GuessCNo = 0; GuessCNo < WordLength; GuessCNo++)
{
//Guess Character defaults to 0 (black).
BullCowPosition[GuessCNo] = 0;
//compare letters against the hidden word
for (int32 HiddenCNo = 0; HiddenCNo < WordLength; HiddenCNo++) {
//if GuessCharacterNumber matches HiddenCharacterNumber either lower or UPPER case
if (Guess[GuessCNo] == MyHiddenWord[HiddenCNo] || (Guess[GuessCNo] == MyHiddenWord[HiddenCNo] - 32)) {
//and if they're in the same place.
if (GuessCNo == HiddenCNo) {
//increment bulls
BullCowCount.Bulls++;
BullCowPosition[GuessCNo] = 1; //set the guess character position to 1 (green).
} else {
//increment cows if not.
BullCowCount.Cows++;
BullCowPosition[GuessCNo] = 2;//set the guess character position to 2 (red).
}
}
}
}
if (BullCowCount.Bulls == GetWordLength()) {
bGameWon = true;
}
return BullCowCount;
}
FBullCowGame.h
/* The game logic (no view code or direct interaction).
the game is a simple guess the word game based on Mastermind
-Zaptruder
*/
#pragma once
#include <string>
#include <map>
#include <vector>
#define TMap std::map
// to make syntax Unreal Engine 4 friendly
using FString = std::string;
using int32 = int;
struct FBullCowCount
{
int32 Bulls = 0;
int32 Cows = 0;
};
enum class EGuessStatus
{
Invalid,
OK,
Not_Isogram,
Incorrect_Length,
Not_Alphabetical,
Not_Lower_Case,
Already_Attempted
};
class FBullCowGame {
public:
std::vector<int32> BullCowPosition; //<LetterPosition, Bull/Cowtype> Used to provide bulls/cows with colour.
// constructor
FBullCowGame();
//Getters
int32 GetMaxTries() const;
int32 GetCurrentTry() const;
int32 GetWordLength() const; //Lets the player know how long the word length setting is.
bool IsGameWon() const;
EGuessStatus CheckGuessValid(FString); //Ensures that the player enters a valid guess
FString GetHiddenWord() const;
int32 GetGameDifficulty() const;
//Setters
void Reset();
FBullCowCount SubmitValidGuess(FString); //Allows the player to input their guess.
void SetDifficulty(int);
// Ignore this for now
private:
int32 MyCurrentTry; //see Constructor > Reset();
int32 MyMaxTries; //see Constructor > Reset();
FString MyHiddenWord;
int GameDifficulty = 1;
bool bGameWon;
bool IsIsogram(FString);
bool IsAlphabetical(FString);
bool IsLowerCase(FString);
bool AlreadyAttempted(FString);
FString GetNewHiddenWord();
std::vector<FString> AlreadyAttemptedList; //<LetterPosition, Bull/Cowtype> Used to provide bulls/cows with colour.
//ISOGRAM WORD LISTS
TMap <int32, FString> EasyIsogramList{ // 5 - 6 letter words
{ 0, "after" },
{ 1, "chokes" },
{ 2, "author" },
{ 3, "ground" },
{ 4, "black" },
{ 5, "horse" },
{ 6, "guild" },
{ 7, "faces" },
{ 8, "facet" },
{ 9, "cards" },
{ 10, "olden" },
{ 11, "plants" },
{ 12, "planet" },
{ 13, "dunce" },
{ 14, "stream" },
{ 15, "waiter" },
{ 16, "twice" },
{ 17, "thrice" },
{ 18, "sights" },
{ 19, "formed" },
{ 20, "parent" },
};
TMap <int32, FString> MediumIsogramList{ // 7 - 8
{ 0, "shocked" },
{ 1, "choking" },
{ 2, "playing" },
{ 3, "personal" },
{ 4, "bankrupt" },
{ 5, "disturb" },
{ 6, "document" },
{ 7, "republic" },
{ 8, "disgrace" },
{ 9, "hospital" },
{ 10, "destroy" },
{ 11, "flouride" },
{ 12, "trample" },
{ 13, "infamous" },
{ 14, "complain" },
{ 15, "farsight" },
{ 16, "judgment" },
{ 17, "mistake" },
{ 18, "consider" },
{ 19, "vouched" },
{ 20, "parents" },
};
TMap <int32, FString> HardIsogramList { // 9 - 10 letter words
{ 0, "conjugate" },
{ 1, "artichokes" },
{ 2, "authorizes" },
{ 3, "background" },
{ 4, "bankruptcy" },
{ 5, "binoculars" },
{ 6, "blackhorse" },
{ 7, "atmosphere" },
{ 8, "boyfriends" },
{ 9, "campground" },
{ 10, "clothespin" },
{ 11, "complaints" },
{ 12, "conjugated" },
{ 13, "despicably" },
{ 14, "desirably" },
{ 15, "downstream" },
{ 16, "dumbwaiter" },
{ 17, "duplicates" },
{ 18, "farsighted" },
{ 19, "formidable" },
{ 20, "godparents" },
};
};