| Software Development, Design Choices, Class Design, Recursion and Iteration, Testing, and Command-line Arguments |
2011-2012 | |
| A Racquetball or Volleyball Simulation | ||
In creating a racquetball/volleyball simulation, a program developer must make numerous design and implementation decisions. Some early decisions address the following questions.
The remainder of this reading identifies ways to address each of these questions.
Since the problem asks for simulations of either racquetball or volleyball, we want our program to simulate either game. Further, we want to make it easy for a user to specify how many games should be simulated in determining results.
From a design perspective, these program parameters might be specified in at least four ways. These possibilities are discussed in several of labs, as outlined in the following table.
| Option | Means of Specifying Game Parameters | Lab Discussing Option |
|---|---|---|
| A | Specify game paramters in a constructor that creates a simulation object | Discussed later in this lab |
| B | Specify game parameters in static variables that can be edited by a user | Discussed in Lab 3 |
| C | Ask user for game parameters at run time | An option in this lab and Lab 3 |
| D | Use command-line parameters | Discussed in Lab 5 |
A common approach in object-oriented problem solving involves collecting relevant data and operations within a class. In running the program, separate objects correspond to specific data elements.
From this perspective, it seems natural to create a separate object for a simulation for a specific game (e.g., racquetball or volleyball) and a specific number of games. Each object would be devoted to "racquetball" or "volleyball", and a field within the object would record the number of games to be simulated (1000 in the problem as stated).
This perspective of connecting the type of game to an object yields the following fields and constructor:
public String game; // either "racquetball" or "volleyball"
public boolean winByTwo;
public int numberOfGames;
/**
* Constructor specifies whether racquetball or volleyball
* will be simulated (default racquetball) and
* the number of games to be simulated
* Constructor also uses the racquetball/volleyball
* information to determine if players of the game
* must win by 2 or 1, respectively.
*/
public Game (String simGame, int simNumGames)
{ game = simGame;
numberOfGames = simNumGames;
winByTwo = simGame.equals("volleyball");
}
After deciding that a separate object will be used for a simulation, we must decide how to organize processing of many games with varying probabilities. For simplicity, we separate the playing of a single game from the overall structure of simulating 1000 games for varying probabilities. This suggests a method simulateGames for the overall simulation itself, while another method playUntilWin might simulate a specific game.
In considering return types, simulateGames should return void, because it will handle the simulation and print the results; there is nothing for simulateGames to return! In contrast, playUntilWin will simulate an individual game. When we use playUntilWin, we will not care about the details of the simulation, but we will need to know the winner. Thus, playUntilWin should return the string "A" or "B".
Within simulateGames, the output can guide the basic structure as suggested by the following basic outline:
Print header of table
For probability being 0.40, 0.41, ..., 0.60,
Number of wins for A and for B starts at 0-0
For each of 1000 games with the given probability
Determine winner and increment win for A or B, as appropriate
Print a line of the table, giving the percentage of wins for both A and B
To implement this outline, we note that neither floats or doubles (e.g., 0.01) are stored exactly. Thus, rather than using a double variable for the loop, we use an int and divide by 100.0 to simulate each probability for 1000 games.
Before tackling the main simulation, we observe the output requires each percentage be printed in a column. Since 100% is possible, we must allocate 3 characters for each percentage followed by a percent sign. Thus, somewhere we will need to compute and format a percentage. The task could be associated with a Game object, but it uses no data structures from the object. Hence, we declare the method as static (although the code would work with static omitted).
/**
* Format a probability (a number between 0.0 and 1.0)
* as a 2-character integer percentage, followed by a
* "%" character.
*/
public static String formatPercent (double value)
{ String str = "" + Math.round(value * 100.0);
while (str.length() < 3)
str = " " + str;
return str + "%";
}
The following code combines several elements discussed above in this lab.
/**
* Run simulation of 1000 games for probability of "A" winning
* a volley covering the range 0.40, 0.41, ..., 0.59, 0.60.
* For each probability of "A" winning,
* simulate games with Player/Team A always serving first
* print one line with the percentage of volleys won
* by A and B and percentage of games won by A and B
*/
public void simulateGames ()
{ // print headings
System.out.println ("\nSimulation of " + game
+ " based on " + numberOfGames + " games");
System.out.println ("Must win by 2: " + winByTwo);
System.out.println ();
System.out.println (" Probabilities Percentage");
System.out.println ("for winning volley of Wins");
System.out.println (" A B A B");
System.out.println ();
// Simulate games for 40% to 60% probabilities for A
for (int prob40To60 = 40; prob40To60 <= 60; prob40To60++)
{ double probWinVolley = prob40To60 / 100.0;
// Simulate games for a given probability
int AWins = 0; // at first neither A nor B
int BWins = 0; // has won any games
for (int i = 0; i < numberOfGames; i++)
{ // tally winner of game
if (playUntilWin ("A", "B", probWinVolley,
0, 0).equals("A"))
AWins++;
else
BWins++;
}
System.out.println (" "
+ formatPercent(probWinVolley) + " "
+ formatPercent(1-probWinVolley) + " "
+ formatPercent(((double) AWins) / numberOfGames)
+ " "
+ formatPercent(((double) BWins) / numberOfGames));
}
System.out.println ("\nEnd of Simulation\n");
}
As the sample output illustrates, typical results from this simulation are very sensitive to the initial probabilities. For example, if A has a 40% chance of winning a volley, then A will win almost no games. Since these results may not be intuitive, it is natural to to wonder how this simulation might be tested. Brainstorm several ways that could help test this type of simulation.
Within simulateGames, the main loop begins
for (int prob40To60 = 40; prob40To60 <= 60; prob40To60++)
{ double probWinVolley = prob40To60 / 100.0;
Alternatively, this loop could begin
for (double probWinVolley = 0.40; probWinVolley <= 0.60;
{ probWinVolley += 0.01)
Explain the advantages of an int control variable rather than a double in this context.
The simulateGames method contains the expression
formatPercent(((double) AWins) / numberOfGames)
Why is the phrase (double) used? (The Java code would compile and run without it.)
The code to format output, formatPercent, is given as a static method, but the method would work if static is removed (so the method is no longer static). Explain why this method can be either static or not.
Combine the class variables with the methods Game, formatPercent, and simulateGames into a full Java program. Add a stub for method playUntilWin that always returns that "A" wins (details of playUntilWin will be discussed in Lab 2). Add any needed additional details so the Java program compiles and runs — printing the full table (with all wins for "A").