1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
=begin
APIs Listing that haven't been encountered in previous sample apps:
- sample: Chooses random element from array.
In this sample app, the target note is set by taking a sample from the collection
of available notes.
Reminders:
- String interpolation: Uses #{} syntax; everything between the #{ and the } is evaluated
as Ruby code, and the placeholder is replaced with its corresponding value or result.
- args.outputs.labels: An array. The values generate a label.
The parameters are [X, Y, TEXT, SIZE, ALIGNMENT, RED, GREEN, BLUE, ALPHA, FONT STYLE]
- reject: Removes elements from a collection if they meet certain requirements.
- first: Returns the first element of an array.
- inside_rect: Returns true or false depending on if the point is inside the rect.
- to_sym: Returns symbol corresponding to string. Will create a symbol if it does
not already exist.
=end
# This sample app allows users to test their musical skills by matching the piano sound that plays in each
# level to the correct note.
# Runs all the methods necessary for the game to function properly.
def tick args
defaults args
render args
calc args
input_mouse args
tick_instructions args, "Sample app shows how to play sounds. args.outputs.sounds << \"path_to_wav.wav\""
end
# Sets default values and creates empty collections
# Initialization happens in the first frame only
def defaults _
_.state.notes ||= []
_.state.click_feedbacks ||= []
_.state.current_level ||= 1
_.state.times_wrong ||= 0 # when game starts, user hasn't guessed wrong yet
end
# Uses a label to display current level, and shows the score
# Creates a button to play the sample note, and displays the available notes that could be a potential match
def render _
# grid.w_half positions the label in the horizontal center of the screen.
_.outputs.labels << [_.grid.w_half, _.grid.top.shift_down(40), "Hole #{_.state.current_level} of 9", 0, 1, 0, 0, 0]
render_score _ # shows score on screen
if _.state.game_over # if game is over, a "play again" button is shown
# Calculations ensure that Play Again label is displayed in center of border
# Remove calculations from y parameters and see what happens to border and label placement
_.state.play_again_border ||= _.state.with_meta([560, _.grid.h * 3 / 4 - 40, 160, 60], 'again') # array definition, text/title
_.outputs.labels << [_.grid.w_half, _.grid.h * 3 / 4, "Play Again", 0, 1, 0, 0, 0] # outputs label
_.outputs.borders << _.state.play_again_border # outputs border
else # otherwise, if game is not over
# Calculations ensure that label appears in center of border
_.state.play_note_border ||= _.state.with_meta([560, _.grid.h * 3 / 4 - 40, 160, 60], 'play') # array definition, text/title
_.outputs.labels << [_.grid.w_half, _.grid.h * 3 / 4, "Play Note ##{_.state.current_level}", 0, 1, 0, 0, 0] # outputs label
_.outputs.borders << _.state.play_note_border # outputs border
end
return if _.state.game_over # return if game is over
_.outputs.labels << [_.grid.w_half, 400, "I think the note is a(n)...", 0, 1, 0, 0, 0] # outputs label
# Shows all of the available notes that can be potential matches.
available_notes.each_with_index do |note, i|
_.state.notes[i] ||= piano_button(_, note, i + 1) # calls piano_button method on each note (creates label and border)
_.outputs.labels << _.state.notes[i].label # outputs note on screen with a label and a border
_.outputs.borders << _.state.notes[i].border
end
# Shows whether or not the user is correct by filling the screen with either red or green
_.outputs.solids << _.state.click_feedbacks.map { |c| c.solid }
end
# Shows the score (number of times the user guesses wrong) onto the screen using labels.
def render_score _
if _.state.times_wrong == 0 # if the user has guessed wrong zero times, the score is par
_.outputs.labels << [_.grid.w_half, _.grid.top.shift_down(80), "Score: PAR", 0, 1, 0, 0, 0]
else # otherwise, number of times the user has guessed wrong is shown
_.outputs.labels << [_.grid.w_half, _.grid.top.shift_down(80), "Score: +#{_.state.times_wrong}", 0, 1, 0, 0, 0] # shows score using string interpolation
end
end
# Sets the target note for the level and performs calculations on click_feedbacks.
def calc _
_.state.target_note ||= available_notes.sample # chooses a note from available_notes collection as target note
_.state.click_feedbacks.each { |c| c.solid[-1] -= 5 } # remove this line and solid color will remain on screen indefinitely
# comment this line out and the solid color will keep flashing on screen instead of being removed from click_feedbacks collection
_.state.click_feedbacks.reject! { |c| c.solid[-1] <= 0 }
end
# Uses input from the user to play the target note, as well as the other notes that could be a potential match.
def input_mouse _
return unless _.inputs.mouse.click # return unless the mouse is clicked
# finds button that was clicked by user
button_clicked = _.outputs.borders.find_all do |b| # go through borders collection to find all borders that meet requirements
_.inputs.mouse.click.point.inside_rect? b # find button border that mouse was clicked inside of
end.reject {|b| !_.state.meta(b)}.first # reject, return first element
return unless button_clicked # return unless button_clicked as a value (a button was clicked)
queue_click_feedback _, # calls queue_click_feedback method on the button that was clicked
button_clicked.x,
button_clicked.y,
button_clicked.w,
button_clicked.h,
150, 100, 200 # sets color of button to shade of purple
if _.state.meta(button_clicked) == 'play' # if "play note" button is pressed
_.outputs.sounds << "sounds/#{_.state.target_note}.wav" # sound of target note is output
elsif _.state.meta(button_clicked) == 'again' # if "play game again" button is pressed
_.state.target_note = nil # no target note
_.state.current_level = 1 # starts at level 1 again
_.state.times_wrong = 0 # starts off with 0 wrong guesses
_.state.game_over = false # the game is not over (because it has just been restarted)
else # otherwise if neither of those buttons were pressed
_.outputs.sounds << "sounds/#{_.state.meta(button_clicked)}.wav" # sound of clicked note is played
if _.state.meta(button_clicked).to_sym == _.state.target_note # if clicked note is target note
_.state.target_note = nil # target note is emptied
if _.state.current_level < 9 # if game hasn't reached level 9
_.state.current_level += 1 # game goes to next level
else # otherwise, if game has reached level 9
_.state.game_over = true # the game is over
end
queue_click_feedback _, 0, 0, _.grid.w, _.grid.h, 100, 200, 100 # green shown if user guesses correctly
else # otherwise, if clicked note is not target note
_.state.times_wrong += 1 # increments times user guessed wrong
queue_click_feedback _, 0, 0, _.grid.w, _.grid.h, 200, 100, 100 # red shown is user guesses wrong
end
end
end
# Creates a collection of all of the available notes as symbols
def available_notes
[:C3, :D3, :E3, :F3, :G3, :A3, :B3, :C4]
end
# Creates buttons for each note, and sets a label (the note's name) and border for each note's button.
def piano_button _, note, position
_.state.new_entity(:button) do |b| # declares button as new entity
b.label = [460 + 40.mult(position), _.grid.h * 0.4, "#{note}", 0, 1, 0, 0, 0] # label definition
b.border = _.state.with_meta([460 + 40.mult(position) - 20, _.grid.h * 0.4 - 32, 40, 40], note) # border definition, text/title; 20 subtracted so label is in center of border
end
end
# Color of click feedback changes depending on what button was clicked, and whether the guess is right or wrong
# If a button is clicked, the inside of button is purple (see input_mouse method)
# If correct note is clicked, screen turns green
# If incorrect note is clicked, screen turns red (again, see input_mouse method)
def queue_click_feedback _, x, y, w, h, *color
_.state.click_feedbacks << _.state.new_entity(:click_feedback) do |c| # declares feedback as new entity
c.solid = [x, y, w, h, *color, 255] # sets color
end
end
def tick_instructions args, text, y = 715
return if args.state.key_event_occurred
if args.inputs.mouse.click ||
args.inputs.keyboard.directional_vector ||
args.inputs.keyboard.key_down.enter ||
args.inputs.keyboard.key_down.escape
args.state.key_event_occurred = true
end
args.outputs.debug << [0, y - 50, 1280, 60].solid
args.outputs.debug << [640, y, text, 1, 1, 255, 255, 255].label
args.outputs.debug << [640, y - 25, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label
end
|