summaryrefslogtreecommitdiffhomepage
path: root/samples/99_genre_platformer/gorillas_basic/app/main.rb
blob: 53f9a4fcbdc7e2a9ea8c078d31cdd63966ae61bc (plain)
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
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
class YouSoBasicGorillas
  attr_accessor :outputs, :grid, :state, :inputs

  def tick
    defaults
    render
    calc
    process_inputs
  end

  def defaults
    outputs.background_color = [33, 32, 87]
    state.building_spacing       = 1
    state.building_room_spacing  = 15
    state.building_room_width    = 10
    state.building_room_height   = 15
    state.building_heights       = [4, 4, 6, 8, 15, 20, 18]
    state.building_room_sizes    = [5, 4, 6, 7]
    state.gravity                = 0.25
    state.first_strike         ||= :player_1
    state.buildings            ||= []
    state.holes                ||= []
    state.player_1_score       ||= 0
    state.player_2_score       ||= 0
    state.wind                 ||= 0
  end

  def render
    render_stage
    render_value_insertion
    render_gorillas
    render_holes
    render_banana
    render_game_over
    render_score
    render_wind
  end

  def render_score
    outputs.primitives << [0, 0, 1280, 31, fancy_white].solid
    outputs.primitives << [1, 1, 1279, 29].solid
    outputs.labels << [  10, 25, "Score: #{state.player_1_score}", 0, 0, fancy_white]
    outputs.labels << [1270, 25, "Score: #{state.player_2_score}", 0, 2, fancy_white]
  end

  def render_wind
    outputs.primitives << [640, 12, state.wind * 500 + state.wind * 10 * rand, 4, 35, 136, 162].solid
    outputs.lines     <<  [640, 30, 640, 0, fancy_white]
  end

  def render_game_over
    return unless state.over
    outputs.primitives << [grid.rect, 0, 0, 0, 200].solid
    outputs.primitives << [640, 370, "Game Over!!", 5, 1, fancy_white].label
    if state.winner == :player_1
      outputs.primitives << [640, 340, "Player 1 Wins!!", 5, 1, fancy_white].label
    else
      outputs.primitives << [640, 340, "Player 2 Wins!!", 5, 1, fancy_white].label
    end
  end

  def render_stage
    return unless state.stage_generated
    return if state.stage_rendered

    outputs.static_solids << [grid.rect, 33, 32, 87]
    outputs.static_solids << state.buildings.map(&:solids)
    state.stage_rendered = true
  end

  def render_gorilla gorilla, id
    return unless gorilla
    if state.banana && state.banana.owner == gorilla
      animation_index  = state.banana.created_at.frame_index(3, 5, false)
    end
    if !animation_index
      outputs.sprites << [gorilla.solid, "sprites/#{id}-idle.png"]
    else
      outputs.sprites << [gorilla.solid, "sprites/#{id}-#{animation_index}.png"]
    end
  end

  def render_gorillas
    render_gorilla state.player_1, :left
    render_gorilla state.player_2, :right
  end

  def render_value_insertion
    return if state.banana
    return if state.over

    if    state.current_turn == :player_1_angle
      outputs.labels << [  10, 710, "Angle:    #{state.player_1_angle}_",    fancy_white]
    elsif state.current_turn == :player_1_velocity
      outputs.labels << [  10, 710, "Angle:    #{state.player_1_angle}",     fancy_white]
      outputs.labels << [  10, 690, "Velocity: #{state.player_1_velocity}_", fancy_white]
    elsif state.current_turn == :player_2_angle
      outputs.labels << [1120, 710, "Angle:    #{state.player_2_angle}_",    fancy_white]
    elsif state.current_turn == :player_2_velocity
      outputs.labels << [1120, 710, "Angle:    #{state.player_2_angle}",     fancy_white]
      outputs.labels << [1120, 690, "Velocity: #{state.player_2_velocity}_", fancy_white]
    end
  end

  def render_banana
    return unless state.banana
    rotation = state.tick_count.%(360) * 20
    rotation *= -1 if state.banana.dx > 0
    outputs.sprites << [state.banana.x, state.banana.y, 15, 15, 'sprites/banana.png', rotation]
  end

  def render_holes
    outputs.sprites << state.holes.map do |s|
      animation_index = s.created_at.frame_index(7, 3, false)
      if animation_index
        [s.sprite, [s.sprite.rect, "sprites/explosion#{animation_index}.png" ]]
      else
        s.sprite
      end
    end
  end

  def calc
    calc_generate_stage
    calc_current_turn
    calc_banana
  end

  def calc_current_turn
    return if state.current_turn

    state.current_turn = :player_1_angle
    state.current_turn = :player_2_angle if state.first_strike == :player_2
  end

  def calc_generate_stage
    return if state.stage_generated

    state.buildings << building_prefab(state.building_spacing + -20, *random_building_size)
    8.numbers.inject(state.buildings) do |buildings, i|
      buildings <<
        building_prefab(state.building_spacing +
                        state.buildings.last.right,
                        *random_building_size)
    end

    building_two = state.buildings[1]
    state.player_1 = new_player(building_two.x + building_two.w.fdiv(2),
                               building_two.h)

    building_nine = state.buildings[-3]
    state.player_2 = new_player(building_nine.x + building_nine.w.fdiv(2),
                               building_nine.h)
    state.stage_generated = true
    state.wind = 1.randomize(:ratio, :sign)
  end

  def new_player x, y
    state.new_entity(:gorilla) do |p|
      p.x = x - 25
      p.y = y
      p.solid = [p.x, p.y, 50, 50]
    end
  end

  def calc_banana
    return unless state.banana

    state.banana.x  += state.banana.dx
    state.banana.dx += state.wind.fdiv(50)
    state.banana.y  += state.banana.dy
    state.banana.dy -= state.gravity
    banana_collision = [state.banana.x, state.banana.y, 10, 10]

    if state.player_1 && banana_collision.intersect_rect?(state.player_1.solid)
      state.over = true
      if state.banana.owner == state.player_2
        state.winner = :player_2
      else
        state.winner = :player_1
      end

      state.player_2_score += 1
    elsif state.player_2 && banana_collision.intersect_rect?(state.player_2.solid)
      state.over = true
      if state.banana.owner == state.player_2
        state.winner = :player_1
      else
        state.winner = :player_2
      end
      state.player_1_score += 1
    end

    if state.over
      place_hole
      return
    end

    return if state.holes.any? do |h|
      h.sprite.scale_rect(0.8, 0.5, 0.5).intersect_rect? [state.banana.x, state.banana.y, 10, 10]
    end

    return unless state.banana.y < 0 || state.buildings.any? do |b|
      b.rect.intersect_rect? [state.banana.x, state.banana.y, 1, 1]
    end

    place_hole
  end

  def place_hole
    return unless state.banana

    state.holes << state.new_entity(:banana) do |b|
      b.sprite = [state.banana.x - 20, state.banana.y - 20, 40, 40, 'sprites/hole.png']
    end

    state.banana = nil
  end

  def process_inputs_main
    return if state.banana
    return if state.over

    if inputs.keyboard.key_down.enter
      input_execute_turn
    elsif inputs.keyboard.key_down.backspace
      state.as_hash[state.current_turn] ||= ""
      state.as_hash[state.current_turn]   = state.as_hash[state.current_turn][0..-2]
    elsif inputs.keyboard.key_down.char
      state.as_hash[state.current_turn] ||= ""
      state.as_hash[state.current_turn]  += inputs.keyboard.key_down.char
    end
  end

  def process_inputs_game_over
    return unless state.over
    return unless inputs.keyboard.key_down.truthy_keys.any?
    state.over = false
    outputs.static_solids.clear
    state.buildings.clear
    state.holes.clear
    state.stage_generated = false
    state.stage_rendered = false
    if state.first_strike == :player_1
      state.first_strike = :player_2
    else
      state.first_strike = :player_1
    end
  end

  def process_inputs
    process_inputs_main
    process_inputs_game_over
  end

  def input_execute_turn
    return if state.banana

    if state.current_turn == :player_1_angle && parse_or_clear!(:player_1_angle)
      state.current_turn = :player_1_velocity
    elsif state.current_turn == :player_1_velocity && parse_or_clear!(:player_1_velocity)
      state.current_turn = :player_2_angle
      state.banana =
        new_banana(state.player_1,
                   state.player_1.x + 25,
                   state.player_1.y + 60,
                   state.player_1_angle,
                   state.player_1_velocity)
    elsif state.current_turn == :player_2_angle && parse_or_clear!(:player_2_angle)
      state.current_turn = :player_2_velocity
    elsif state.current_turn == :player_2_velocity && parse_or_clear!(:player_2_velocity)
      state.current_turn = :player_1_angle
      state.banana =
        new_banana(state.player_2,
                   state.player_2.x + 25,
                   state.player_2.y + 60,
                   180 - state.player_2_angle,
                   state.player_2_velocity)
    end

    if state.banana
      state.player_1_angle = nil
      state.player_1_velocity = nil
      state.player_2_angle = nil
      state.player_2_velocity = nil
    end
  end

  def random_building_size
    [state.building_heights.sample, state.building_room_sizes.sample]
  end

  def int? v
    v.to_i.to_s == v.to_s
  end

  def random_building_color
    [[ 99,   0, 107],
     [ 35,  64, 124],
     [ 35, 136, 162],
     ].sample
  end

  def random_window_color
    [[ 88,  62, 104],
     [253, 224, 187]].sample
  end

  def windows_for_building starting_x, floors, rooms
    floors.-(1).combinations(rooms - 1).map do |floor, room|
      [starting_x +
       state.building_room_width.*(room) +
       state.building_room_spacing.*(room + 1),
       state.building_room_height.*(floor) +
       state.building_room_spacing.*(floor + 1),
       state.building_room_width,
       state.building_room_height,
       random_window_color]
    end
  end

  def building_prefab starting_x, floors, rooms
    state.new_entity(:building) do |b|
      b.x      = starting_x
      b.y      = 0
      b.w      = state.building_room_width.*(rooms) +
                 state.building_room_spacing.*(rooms + 1)
      b.h      = state.building_room_height.*(floors) +
                 state.building_room_spacing.*(floors + 1)
      b.right  = b.x + b.w
      b.rect   = [b.x, b.y, b.w, b.h]
      b.solids = [[b.x - 1, b.y, b.w + 2, b.h + 1, fancy_white],
                  [b.x, b.y, b.w, b.h, random_building_color],
                  windows_for_building(b.x, floors, rooms)]
    end
  end

  def parse_or_clear! game_prop
    if int? state.as_hash[game_prop]
      state.as_hash[game_prop] = state.as_hash[game_prop].to_i
      return true
    end

    state.as_hash[game_prop] = nil
    return false
  end

  def new_banana owner, x, y, angle, velocity
    state.new_entity(:banana) do |b|
      b.owner     = owner
      b.x         = x
      b.y         = y
      b.angle     = angle % 360
      b.velocity  = velocity / 5
      b.dx        = b.angle.vector_x(b.velocity)
      b.dy        = b.angle.vector_y(b.velocity)
    end
  end

  def fancy_white
    [253, 252, 253]
  end
end

$you_so_basic_gorillas = YouSoBasicGorillas.new

def tick args
  $you_so_basic_gorillas.outputs = args.outputs
  $you_so_basic_gorillas.grid    = args.grid
  $you_so_basic_gorillas.state    = args.state
  $you_so_basic_gorillas.inputs  = args.inputs
  $you_so_basic_gorillas.tick
end