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.
Students will be able to:
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.
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
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.
For our programming project we will:
A sample screen image is shown below:
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.
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:
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.
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.
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.
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.
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).
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.
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
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.
Run, Test, Debug!
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:
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.Colorline 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:
We can then add the color to the Drawable object by adding a third parameter to the existing "LoadFilledRectangle()" action. For example:
Color color Color red = color:Red() Color blue = color:Blue()
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.
Run, Test, Debug!
Using what we have learned so far, finish building the Visual Keyboard. You will need to:
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.
You can copy and paste the code from the previous section, then add the rest of your code to it.
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.
Finish adding and loading the rest of the Audio for your Musical Keyboard. You will need to:
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
class Main is Game, KeyboardListenerand 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 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()
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.
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.
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
action Update(number seconds) keyC:Rotate(360*seconds*rotateC) end
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
Finish adding Event Driven rotation to the rest of the piano keys. You will need to:
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.
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.