Challenge
This is my first 'big' project in C++. (Another number-guessing game, yay!) It's a slightly modified version of the learncpp.com quiz I set as a challenge for myself. The original challenge statement reads:
Implement a game of hi-lo. First, your program should pick a random integer between 1 and 100. The user is given 7 tries to guess the number.
If the user does not guess the correct number, the program should tell them whether they guessed too high or too low. If the user guesses the right number, the program should tell them they won. If they run out of guesses, the program should tell them they lost, and what the correct number is. At the end of the game, the user should be asked if they want to play again. If the user doesn’t enter ‘y’ or ‘n’, ask them again.
Note: You do not need to implement error handling for the user’s guess.
Objectives
I set some additional objectives:
Write modern, C++11 / C++14-compliant code;
Add some basic documentation, explaining the purpose of functions and their parameters;
Do basic error handling for all user input. This is actually part two of the quiz.
Code
main.cpp:
#include <ios> // `std::streamsize`
#include <iostream> // `std::cin`, `std::cout`
#include <limits> // `std::numeric_limits`
#include <cstdlib> // `rand()`, `srand()`
#include <ctime> // `time()`
/* Get a random int in the range [min, max] (max inclusive). */
// CSRE: Take note: not my code, so don't review this function, please :)
int getRandomInt(int min, int max) {
// source:
// http://www.learncpp.com/cpp-tutorial/59-random-number-generation/
static const double fraction = 1.0 / (RAND_MAX + 1.0);
return min + static_cast<int>((max - min + 1) * (rand() * fraction));
}
/* Get the user's next guess. `try_` is the try count. */
int getUserGuess(int try_) {
int guess;
while (true) {
std::cout << "Guess # " << try_ << ": ";
std::cin >> guess;
if (std::cin.fail()) {
// extraction failure
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
} else {
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return guess;
}
}
}
/* Play the number guessing game. Return WON_GAME if the user wins the
game, LOST_GAME otherwise. `min` and `max` are the lower and upper
bounds for the randomly generated number, respectively. `tries` is the
amount of tries the user gets before having lost the game. */
void playNumberGuessingGame(int min, int max, int tries) {
std::cout << "Let's play a game. I'm thinking of a number. ";
std::cout << "You have " << tries << " tries to guess what it is.\n";
int number = getRandomInt(min, max);
for (int try_ = 0; try_ < tries; try_++) {
int guess = getUserGuess(try_);
if (guess == number) {
std::cout << "Correct! You win!\n";
return;
} else if (guess < number) {
std::cout << "Your guess is too low.\n";
} else {
std::cout << "Your guess is too high.\n";
}
}
// if we fall through, the user has lost :(
std::cout << "Sorry, you lose. The correct number was " << number << '\n';
}
/* Ask the user if they want to play again. */
bool playAgain() {
char response;
while (true) {
std::cout << "Would you like to play again (y/n)? ";
std::cin >> response;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
if (response == 'y') {
return true;
} else if (response == 'n') {
return false;
}
}
}
int main() {
constexpr int MIN = 0;
constexpr int MAX = 99;
constexpr int TRIES = 7;
srand(time(0));
rand();
do {
playNumberGuessingGame(MIN, MAX, TRIES);
} while (playAgain());
std::cout << "Thank you for playing.\n";
}
Compilation
I compiled my code with the following command:
g++ -std=c++14 -Wall -Werror -Wextra -Wpedantic -o main main.cpp
Replace g++
with your compiler of choice.
Concerns
I normally only write high-level code. The code I've written here feels like an awkward translation from Python to C++. These are my main concerns / questions:
Is there some default format for C++ documentation? For example, in Python, there's docstrings, including a standard (PEP-257).
How did I do on error handling? I just implemented what I learned here (calling
std::cin.ignore()
and checking for extraction failure withstd::cin.fail()
). Did I miss anything? Also, is there really a need to type outstd::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
, or is it worth extracting that into a function of its own?How did I do on code style? What are the rules regarding naming conventions in C++? The most common scheme I've seen is
functionCamelCase()
,ClassPascalCase
,CONSTANT_UPPERCASE
,MACROUPPERCASE
, but I've seen variations, likefunction_snake_case()
andconstantCamelCase
.Should I make parameters
const
? From what I recall, unless there's some compelling reason to make themconst
, you shouldn't. However, I'm new to the language, so please correct me if I'm wrong.