Designing the Soccer Game

The Soccer program you saw at the beginning of this chapter relies on a table. If you analyze the Soccer program, you will see that all the action revolves around the user's clicking the various players. Somehow the computer has to know the likelihood that a pass from one player to another will succeed. Figure 8.10 shows a program that illustrates the likelihood of any one player's passing to another.

Figure 8.10. The halfback should complete a pass to the wing 80 percent of the time.


Usually I use the terms player and user interchangeably, but in this chapter, I have decided to use these terms more carefully. The soccer game has entities called players, and I have chosen to use that term only when I’m referring to these elements inside the game. I use the term user to describe the person playing the game.

Because this is the most critical part of the Soccer game that concludes this chapter, you should learn the basic concepts in a simpler program first, such as the Shot Demo illustrated in Figure 8.10.

As you can see in the Shot Demo program, the user can select one player to kick the ball and one player (or shot) as the target. Whenever the user makes a selection in one of the list boxes, the percentage likelihood this shot will succeed is displayed. The user can then press the kick button, which will succeed at the indicated percentage, to get a feel for how often the indicated percentage succeeds.

Solving a Subset of the Problem

One way to set this up would be to think about a certain player at first. For example, take the fullback, who generally stays near his own goal in a defensive position. It is very likely that the fullback will complete a pass to the goalie or halfback, because these two players are usually in close proximity. It is less likely that the fullback will complete a pass directly to the wing or center and highly unlikely that an attempt on the opponent’s goal will succeed from the fullback’s position. You could encode this information in a simple chart like Table 8.1.

Table 8.1. THE LIKELIHOOD OF SUCCESS FROM THE FULLBACK SPOT
Goalie Halfback Wing Center Shot
80% 80% 60% 40% 2%

Table 8.1 summarizes the chances of success for a shot or pass from the fullback to other members of the team (or the opposing goal). Although there is absolutely nothing scientific about these values, they are an approximation of the options facing a fullback during a soccer game. Having percentages is nice from a programming point of view because generating a random value that will approximate any percentage is very easy. For example, if you want to set up a condition that will be true 60 percent of the time (to simulate a pass from the fullback to the wing), you could use something like this:

Random roller = new Random();
if (roller.nextDouble() < .6){
  MessageBox(“Good!”);
} else {
  MessageBox(“No good.”);
} // end if

The nextDouble() method produces a random number between 0 and 1. That value will be less than 0.6 about 60 percent of the time. If you have percentages for the likelihood of various situations, writing conditional statements to test whether those situations occur is easy.

Adding Percentages for the Other Players

It would be possible to encapsulate all the information about the fullback’s options in a normal array, but sometimes other players have the ball, too. You can build a more complex table that tries to show all the possible choices in the game. Table 8.2 illustrates one such chart.

Table 8.2. PERCENTAGES FOR ALL PLAYS
Kicker Goalie Fullback Halfback Wing Center Goal
Goalie -1 .8 .6 .4 .1 .01
Fullback .8 -1 .8 .6 .4 .02
Halfback .8 .8 -1 .8 .6 .03
Wing .8 .8 .8 -1 .8 .04
Center .8 .8 .8 .8 -1 .2

Table 8.2 extends Table 8.1 to consider the likelihood of every opportunity offered to the player during the Soccer game. In this table, the percentages are written as double values (so 80% is written as .8, for example). Also, I used a –1 to indicate an impossible shot. I wanted to require the user to keep moving the ball around the field so that if he attempts to pass from a player to that same player, it will always fail (and in the final game, the opposing team will get a chance to score).

There is absolutely nothing scientific about the percentages I chose. I pulled them completely out of the air as a starting point. When the mechanics of the game are working, it will probably be necessary to tweak the values in this table.

Setting Up the Shot Demo Program

The Shot Demo program uses a special form of array to duplicate the information from Table 8.2 in the computer’s memory. The class-level code includes a new type of array to hold the data:

double chance = 0d;

private double[,] shotChance = {
  {-1, .8, .6, .4, .1, .01},
  {.8, -1, .8, .6, .4, .02}, 
  {.8, .8, -1, .8, .6, .03},
  {.8, .8, .8, -1, .8, .04},
  {.8, .8, .8, .8, -1, .2},
};

Because the program uses percentages extensively, the double type is used to handle all percentage values. The player’s likelihood of succeeding at a shot is stored in the chance variable. The shotChance is an array of doubles. However, you can see that its structure is fundamentally different from the other arrays you’ve seen so far. The data I’m trying to encapsulate in shotDemo comes from a two-dimensional table, so I store it in a two-dimensional array. You set up an array to have two (or more) dimensions by adding a comma (or more) to the brackets that follow the array type. You can predefine the values of the array if you know what the array will contain when you are initializing it. Each row is enclosed in braces, and the entire structure is enclosed in another set of braces. The rows, just like the data inside the rows, are separated by commas.

You can also set up the array without initializing it. If you wanted to make a 3-by-4 double array, for example, you could use a line such as

private double[,] myArray = new double[3,4];

When you have a two-dimensional array, you refer to it using two indices. For example, shotChance[3,2] refers to the third column, second row of the shotChance array, which is the value .8. (Don’t forget that the computer starts counting with 0, so the third column is what people would consider the fourth column.)

