Introduction
The rules of Ten Pin bowling are easy to follow, but can be tricky to implement in code. An example by Tomas Brennan can be found here, which hosts a single-player GUI in C# for entering scores and calculating the result. The approach I have used instead represents a scoreboard, including the appropriate layout and calculations printed in the command prompt. It also stores internal data in a very different way, allowing for a different set of features.
Background
As a technical test, I was tasked with creating a scoreboard for a game of Ten Pin bowling, validating all input and incorporating all rules associated with the game. The examples provided with this article cover printing the scoreboard as text in the Windows command prompt or in the Mac OS X terminal. This could, of course, be extended to displaying values in a GUI.
Using the Code
The underlying system (i.e., not including the implementation of InterfaceManager
) was designed to rely solely on STL classes, and therefore be completely cross-platform. While multiple forms of game have not been created, the game logic has been separated from the main program (by using two classes: Program
and BowlingGame
) so that such changes would be trivial. For the sake of simplicity and time, the InterfaceManager
was written twice in separate deployments instead of creating an abstract interface to determine the output methodology (or worse, wrapping up the interface in large blocks of preprocessor directives).
Getting Started: Initialization
Initialization is straightforward; set initial default values and request the number of players. The technical specification did not provide a maximum number of players, so I set this to six. In order to get values from the user, everything is performed through the InterfaceManager
(externally referred to as Interface). This constitutes a simple std::cin
to an std::string
value, which is then parsed to the appropriate type. To get the number of players between one and six from the user, the following code is used:
int playerCount = 0;
do
{
Interface.drawSplashScreen(*this);
playerCount = Interface.getIntegerValue(" Please enter the number of players (1-6): ");
}
while (playerCount < 1 || playerCount > 6);
This simply draws the splash screen (involving a little ASCII art in these console versions), prints the message requesting an integer input, and waits for the value to be entered. I do not trust std::cin
to directly put input values into anything other than a string
, so I instead use a stringToInteger
function from my GameUtil
class.
Once the program has a number between one and six, it proceeds to ask for the names of each of the new players. The validation for this is straightforward; any input between three and sixteen characters long that isn't already the name of an entered player.
Additional input validation would be straightforward to implement (i.e., alphabets only, first letter uppercase, subsequent letters lowercase). With all of the players set up, the game's initialization is complete - it begins on game one, frame one, and the program begins the game's update cycle.
Taking turns: Update Loop
The following line determines which player's turn it is:
_turn = (_turn + 1) % _players.size();
And when it comes back to Player 1's turn, it begins the next round (or frame in game terms).
if (_turn == 0)
_frame++;
Rather than handling each update cycle for each ball being bowled, a given cycle is for a player's whole turn. This means a player will continue bowling until their turn of this frame is over. A player progresses their turn by calling their bowlBall
function until it returns true
, denoting the end of their turn.
Bowling a ball is a straightforward process, using the following code:
do
{
scoreThisBowl = Interface.getIntegerValue(" How many pins were hit on this bowl? ");
}
while (!isValidScore(scoreThisBowl, currentFrame));
Note: This has been abbreviated from the Windows version, as the console class is not valid in non-Windows systems.
The isValidScore
function simply ensures that a logical number has been entered. Most of the time, this is a value between 0 and 10. Otherwise, if it is not the final frame and a non-Strike first ball has been bowled, it is a value between 0 and (10 - first score).
With a valid score entered, the player "updates" all of his previous scores with the new score. This could be optimised somewhat by not updating more than the last two scores, but it is hardly a system performance bottleneck. The update
call simply adds the new score as a bonus to the old score, if applicable. This is determined when the score is first recorded; if it is a Strike, it records the next two scores as bonuses. If it is a Spare, it records the next single score as a bonus. When all bonuses have been applied to a score, it no longer adds bonuses to itself.
Displaying the Scoreboard: The Interface
Along with some utilities in the Windows version, the InterfaceManager
class is comprised of three main functions: drawSplashScreen
, drawScoreTable
, and drawGameOverScreen
. The splash screen simply shows some bowling ASCII art and the list of player names.
The score table is the most varied part of this program, requiring the most alteration between the Windows and Mac OS X versions. This is because the Mac terminal does not support utility features relating to different font colours or manually positioning the caret around the window. As a result, it needs to generate the score table line-by-line in the correct order, as opposed to printing the score table with no formatting and then filling in the individual scores as appropriate. In Windows, this included some extra formatting to change the colour of the text depending on whose turn it is, highlighting the current player's name and scores in yellow. Finally, the scores themselves are formatted according to the rules; a Strike is shown as an "X", a Spare is shown as a "/", and a score of zero is shown as a "-".
The game over screen simply shows the score table and an ASCII trophy, declaring the winner and the score they achieved. Ties for the top score are shown as necessary, and the total score of the whole team added together is shown at the bottom.
Ending the Game: Replay and Shutdown
When a game has been played through ten frames and the winner(s) declared, the program prompts the user whether they want to play another game or not. If so, the game resets all player data and runs its initialization again. Otherwise, the game shuts down and closes the program. There is no dynamically allocated memory in this program, so everything is automatically cleared up on shutdown.
Points of Interest
I'd learned how to write code that is cross-platform and does not rely on obscure, outdated, or experimental libraries that are not likely to exist on every machine. However, before this project, I had not attempted to print out a program in the Mac OS X terminal; my assumptions had been that, because they both print text using std::cout
, I could very easily redeploy my program.
I had not counted on the different features of the terminal, and ended up rewriting fairly large sections of the interface. This mostly involved taking out the changing font colours and repositioning the caret; everything was instead printed line-by-line, resulting in similar output but requiring a bit more care and calculation.
History
- 6th February, 2012: Initial post
Deployed and tested version 1.0 on Windows 7 x64 and Mac OS X Snow Leopard