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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
|
class FlappyDragon
attr_accessor :grid, :inputs, :state, :outputs
def tick
defaults
render
calc
process_inputs
end
def defaults
state.flap_power = 11
state.gravity = 0.9
state.ceiling = 600
state.ceiling_flap_power = 6
state.wall_countdown_length = 100
state.wall_gap_size = 100
state.wall_countdown ||= 0
state.hi_score ||= 0
state.score ||= 0
state.walls ||= []
state.x ||= 50
state.y ||= 500
state.dy ||= 0
state.scene ||= :menu
state.scene_at ||= 0
state.difficulty ||= :normal
state.new_difficulty ||= :normal
state.countdown ||= 4.seconds
state.flash_at ||= 0
end
def render
outputs.sounds << "sounds/flappy-song.ogg" if state.tick_count == 1
render_score
render_menu
render_game
end
def render_score
outputs.primitives << { x: 10, y: 710, text: "HI SCORE: #{state.hi_score}", **large_white_typeset }
outputs.primitives << { x: 10, y: 680, text: "SCORE: #{state.score}", **large_white_typeset }
outputs.primitives << { x: 10, y: 650, text: "DIFFICULTY: #{state.difficulty.upcase}", **large_white_typeset }
end
def render_menu
return unless state.scene == :menu
render_overlay
outputs.labels << { x: 640, y: 700, text: "Flappy Dragon", size_enum: 50, alignment_enum: 1, **white }
outputs.labels << { x: 640, y: 500, text: "Instructions: Press Spacebar to flap. Don't die.", size_enum: 4, alignment_enum: 1, **white }
outputs.labels << { x: 430, y: 430, text: "[Tab] Change difficulty", size_enum: 4, alignment_enum: 0, **white }
outputs.labels << { x: 430, y: 400, text: "[Enter] Start at New Difficulty ", size_enum: 4, alignment_enum: 0, **white }
outputs.labels << { x: 430, y: 370, text: "[Escape] Cancel/Resume ", size_enum: 4, alignment_enum: 0, **white }
outputs.labels << { x: 640, y: 300, text: "(mouse, touch, and game controllers work, too!) ", size_enum: 4, alignment_enum: 1, **white }
outputs.labels << { x: 640, y: 200, text: "Difficulty: #{state.new_difficulty.capitalize}", size_enum: 4, alignment_enum: 1, **white }
outputs.labels << { x: 10, y: 100, text: "Code: @amirrajan", **white }
outputs.labels << { x: 10, y: 80, text: "Art: @mobypixel", **white }
outputs.labels << { x: 10, y: 60, text: "Music: @mobypixel", **white }
outputs.labels << { x: 10, y: 40, text: "Engine: DragonRuby GTK", **white }
end
def render_overlay
overlay_rect = grid.rect.scale_rect(1.1, 0, 0)
outputs.primitives << { x: overlay_rect.x,
y: overlay_rect.y,
w: overlay_rect.w,
h: overlay_rect.h,
r: 0, g: 0, b: 0, a: 230 }.solid!
end
def render_game
render_game_over
render_background
render_walls
render_dragon
render_flash
end
def render_game_over
return unless state.scene == :game
outputs.labels << { x: 638, y: 358, text: score_text, size_enum: 20, alignment_enum: 1 }
outputs.labels << { x: 635, y: 360, text: score_text, size_enum: 20, alignment_enum: 1, r: 255, g: 255, b: 255 }
outputs.labels << { x: 638, y: 428, text: countdown_text, size_enum: 20, alignment_enum: 1 }
outputs.labels << { x: 635, y: 430, text: countdown_text, size_enum: 20, alignment_enum: 1, r: 255, g: 255, b: 255 }
end
def render_background
outputs.sprites << { x: 0, y: 0, w: 1280, h: 720, path: 'sprites/background.png' }
scroll_point_at = state.tick_count
scroll_point_at = state.scene_at if state.scene == :menu
scroll_point_at = state.death_at if state.countdown > 0
scroll_point_at ||= 0
outputs.sprites << scrolling_background(scroll_point_at, 'sprites/parallax_back.png', 0.25)
outputs.sprites << scrolling_background(scroll_point_at, 'sprites/parallax_middle.png', 0.50)
outputs.sprites << scrolling_background(scroll_point_at, 'sprites/parallax_front.png', 1.00, -80)
end
def scrolling_background at, path, rate, y = 0
[
{ x: 0 - at.*(rate) % 1440, y: y, w: 1440, h: 720, path: path },
{ x: 1440 - at.*(rate) % 1440, y: y, w: 1440, h: 720, path: path }
]
end
def render_walls
state.walls.each do |w|
w.sprites = [
{ x: w.x, y: w.bottom_height - 720, w: 100, h: 720, path: 'sprites/wall.png', angle: 180 },
{ x: w.x, y: w.top_y, w: 100, h: 720, path: 'sprites/wallbottom.png', angle: 0 }
]
end
outputs.sprites << state.walls.map(&:sprites)
end
def render_dragon
state.show_death = true if state.countdown == 3.seconds
if state.show_death == false || !state.death_at
animation_index = state.flapped_at.frame_index 6, 2, false if state.flapped_at
sprite_name = "sprites/dragon_fly#{animation_index.or(0) + 1}.png"
state.dragon_sprite = { x: state.x, y: state.y, w: 100, h: 80, path: sprite_name, angle: state.dy * 1.2 }
else
sprite_name = "sprites/dragon_die.png"
state.dragon_sprite = { x: state.x, y: state.y, w: 100, h: 80, path: sprite_name, angle: state.dy * 1.2 }
sprite_changed_elapsed = state.death_at.elapsed_time - 1.seconds
state.dragon_sprite.angle += (sprite_changed_elapsed ** 1.3) * state.death_fall_direction * -1
state.dragon_sprite.x += (sprite_changed_elapsed ** 1.2) * state.death_fall_direction
state.dragon_sprite.y += (sprite_changed_elapsed * 14 - sprite_changed_elapsed ** 1.6)
end
outputs.sprites << state.dragon_sprite
end
def render_flash
return unless state.flash_at
outputs.primitives << { **grid.rect.to_hash,
**white,
a: 255 * state.flash_at.ease(20, :flip) }.solid!
state.flash_at = 0 if state.flash_at.elapsed_time > 20
end
def calc
return unless state.scene == :game
reset_game if state.countdown == 1
state.countdown -= 1 and return if state.countdown > 0
calc_walls
calc_flap
calc_game_over
end
def calc_walls
state.walls.each { |w| w.x -= 8 }
walls_count_before_removal = state.walls.length
state.walls.reject! { |w| w.x < -100 }
state.score += 1 if state.walls.count < walls_count_before_removal
state.wall_countdown -= 1 and return if state.wall_countdown > 0
state.walls << state.new_entity(:wall) do |w|
w.x = grid.right
w.opening = grid.top
.randomize(:ratio)
.greater(200)
.lesser(520)
w.bottom_height = w.opening - state.wall_gap_size
w.top_y = w.opening + state.wall_gap_size
end
state.wall_countdown = state.wall_countdown_length
end
def calc_flap
state.y += state.dy
state.dy = state.dy.lesser state.flap_power
state.dy -= state.gravity
return if state.y < state.ceiling
state.y = state.ceiling
state.dy = state.dy.lesser state.ceiling_flap_power
end
def calc_game_over
return unless game_over?
state.death_at = state.tick_count
state.death_from = state.walls.first
state.death_fall_direction = -1
state.death_fall_direction = 1 if state.x > state.death_from.x
outputs.sounds << "sounds/hit-sound.wav"
begin_countdown
end
def process_inputs
process_inputs_menu
process_inputs_game
end
def process_inputs_menu
return unless state.scene == :menu
changediff = inputs.keyboard.key_down.tab || inputs.controller_one.key_down.select
if inputs.mouse.click
p = inputs.mouse.click.point
if (p.y >= 165) && (p.y < 200) && (p.x >= 500) && (p.x < 800)
changediff = true
end
end
if changediff
case state.new_difficulty
when :easy
state.new_difficulty = :normal
when :normal
state.new_difficulty = :hard
when :hard
state.new_difficulty = :flappy
when :flappy
state.new_difficulty = :easy
end
end
if inputs.keyboard.key_down.enter || inputs.controller_one.key_down.start || inputs.controller_one.key_down.a
state.difficulty = state.new_difficulty
change_to_scene :game
reset_game false
state.hi_score = 0
begin_countdown
end
if inputs.keyboard.key_down.escape || (inputs.mouse.click && !changediff) || inputs.controller_one.key_down.b
state.new_difficulty = state.difficulty
change_to_scene :game
end
end
def process_inputs_game
return unless state.scene == :game
clicked_menu = false
if inputs.mouse.click
p = inputs.mouse.click.point
clicked_menu = (p.y >= 620) && (p.x < 275)
end
if clicked_menu || inputs.keyboard.key_down.escape || inputs.keyboard.key_down.enter || inputs.controller_one.key_down.start
change_to_scene :menu
elsif (inputs.mouse.down || inputs.mouse.click || inputs.keyboard.key_down.space || inputs.controller_one.key_down.a) && state.countdown == 0
state.dy = 0
state.dy += state.flap_power
state.flapped_at = state.tick_count
outputs.sounds << "sounds/fly-sound.wav"
end
end
def white
{ r: 255, g: 255, b: 255 }
end
def large_white_typeset
{ size_enum: 5, alignment_enum: 0, r: 255, g: 255, b: 255 }
end
def at_beginning?
state.walls.count == 0
end
def dragon_collision_box
state.dragon_sprite
.scale_rect(1.0 - collision_forgiveness, 0.5, 0.5)
.rect_shift_right(10)
.rect_shift_up(state.dy * 2)
end
def game_over?
return true if state.y <= 0.-(500 * collision_forgiveness) && !at_beginning?
state.walls
.flat_map { |w| w.sprites }
.any? do |s|
s && s.intersect_rect?(dragon_collision_box)
end
end
def collision_forgiveness
case state.difficulty
when :easy
0.9
when :normal
0.7
when :hard
0.5
when :flappy
0.3
else
0.9
end
end
def countdown_text
state.countdown ||= -1
return "" if state.countdown == 0
return "GO!" if state.countdown.idiv(60) == 0
return "GAME OVER" if state.death_at
return "READY?"
end
def begin_countdown
state.countdown = 4.seconds
end
def score_text
return "" unless state.countdown > 1.seconds
return "" unless state.death_at
return "SCORE: 0 (LOL)" if state.score == 0
return "HI SCORE: #{state.score}" if state.score == state.hi_score
return "SCORE: #{state.score}"
end
def reset_game set_flash = true
state.flash_at = state.tick_count if set_flash
state.walls = []
state.y = 500
state.dy = 0
state.hi_score = state.hi_score.greater(state.score)
state.score = 0
state.wall_countdown = state.wall_countdown_length.fdiv(2)
state.show_death = false
state.death_at = nil
end
def change_to_scene scene
state.scene = scene
state.scene_at = state.tick_count
inputs.keyboard.clear
inputs.controller_one.clear
end
end
$flappy_dragon = FlappyDragon.new
def tick args
$flappy_dragon.grid = args.grid
$flappy_dragon.inputs = args.inputs
$flappy_dragon.state = args.state
$flappy_dragon.outputs = args.outputs
$flappy_dragon.tick
end
|