Overview

In this lesson students will create an "Event-Driven Program" from scratch using Quorum Programming Language. Students will learn the concept of "Object Oriented Programming" through the creation of this project. The "Musical Keyboard" program will be built in steps (section by section), allowing students to run their programs and test the functionality of each section along the way while incorporating the concept of "Run, Test, Debug!" introduced in Lesson 1.

Vocabulary

Goals

Students will be able to:

Purpose

It takes a lot of time and effort to write a computer program. The length of a modern computer program, such as a game, could contain millions of lines of code. Programmers spend days, weeks, and months building a single program. Programmers may choose to focus on writing one specific section of a program at a time and test the program as they complete each section to make sure the output matches what is expected. This lesson allows students to experience the basics of programming and the programming process by focusing on each action of the program separately.

Getting Started

Quorum Programming Language is an "Object Oriented Language." To get a better idea of what an "Object Oriented Language" is let's look at the code example below:

use Libraries.Compute.Random

Random random

integer randomInt = random:RandomIntegerBetween(1, 10)
output randomInt

Looking at the code line by line:

  1. On the first line, we wrote the "use statement" to include the "Random class" in this project. The "use statement" is always followed by the word Libraries. The "Random class" will support the "class Main" that we are writing our code in.
  2. On the second line, we wrote "Random random." The first Random, capital R, is the class name and the second, lowercase r, is the object name. In Quorum we declare objects in this manner. Note this object has a type (the class name), and a name (that we will use to reference it in our code).
  3. On the third line, we wrote "integer randomInt = random:RandomIntegerBetween(1, 10)." This line has two different parts:
    • The first part is "integer randomInt." This creates a container (or variable) that holds a specfic value. Variables have two parts, a type(integer) and a name(randomInt). The "=" sign is an operator that assigns the expression on the right side to the expression on the left side. We will explore "types, variables and values" in more detail in lesson 4.
    • The second part of this line of code is the focus of this lesson, "random:RandomIntegerBetween(1, 10)." The object we created on line 2, "random," is accessing (or calling) the action "RandomIntegerBetween()" from the Random class with the parameters of 1 and 10. When this line of code is executed it will return a random integer between 1 and 10 by accessing an action that has already been written to acheive this goal in the Random class.
    • When these two parts are combined, the random integer between 1 and 10 is returned and then assigned to the variable "randomInt."
  4. The last line outputs (prints) the random integer "value" that the variable "randomInt" contains to the output window.

Activity

Objects are useful in programming because they allow programmers to create many objects from one class. In this lesson and the next lesson, students will use classes to instantiate several "objects." Students will use these "objects" to create a program that will turn their computer keyboard into a musical keyboard.

Student Instructions:

For our programming project we will:

  1. Create "Drawable" square-shaped objects to represent each key.
  2. Assign a "Color" to each key.
  3. Assign a specific "Audio" tone to each key.
  4. Make each key turn (rotate) each time the user presses a key.
  5. We will assign KeyboardEvent elements to all of the keys so that the computer makes a note sound and rotates the key when the user presses the associated key on the keyboard.

A sample screen image is shown below:

A picture showing 6 squares with different colors. The squares are laid out in a horizontal pane with a little space in-between them.

Now that you understand what we are aiming to create in this lesson, lets get started with our first step - creating the "Drawable" objects and loading them to the screen.

Step 1: Creating the Keys

In this first step, we begin by creating five keys that will play the notes C, D, E, F, G and A individually. If you are familiar with piano keys, these are the white keys in the middle area as shown in the picture below:

A picture showing the letters C, D, E, F, G, A on the corresponding keys on a section of a piano keyboard.

Instantiating an "Object" from the "Drawable" Class

We will begin by creating a square-shaped "object" that will represent a single piano key in our program. We create this square-shaped "object" using the Drawable class. Creating an "object" that inherits properties and functionality from a specific class is referred to as "Instantiation." The first thing we need to do to make this possible is to add the appropriate "use statement" at the beginning of our program. Since we are creating Drawable objects in this step, we will add the "Libraries.Game.Graphics.Drawable" class to our program. This will allow us to create or "Instantiate" Drawable objects in our program. Using the provided template to get started, go ahead and add the Drawable class at the beginning of your own project now.

