CS 51 Laboratory # 9
Simon
Objective: To gain experience working with arrays.
For this laboratory, you will write a Java program that allows one to play a simple game like "Simon". Like the original, our game will involve four buttons which the player will have to press in an order determined by the computer. Given the limitations of Java's layout managers, we will keep the graphics simple by placing the four buttons in a 2 by 2 grid as shown below.
Your program will consist of five main classes:
The good news is that we will provide complete implementations of the first two classes as part of the starter for this lab. The details of how to use our code are described in the following section.
To complete this lab, you will need to work with our ButtonPanel and NoisyButton classes and one new feature of the Java system, support for manipulating audio files.
Working with audio in Java is straightforward (we've seen one example in the BouncingBasketball demo). There is a method named "getAudio" associated with the "Controller" class you extend when defining your main class. Like "getImage", this method expects a string that names a file as a parameter. This file must be in a standard format that represents audio segments. If it is, the system will read the file and return an object of type AudioClip. We will include five audio files in the starter folder for this lab. The files "tone.0.au", "tone.1.au", "tone.2.au" and "tone.3.au" describe the sounds the NoisyButtons should make. The file "razz.au" contains the unpleasant noise your program should make when the user messes up.
There is only one method of the AudioClip class you will use in this lab. The method is named play. It expects no parameters and simply plays a sound. So, if you declare a variable as:
AudioClip nastyNoise;and in your Controller you assign it a value using:
nastyNoise = getAudio("razz.au");then you can say:
nastyNoise.play();when you want to make a funny sound.
The NoisyButton class produces buttons that look and act like those found on a Simon game. These buttons know how to beep and flash. The NoisyButton class provides one method which you will use in your program. The method is named "flash()". It makes the button flash and plays the sound associated with the button. In the begin method of SimonController we have already created the buttons and set them up with the appropriate sounds.
Like other GUI components, a NoisyButton needs to have some other object that "listens" for events that involve the button. The NoisyButtonListener is used by a class that wants to respond to a NoisyButton being clicked. The interface only has one method and is defined as:
public interface NoisyButtonListener { // Method invoked when a NoisyButton is clicked public void noisyButtonClicked(NoisyButton source); }
To listen for (and repsond to) NoisyButton events, an object must provide a noisyButtonClicked method. The SimonController class implements this listener. We have included the method, but you will need to fill in the details. When the noisyButtonClicked method is invoked, the NoisyButton that has been clicked will be passed as a parameter.
The other class we will provide is called ButtonPanel. It creates the collection of NoisyButtons that form the game board. The ButtonPanel class is also a GUI component and can be added to the main panel, which we have done for you already in the SimonController class.
The ButtonPanel class provides two methods. The first method is used to assign a listener to all of the buttons in the panel. It is named addListener and expects an object that implements the NoisyButtonListener interface as a parameter. The other method will be used when you need to add a button to your "song". It is named getRandomButton and it simply returns a randomly chosen button from the panel.
The ButtonPanel constructor requires that you pass it the AudioClips for the sounds the buttons will make. Rather than expecting four separate parameters, the class is defined to expect as the only parameter to its constructor one array of AudioClips that refers to the four button sounds.
The Song class will manage the sequence of tones corresponding to the "song" currently played by the game. Internally, this class will represent the song using an array of NoisyButtons. The actual number of notes in the song will vary depending on just how good the player is at the game. So, your Song class will need to construct an array big enough to hold a sequence longer than any player is likely to remember (100 is certainly safe!) and then use a separate int variable to keep track of how many notes are currently in the sequence. You will also need to use another int variable to keep track of which note the user is expected to play next. For example, suppose there are currently 8 notes in the song, but the user has not yet guessed any notes. The class Song will need to keep track that only 8 of the possible 100 notes are in the current song, and it will need to keep track that it is waiting for the user to play the first note. If the user gets the first note right, then it will need to remember that it is now waiting for the second note, etc.
The Song class must provide methods to
Determining exactly what methods are appropriate to include in Song and what parameters they should expect will be an important part of the work you should do to prepare for this lab.
The Song class is used to keep track of what song the user should be playing as well as where the user is in the song. You will also need to create a SongPlayer class that is used to "play" the song to the user so that the user knows what pattern/song he/she is supposed to copy.
The song is represented by an array of NoisyButtons. Each NoisyButton knows how to play itself (i.e. each will respond to the invocation of its play method). The problem is that in order to play the song correctly, you will need to pause between the individual notes (and it will be best if you also pause for a second or so before beginning to play the song). The pause method can only be used within an ActiveObject. The SongPlayer class will be a class that extends ActiveObject. Whenever the Song class is asked to play itself, it will create a SongPlayer to actually do the work. The array that holds the song and the song's current length will be passed as parameters to the SongPlayer constructor. The run method of the SongPlayer will simply play all the notes (with appropriate pauses) and then terminate.
Finally, you will need to fill in a few details of the SimonController class. In the begin method, you will need to create a new Song and play it for the user to get things going. In the noisyButtonClicked method you will need to handle the user button clicks appropriately. You can determine which button was clicked on by by examining the button that is passed as a parameter to the method. What happens next depends on whether or not the user clicked on the button corresponding to the next note in the song. If not, the program should make a nasty noise and start a new game (by creating the first note and playing it).
If the user got it right, there are two possibilities. The first is that it was the last note of the song. If so, add a new note and play the entire song to the user so they can start over with emulating the notes. If instead there are more notes to play, the program should keep track that the user is ready to play the next note, but then do nothing more.
Most of this functionality should actually be implemented in the Song class. You will just be utilizing it appropriately in the noisyButtonClicked method.
Before submitting your work, make sure that each of the .java files includes a comment containing your name. Also, before turning in your work, be sure to double check both its logical organization and your style of presentation. Make your code as clear as possible and include appropriate comments describing major sections of code and declarations. Use the Format command in the Source menu to make sure your indentation is consistent. Refer to the lab style sheet for more information about style.
Turn in your project the same as in past weeks; though make sure that the folder name begins with your last name (and includes the lab number).
This lab is due Monday at 11 p.m.
public class ButtonPanel extends JPanel { // Construct button panel. // sounds is an array containing the tones played when the buttons flash public ButtonPanel(AudioClip[] sounds); // add a listener to all four buttons public void addNoisyButtonListener(NoisyButtonListener listener); // return a random button from the panel public NoisyButton getRandomButton(); }
public class NoisyButton extends JPanel implements MouseListener { // construct a new NoisyButton // noise determines the tone played when the button is clicked // shade determines the unhighlighted color of the button public NoisyButton(AudioClip noise, Color shade); // Assign an object to listen for when someone clicks on the button public void addListener(NoisyButtonListener listener); // Make the button flash by creating an ActiveObject that does the work public void flash(); } public class AudioClip { public void play(); }
import objectdraw.*; import java.applet.AudioClip; // Name: // Lab: public class SimonController extends Controller implements NoisyButtonListener { private static final int NUM_SOUNDS = 4; // the number of distinct sounds // corresponding to the game buttons private AudioClip nastyNoise; // a razzing noise private Song theSong; // a sequence of buttons/tones that // the user must reproduce // create the display of four buttons on the screen and // associate appropriate noises with those buttons public void begin() { // load the nasty noise nastyNoise = getAudio("razz.au"); // create the array of audio clips for the buttons AudioClip[] buttonSounds = new AudioClip[NUM_SOUNDS]; for (int i = 0; i < NUM_SOUNDS; i++) { buttonSounds[i] = getAudio("tone."+i+".au"); } // create the button panel ButtonPanel theButtons = new ButtonPanel(buttonSounds); // add a listener for user clicks on the buttons theButtons.addNoisyButtonListener(this); // add the panel of buttons to the window getContentPane().add(theButtons); validate(); // add code to start a new song and play it for the user } public void noisyButtonClicked(NoisyButton theButton) { // Uncomment and use this if/else statement //if () if the expected button was clicked //{ // worry about whether it was the end of the song or not //} //else the player made a mistake //{ //} } }
Grading Point AllocationsValue | Feature |
Design preparation (4 points total) | |
1 point | instance variables & constants |
1 point | constructors |
1 point | methods |
1 point | noisyButtonClicked method |
Syntax style (5 points total) | |
2 points | Descriptive comments |
1 points | Good names |
1 points | Good use of constants |
1 point | Appropriate formatting |
Semantic style (7 points total) | |
1 point | Conditionals and loops |
2 points | General correctness/design/efficiency issues |
1 point | Parameters, variables, and scoping |
2 points | Good correct use of arrays |
1 point | Miscellaneous |
Correctness (4 points total) | |
1 point | Playing songs |
1 point | Comparing user input with songs |
1 point | Restarting correctly when user makes mistake |
1 point | Lengthening song correctly when user is right |
Extra Credit (2 points maximum) | |
.5 point | Better graphics |
.5 point | Keeping score |