Bulgarian Solitaire "Live" Case Study
This case study describes the development of a solution to a Java
programming problem. This case study was not rehearsed in
advance. The documents here are the actual work products
created during an onsight attempt at solving the problem. The
steps in italics describe the process to be followed.
Program Development Process
Step 1 Read the problem. Note anything that is
unclear, ambiguous, or omitted.
The problem is called Bulgarian Solitaire, is given in
Horstmann's Big Java as problem 7.12.
"In this assignment, you will model the game of Bulgarian Solitaire.
The game starts with 45 cards. ( They need not be playing cards.
Unmarked index cards work just as well.) Randomly divide them into
some number of piles of random size. For example, you might start
with piles of size 20, 5, 1, 9, and 10. In each round, you take one
card from each pile, forming a new pile with these cards. For
example, the sample starting configuration would be transformed into
piles of size 19, 4, 8, 10, and 5. The solitaire is over when the
piles have size 1, 2, 3, 4, 5, 6, 7, 8, and 9, in some order. ( It
can be shown that you always end up with such a configuration.) In
your program, produce a random starting configuration and print it.
Then keep applying the solitaire step and print the result. Stop
when the solitaire final configuration is reached."
Questions:
1. "Unmarked index cards" ... so, no ranks or suits at all?
What kind of solitaire is this?
2. When I try the example,
20 5 1 9 10
I get 19 4 0 8 9, not 19, 4, 8, 10, 5.
Am I misunderstanding the rules?
3. "you always end up with such a configuration" Who is
"you"? Me the reader? The player of the game? Does
"always end up with" mean every game ends this way?
Does that mean every game concludes with a winning configuration?
Assumptions:
After re-reading the problem several times, I conclude it's a very
boring game where the user has no decisions to make at all, and
every game is winnable. Thus, in the simulation, there
is no user input required.
I assume that piles of size zero get eliminated.
I think the problem has a typographical error, the 10 should be a 9.
Step 2 Analyze the problem. Write a description of
what is required.
1. What functions must be performed? (Include any error
handling.)
2. What are the input and output data to the program?
3. How does the user interface operate (if appropriate)?
Note any assumptions made.
Functions
1. Generate a random board.
2. Play a round, producing a new board.
3. Determine if the game is over
3a. Does the board have the proper number of
piles?
3b. Do the piles have the correct number of
cards?
4. Print the board.
Then, to make it more interesting, I'm adding an additional
requirement
5. Count the number of rounds and display it at the end of the game.
Inputs
There is no external data input to the program.
Outputs
Display of the board after each round.
Display the number of rounds.
User Interface
The board is displayed as a sequence of numbers where each number
shows the number of cards in that pile. There's a blank
between each number.
All numbers are on one line.
E.g.,
20 5 1 9 10
The game over message text is: "Game over after __ rounds."
Step 3 Create Test Data
Since there is no input data, there's no obvious way to provide test
data to the program. In the worst case, the output of
the program would have to be manually verified by following each
round and verifying the piles are calculated correctly. This
could be very time-consuming, as a game might easily last 50 or 60
rounds.
An alternative would be to modify the program so that it can accept
an initial board configuration. Then the tester can
provide a starting configuration for which the expected output can
be determined in advance. So I'll add a new functional
requirement
6. Accept an initial configuration on the command line.
E.g.,
java Solitaire 20 5 1 9 10
Assume that only integers are entered, separated by blanks.
Now I can create test cases:
Test Case 1 - shortest possible game
Input
2 3 4 5 6 7 8 10
Output
1 2 3 4 5 6 7 9 8
Game over after 1 rounds.
Test Case 2 - shortest possible game reversed
Input
10 8 7 6 5 4 3 2
Output
9 7 6 5 4 3 2 1 8
Game over after 1 rounds.
Test Case 3 - shortest possible game scrambled
Input
3 8 4 7 5 6 2 10
Output
2 7 3 6 4 5 1 9 8
Game over after 1 rounds.
Test Case 4 - rounds greater than 1
Input
2 2 2 4 5 6 7 8 9
Output
1 1 1 3 4 5 6 7 8 9
2 3 4 5 6 7 8 10
1 2 3 4 5 6 7 9 8
1 2 3 4 5 6 7 8 9
Game over after 3 rounds.
Test Case 5 - start with a single pile
Input
45
Output
44 1
43 2
42 1 2
41 1 3
40 2 3
39 1 2 3
38 1 2 4
37 1 3 4
36 2 3 4
35 1 2 3 4
....
10 2 3 4 5 6 7 8
9 1 2 3 4 5 6 7 8
Game over after 36 rounds
Test Case 6: Run the game several times and verify it starts with a
different random configuration each time.
Step 4 Create a design of the solution
4a Identify the classes.
Create a list of candidate classes from the nouns in the functional
requirements:
Game
Board
Pile
Card
Round
I decide I can omit card, because it's just a placeholder.
A pile is really just a positive integer representing the number of
cards it contains. We don't actually need any cards.
A board is a collection of piles.
The game is the simulation itself.
A round is just a counter.
4b Identify the attributes for each class.
class Board
The primary data being encapsulated by
Board is the collection of piles. Since each pile is just an
integer, we don't need a Pile class.
private Collection<Integer>;
class Game
The game has a Board and a counter for
rounds.
private Board board;
private int rounds;
4c Identify the responsibilities of each class.
(Usually these are derived from the functional requirements).
- The board can generate a random initial configuration.
- The board can setup a desired preset initial configuration.
- The board can perform the "solitaire step" to determine the
next configuration.
- The board knows if it's a winning configuration or not.
- The board can return a printable representation of itself.
- The Game initializes the board.
- The Game performs rounds until the game is over.
4d Determine the relationships between each class:
Inheritance, Aggregation, or Dependency
Game aggregates Board
4e Draw the class diagram
Game <>----- Board
4f Design the public interface to each class. This
is the compilable class skeleton.
Completed class skeletons.
Step 5 Write JUnit tests for each class. Add
minimal implementation for each class so the tests will run and
fail.
BoardTest.java
GameTest.java
Step 6 Write the algorithm for each method (in pseudocode)
Completed pseudocode.
Step 7 Desk check the algorithms with a hand trace (using the
test cases for input data)
Completed Desk Check.
Step 8 Translate the algorithms into program statements.
8a. Start with lowest level classes.
8b. Run the unit tests on each class as it is completed.
8c. Correct any defects until the unit tests all pass.
See finished implementation below.
Step 9 Run the Test Cases. Correct any defects until the test
cases all pass.
View Compile and Test Log.
Completed source code: BulgarianSolitaire.zip
Download jar: Solitaire.jar