Creating the Keyboard

The Drawable class is essential for placing graphics in your program. In a way, an object from the Drawable class works like an empty drawing pad. You will need to instantiate a separate Drawable object for each graphic element (piano key) and give each object you instantiate a unique and identifiable name. Using easily identifiable names for the objects you create will make your code much easier to read and debug. We recommend that you use a lower case letter to start the name of an object. For example "keyC" instead of "KeyC." Typically we reserve expressions that start with an Uppercase letter for naming Actions. To learn more about the naming conventions in Quorum Language, you can visit - Tutorial: Quorum Code Naming Conventions.

When you instantiate an object from a class, you will use the following syntax ClassName objectName. Instantiate your own Drawable objects for each of your piano keys in the provided template under the "class Main is Game" section. Below is an example of instantiating one Drawable object, named keyC, that will represent the "C" key on our piano.
Drawable keyC

Screen Dimension

The "Screen Dimension" or "Screen Size" is normally expressed by its width and height in pixels. As we learned in Unit 2, a pixel (short for "Picture Element") is a tiny square or dot which contains a single point of color of a larger image. The Quorum Game/Visual screen has an 800 pixel width and a 600 pixel height. To display an image on this screen, we need to use simple Mathematics to evaluate if an image fits on the screen, and then estimate how large the image needs to be in comparison to the entire screen size and where the image should be placed within the display screen dimensions.

Pro Tip

Think of the upper-right quadrant of an X-Y graph in Mathematics. The screen coordinate of (0, 0) - where the value of X (width) is 0 and the value of Y (height) is 0 - is the lower-left corner of the screen. From that point, the value of X increases to the right and the value of Y increases upward. Therefore the (X, Y) coordinate of the lower-right corner is (800, 0), the upper-right corner is (800, 600), and upper-left corner is (0, 600).

A picture showing the blank online screen with '800 pixels wide' written horizontally a the bottom side, and '600 pixels tall' along the left side of the Quorum online IDE's output scren area.

In the following section, we will learn how to use this (X, Y) coordinate system to set the size of our graphics and then load them to the "Visual Output" screen.

Loading and Displaying a Drawable object

To load an image or graphic to the Drawable object, we will need to call the action "LoadFilledRectangle(x-width, y-height)" on the object "keyC" that you instantiated from the Drawable class in the previous step keyC:LoadFilledRectangle(30, 30). The parameters for this action will be the width followed by the height in pixels. Now that we have Loaded our image/graphic to the Drawable object, we can Display the object using the Add(keyC) command. The Load and Display actions are called within the "action CreateGame" block as shown below.

use Libraries.Game.Game
use Libraries.Game.Graphics.Drawable

class Main is Game
   Drawable keyC

   action Main
       StartGame()
   end

   action CreateGame
       keyC:LoadFilledRectangle(30, 30)
       Add(keyC)
   end

   action Update(number seconds)
   end
end

"Run, Test, Debug!"

Programmers often write their code section by section, and then test/run each section to make sure that the section is achieving the desired behavior. If we try running the code as it is now, we should see a small black rectangular shape (30 pixels wide and 30 pixels high) in the lower left corner of the gray screen.

Loading an Image to the Screen

Run, Test, Debug!

Step 2: Adding Color and Setting the Position for a Drawable object

When we ran our code in the previous step the Drawable object was filled with black color and displayed in the lower left hand corner, at position (0, 0) by default.

We can set (choose) where our Drawable object will be displayed on the "Visual Output" screen by calling the SetPosition(X, Y) action on our Drawable object "keyC" before we "Add" the image-loaded object to the screen. For example when setting the position for "keyC" we can use the following line of code: keyC:SetPosition(100, 200)

Pro Tip: Screen Dimensions

