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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
|
=begin
APIs listing that haven't been encountered in previous sample apps:
- Hashes: Collection of unique keys and their corresponding values. The value can be found
using their keys.
For example, if we have a "numbers" hash that stores numbers in English as the
key and numbers in Spanish as the value, we'd have a hash that looks like this...
numbers = { "one" => "uno", "two" => "dos", "three" => "tres" }
and on it goes.
Now if we wanted to find the corresponding value of the "one" key, we could say
puts numbers["one"]
which would print "uno" to the console.
- num1.greater(num2): Returns the greater value.
For example, if we have the command
puts 4.greater(3)
the number 4 would be printed to the console since it has a greater value than 3.
Similar to lesser, which returns the lesser value.
- num1.lesser(num2): Finds the lower value of the given options.
For example, in the statement
a = 4.lesser(3)
3 has a lower value than 4, which means that the value of a would be set to 3,
but if the statement had been
a = 4.lesser(5)
4 has a lower value than 5, which means that the value of a would be set to 4.
- reject: Removes elements from a collection if they meet certain requirements.
For example, you can derive an array of odd numbers from an original array of
numbers 1 through 10 by rejecting all elements that are even (or divisible by 2).
- find_all: Finds all values that satisfy specific requirements.
For example, you can find all elements of a collection that are divisible by 2
or find all objects that have intersected with another object.
- abs: Returns the absolute value.
For example, the command
(-30).abs
would return 30 as a result.
- map: Ruby method used to transform data; used in arrays, hashes, and collections.
Can be used to perform an action on every element of a collection, such as multiplying
each element by 2 or declaring every element as a new entity.
Reminders:
- args.inputs.keyboard.KEY: Determines if a key has been pressed.
For more information about the keyboard, take a look at mygame/documentation/06-keyboard.md.
- ARRAY#intersect_rect?: Returns true or false depending on if the two rectangles intersect.
- args.outputs.solids: An array. The values generate a solid.
The parameters are [X, Y, WIDTH, HEIGHT, RED, GREEN, BLUE]
For more information about solids, go to mygame/documentation/03-solids-and-borders.md.
=end
# Calls methods needed for game to run properly
def tick args
tick_instructions args, "Use LEFT and RIGHT arrow keys to move and SPACE to jump."
defaults args
render args
calc args
input args
end
# sets default values and creates empty collections
# initialization only happens in the first frame
def defaults args
fiddle args
args.state.enemy.hammers ||= []
args.state.enemy.hammer_queue ||= []
args.state.tick_count = args.state.tick_count
args.state.bridge_top = 128
args.state.player.x ||= 0 # initializes player's properties
args.state.player.y ||= args.state.bridge_top
args.state.player.w ||= 64
args.state.player.h ||= 64
args.state.player.dy ||= 0
args.state.player.dx ||= 0
args.state.enemy.x ||= 800 # initializes enemy's properties
args.state.enemy.y ||= 0
args.state.enemy.w ||= 128
args.state.enemy.h ||= 128
args.state.enemy.dy ||= 0
args.state.enemy.dx ||= 0
args.state.game_over_at ||= 0
end
# sets enemy, player, hammer values
def fiddle args
args.state.gravity = -0.3
args.state.enemy_jump_power = 10 # sets enemy values
args.state.enemy_jump_interval = 60
args.state.hammer_throw_interval = 40 # sets hammer values
args.state.hammer_launch_power_default = 5
args.state.hammer_launch_power_near = 2
args.state.hammer_launch_power_far = 7
args.state.hammer_upward_launch_power = 15
args.state.max_hammers_per_volley = 10
args.state.gap_between_hammers = 10
args.state.player_jump_power = 10 # sets player values
args.state.player_jump_power_duration = 10
args.state.player_max_run_speed = 10
args.state.player_speed_slowdown_rate = 0.9
args.state.player_acceleration = 1
args.state.hammer_size = 32
end
# outputs objects onto the screen
def render args
args.outputs.solids << 20.map_with_index do |i| # uses 20 squares to form bridge
# sets x by multiplying 64 to index to find pixel value (places all squares side by side)
# subtracts 64 from bridge_top because position is denoted by bottom left corner
[i * 64, args.state.bridge_top - 64, 64, 64]
end
args.outputs.solids << [args.state.x, args.state.y, args.state.w, args.state.h, 255, 0, 0]
args.outputs.solids << [args.state.player.x, args.state.player.y, args.state.player.w, args.state.player.h, 255, 0, 0] # outputs player onto screen (red box)
args.outputs.solids << [args.state.enemy.x, args.state.enemy.y, args.state.enemy.w, args.state.enemy.h, 0, 255, 0] # outputs enemy onto screen (green box)
args.outputs.solids << args.state.enemy.hammers # outputs enemy's hammers onto screen
end
# Performs calculations to move objects on the screen
def calc args
# Since velocity is the change in position, the change in x increases by dx. Same with y and dy.
args.state.player.x += args.state.player.dx
args.state.player.y += args.state.player.dy
# Since acceleration is the change in velocity, the change in y (dy) increases every frame
args.state.player.dy += args.state.gravity
# player's y position is either current y position or y position of top of
# bridge, whichever has a greater value
# ensures that the player never goes below the bridge
args.state.player.y = args.state.player.y.greater(args.state.bridge_top)
# player's x position is either the current x position or 0, whichever has a greater value
# ensures that the player doesn't go too far left (out of the screen's scope)
args.state.player.x = args.state.player.x.greater(0)
# player is not falling if it is located on the top of the bridge
args.state.player.falling = false if args.state.player.y == args.state.bridge_top
args.state.player.rect = [args.state.player.x, args.state.player.y, args.state.player.h, args.state.player.w] # sets definition for player
args.state.enemy.x += args.state.enemy.dx # velocity; change in x increases by dx
args.state.enemy.y += args.state.enemy.dy # same with y and dy
# ensures that the enemy never goes below the bridge
args.state.enemy.y = args.state.enemy.y.greater(args.state.bridge_top)
# ensures that the enemy never goes too far left (outside the screen's scope)
args.state.enemy.x = args.state.enemy.x.greater(0)
# objects that go up must come down because of gravity
args.state.enemy.dy += args.state.gravity
args.state.enemy.y = args.state.enemy.y.greater(args.state.bridge_top)
#sets definition of enemy
args.state.enemy.rect = [args.state.enemy.x, args.state.enemy.y, args.state.enemy.h, args.state.enemy.w]
if args.state.enemy.y == args.state.bridge_top # if enemy is located on the top of the bridge
args.state.enemy.dy = 0 # there is no change in y
end
# if 60 frames have passed and the enemy is not moving vertically
if args.state.tick_count.mod_zero?(args.state.enemy_jump_interval) && args.state.enemy.dy == 0
args.state.enemy.dy = args.state.enemy_jump_power # the enemy jumps up
end
# if 40 frames have passed or 5 frames have passed since the game ended
if args.state.tick_count.mod_zero?(args.state.hammer_throw_interval) || args.state.game_over_at.elapsed_time == 5
# rand will return a number greater than or equal to 0 and less than given variable's value (since max is excluded)
# that is why we're adding 1, to include the max possibility
volley_dx = (rand(args.state.hammer_launch_power_default) + 1) * -1 # horizontal movement (follow order of operations)
# if the horizontal distance between the player and enemy is less than 128 pixels
if (args.state.player.x - args.state.enemy.x).abs < 128
# the change in x won't be that great since the enemy and player are closer to each other
volley_dx = (rand(args.state.hammer_launch_power_near) + 1) * -1
end
# if the horizontal distance between the player and enemy is greater than 300 pixels
if (args.state.player.x - args.state.enemy.x).abs > 300
# change in x will be more drastic since player and enemy are so far apart
volley_dx = (rand(args.state.hammer_launch_power_far) + 1) * -1 # more drastic change
end
(rand(args.state.max_hammers_per_volley) + 1).map_with_index do |i|
args.state.enemy.hammer_queue << { # stores hammer values in a hash
x: args.state.enemy.x,
w: args.state.hammer_size,
h: args.state.hammer_size,
dx: volley_dx, # change in horizontal position
# multiplication operator takes precedence over addition operator
throw_at: args.state.tick_count + i * args.state.gap_between_hammers
}
end
end
# add elements from hammer_queue collection to the hammers collection by
# finding all hammers that were thrown before the current frame (have already been thrown)
args.state.enemy.hammers += args.state.enemy.hammer_queue.find_all do |h|
h[:throw_at] < args.state.tick_count
end
args.state.enemy.hammers.each do |h| # sets values for all hammers in collection
h[:y] ||= args.state.enemy.y + 130
h[:dy] ||= args.state.hammer_upward_launch_power
h[:dy] += args.state.gravity # acceleration is change in gravity
h[:x] += h[:dx] # incremented by change in position
h[:y] += h[:dy]
h[:rect] = [h[:x], h[:y], h[:w], h[:h]] # sets definition of hammer's rect
end
# reject hammers that have been thrown before current frame (have already been thrown)
args.state.enemy.hammer_queue = args.state.enemy.hammer_queue.reject do |h|
h[:throw_at] < args.state.tick_count
end
# any hammers with a y position less than 0 are rejected from the hammers collection
# since they have gone too far down (outside the scope's screen)
args.state.enemy.hammers = args.state.enemy.hammers.reject { |h| h[:y] < 0 }
# if there are any hammers that intersect with (or hit) the player,
# the reset_player method is called (so the game can start over)
if args.state.enemy.hammers.any? { |h| h[:rect].intersect_rect?(args.state.player.rect) }
reset_player args
end
# if the enemy's rect intersects with (or hits) the player,
# the reset_player method is called (so the game can start over)
if args.state.enemy.rect.intersect_rect? args.state.player.rect
reset_player args
end
end
# Resets the player by changing its properties back to the values they had at initialization
def reset_player args
args.state.player.x = 0
args.state.player.y = args.state.bridge_top
args.state.player.dy = 0
args.state.player.dx = 0
args.state.enemy.hammers.clear # empties hammer collection
args.state.enemy.hammer_queue.clear # empties hammer_queue
args.state.game_over_at = args.state.tick_count # game_over_at set to current frame (or passage of time)
end
# Processes input from the user to move the player
def input args
if args.inputs.keyboard.space # if the user presses the space bar
args.state.player.jumped_at ||= args.state.tick_count # jumped_at is set to current frame
# if the time that has passed since the jump is less than the player's jump duration and
# the player is not falling
if args.state.player.jumped_at.elapsed_time < args.state.player_jump_power_duration && !args.state.player.falling
args.state.player.dy = args.state.player_jump_power # change in y is set to power of player's jump
end
end
# if the space bar is in the "up" state (or not being pressed down)
if args.inputs.keyboard.key_up.space
args.state.player.jumped_at = nil # jumped_at is empty
args.state.player.falling = true # the player is falling
end
if args.inputs.keyboard.left # if left key is pressed
args.state.player.dx -= args.state.player_acceleration # dx decreases by acceleration (player goes left)
# dx is either set to current dx or the negative max run speed (which would be -10),
# whichever has a greater value
args.state.player.dx = args.state.player.dx.greater(-args.state.player_max_run_speed)
elsif args.inputs.keyboard.right # if right key is pressed
args.state.player.dx += args.state.player_acceleration # dx increases by acceleration (player goes right)
# dx is either set to current dx or max run speed (which would be 10),
# whichever has a lesser value
args.state.player.dx = args.state.player.dx.lesser(args.state.player_max_run_speed)
else
args.state.player.dx *= args.state.player_speed_slowdown_rate # dx is scaled down
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.space ||
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
|