diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..077cf8f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.15) +project(hangman) + +set(CMAKE_CXX_STANDARD 17) +file(GLOB SOURCES "src/cpp/*.cpp" "src/cpp/*.hpp") +add_executable(hangman ${SOURCES}) diff --git a/README.md b/README.md index ffd2d35..56f14c1 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,18 @@ new methods, anything. Just make sure it still builds. When ready, please submit a pull request. +## Refactored object-oriented C++ version +A refactored version written in C++ is located at ``src/cpp/``. +Instructions to build and run (CMake is required): +``` +cmake . +make +./hangman +``` + ## License + The MIT License (MIT) Copyright (c) 2017 Yegor Bugayenko diff --git a/src/cpp/attempt_display.cpp b/src/cpp/attempt_display.cpp new file mode 100644 index 0000000..be0b622 --- /dev/null +++ b/src/cpp/attempt_display.cpp @@ -0,0 +1,20 @@ +#include "attempt_display.hpp" +#include + +AttemptDisplay::AttemptDisplay(const GuessingAttempt& attempt, const Mistakes& mistakes) + : m_mistakes(mistakes) { + if (attempt.IsSuccessful()) { + ShowHit(); + } else { + ShowMiss(); + } +} + +void AttemptDisplay::ShowHit() const { + std::cout << "Hit!\n"; +} + +void AttemptDisplay::ShowMiss() const { + std::cout << "Missed, mistake " << m_mistakes.GetAmount() << " out of " + << m_mistakes.GetMaxAmount() << "\n"; +} diff --git a/src/cpp/attempt_display.hpp b/src/cpp/attempt_display.hpp new file mode 100644 index 0000000..f1469ac --- /dev/null +++ b/src/cpp/attempt_display.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "guessing_attempt.hpp" +#include "mistakes.hpp" + +class AttemptDisplay { +public: + explicit AttemptDisplay(const GuessingAttempt& attempt, const Mistakes& mistakes); +private: + const Mistakes& m_mistakes; + + void ShowHit() const; + void ShowMiss() const; +}; diff --git a/src/cpp/game.cpp b/src/cpp/game.cpp new file mode 100644 index 0000000..d4f9426 --- /dev/null +++ b/src/cpp/game.cpp @@ -0,0 +1,43 @@ +#include "game.hpp" +#include "guessing_attempt.hpp" +#include "result_display.hpp" +#include "attempt_display.hpp" + +constexpr size_t MAX_MISTAKES = 5; + +Game::Game() + : m_guessed_word() + , m_mistakes(MAX_MISTAKES) + , m_word_display(m_guessed_word.GetSize()) + , m_user_input() { +} + +void Game::Play() { + while (!IsOver()) { + char letter = m_user_input.InputLetter(); + GuessingAttempt attempt( + letter, + m_guessed_word, + m_mistakes, + m_word_display + ); + + AttemptDisplay(attempt, m_mistakes); + m_word_display.Show(); + } + + ResultDisplay(*this); +} + +bool Game::IsOver() const { + return IsWon() || IsLost(); +} + +bool Game::IsWon() const { + return m_guessed_word.IsGuessed(); +} + +bool Game::IsLost() const { + return m_mistakes.IsMaxAmountReached(); +} + diff --git a/src/cpp/game.hpp b/src/cpp/game.hpp new file mode 100644 index 0000000..f86d35e --- /dev/null +++ b/src/cpp/game.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "guessed_word.hpp" +#include "mistakes.hpp" +#include "word_display.hpp" +#include "user_input.hpp" + +class Game { +public: + Game(); + void Play(); + + bool IsOver() const; + bool IsWon() const; + bool IsLost() const; +private: + GuessedWord m_guessed_word; + Mistakes m_mistakes; + WordDisplay m_word_display; + UserInput m_user_input; +}; diff --git a/src/cpp/guessed_word.cpp b/src/cpp/guessed_word.cpp new file mode 100644 index 0000000..fe09062 --- /dev/null +++ b/src/cpp/guessed_word.cpp @@ -0,0 +1,40 @@ +#include "guessed_word.hpp" +#include +#include + +static const std::vector WORDS = { + "simplicity", "equality", "grandmother", + "neighborhood", "relationship", "mathematics", + "university", "explanation" +}; + +GuessedWord::GuessedWord() + : m_word(WORDS[std::rand() % WORDS.size()]) + , m_guessed_amount(0) + , m_already_guessed() { +} + +GuessedVec GuessedWord::Guess(char letter) { + GuessedVec result; + + for (size_t pos = 0; pos < m_word.size(); ++pos) { + if (m_word[pos] == letter) { + result.push_back(pos); + } + } + + if (m_already_guessed.count(letter) == 0) { + m_already_guessed.insert(letter); + m_guessed_amount += result.size(); + } + + return result; +} + +size_t GuessedWord::GetSize() const { + return m_word.size(); +} + +bool GuessedWord::IsGuessed() const { + return m_guessed_amount == m_word.size(); +} diff --git a/src/cpp/guessed_word.hpp b/src/cpp/guessed_word.hpp new file mode 100644 index 0000000..5d05a8d --- /dev/null +++ b/src/cpp/guessed_word.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +using GuessedVec = std::vector; + +class GuessedWord { +public: + GuessedWord(); + + GuessedVec Guess(char letter); + size_t GetSize() const; + bool IsGuessed() const; +private: + std::string m_word; + std::size_t m_guessed_amount; + std::set m_already_guessed; +}; diff --git a/src/cpp/guessing_attempt.cpp b/src/cpp/guessing_attempt.cpp new file mode 100644 index 0000000..61403ec --- /dev/null +++ b/src/cpp/guessing_attempt.cpp @@ -0,0 +1,14 @@ +#include "guessing_attempt.hpp" + +GuessingAttempt::GuessingAttempt(char letter, GuessedWord& word, Mistakes& mistakes, WordDisplay& word_display) + : m_guessed(word.Guess(letter)) + , m_successful(!m_guessed.empty()) { + if (!m_successful) { + mistakes.MakeOne(); + } + word_display.Reveal(letter, m_guessed); +} + +bool GuessingAttempt::IsSuccessful() const { + return m_successful; +} diff --git a/src/cpp/guessing_attempt.hpp b/src/cpp/guessing_attempt.hpp new file mode 100644 index 0000000..2d35308 --- /dev/null +++ b/src/cpp/guessing_attempt.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "guessed_word.hpp" +#include "mistakes.hpp" +#include "word_display.hpp" + +class GuessingAttempt { +public: + GuessingAttempt(char letter, GuessedWord& word, Mistakes& mistakes, WordDisplay& display); + + bool IsSuccessful() const; +private: + GuessedVec m_guessed; + bool m_successful; +}; diff --git a/src/cpp/main.cpp b/src/cpp/main.cpp new file mode 100644 index 0000000..ca62bac --- /dev/null +++ b/src/cpp/main.cpp @@ -0,0 +1,10 @@ +#include "game.hpp" +#include +#include + +int main() { + std::srand(std::time(nullptr)); + Game().Play(); + + return 0; +} diff --git a/src/cpp/mistakes.cpp b/src/cpp/mistakes.cpp new file mode 100644 index 0000000..7c1ace5 --- /dev/null +++ b/src/cpp/mistakes.cpp @@ -0,0 +1,22 @@ +#include "mistakes.hpp" + +Mistakes::Mistakes(int max_amount) + : m_max_amount(max_amount) + , m_amount(0) { +} + +void Mistakes::MakeOne() { + ++m_amount; +} + +int Mistakes::GetAmount() const { + return m_amount; +} + +int Mistakes::GetMaxAmount() const { + return m_max_amount; +} + +int Mistakes::IsMaxAmountReached() const { + return m_amount >= m_max_amount; +} diff --git a/src/cpp/mistakes.hpp b/src/cpp/mistakes.hpp new file mode 100644 index 0000000..79b3796 --- /dev/null +++ b/src/cpp/mistakes.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +class Mistakes { +public: + explicit Mistakes(int max_amount); + + void MakeOne(); + int GetAmount() const; + int GetMaxAmount() const; + int IsMaxAmountReached() const; +private: + int m_max_amount; + int m_amount; +}; diff --git a/src/cpp/result_display.cpp b/src/cpp/result_display.cpp new file mode 100644 index 0000000..a00e5f1 --- /dev/null +++ b/src/cpp/result_display.cpp @@ -0,0 +1,19 @@ +#include "result_display.hpp" +#include "game.hpp" +#include + +ResultDisplay::ResultDisplay(const Game& game) { + if (game.IsWon()) { + ShowWin(); + } else { + ShowLoss(); + } +} + +void ResultDisplay::ShowWin() const { + std::cout << "You won!\n"; +} + +void ResultDisplay::ShowLoss() const { + std::cout << "You lost.\n"; +} diff --git a/src/cpp/result_display.hpp b/src/cpp/result_display.hpp new file mode 100644 index 0000000..d90f25a --- /dev/null +++ b/src/cpp/result_display.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "mistakes.hpp" +#include "guessing_attempt.hpp" + +class Game; + +class ResultDisplay { +public: + explicit ResultDisplay(const Game& game); +private: + void ShowWin() const; + void ShowLoss() const; +}; diff --git a/src/cpp/user_input.cpp b/src/cpp/user_input.cpp new file mode 100644 index 0000000..08f38f2 --- /dev/null +++ b/src/cpp/user_input.cpp @@ -0,0 +1,9 @@ +#include "user_input.hpp" +#include + +char UserInput::InputLetter() const { + char c = 0; + std::cout << "Guess a letter: "; + std::cin >> c; + return c; +} diff --git a/src/cpp/user_input.hpp b/src/cpp/user_input.hpp new file mode 100644 index 0000000..c063508 --- /dev/null +++ b/src/cpp/user_input.hpp @@ -0,0 +1,6 @@ +#pragma once + +class UserInput { +public: + char InputLetter() const; +}; diff --git a/src/cpp/word_display.cpp b/src/cpp/word_display.cpp new file mode 100644 index 0000000..146c3b3 --- /dev/null +++ b/src/cpp/word_display.cpp @@ -0,0 +1,18 @@ +#include "word_display.hpp" +#include + +WordDisplay::WordDisplay(size_t word_size) + : m_buffer(word_size, HIDDEN_LETTER) { + // initialize the buffer with hidden letters +} + +void WordDisplay::Reveal(char letter, const GuessedVec& positions) { + for (auto pos : positions) { + m_buffer[pos] = letter; + } +} + +void WordDisplay::Show() const { + std::cout << "The word: "; + std::cout << m_buffer << "\n\n"; +} diff --git a/src/cpp/word_display.hpp b/src/cpp/word_display.hpp new file mode 100644 index 0000000..46a8243 --- /dev/null +++ b/src/cpp/word_display.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "guessed_word.hpp" +#include +#include + +class WordDisplay { +public: + static constexpr char HIDDEN_LETTER = '?'; + + explicit WordDisplay(size_t word_size); + + void Reveal(char letter, const GuessedVec& positions); + void Show() const; +private: + std::string m_buffer; +};