| Software Development, Design Choices, Class Design, Recursion and Iteration, Testing, and Command-line Arguments |
2011-2012 | |
| A Racquetball or Volleyball Simulation | ||
While details of a game may be handled in many ways, it is natural to use the flow of a game to structure the code. Ignoring initialization, there are two basic approaches:
Game Flow 1: Proceed serve by serve until someone wins
Serve
if server wins volley
score a point
if server wins game
report winner
else
same server serves again
else
other team serves
Game Flow 2: Game proceeds with the sequence A serves, then B serves
Continue until someone wins
A. A serves until A wins game or loses serve
B. If A has not won
B serves until B winds game or loses serve
In moving from high-level design to an implementation, an important consideration involves how to work with probabilities. Winning a serve depends upon the probability for that team. Suppose probAWin and probBWin are the probabilities that A and B, respectively, win a volley. For every volley, either A or B will win, so probAWin + probBWin = 1.0. To simulate winning a volley, we can use the function random() that returns numbers between 0.0 and 1.0 with approximately equal probability. In particular, the test random() < probAWin will be true the fraction of time specified by probAWin.
In Game Flow 1, processing proceeds serve by serve until one team wins. Within a program, the flow from one serve to the next may be done in either of two main ways: recursion or iteration.
When implementing this approach recursively, we utilize parameters for the scores and probabilities and call the method with the correct values. When implementing this approach iteratively, we need separate variables for the server and receiver, the values need to be swapped when the serve changes, and an outer loop indicates that the volleys continue until the game is won.
The recursive is particularly simple and intuitive according to the outline. In the recursive approach, initialization of scores and server is accomplished in the initial call:
playUntilWin ("A", "B", probWinVolley, 0, 0)
The recursive method for Game Approach 1 follows:
/**
* Play one game of racquetball or volleyball to conclusion
* @parms server and receiver indicate the team "A" or "B"
* probWinVolley specifies the likelihood the
* server wins a volley
* serverScore, recScore contain current score of
* server and receiver
* @returns winner of game: either "A" or "B"
*/
public String playUntilWin (String server, String receiver,
double probWinVolley,
int serverScore, int recScore)
{ // serve
if (Math.random() < probWinVolley)
{ // score point
serverScore++;
// if win, return winner
if ((serverScore >= 15)
&& ((! winByTwo)
|| (serverScore >= recScore + 2)))
return server;
// if not win, serve again
else
return playUntilWin (server, receiver, probWinVolley,
serverScore, recScore);
}
else
{ // other side wins; other player serves
return playUntilWin (receiver, server, 1.0-probWinVolley,
recScore, serverScore);
}
}
In this recursive approach, method playUntilWin assumes the server, receiver, probabilities, and scores are established before a serve is to take place, and the method is responsible for handling the details of a single serve.
Program Game1Recur.java provides the full code for a simulation using Game Flow 1 with recursion.
When processing uses iterative to proceed from serve to serve, separate variables are needed to keep track of who the server is and what points and probabilities are associated with each team.
When using loops, it is helpful to identify a loop invariant that describes the state of each variable just before a serve takes place:
serverScore // variable indicates the current score for the server
receiverScore // variable indicates the current score receiving team
server // the team currently serving ("A" or "B")
receiver // the team receiving the serve ("B" or "A")
probWinVolley // probability that the server will win a volley
The initial probability for Team A is specified in the method's parameters. Otherwise, initialization within playUntilWin establishes the loop invariants for the first time the loop is executred.
// start at 0-0 int serverScore = 0; // the current score for the server int receiverScore = 0; // the current score for the team receiving the serve String server = "A"; // "A" always serves first in the simulation String receiver = "B"; // "B" always receives the first serve
Processing in the main loop examines the results of the serve to determine if the server scores a point or if the serve changes. The main work of the loop, then, is to update variables, so that the above loop invariants are maintained for when the loop starts the next time. In the process, a temp variable may be needed as values are swapped. The loop terminates when one team wins.
Code for a full iterative simulation of a game follows:
/**
* Play one game of racquetball or volleyball to conclusion
* @parms probWinVolley specifies the likelihood the
* server wins a volley
* @returns winner of game: either "A" or "B"
*/
public String playUntilWin (double probWinVolley)
{ // start at 0-0
int serverScore = 0;
int receiverScore = 0;
int tempScore;
String server = "A";
String receiver = "B";
String temp;
// continue indefinitely until server wins
while (true)
{ // server serves
if (Math.random() < probWinVolley)
{ // server wins volley
serverScore++;
// check if server wins
if ((serverScore >= 15)
&& ((! winByTwo)
|| serverScore >= receiverScore + 2))
return server;
}
else
{ // swap information for server and receiver
tempScore = serverScore;
serverScore = receiverScore;
receiverScore = tempScore;
temp = server;
server = receiver;
receiver = temp;
probWinVolley = 1.0 - probWinVolley;
}
}
}
Program Game1Iter1.java provides the full code for a simulation using Game Flow 1 with iteration.
Turning to Game Approach 2, the phrase "A serves until A wins" translates naturally into a while loop. Processing to handle B's serve can proceed naturally with either recursion or iteration.
In the fully-iterative approach, the translation of the full outline can seem somewhat verbose if auxiliary methods are not used. Here is a sample iterative solution for Game Flow 2:
/**
* Play one game of racquetball or volleyball to conclusion
* @parms probWinVolley specifies the likelihood
* the server wins a volley
* @returns winner of game: either "A" or "B"
*/
public String playUntilWin (double probWinVolley)
{ // start at 0-0
int AScore = 0;
int BScore = 0;
// continue indefinitely
// (until A wins after A's serve or B wins after B's serve)
while (true)
{ // A serves
while (Math.random() < probWinVolley)
{ AScore++;
if ((AScore >= 15)
&& ((! winByTwo)
|| (AScore >= BScore + 2)))
return "A";
}
// B serves
while (Math.random() < 1.0 - probWinVolley)
{ BScore++;
if ((BScore >= 15)
&& ((! winByTwo)
|| (BScore >= AScore + 2)))
return "B";
}
}
}
Program Game1Iter2.java provides the full code for a simulation using Game Flow 2 with iteration (but without helper functions).
As an alternative, separate methods can be defined for the details of serving and winning. This clarifies the main method for playing the game, although the overall code for the game and its helper methods is somewhat longer.
/**
* Determine if player with first score has won
* @parms firstScore, secondScore are scores of
* opposing players/teams
* @returns true if player with first score has won;
* false otherwise
*/
public boolean won (int firstScore, int secondScore)
{ // check if first person has won
return ((firstScore >= 15)
&& ((! winByTwo)
|| (firstScore >= secondScore + 2)));
}
/**
* allows player with first score to continue serving,
* until player has won game or lost volley
* @parms probWinVolley is probability that player
* with first score wins volley
* firstScore, secondScore are scores of
* the two players/teams
* @returns final score of server
*/
public int serve (double probWinVolley,
int firstScore, int secondScore)
{ while (Math.random() < probWinVolley)
{ firstScore++;
if (won (firstScore, secondScore))
break;
}
return firstScore;
}
/**
* Play one game of racquetball or volleyball to conclusion
* @parms probWinVolley specifies the likelihood the
* server wins a volley
* @returns winner of game: either "A" or "B"
*/
public String playUntilWin (double probWinVolley)
{ // start at 0-0
int AScore = 0;
int BScore = 0;
// continue indefinitely
//(until A wins after A's serve or B wins after B's serve)
while (true)
{ // A serves
AScore = serve (probWinVolley, AScore, BScore);
// stop if A has won
if (won (AScore, BScore))
return "A";
// B serves
BScore = serve (1.0-probWinVolley, BScore, AScore);
// stop if B has won
if (won (BScore, AScore))
return "B";
}
}
Program Game1Iter2Alt.java provides the full code for a simulation using Game Flow 2 with iteration and helper functions.
The following table presents the lines of code for playing a game according to each game flow and the file name for the full program.
| Approach | Lines for game method, including comments and formatting (from actual program) | Lines for game method, excluding comments and formatting | File name of actual program |
|---|---|---|---|
| Game Flow 1, Recursive | 33 lines | 20 lines | Game1Recur.java |
| Game Flow 1, Iterative | 45 lines | 27 lines | Game1Iter1.java |
| Game Flow 2, Iterative, no helper methods | 34 lines | 21 lines | Game1Iter2.java |
| Game Flow 2, Iterative with helper methods | 57 lines (with helper functions) | 26 lines (with helper functions) | Game1Iter2Alt.java |
For each of these programs, the code to print headings, iterate over the probabilities, and iterate for 1000 games has about the same size N 120 lines (for game approach 1) or 121 lines (for game approach 2), including comments. (Approach 2 has an extra line of initial comments.)
Review each of the four program variations discussed in this Lab:
Compare and contrast these problem-solving approaches and their resulting programs.
Program Game1Iter2Alt.java uses two helper methods, won and serve.
The solutions given above for Game Flow 2 are all iterative. However, recursion might be used in either of two ways:
Consider how recursion might be used in place of iteration in Game Flow 2.
How might the simulations be modified so that A's probability of winning a volley could be different if A serves or if B serves? Choose one of the Java programs from Step 1, and modify it so that
As stated, these simulation programs specify the same probabilty of A winning a volley, regardless of who is serving. (E.g., A wins 40% of the serves, when either A or B serves.)
Review the simulations presented and identify the assumptions that are being made throughout the code. To what extent are these assumptions realistic?