Keep the Screen Dimensions (800 x 600) in mind when setting the position for your Drawable object and note that the lower left corner of the Drawable object is used as a reference point when Adding an image to the screen. Therefor, setting the Position to (800, 600) will Add the Drawable object outside of the "Visual Output" screen dimensions.

To add a color to the rectangle image, we need to add the use Libraries.Game.Graphics.Color line at the top of our program. "Using" the Color class will allow us to instantiate a "Color" object. When using multiple colors in your program it is best to start by instantiating a general Color object "color" first, and then instantiate specific Color objects (eg. "red", "blue") which will use the general color object to load a specific color as shown in the example below:

Color color
Color red = color:Red()
Color blue = color:Blue()

We can then add the color to the Drawable object by adding a third parameter to the existing "LoadFilledRectangle()" action. For example: keyC:LoadFilledRectangle(30, 30, red)

So far your code should look similar to this:

use Libraries.Game.Game
use Libraries.Game.Graphics.Drawable
use Libraries.Game.Graphics.Color

class Main is Game
   // Instatiating Drawable objects
   Drawable keyC

   // Instantiating Color objects
   Color color
   Color red = color:Red()
   Color blue = color:Blue()

   // Instatiating Audio objects

   action Main
       StartGame()
   end

   action CreateGame
       //Loading and adding the images to the screen
       keyC:LoadFilledRectangle(30, 30, red)
       keyC:SetPosition(100, 200)
       Add(keyC)

       //Loading audio files to the audio objects

   end

   action Update(number seconds)
   end
end

Try running your code to make sure that everything is working as you would expect.

Setting the position and assigning a color to the Drawable object

Run, Test, Debug!

Complete the Visual Keyboard

Using what we have learned so far, finish building the Visual Keyboard. You will need to:

Pro Tip: Visual Elements

If you do not have the benefit of seeing the screen, it is recommended that you have someone check your screen after you successfully run your program. The visual elements of a screen based program are challenging but essential for this type of programming. You will need to do a lot of mental picturing and mathematics so that your image-loaded objects are not overlapping. If you do not have a person to check your screen, just aim for avoiding error messages at this point.

Setting the initial visual environment for the project

You can copy and paste the code from the previous section, then add the rest of your code to it.

Step 3: Loading and Adding Audio objects

When using Audio in our program we will need to add the use Libraries.Sound.Audioline at the beginning of the code and instantiate an "Audio object" for each note Audio noteC. After our Audio object has been instantiated we can load an audio file to the object in the "action CreateGame" section. In order for us to "Load" an audio file to an Audio object, we will use the "Load("location/address of audio file")" action on the Audio object. The location/address is a text value and must be enclosed in double quotes within the parentheses. You can use the following audio file locations (file paths) for loading audio to your Audio objects.For example to Load the Audio for the "C" note we would type noteC:Load("/media/code/Guitar-C.ogg").

Load the Audio Files for your Musical Keyboard

Finish adding and loading the rest of the Audio for your Musical Keyboard. You will need to:

Loading and Adding Audio

Copy and Paste the code you have been working on, then add the code for the Audio.

The Musical Keyboard program should not be playing any Audio at this point. We have only loaded the Audio to the Drawable object, now we need to tell the Audio to Play. We do not want to "Add" the audio-loaded object to the project as we did for the Drawable object because we do not want to continuously listen to the audio every time we start the program. Rather, we would like the user to determine when the Audio starts and stops playing using "KeyboardEvents" to trigger the audio files to play or stop playing.

We will use the following template with the added "KeyboardListener" and "PressedKey() / ReleasedKey()" actions which will allow us to add Keyboard Events to the Musical Keyboard. These Keyboard Events will trigger the note to Play when the associated keyboard key is "Pressed" and stop the audio when the associated keyboard key is "Released." In this example we are using the following keyboard keys to control the corresponding Audio notes:

use Libraries.Game.Game
use Libraries.Sound.Audio
use Libraries.Interface.Events.KeyboardEvent
use Libraries.Interface.Events.KeyboardListener