NOTE

IN THE REAL WORLD

Beginning programmers almost never use an array in this situation. Instead, they shy away from data structures and look at a solution that uses control structures. This is usually a mistake. It would be possible to use a series of ifelseif-endif statements or a nested switch structure to get the same behavior as the array technique I’ll show you here, but that technique would require between 75 and 100 lines of code. A lot of repeated code means many places where things can go wrong and a major headache if you find that you need to tweak your logic. By taking the time to think carefully about a data structure, the 75 lines of code can be shrunk to 4. It’s an amazing improvement in efficiency, and those four lines of code are easier to fix and modify. Experienced programmers often look for control structures that are more complex in the short run but pay off in code complexity for the long run.

There’s no reason to stop at two dimensions. It’s possible to build arrays with four, five, or as many dimensions as you like. Usually, programmers don’t need to make arrays much larger than three dimensions because they can store objects in the arrays, which allow for even more complex and flexible data.

Setting Up the List Boxes

The user will use the list boxes to control the program, so setting them up properly is very important. In particular, the elements in the lists must go in the correct order. The 1stKicker list box represents all the various kickers. Each player can be a kicker, but not the opponent’s goal (it is strictly a target), so the list box is filled with the names of the positions. I took special care to associate the names with the rows of the original table (Goalie, Fullback, Halfback, Wing, and Center). The 1stTarget list box contains all the various targets. Each player position is a potential target, as is the opposing goal, so this list box has six elements, corresponding to the columns in the original table.

To ensure that the list boxes would begin with legal values, I added code to the constructor to initialize the list boxes:

1stKicker.SelectedIndex = 0;
1stTarget.SelectedIndex = 1;

The SelectedIndex property is used to determine which element in a list box is currently selected. In this case, I chose to preset lstKicker at the 0 element (goalie) and lstTarget at element 1 (fullback).

Using a Custom Event Handler

After I designed the visual interface of the Shot Demo program, I started to think about the event handling. I soon realized that the exact same code should happen whenever either list box is changed, because the program will always need the values of both list boxes to determine how likely the shot will be. There are a couple ways to handle this. The most straightforward is to write code in the event handler for one list box and then copy and paste the code to the other. However, if you have to change the code, you need to change it in two places. Another solution would be to build a special method that evaluates the list boxes and to call that method from both event handlers. C# has another nice trick, though. Rather than have C# make event handlers automatically, you can build your own method and designate it to be an event handler. All event handlers must have two parameters. To be consistent with the automatically generated code, I used the same names as the default names for the sender and EventArgs variables.

The declaration for the new changeStatus method looks like this:

public void changeStatus(object sender,
    System.EventArgs e){

After you write a method that you want to use as an event handler, you connect it to the events it should respond to. This is easily done with the Visual Designer in the events panel of the Properties window. Figure 8.11 shows assigning a custom method to a list box’s events.

Figure 8.11. The drop-down menu is automatically populated with all methods detected by the IDE.


The Visual Designer can detect event handlers by their parameters, so it automatically puts them in the list box associated with an event. In this way, you can assign several events to the same method, if you like.

Writing the changeStatus() Method

The behavior of the changeStatus() method is not difficult. It starts with two int variables representing the kicker and the target. The kicker variable contains the selectedIndex property of the lstKicker list box. Likewise, the target variable is extracted from the 1stTarget list box. The selectedIndex property returns an integer showing which element in a list box is currently selected.

public void changeStatus(object sender,
  System.EventArgs e){
  //handles a change in either listbox
  int kicker = 0;
  int target = 0;

  //get the kicker
  kicker = 1stKicker.SelectedIndex; 
  //get the target
  target = lstTarget.SelectedIndex + 1;

  chance = shotChance[kicker, target];
  lblChance.Text = chance.ToString();
}  // end changeStatus

When there is a legitimate value in the kicker and target variables, the method looks up a value in the shotChance array and assigns this value to the chance double. It also sends a text version of this value out to the text box to tell the user which value was extracted.

Kicking the Ball

The last part of the Shot Demo program involves calculating whether a shot is successful. If the Shot Demo program can demonstrate this behavior in this simpler environment, transferring this code to the more complex Soccer program should be easy:

private void btnKick_Click(object sender,
  System.EventArgs e) {
  Random roller = new Random();
  double toHit = roller.NextDouble();
  if (toHit < chance){
    lblResult.Text = "Hit";
  } else {
    lblResult.Text = "Miss";
  } // end if
} // end btnKick

NOTE

IN THE REAL WORLD

Two-dimensional arrays are often called lookup tables because they can be used just as I have done in this program. I stored information in a table and then looked it up by the row and column. You can use two-dimensional arrays any time what you are working on requires you to look up an item in a table. In nongame programming, lookup tables are frequently used for tasks such as determining shipping rates, sales tax, or any other value that might commonly be stored in a table.

Whenever the user clicks the button, this method springs into action. Like any other method that uses random numbers, I created an instance of the Random class. The toHit variable comes from the Random object’s nextDouble() method. Remember, the program will be comparing against a double value from the lookup table, so it needs to be a double as well. If toHit is less than chance (which was set when the user made a selection from one of the list boxes), the method reports a hit. Otherwise, the result label gets the value Miss.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.185.87