summaryrefslogtreecommitdiffhomepage
path: root/samples/99_genre_platformer/clepto_frog/app/main.rb
blob: a4eb069c564a46391ee49f0947cab4ddb5100688 (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
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
MAP_FILE_PATH = 'app/map.txt'

require 'app/map.rb'

class CleptoFrog
  attr_gtk

  def render_ending
    state.game_over_at ||= state.tick_count

    outputs.labels << [640, 700, "Clepto Frog", 4, 1]

    if state.tick_count >= (state.game_over_at + 120)
      outputs.labels << [640, 620, "\"I... I.... don't believe it.\" - New Guy",
                         4, 1, 0, 0, 0, 255 * (state.game_over_at + 120).ease(60)]
    end

    if state.tick_count >= (state.game_over_at + 240)
      outputs.labels << [640, 580, "\"He actually stole all the mugs?\" - New Guy",
                         4, 1, 0, 0, 0, 255 * (state.game_over_at + 240).ease(60)]
    end

    if state.tick_count >= (state.game_over_at + 360)
      outputs.labels << [640, 540, "\"Kind of feel bad STARTING HIM WITH NOTHING again.\" - New Guy",
                         4, 1, 0, 0, 0, 255 * (state.game_over_at + 360).ease(60)]
    end

    outputs.sprites << [640 - 50, 360 - 50, 100, 100,
                        "sprites/square-green.png"]

    outputs.labels << [640, 300, "Current Time: #{"%.2f" % state.stuff_time}", 4, 1]
    outputs.labels << [640, 270, "Best Time: #{"%.2f" % state.stuff_best_time}", 4, 1]

    if state.tick_count >= (state.game_over_at + 550)
      restart_game
    end
  end

  def restart_game
    state.world = nil
    state.x = nil
    state.y = nil
    state.dx = nil
    state.dy = nil
    state.stuff_score = 0
    state.stuff_time = 0
    state.intro_tick_count = nil
    defaults
    state.game_start_at = state.tick_count
    state.scene = :game
    state.game_over_at = nil
  end

  def render_intro
    outputs.labels << [640, 700, "Clepto Frog", 4, 1]
    if state.tick_count >= 120
      outputs.labels << [640, 620, "\"Uh... your office has a pet frog?\" - New Guy",
                         4, 1, 0, 0, 0, 255 * 120.ease(60)]
    end

    if state.tick_count >= 240
      outputs.labels << [640, 580, "\"Yep! His name is Clepto.\" - Jim",
                         4, 1, 0, 0, 0, 255 * 240.ease(60)]
    end

    if state.tick_count >= 360
      outputs.labels << [640, 540, "\"Uh...\" - New Guy",
                         4, 1, 0, 0, 0, 255 * 360.ease(60)]
    end

    if state.tick_count >= 480
      outputs.labels << [640, 500, "\"He steals mugs while we're away...\" - Jim",
                         4, 1, 0, 0, 0, 255 * 480.ease(60)]
    end

    if state.tick_count >= 600
      outputs.labels << [640, 460, "\"It's not a big deal, we take them back in the morning.\" - Jim",
                         4, 1, 0, 0, 0, 255 * 600.ease(60)]
    end

    outputs.sprites << [640 - 50, 360 - 50, 100, 100,
                        "sprites/square-green.png"]

    if state.tick_count == 800
      state.scene = :game
      state.game_start_at = state.tick_count
    end
  end

  def tick
    defaults
    if state.scene == :intro && state.tick_count <= 800
      render_intro
    elsif state.scene == :ending
      render_ending
    else
      render
    end
    calc
    process_inputs
  end

  def defaults
    state.scene ||= :intro
    state.stuff_score     ||= 0
    state.stuff_time      ||= 0
    state.stuff_best_time ||= nil
    state.camera_x ||= 0
    state.camera_y ||= 0
    state.target_camera_scale ||= 1
    state.camera_scale ||= 1
    state.tongue_length          ||= 100
    state.dev_action             ||= :collision_mode
    state.action                 ||= :aiming
    state.tongue_angle           ||= 90
    state.tile_size                = 64
    state.gravity                  = -0.1
    state.air                      = -0.01
    state.player_width             = 60
    state.player_height            = 60
    state.collision_tolerance      = 0.0
    state.previous_tile_size     ||= state.tile_size
    state.x                      ||= 2400
    state.y                      ||= 200
    state.dy                     ||= 0
    state.dx                     ||= 0
    attempt_load_world_from_file
    state.world_lookup           ||= { }
    state.world_collision_rects  ||= []
    state.mode                   ||= :creating
    state.select_menu            ||= [0, 720, 1280, 720]
    state.sprite_quantity        ||= 20
    state.sprite_coords          ||= []
    state.banner_coords          ||= [640, 680 + 720]
    state.sprite_selected        ||= 1
    state.map_saved_at           ||= 0
    state.intro_tick_count       ||= state.tick_count
    if state.sprite_coords == []
      count = 1
      temp_x = 165
      temp_y = 500 + 720
      state.sprite_quantity.times do
        state.sprite_coords += [[temp_x, temp_y, count]]
        temp_x += 100
        count += 1
        if temp_x > 1280 - (165 + 50)
          temp_x = 165
          temp_y -= 75
        end
      end
    end
  end

  def start_of_tongue x = nil, y = nil
    x ||= state.x
    y ||= state.y
    [
      x + state.player_width.half,
      y + state.player_height.half
    ]
  end

  def stage_definition
    outputs.sprites << [vx(0), vy(0), vw(10000), vw(5875), 'sprites/level-map.png']
  end

  def render
    stage_definition
    start_of_tongue_render = [vx(start_of_tongue.x), vy(start_of_tongue.y)]
    end_of_tongue_render = [vx(end_of_tongue.x), vy(end_of_tongue.y)]

    if state.anchor_point
      anchor_point_render = [vx(state.anchor_point.x), vy(state.anchor_point.y)]
      outputs.sprites << { x: start_of_tongue_render.x,
                           y: start_of_tongue_render.y,
                           w: vw(2),
                           h: args.geometry.distance(start_of_tongue_render, anchor_point_render),
                           path:  'sprites/square-pink.png',
                           angle_anchor_y: 0,
                           angle: state.tongue_angle - 90 }
    else
      outputs.sprites << { x: vx(start_of_tongue.x),
                           y: vy(start_of_tongue.y),
                           w: vw(2),
                           h: vh(state.tongue_length),
                           path:  'sprites/square-pink.png',
                           angle_anchor_y: 0,
                           angle: state.tongue_angle - 90 }
    end

    outputs.sprites << state.objects.map { |o| [vx(o.x), vy(o.y), vw(o.w), vh(o.h), o.path] }

    if state.god_mode
      # SHOW HIDE COLLISIONS
      outputs.sprites << state.world.map do |x, y, w, h|
        x = vx(x)
        y = vy(y)
        if x > -80 && x < 1280 && y > -80 && y < 720
          {
            x: x,
            y: y,
            w: vw(w || state.tile_size),
            h: vh(h || state.tile_size),
            path: 'sprites/square-gray.png',
            a: 128
          }
        end
      end
    end

    render_player
    outputs.sprites << [vx(2315), vy(45), vw(569), vh(402), 'sprites/square-blue.png', 0, 40]

    # Label in top left of the screen
    outputs.primitives << [20, 640, 180, 70, 255, 255, 255, 128].solid
    outputs.primitives << [30, 700, "Stuff: #{state.stuff_score} of #{$mugs.count}", 1].label
    outputs.primitives << [30, 670, "Time: #{"%.2f" % state.stuff_time}", 1].label

    if state.god_mode
      if state.map_saved_at > 0 && state.map_saved_at.elapsed_time < 120
        outputs.primitives << [920, 670, 'Map has been exported!', 1, 0, 50, 100, 50].label
      end


      # Creates sprite following mouse to help indicate which sprite you have selected
      outputs.primitives << [inputs.mouse.position.x, inputs.mouse.position.y,
                             state.tile_size, state.tile_size, 'sprites/square-indigo.png', 0, 100].sprite
    end

    render_mini_map
    outputs.primitives << [0, 0, 1280, 720, 255, 255, 255, 255 * state.game_start_at.ease(60, :flip)].solid
  end

  def render_mini_map
    x, y = 1170, 10
    outputs.primitives << [x, y, 100, 58, 0, 0, 0, 200].solid
    outputs.primitives << [x + args.state.x.fdiv(100) - 1, y + args.state.y.fdiv(100) - 1, 2, 2, 0, 255, 0].solid
    t_start = start_of_tongue
    t_end = end_of_tongue
    outputs.primitives << [
      x + t_start.x.fdiv(100), y + t_start.y.fdiv(100),
      x + t_end.x.fdiv(100), y + t_end.y.fdiv(100),
      255, 255, 255
    ].line

    state.objects.each do |o|
      outputs.primitives << [x + o.x.fdiv(100) - 1, y + o.y.fdiv(100) - 1, 2, 2, 200, 200, 0].solid
    end
  end

  def calc_camera percentage_override = nil
    percentage = percentage_override || (0.2 * state.camera_scale)
    target_scale = state.target_camera_scale
    distance_scale = target_scale - state.camera_scale
    state.camera_scale += distance_scale * percentage

    target_x = state.x * state.target_camera_scale
    target_y = state.y * state.target_camera_scale

    distance_x = target_x - (state.camera_x + 640)
    distance_y = target_y - (state.camera_y + 360)
    state.camera_x += distance_x * percentage if distance_x.abs > 1
    state.camera_y += distance_y * percentage if distance_y.abs > 1
    state.camera_x = 0 if state.camera_x < 0
    state.camera_y = 0 if state.camera_y < 0
  end

  def vx x
     (x * state.camera_scale) - state.camera_x
  end

  def vy y
    (y * state.camera_scale) - state.camera_y
  end

  def vw w
    w * state.camera_scale
  end

  def vh h
    h * state.camera_scale
  end

  def calc
    calc_camera
    calc_world_lookup
    calc_player
    calc_on_floor
    calc_score
  end

  def set_camera_scale v = nil
    return if v < 0.1
    state.target_camera_scale = v
  end

  def process_inputs_god_mode
    return unless state.god_mode

    if inputs.keyboard.key_down.equal_sign || (inputs.keyboard.equal_sign && state.tick_count.mod_zero?(10))
      set_camera_scale state.camera_scale + 0.1
    elsif inputs.keyboard.key_down.hyphen || (inputs.keyboard.hyphen && state.tick_count.mod_zero?(10))
      set_camera_scale state.camera_scale - 0.1
    elsif inputs.keyboard.eight || inputs.keyboard.zero
      set_camera_scale 1
    end

    if input_up?
      state.y += 10
      state.dy = 0
    elsif input_down?
      state.y -= 10
      state.dy = 0
    end

    if input_left?
      state.x -= 10
      state.dx = 0
    elsif input_right?
      state.x += 10
      state.dx = 0
    end
  end

  def process_inputs
    if state.scene == :game
      process_inputs_player_movement
      process_inputs_god_mode
    elsif state.scene == :intro
      if args.inputs.keyboard.key_down.enter || args.inputs.keyboard.key_down.space
        if Kernel.tick_count < 600
          Kernel.tick_count = 600
        end
      end
    end
  end

  def input_up?
    inputs.keyboard.w || inputs.keyboard.up || inputs.keyboard.k
  end

  def input_up_released?
    inputs.keyboard.key_up.w ||
    inputs.keyboard.key_up.up ||
    inputs.keyboard.key_up.k
  end

  def input_down?
    inputs.keyboard.s || inputs.keyboard.down || inputs.keyboard.j
  end

  def input_down_released?
    inputs.keyboard.key_up.s ||
    inputs.keyboard.key_up.down ||
    inputs.keyboard.key_up.j
  end

  def input_left?
    inputs.keyboard.a || inputs.keyboard.left || inputs.keyboard.h
  end

  def input_right?
    inputs.keyboard.d || inputs.keyboard.right || inputs.keyboard.l
  end

  def set_object path, w, h
    state.object = path
    state.object_w = w
    state.object_h = h
  end

  def collision_mode
    state.dev_action = :collision_mode
  end

  def process_inputs_player_movement
    if inputs.keyboard.key_down.g
      state.god_mode = !state.god_mode
      puts state.god_mode
    end

    if inputs.keyboard.key_down.u && state.dev_action == :collision_mode
      state.world = state.world[0..-2]
      state.world_lookup = {}
    end

    if inputs.keyboard.key_down.space && !state.anchor_point
      state.tongue_length = 0
      state.action = :shooting
      outputs.sounds << 'sounds/shooting.wav'
    elsif inputs.keyboard.key_down.space
      state.action = :aiming
      state.anchor_point  = nil
      state.tongue_length = 100
    end

    if state.anchor_point
      if input_up?
        if state.tongue_length >= 105
          state.tongue_length -= 5
          state.dy += 0.8
        end
      elsif input_down?
        state.tongue_length += 5
        state.dy -= 0.8
      end

      if input_left? && state.dx > 1
        state.dx *= 0.98
      elsif input_left? && state.dx < -1
        state.dx *= 1.03
      elsif input_left? && !state.on_floor
        state.dx -= 3
      elsif input_right? && state.dx > 1
        state.dx *= 1.03
      elsif input_right? && state.dx < -1
        state.dx *= 0.98
      elsif input_right? && !state.on_floor
        state.dx += 3
      end
    else
      if input_left?
        state.tongue_angle += 1.5
        state.tongue_angle = state.tongue_angle
      elsif input_right?
        state.tongue_angle -= 1.5
        state.tongue_angle = state.tongue_angle
      end
    end
  end

  def add_floors
    # floors
    state.world += [
      [0,       0, 10000, 40],
      [0,    1670, 3250, 60],
      [6691, 1653, 3290, 60],
      [1521, 3792, 7370, 60],
      [0, 5137, 3290, 60]
    ]
  end

  def attempt_load_world_from_file
    return if state.world
    # exported_world = gtk.read_file(MAP_FILE_PATH)
    state.world = []
    state.objects = []

    if $collisions
      $collisions.map do |x, y, w, h|
        state.world << [x, y, w, h]
      end

      add_floors
    # elsif exported_world
    #   exported_world.each_line.map do |l|
    #     tokens = l.strip.split(',')
    #     x    = tokens[0].to_i
    #     y    = tokens[1].to_i
    #     type = tokens[2].to_i
    #     if type == 1
    #       state.world << [x, y, state.tile_size, state.tile_size]
    #     elsif type == 2
    #       w, h, path = tokens[3..-1]
    #       state.objects << [x, y, w.to_i, h.to_i, path]
    #     end
    #   end

    #   add_floors
    end

    if $mugs
      $mugs.map do |x, y, w, h, path|
        state.objects << [x, y, w, h, path]
      end
    end
  end

  def calc_world_lookup
    if state.tile_size != state.previous_tile_size
      state.previous_tile_size = state.tile_size
      state.world_lookup = {}
    end

    return if state.world_lookup.keys.length > 0
    return unless state.world.length > 0

    # Searches through the world and finds the cordinates that exist
    state.world_lookup = {}
    state.world.each do |x, y, w, h|
      state.world_lookup[[x, y, w || state.tile_size, h || state.tile_size]] = true
    end

    # Assigns collision rects for every sprite drawn
    state.world_collision_rects =
      state.world_lookup
           .keys
           .map do |x, y, w, h|
             s = state.tile_size
             w ||= s
             h ||= s
             {
               args:       [x, y, w, h],
               left_right: [x,     y + 4, w,     h - 6],
               top:        [x + 4, y + 6, w - 8, h - 6],
               bottom:     [x + 1, y - 1, w - 2, h - 8],
             }
           end

  end

  def calc_pendulum
    return if !state.anchor_point
    target_x = state.anchor_point.x - start_of_tongue.x
    target_y = state.anchor_point.y -
               state.tongue_length - 5 - 20 - state.player_height

    diff_y = state.y - target_y

    if target_x > 0
      state.dx += 0.6
    elsif target_x < 0
      state.dx -= 0.6
    end

    if diff_y > 0
      state.dy -= 0.1
    elsif diff_y < 0
      state.dy += 0.1
    end

    state.dx *= 0.99

    if state.dy.abs < 2
      state.dy *= 0.8
    else
      state.dy *= 0.90
    end

    if state.tongue_length && state.y
      state.dy += state.tongue_angle.vector_y state.tongue_length.fdiv(1000)
    end
  end

  def calc_tongue_angle
    return unless state.anchor_point
    state.tongue_angle = args.geometry.angle_from state.anchor_point, start_of_tongue
    state.tongue_length = args.geometry.distance(start_of_tongue, state.anchor_point)
    state.tongue_length = state.tongue_length.greater(100)
  end

  def player_from_end_of_tongue
    p = state.tongue_angle.vector(state.tongue_length)
    derived_start = [state.anchor_point.x - p.x, state.anchor_point.y - p.y]
    derived_start.x -= state.player_width.half
    derived_start.y -= state.player_height.half
    derived_start
  end

  def end_of_tongue
    p = state.tongue_angle.vector(state.tongue_length)
    [start_of_tongue.x + p.x, start_of_tongue.y + p.y]
  end

  def calc_shooting
    return unless state.action == :shooting
    state.tongue_length += 30
    potential_anchor = end_of_tongue
    if potential_anchor.x <= 0
      state.anchor_point = potential_anchor
      state.action = :anchored
      outputs.sounds << 'sounds/attached.wav'
    elsif potential_anchor.x >= 10000
      state.anchor_point = potential_anchor
      state.action = :anchored
      outputs.sounds << 'sounds/attached.wav'
    elsif potential_anchor.y <= 0
      state.anchor_point = potential_anchor
      state.action = :anchored
      outputs.sounds << 'sounds/attached.wav'
    elsif potential_anchor.y >= 5875
      state.anchor_point = potential_anchor
      state.action = :anchored
      outputs.sounds << 'sounds/attached.wav'
    else
      anchor_rect = [potential_anchor.x - 5, potential_anchor.y - 5, 10, 10]
      collision = state.world_collision_rects.find_all do |v|
        [v[:args].x, v[:args].y, v[:args].w, v[:args].h].intersect_rect?(anchor_rect)
      end.first
      if collision
        state.anchor_point = potential_anchor
        state.action = :anchored
      outputs.sounds << 'sounds/attached.wav'
      end
    end
  end

  def calc_player
    calc_shooting
    if !state.god_mode
      state.dy += state.gravity  # Since acceleration is the change in velocity, the change in y (dy) increases every frame
      state.dx += state.dx * state.air
    end
    calc_pendulum
    calc_box_collision
    calc_edge_collision
    if !state.god_mode
      state.y  += state.dy
      state.x  += state.dx
    end
    calc_tongue_angle
  end

  def calc_box_collision
    return unless state.world_lookup.keys.length > 0
    collision_floor
    collision_left
    collision_right
    collision_ceiling
  end

  def calc_edge_collision
    # Ensures that player doesn't fall below the map
    if next_y < 0 && state.dy < 0
      state.y = 0
      state.dy = state.dy.abs * 0.8
      state.collision_on_y = true
    # Ensures player doesn't go insanely high
    elsif next_y > 5875 - state.tile_size && state.dy > 0
      state.y = 5875 - state.tile_size
      state.dy = state.dy.abs * 0.8 * -1
      state.collision_on_y = true
    end

    # Ensures that player remains in the horizontal range its supposed to
    if state.x >= 10000 - state.tile_size && state.dx > 0
      state.x = 10000 - state.tile_size
      state.dx = state.dx.abs * 0.8 * -1
      state.collision_on_x = true
    elsif state.x <= 0 && state.dx < 0
      state.x = 0
      state.dx = state.dx.abs * 0.8
      state.collision_on_x = true
    end
  end

  def next_y
    state.y + state.dy
  end

  def next_x
    if state.dx < 0
      return (state.x + state.dx) - (state.tile_size - state.player_width)
    else
      return (state.x + state.dx) + (state.tile_size - state.player_width)
    end
  end

  def collision_floor
    return unless state.dy <= 0

    player_rect = [state.x, next_y, state.tile_size, state.tile_size]

    # Runs through all the sprites on the field and determines if the player hits the bottom of sprite (hence "-0.1" above)
    floor_collisions = state.world_collision_rects
                         .find_all { |r| r[:top].intersect_rect?(player_rect, state.collision_tolerance) }
                         .first

    return unless floor_collisions
    state.y = floor_collisions[:top].top
    state.dy = state.dy.abs * 0.8
  end

  def collision_left
    return unless state.dx < 0
    player_rect = [next_x, state.y, state.tile_size, state.tile_size]

    # Runs through all the sprites on the field and determines if the player hits the left side of sprite (hence "-0.1" above)
    left_side_collisions = state.world_collision_rects
                             .find_all { |r| r[:left_right].intersect_rect?(player_rect, state.collision_tolerance) }
                             .first

    return unless left_side_collisions
    state.x = left_side_collisions[:left_right].right
    state.dx = state.dy.abs * 0.8
    state.collision_on_x = true
  end

  def collision_right
    return unless state.dx > 0

    player_rect = [next_x, state.y, state.tile_size, state.tile_size]
    # Runs through all the sprites on the field and determines if the player hits the right side of sprite (hence "-0.1" above)
    right_side_collisions = state.world_collision_rects
                              .find_all { |r| r[:left_right].intersect_rect?(player_rect, state.collision_tolerance) }
                              .first

    return unless right_side_collisions
    state.x = right_side_collisions[:left_right].left - state.tile_size
    state.dx = state.dx.abs * 0.8 * -1
    state.collision_on_x = true
  end

  def collision_ceiling
    return unless state.dy > 0

    player_rect = [state.x, next_y, state.player_width, state.player_height]

    # Runs through all the sprites on the field and determines if the player hits the ceiling of sprite (hence "+0.1" above)
    ceil_collisions = state.world_collision_rects
                        .find_all { |r| r[:bottom].intersect_rect?(player_rect, state.collision_tolerance) }
                        .first

    return unless ceil_collisions
    state.y = ceil_collisions[:bottom].y - state.tile_size
    state.dy = state.dy.abs * 0.8 * -1
    state.collision_on_y = true
  end

  def to_coord point
    # Integer divides (idiv) point.x to turn into grid
    # Then, you can just multiply each integer by state.tile_size
    # later and huzzah. Grid coordinates
    [point.x.idiv(state.tile_size), point.y.idiv(state.tile_size)]
  end

  def export_map
    export_string = state.world.map do |x, y|
      "#{x},#{y},1"
    end
    export_string += state.objects.map do |x, y, w, h, path|
      "#{x},#{y},2,#{w},#{h},#{path}"
    end
    gtk.write_file(MAP_FILE_PATH, export_string.join("\n"))
    state.map_saved_at = state.tick_count
  end

  def inputs_export_stage
  end

  def calc_score
    return unless state.scene == :game
    player = [state.x, state.y, state.player_width, state.player_height]
    collected = state.objects.find_all { |s| s.intersect_rect? player }
    state.stuff_score += collected.length
    if collected.length > 0
      outputs.sounds << 'sounds/collectable.wav'
    end
    state.objects = state.objects.reject { |s| collected.include? s }
    state.stuff_time += 0.01
    if state.objects.length == 0
      if !state.stuff_best_time || state.stuff_time < state.stuff_best_time
        state.stuff_best_time = state.stuff_time
      end
      state.game_over_at = nil
      state.scene = :ending
    end
  end

  def calc_on_floor
    if state.action == :anchored
      state.on_floor = false
      state.on_floor_debounce = 30
    else
      state.on_floor_debounce ||= 30

      if state.dy.round != 0
        state.on_floor_debounce = 30
        state.on_floor = false
      else
        state.on_floor_debounce -= 1
      end

      if state.on_floor_debounce <= 0
        state.on_floor_debounce = 0
        state.on_floor = true
      end
    end
  end

  def render_player
    path = "sprites/square-green.png"
    angle = 0
    # outputs.labels << [vx(state.x), vy(state.y) - 30, "dy: #{state.dy.round}"]
    if state.action == :idle
      # outputs.labels << [vx(state.x), vy(state.y), "IDLE"]
      path = "sprites/square-green.png"
    elsif state.action == :aiming && !state.on_floor
      # outputs.labels << [vx(state.x), vy(state.y), "AIMING AIR BORN"]
      angle = state.tongue_angle - 90
      path = "sprites/square-green.png"
    elsif state.action == :aiming # ON THE GROUND
      # outputs.labels << [vx(state.x), vy(state.y), "AIMING GROUND"]
      path = "sprites/square-green.png"
    elsif state.action == :shooting && !state.on_floor
      # outputs.labels << [vx(state.x), vy(state.y), "SHOOTING AIR BORN"]
      path = "sprites/square-green.png"
      angle = state.tongue_angle - 90
    elsif state.action == :shooting
      # outputs.labels << [vx(state.x), vy(state.y), "SHOOTING ON GROUND"]
      path = "sprites/square-green.png"
    elsif state.action == :anchored
      # outputs.labels << [vx(state.x), vy(state.y), "SWINGING"]
      angle = state.tongue_angle - 90
      path = "sprites/square-green.png"
    end

    outputs.sprites << [vx(state.x),
                        vy(state.y),
                        vw(state.player_width),
                        vh(state.player_height),
                        path,
                        angle]
  end

  def render_player_old
    # Player
    if state.action == :aiming
      path = 'sprites\frg\idle\frog_idle.png'
      if state.dx > 2
	  #directional right sprite was here but i needa redo it
        path = 'sprites\frg\anchor\frog-anchor-0.png'
      #directional left sprite was here but i needa redo it
	  elsif state.dx < -2
        path = 'sprites\frg\anchor\frog-anchor-0.png'
      end
      outputs.sprites << [vx(state.x),
                          vy(state.y),
                          vw(state.player_width),
                          vh(state.player_height),
                          path,
                          (state.tongue_angle - 90)]
    elsif state.action == :anchored || state.action == :shooting
      outputs.sprites << [vx(state.x),
                          vy(state.y),
                          vw(state.player_width),
                          vw(state.player_height),
                          'sprites/animations_povfrog/frog_bwah_up.png',
                          (state.tongue_angle - 90)]
    end
  end
end


$game = CleptoFrog.new

def tick args
  if args.state.scene == :game
    tick_instructions args, "SPACE to SHOOT and RELEASE tongue. LEFT, RIGHT to SWING and BUILD momentum. MINIMAP in bottom right corner.", 360
  end
  $game.args = args
  $game.tick
end

def tick_instructions args, text, y = 715
  return if args.state.key_event_occurred
  if args.inputs.keyboard.directional_vector || args.inputs.keyboard.key_down.space
    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, "(SPACE to dismiss instructions)" , -2, 1, 255, 255, 255].label
end