class Main is Game, KeyboardListener
   // Instatiating Audio objects
   Audio noteC

   // Instantiating Color objects

   action Main
       StartGame()
   end

   action CreateGame
       //Loading the Audio files to the Audio objects to be played later
       noteC:Load("media/code/Guitor-C.ogg")

       //Making the project to 'listen for' any keyboard input from the user

       AddKeyboardListener(me)

   end

   action Update(number seconds)
   end

   action PressedKey(KeyboardEvent event)
       if event:keyCode = event:A
           noteC:Play()

       elseif event:keyCode = event:S
   
       elseif event:keyCode = event:D
   
       elseif event:keyCode = event:F
   
       elseif event:keyCode = event:G
   
       elseif event:keyCode = event:H
   
       end
   end

   action ReleasedKey(KeyboardEvent event)
       if event:keyCode = event:A
           noteC:Stop()

       elseif event:keyCode = event:S
   
       elseif event:keyCode = event:D
   
       elseif event:keyCode = event:F
   
       elseif event:keyCode = event:G
   
       elseif event:keyCode = event:H
   
       end
   end

end

Step 4: Adding Keyboard Events

We will use elements of "Event Driven Programming" in the same way we learned in Lesson 1. Begin by adding the "KeyboardListener" and "KeyboardEvent" libraries at the beginning of the program. To make this program a "Keyboard Event Driven Program" add KeyboardListener to the "class Main is Game" line: class Main is Game, KeyboardListener and add the AddKeyboardListener(me) line at the bottom of the "class Main is Game, KeyboardListener" section to set the Keyboard to "listen" for Keyboard events such as Pressing a key or Releasing a key to occur. The action PressedKey() and action ReleasedKey() action blocks will control the "Event" that occurs in response to a key Press or key Release and will need to be added to the bottom of the program, before the final end statement. These actions will contain several "if-statement" structures which will be described in detail later in this Unit. For this lesson we have included the "if-statement" structure blocks in the template for you to use. Each key Press and key Release will have its own if-statement code block. For example in the following code block "if" the "A" key on the keyboard is "Pressed," then the noteC Audio object will "Play."
action PressedKey(KeyboardEvent event)
   if event:keyCode = event:A
       noteC:Play()

Make the Musical Keyboard "Event Driven"

Finish adding Keyboard Events for each Audio object (note) to Play when the associated key on the keyboard is "Pressed" and for each Audio object (note) to Stop when the associated key on the keyboard is "Released." You can copy and paste the code sections you have been working on into the provided IDE below before finishing the "PressedKey()" and "ReleasedKey()" action blocks.

Add Audio Keyboard Events

Copy and Paste the code you have been working on, then add keyboard events that trigger the Audio to Play when the associated key is Pressed and Stop the Audio when the associated key is Released.

Step 5: Adding Event-Driven Rotation to the Drawable objects

We will use the action Update(number seconds)section to give movement to our Drawable objects (piano keys). The movement of the Drawable object on the screen is not actually a "movement," but a change in the position of the object which is continuously Updated using the "Update" action.

When we write code for movement to occur in the "action Update()" section, we are telling the computer to draw a slightly different picture with each "update." Computers today can draw a new image on the screen so rapidly - 60 times per second or more - that the image or graphic appears to be moving. Modern video games are all made possible using this same basic principle. Programmers often use mathematics and an "update loop" to create "movement." The "update loop" is very similar to the concept of "repeat" which we learned about in Unit 3. The update loop occurs within the "action Update()" section. When you run the program, the code written inside the "action Update()" section will be executed by your computer every 60th of a second or more - depending on the capability of your computer system.

To add Rotation to our keyboard event we start by creating an integer variable "rotateC" and setting it to zero. Variables will be discussed in further detail later in this unit, for now all we need to know is that this "variable" will be used to control whether the Drawable object is "rotating" (key pressed) or "not rotating" (key released). We create and set this variable as shown in the example below:
class Main is Game, KeyboardListener

   integer rotateC = 0

   action Main
       StartGame()
   end

