Students will extend the concepts and the coding syntax learned with the "My Favorite Things" app from the previous lesson to create a similar program that manages and displays a collection of images and responds to keyboard events. Additionally, they are introduced to the practice of refactoring code in order to keep programs consistent and remove redundancies when adding new functionality. This lesson also serves as further practice with using Arrays in programs.
Students will be able to:
Most applications you use are not based on static pieces of code. Instead the software will be continuously updated both to correct errors and introduce new pieces of functionality. If later improvements are anticipated it is generally possible to develop programs in a way that easily incorporates new functionality. At other times it is necessary to make larger changes to the way a program operates in order to incorporate new features while maintaining existing functionality. Refactoring code in this way can be a tedious and challenging endeavor, but it helps ensure that the final product is consistent and easy to maintain. If software is not kept in a logical, consistent, and succinct form, then it will only get harder to keep introducing new features, increasing the likelihood of errors.
When we want to add new functionality to our programs, we'll of course have to write new code. Sometimes, when we add new code to an existing program, we'll also have to make changes to the original components of our program. Why might this be the case?
Writing software is an iterative process. We continuously update and improve our ideas as we learn new techniques, debug our software, or identify new features we'd like to add to our code. While our code will constantly be changing, we'd like it to remain organized, consistent, and readable. The use of "refactoring" helps in this process. Refactoring is similar to the concept of "encapsulating." It helps the programmer to reduce redundancy in the code and to manage the complexity in coding. Further, the "refactoring" gives the programmer clarity in reading the logical working of the code. In return the "refactoring" eases the process of modifying the entire program's function if necessary later on.
In this activity, students will put together everything they've learned in previous lessons to create an application. This application will be similar to the "My Favorite Things" app from the previous lesson, in that the major component is an Array that the program moves through. Instead of an Array of text, this lesson uses an Array of Drawables, which display images. For the most part, students are walked-through how to put together a basic version of the program, but then shift focus to refactoring, the process of restructing code to be more readable and/or less complex.
For today's lesson, we'll be creating an "Image Scroller" app, which is similar to the "My Favorite Things" app we created in the previous lesson. However, rather than store various text variables in an Array, this program will store pictures via the Drawable class. For this lesson, we'll show you exactly what you need to do with the Drawable class, but if you would like to know more you can see the documentation for the Drawable class here.
Before we begin, we need a good idea of what we want our program to accomplish. The app should have an Array of images, one of which is displayed on the screen at any given time. The user can then use the Up Arrow and Down Arrow keys to cycle through which image is currently displayed. Lastly, we will add a description to each image, which the program will read aloud when the Spacebar key is pressed. Below is streamlined list.
Now that we have a good idea of what we want our program to do, we need to come up with a plan for acheiving it. Below is a basic outline of the program's design.
Below is a template for the program. It contains all the necessary use statements, though we will end up adding a few extra actions to it. An online IDE is provided at the end of this section for you to test your code. To avoid repeatedly scrolling between the instructions and the IDE, we recommend that you write your code in a text editor, and then copy and paste it into the online IDE at the end of the section.
use Libraries.Game.Game use Libraries.Game.Graphics.Drawable use Libraries.Containers.Array use Libraries.Interface.Events.KeyboardEvent use Libraries.Interface.Events.KeyboardListener use Libraries.Sound.Audio class Main is Game action Main StartGame() end action CreateGame end action Update(number seconds) end action PressedKey(KeyboardEvent event) end end
Before we do anything else, we need to create our Array and our objects. Remember that this Array will contain Drawable objects, rather than integers like we did in the previous lesson. We will also need to declare four Drawables, an integer to keep track of the current picture, and two Audio variables to play sounds. These variables are used in multiple actions, so they need to be declared as class variables; that is, they need to be declared within the class, but outside any actions. As such, your declarations should look something like the following code.
Array<Drawable> arrayOfPictures Drawable picture0 Drawable picture1 Drawable picture2 Drawable picture3 integer currentPic = 0 Audio newPicSound Audio lastPicSound
Now that we have our Array declared, we can simply use the "Add" action with our Drawables. The first is done for you below. These additions should be made in the "CreateGame" action. You don't need to know the ins and outs of how the Quorum Game Engine is used for this lesson, but it is important to know that the "CreateGame" action is called just once, when the application first starts. Until otherwise stated, the following sections will also be adding code to the "CreateGame" action.
At this point, we have an Array of Drawables, but we haven't loaded any images to those Drawables yet. This is simply done by writing four lines of code to load each of our images. The following lines load each image, and the filepath must be typed exactly as shown.
picture0:Load("/media/code/picture 0.png") picture1:Load("/media/code/picture 1.png") picture2:Load("/media/code/picture 2.png") picture3:Load("/media/code/picture 3.png")
While we are loading our Drawables, we should also go ahead and load our Audio objects. These will be used later to indicate for us when we have cycled through the Array of pictures, but for now you only need to add the following lines of code. Like the Drawables, these filepaths must be typed exactly as shown.
As previously stated, our program should have a description of each picture that will be read aloud when the Spacebar key is pressed. These descriptions need to be set manually by the "SetDescription" action. You may either make your own descriptions, or simply use the ones below.
arrayOfPictures:Get(0):SetDescription("Fall: Scarlet leaves on an autumn blaze maple tree") arrayOfPictures:Get(1):SetDescription("Winter: Snow on the ground and an evergreen tree") arrayOfPictures:Get(2):SetDescription("Spring: Two butterflies drinking nectar from flowers") arrayOfPictures:Get(3):SetDescription("Summer: Fireworks shooting up into the night sky")
Now that we have our pictures and descriptions set, we can finally add input to our application. To start, we need to add a Keyboard Listener to the application. This is done by adding the following line of code.
Note: You don't need to understand exactly what this line of code does for this application or the Computer Science Principles course. What is important for our purposes is the effect: the application will pay attention to the keyboard while it is active, and will automatically call the "PressedKey" action when any key is pressed.
For the next bit of code, we'll move down to the "PressedKey" action. This action is called when any key is pressed, and the specific key pressed is saved as the "keyCode" value held within the "event" parameter. This "keyCode" is actually just an integer, so we simply need to compare it to the right values using conditional if-statements. Fortunately, you don't need to have these values memorized; instead, we can access the correct values by the "event" parameter's UP, DOWN, and SPACE values. The format of the first if-statement is shown below.
if event:keyCode = event:DOWN end
Within the conditional if-statement for the Down arrow key, use nested if-statements to go to the previous picture in the Array. You'll need to hide the current picture, decrement the "currentPic" variable, and show the new current picture. Below are the commands for showing and hiding a picture.
Hint: Remember to check for the value of "currentPic" before you change it! Since we want the program to cycle through the pictures, it should reset "currentPic" to the last index of the Array instead of decreasing below the first index. To signify for the user when this happens, you should also play the "lastPicSound" variable using the line "lastPicSound:Play()."
Once you've finished the conditional for the Down arrow key, do the opposite for the Up arrow key. Similar to the Down arrow key you just finished, make sure to reset the "currentPic" variable to the first index of the Array instead of increasing past the last index. To differentiate this for the user, play the "newPicSound" variable using the line "newPicSound:Play()."
Lastly, we need to add code for when the Spacebar key is pressed. This is the simplest, as we simply need to use the "say" command on the current picture's description, giving us the following line in our if-statement.
With this, we are just about done with the application! In fact, all we have left to do is add the Drawables to the screen and hide all but the first picture the program starts with. This is done by simply adding the following lines of code to the end of the "CreateGame" action.
Add(arrayOfPictures:Get(0)) Add(arrayOfPictures:Get(1)) Add(arrayOfPictures:Get(2)) Add(arrayOfPictures:Get(3)) arrayOfPictures:Get(1):Hide() arrayOfPictures:Get(2):Hide() arrayOfPictures:Get(3):Hide()
While creating the application, you may have noticed that a few lines of code in our program are very similar, sometimes only changing the variable. Hopefully warning bells went off in your head! Any time you see redundancies like this, especially if you happen to be copying portions of code from one area of your program to another, it's a good indication that you should write a function to capture that behavior in one place.
When you add new features to your code you will often create redundancies. To keep your code readable and consistent, you may need to rewrite old pieces of code. This process of restructuring existing code without changing its external behavior is called refactoring. It is an important process when developing software that improves code readability and reduces complexity. As a result, code is much easier to maintain.
To demonstrate refactoring, we're going to alter this application to make adding further functionality to the code easier. Specifically, we'll:
The first change we'll make is creating a function for setting the description of the passed Drawable's description. This is an example of refactoring done primarily to make code easier to read; our original line of "arrayOfPictures:Get(0):SetDescription("Fall: Scarlet leaves on an autumn blaze maple tree")" can be a little difficult to read for those other than the code's writer, particularly if the reader is a less experienced programmer. Instead, we will create an action for this line of code, which we can call in the "CreateGame" action.
Create a new action, named something like "SetDescriptionToPicture," and have it accept an integer parameter (the Array index), and a text parameter (the image description). This would simplify the action calls in "CreateGame" to the following, which are easier to read.
SetDescriptionToPicture(0, "Fall: Scarlet leaves on an autumn blaze maple tree") SetDescriptionToPicture(1, "Winter: Snow on the ground and an evergreen tree") SetDescriptionToPicture(2, "Spring: Two butterflies drinking nectar from flowers") SetDescriptionToPicture(3, "Summer: Fireworks shooting up into the night sky")
Next we'll do some refactoring with a bit more practical use; first, we'll set up a function to load one picture, and then a second function to load all the pictures in our Array. Not only does this make the code easier to read, but it will also reduce the amount of changes we would have to make if we added more Drawables to the Array.
To start, create a new action, with a name like "LoadOnePicture." This action should accept a Drawable parameter and a text parameter. Since the "Load" action is case-sensitive and the image files are stored on the website, we've provided this action for you below.
action LoadOnePicture(Drawable object, text filename) object:Load("/media/code/" + filename) end
Now we'll create the second action, named "LoadAllPictures." This action has no parameters, and should cycle through the elements of our Array using a "repeat while" loop, calling the "LoadOnePicture" action we made in the last step for each iteration of the loop. Additionally, you should add the picture to the screen and hide it for each iteration of the loop. This lets you remove the section at the end of the "CreateGame" action where we added and hid the pictures.
Hint: due to the way in which our image files are named, you can use your loop's counter for the text parameter of the "LoadOnePicture" action: try using "picture " + counter + ".png" in the action call.
Lastly, let's consider that you wanted to add the ability to use the Left and Right arrow keys to cycle through the pictures, while also allowing users to still use the Down and Up arrow keys. Altering the "PressedKey" action is simple; we simply need two more conditional if-statements, comparing "event:keyCode" to "event:LEFT" and "event:RIGHT." However, our code would start looking long and cluttered if we copied the code within the conditionals for the Down and Up arrow keys.
Instead, we should create two actions, "LastPicture" and "NextPicture," where we can simply pass it the "KeyEvent event" variable as a parameter and then copy the code for the Down arrow key and Up arrow key conditionals, respectively. Then we can simply call the "LastPicture" action when either the Down or Left arrow key is pressed, and call the "NextPicture" action when either the Up or Right arrow key is pressed.
After students are finished with the lesson, hold a class discussion about refactoring. Consider prompting the students with the paragraph below. Under it are points you should consider making if they do not come out naturally during the discussion.
In today's activity, we needed to make some changes to our original code in order to incorporate new functionality. Sometimes this meant we needed to make changes to our old code as well. Why might you want to change or refactor old code? Is it necessarily a bad thing to refactor code? What steps can we take to avoid refactoring code too frequently?