Bulls & Cows

BULLS & COWS

What is this?

Bulls & Cows is a mind game, where the player guess a hidden word. To be able to find the word, the player gets clues if the letters in the word chosen by the player is correct or not.


If the matching letter are in the right position, they are Bull.


If the matching letter are in a different position, they are Cow.


If the letter does not exist in the hidden word, nothing happens.

  • Engine: Visual Studio
  • Language: C++
  • Platform: PC
  • Development Time: 5 days
  • Team Size: Solo

Functions

Game Loop

While the game isn’t won or lost, this is the function that is being called in the Main function.  A Struct called GameCounter has two int variables; Cow and Bull. In the PlayGame function the corresponding name is printed depending on which letters are correct.

Submit Valid Guess

The function SubmitValidGuess checks if the guess the player has written is the same as the hidden word.

 

It starts by looping through all the letters in the hidden word and then the letters in word the player submitted.

 

Depending on how the letters matches, the counters for the “bull” and “cow” increase.  

 

Get Max Tries

Depending on how long a hidden word is, the player will have a different number of max tries. This is stored in a Map, where the key is the number of letters in the hidden word and the value is Max tries. I made this function to adapt the difficulty of the game depending on the number of letters in the hidden word.

 

If a word is 3 letters long, the player will only get 4 guesses.

Guess Validity

To make it easier, there is a few rules for typing a word.  An enum class is being called on, depending on the type of word the player is returning.

The word the player enters goes through a switch case and checks which enum corresponds to the word.


        
void PlayGame()
{
	Game.Reset();
	int MaxTries = Game.GetMaxTries();

	while (!Game.IsGameWoon() && Game.GetCurrentTry() <= MaxTries)
	{
		std::string Guess = GetValidGuess();
		GameCounter BullCowCount = Game.SubmitValidGuess(Guess);

		std::cout << "Bulls = " << BullCowCount.Bulls;
		std::cout << ", Cows = " << BullCowCount.Cow << "\n \n";
	}
	PrintGameSummary();
}

std::string GetValidGuess()
{
	std::string Guess = "";
	GameStatus Status = GameStatus::Invalid_Status;
	do 
	{
		int CurrentTry = Game.GetCurrentTry();
		std::cout << "Try " << CurrentTry << " of " << Game.GetMaxTries();
		std::cout << ". Enter your guess: ";
		
		std::getline(std::cin, Guess);

		Status = Game.CheckGuessValidity(Guess);
		switch (Status)
		{
		case GameStatus::Not_Word:
			std::cout << "Please enter a word without repeating letters.\n\n";
			break;
		case GameStatus::Wrong_Length:
			std::cout << "Please enter a " << Game.GetHiddenWordLength() 
			    << " letter word.\n\n";
			break;
		case GameStatus::Not_Lowercase:
			std::cout << "Please only enter lowercase letters.\n\n";
			break;
		case GameStatus::OK:
		default:
			break;
		}
	} while (Status != GameStatus::OK);
	return Guess;

}

bool Restart()
{
	std::cout << "Do you wanna play again with the same hidden word (Y/N)? ";
	std::string Response = "";
	std::getline(std::cin, Response);
	std::cout << std::endl;

	return (Response[0] == 'y') || (Response[0] == 'Y');
}

void PrintGameSummary()
{
	if (Game.IsGameWoon())
		std::cout << "GOOD JOB, YOU WIN! \n";
	else
		std::cout << "Better luck next time! \n";
}


    


int BullCowGame::GetMaxTries() const
{
	std::map< int, int> WordLengthToMaxTries{ { 2,3 },{ 3,4 },{ 4,7 },{ 5,10 },{ 6,15 },{ 7,20 } };

	return WordLengthToMaxTries[MyHiddenWord.length()];
}

GameStatus BullCowGame::CheckGuessValidity(std::string Guess) const
{
	if (!IsWord(Guess)) 
		return GameStatus::Not_Word;

	else if (!IsLowercase(Guess)) 
		return GameStatus::Not_Lowercase;
	
	else if (Guess.length() != GetHiddenWordLength())
		return GameStatus::Wrong_Length;
	
	else 
		return GameStatus::OK;
	
}

void BullCowGame::Reset()
{
	const std::string HIDDEN_WORD = "planet"; 
	MyHiddenWord = HIDDEN_WORD;

	MyCurrentTry = 1;
	GameIsWon = false;
}

GameCounter BullCowGame::SubmitValidGuess(std::string Guess)
{
	MyCurrentTry++;
	GameCounter BullCowCount;

	int WordLength = MyHiddenWord.length();
	for (int i = 0; i < WordLength; i++) 
	{
		for (int j = 0; j < WordLength; j++) 
		{
			if (Guess[j] == MyHiddenWord[i])
			{
				if (i == j)
					BullCowCount.Bulls++;
				else
					BullCowCount.Cow++;
			}
		}
	}
	if (BullCowCount.Bulls == WordLength)
		GameIsWon = true;
	else 
		GameIsWon = false;

	return BullCowCount;
}

bool BullCowGame::IsWord(std::string Word) const
{
	if (Word.length() <= 1) return true;

	std::map< , bool> LetterSeen;
	for (char Letter : Word) // For all letters of the word
	{
		Letter = tolower(Letter);
		if (LetterSeen[Letter])
			return false;
		else
			LetterSeen[Letter] = true;
	}
}
bool BullCowGame::IsLowercase(std::string Word) const
{
	for (char Letter : Word)
	{
		if (!islower(Letter)) return false;		

		return true;
	}
}


    

The Process

I wanted to pick up on my C++ knowledge and what better way to do so by making a small game?

Challenges

When I made this project, I had previously only made games in C#. It was challenging at first to create a game with no help from a game engine. The most challenging part with this project was to finish it. The project started with me just wanting to refresh my C++ knowledge in a fun way. When I felt comfortable with it, I almost put it aside. But then I started to explore ascii art and after spending a little bit to long creating an ascii-cow I decided to commit and finish it!

Iteration

I did multiple iterations on this game. As soon as I realized I waned to add new features I optimized the way I called the functions. Instead of doing everything in the main-loop I started creating functions which would return the correct thing to make it easier to pick up the code if I waned to ad more to it later.