Next we will add the "Rotate(degrees)" action to our Drawable object in the "action Update()" section which will rotate our "keyC" Drawable object 360 degrees per second when rotateC = 1.
action Update(number seconds)
   keyC:Rotate(360*seconds*rotateC)
end

Since we have set the variable "rotateC" to 0, the parameter for the Rotate action becomes 0 as well (anything multiplied by 0 equals 0), so by default the Drawable object is not going to appear to be moving. The change in "rotateC's" value will change the parameter for this action causing the Drawable object to appear to be moving. To do this we will change "rotateC's" value to 1 when the associated key is being pressed, (in the PressedKey() action) and change the value of "rotateC" to 0 when the associated key is released, (in the ReleasedKey() action) as shown below:
   action PressedKey(KeyboardEvent event)
       if event:keyCode = event:A
           noteC:Play()
           rotateC = 1

       elseif event:keyCode = event:S

       elseif event:keyCode = event:D

       elseif event:keyCode = event:F

       elseif event:keyCode = event:G

       elseif event:keyCode = event:H

       end
   end

   action ReleasedKey(KeyboardEvent event)
       if event:keyCode = event:A
           noteC:Stop()
           rotateC = 0

       elseif event:keyCode = event:S

       elseif event:keyCode = event:D

       elseif event:keyCode = event:F

       elseif event:keyCode = event:G

       elseif event:keyCode = event:H

       end
   end

Add Event Driven Rotation to the Drawable Objects

Finish adding Event Driven rotation to the rest of the piano keys. You will need to:

  1. Copy and paste the code you have been working on into the IDE below.
  2. Create (declare) a variable for controlling the rotation of each Drawable object (rotateC, rotateD, rotateE, etc.) and set (initialize) the variable to 0 so that the Drawable object is not rotating by default.
  3. Call the "Rotate(degree)" action on all of the Drawable objects in the "action Update()" section. Be sure to use the equation in the previous section which allows us to control the rotation by changing the value of our rotate variable to 0 (off) or 1 (on).
  4. Write the code for each Key Press in the associated if-statement block (eg. rotateC = 1) within the "action PressedKey()" section, and write the code for each Key Release in the associated if-statement block (eg. rotateC = 0) within the "action ReleasedKey()" section.

Add Visual Keyboard Events

Copy and paste the code you have been working on, then add the code to trigger the piano key to Rotate when the associated key is Pressed and Stop the Rotation when the associated key is Released.

Wrap Up

This lesson is one of the first times students will likely have consistently generated and responded to error messages. Students may (incorrectly) view error messages as "bad," when in reality they are an entirely normal part of programming and extremely useful when debugging code.

In fact, logical error messages, which can be "silent" and don't generate error messages are much worse, since they are much harder to catch. Use this early moment to normalize getting error messages and needing to debug code.

Prompt: Today was one of the first times we have seen error messages in our programs and had to start thinking about debugging our code. Is it "bad" to generate an error message? Will every error in our programs generate an error? Why might a programmer actually "like" to get an error message?

Discuss: Give students an opportunity to share their thoughts, either in small groups or as a class. Points that might come up:

We're making a big deal out of error messages and debugging because they are often hurdles for new learners. You just need to have the right attitude about writing code - debugging is part of the process. You will get used to a pattern of:

If you follow this pattern when writing programs, the errors you make will tend to be smaller and easier to catch.

Assessment

  1. Which of the following statements about debugging and program errors is FALSE?
    • Error messages help programmers identify problems in their code.
    • Not all errors in a program will generate an error message.
    • Debugging is the process of locating and correcting errors in a program.
    • It is common for programs to contain errors the first time they are written.
    • A program that does not generate any error messages can be assumed to run as intended.
  2. The instantiated objects in your Musical Keyboard program were all created with unique names or "identifiers." Given what you know about how event handlers work, why is it important for each of these "identifiers" to be unique?

Standards Alignment