tank_island 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +22 -0
  6. data/README.md +58 -0
  7. data/Rakefile +2 -0
  8. data/bin/tank_island +30 -0
  9. data/lib/entities/box.rb +28 -0
  10. data/lib/entities/bullet.rb +26 -0
  11. data/lib/entities/camera.rb +113 -0
  12. data/lib/entities/components/ai/gun.rb +114 -0
  13. data/lib/entities/components/ai/tank_chasing_state.rb +30 -0
  14. data/lib/entities/components/ai/tank_fighting_state.rb +47 -0
  15. data/lib/entities/components/ai/tank_fleeing_state.rb +50 -0
  16. data/lib/entities/components/ai/tank_motion_fsm.rb +102 -0
  17. data/lib/entities/components/ai/tank_motion_state.rb +84 -0
  18. data/lib/entities/components/ai/tank_navigating_state.rb +34 -0
  19. data/lib/entities/components/ai/tank_roaming_state.rb +83 -0
  20. data/lib/entities/components/ai/tank_stuck_state.rb +45 -0
  21. data/lib/entities/components/ai/vision.rb +109 -0
  22. data/lib/entities/components/ai_input.rb +70 -0
  23. data/lib/entities/components/box_graphics.rb +39 -0
  24. data/lib/entities/components/bullet_graphics.rb +13 -0
  25. data/lib/entities/components/bullet_physics.rb +65 -0
  26. data/lib/entities/components/bullet_sounds.rb +15 -0
  27. data/lib/entities/components/component.rb +32 -0
  28. data/lib/entities/components/damage_graphics.rb +20 -0
  29. data/lib/entities/components/explosion_graphics.rb +43 -0
  30. data/lib/entities/components/explosion_sounds.rb +16 -0
  31. data/lib/entities/components/health.rb +87 -0
  32. data/lib/entities/components/player_input.rb +100 -0
  33. data/lib/entities/components/player_sounds.rb +16 -0
  34. data/lib/entities/components/powerup_graphics.rb +22 -0
  35. data/lib/entities/components/powerup_sounds.rb +15 -0
  36. data/lib/entities/components/tank_graphics.rb +46 -0
  37. data/lib/entities/components/tank_health.rb +32 -0
  38. data/lib/entities/components/tank_physics.rb +179 -0
  39. data/lib/entities/components/tank_sounds.rb +43 -0
  40. data/lib/entities/components/tree_graphics.rb +69 -0
  41. data/lib/entities/damage.rb +26 -0
  42. data/lib/entities/explosion.rb +34 -0
  43. data/lib/entities/game_object.rb +54 -0
  44. data/lib/entities/hud.rb +79 -0
  45. data/lib/entities/map.rb +183 -0
  46. data/lib/entities/object_pool.rb +59 -0
  47. data/lib/entities/powerups/fire_rate_powerup.rb +14 -0
  48. data/lib/entities/powerups/health_powerup.rb +12 -0
  49. data/lib/entities/powerups/powerup.rb +35 -0
  50. data/lib/entities/powerups/powerup_respawn_queue.rb +23 -0
  51. data/lib/entities/powerups/repair_powerup.rb +14 -0
  52. data/lib/entities/powerups/tank_speed_powerup.rb +14 -0
  53. data/lib/entities/radar.rb +62 -0
  54. data/lib/entities/score_display.rb +35 -0
  55. data/lib/entities/tank.rb +64 -0
  56. data/lib/entities/tree.rb +18 -0
  57. data/lib/game_states/demo_state.rb +49 -0
  58. data/lib/game_states/game_state.rb +27 -0
  59. data/lib/game_states/menu_state.rb +60 -0
  60. data/lib/game_states/pause_state.rb +61 -0
  61. data/lib/game_states/play_state.rb +119 -0
  62. data/lib/misc/axis_aligned_bounding_box.rb +33 -0
  63. data/lib/misc/game_window.rb +30 -0
  64. data/lib/misc/names.rb +13 -0
  65. data/lib/misc/quad_tree.rb +91 -0
  66. data/lib/misc/stats.rb +55 -0
  67. data/lib/misc/stereo_sample.rb +96 -0
  68. data/lib/misc/utils.rb +145 -0
  69. data/media/armalite_rifle.ttf +0 -0
  70. data/media/boxes_barrels.json +60 -0
  71. data/media/boxes_barrels.png +0 -0
  72. data/media/bullet.png +0 -0
  73. data/media/c_dot.png +0 -0
  74. data/media/country_field.png +0 -0
  75. data/media/crash.ogg +0 -0
  76. data/media/damage1.png +0 -0
  77. data/media/damage2.png +0 -0
  78. data/media/damage3.png +0 -0
  79. data/media/damage4.png +0 -0
  80. data/media/decor.json +516 -0
  81. data/media/decor.png +0 -0
  82. data/media/decor.psd +0 -0
  83. data/media/explosion.mp3 +0 -0
  84. data/media/explosion.png +0 -0
  85. data/media/fire.mp3 +0 -0
  86. data/media/ground.json +492 -0
  87. data/media/ground.png +0 -0
  88. data/media/ground_units.json +900 -0
  89. data/media/ground_units.png +0 -0
  90. data/media/menu_music.mp3 +0 -0
  91. data/media/metal_interaction2.wav +0 -0
  92. data/media/names.txt +279 -0
  93. data/media/pickups.json +68 -0
  94. data/media/pickups.png +0 -0
  95. data/media/powerup.mp3 +0 -0
  96. data/media/respawn.wav +0 -0
  97. data/media/tank_driving.mp3 +0 -0
  98. data/media/top_secret.ttf +0 -0
  99. data/media/trees.png +0 -0
  100. data/media/trees_packed.json +388 -0
  101. data/media/trees_packed.png +0 -0
  102. data/media/water.png +0 -0
  103. data/spec/misc/aabb_spec.rb +85 -0
  104. data/spec/misc/quad_tree_spec.rb +137 -0
  105. data/tank_island.gemspec +29 -0
  106. metadata +223 -0
@@ -0,0 +1,47 @@
1
+ class TankFightingState < TankMotionState
2
+ def initialize(object, vision)
3
+ super
4
+ @object = object
5
+ @vision = vision
6
+ end
7
+
8
+ def update
9
+ change_direction if should_change_direction?
10
+ if substate_expired?
11
+ rand > 0.1 ? drive : wait
12
+ end
13
+ end
14
+
15
+ def change_direction
16
+ change = case rand(0..100)
17
+ when 0..20
18
+ -45
19
+ when 20..40
20
+ 45
21
+ when 40..60
22
+ 90
23
+ when 60..80
24
+ -90
25
+ when 80..90
26
+ 135
27
+ when 90..100
28
+ -135
29
+ end
30
+ @object.physics.change_direction(
31
+ @object.direction + change)
32
+ @changed_direction_at = Gosu.milliseconds
33
+ @will_keep_direction_for = turn_time
34
+ end
35
+
36
+ def wait_time
37
+ rand(50..300)
38
+ end
39
+
40
+ def drive_time
41
+ rand(5000..10000)
42
+ end
43
+
44
+ def turn_time
45
+ rand(300..3000)
46
+ end
47
+ end
@@ -0,0 +1,50 @@
1
+ class TankFleeingState < TankMotionState
2
+ MAX_FLEE_TIME = 15 * 1000 # 15 seconds
3
+
4
+ def initialize(object, vision, gun)
5
+ super(object, vision)
6
+ @object = object
7
+ @vision = vision
8
+ @gun = gun
9
+ end
10
+
11
+ def can_flee?
12
+ return true unless @started_fleeing
13
+ Gosu.milliseconds - @started_fleeing < MAX_FLEE_TIME
14
+ end
15
+
16
+ def enter
17
+ @started_fleeing ||= Gosu.milliseconds
18
+ end
19
+
20
+ def update
21
+ change_direction if should_change_direction?
22
+ drive
23
+ end
24
+
25
+ def change_direction
26
+ closest_powerup = @vision.closest_powerup(
27
+ RepairPowerup, HealthPowerup)
28
+ if closest_powerup
29
+ angle = Utils.angle_between(
30
+ @object.x, @object.y,
31
+ closest_powerup.x, closest_powerup.y)
32
+ @object.physics.change_direction(
33
+ angle - angle % 45)
34
+ else
35
+ @object.physics.change_direction(
36
+ 180 + @gun.desired_gun_angle -
37
+ @gun.desired_gun_angle % 45)
38
+ end
39
+ @changed_direction_at = Gosu.milliseconds
40
+ @will_keep_direction_for = turn_time
41
+ end
42
+
43
+ def drive_time
44
+ 10000
45
+ end
46
+
47
+ def turn_time
48
+ rand(300..600)
49
+ end
50
+ end
@@ -0,0 +1,102 @@
1
+ class TankMotionFSM
2
+ STATE_CHANGE_DELAY = 500
3
+ LOCATION_CHECK_DELAY = 5000
4
+
5
+ def initialize(object, vision, gun)
6
+ @object = object
7
+ @vision = vision
8
+ @gun = gun
9
+ @roaming_state = TankRoamingState.new(object, vision)
10
+ @fighting_state = TankFightingState.new(object, vision)
11
+ @fleeing_state = TankFleeingState.new(object, vision, gun)
12
+ @chasing_state = TankChasingState.new(object, vision, gun)
13
+ @stuck_state = TankStuckState.new(object, vision, gun)
14
+ @navigating_state = TankNavigatingState.new(object, vision)
15
+ set_state(@roaming_state)
16
+ end
17
+
18
+ def on_collision(with)
19
+ @current_state.on_collision(with)
20
+ end
21
+
22
+ def on_damage(amount)
23
+ if @current_state == @roaming_state
24
+ set_state(@fighting_state)
25
+ end
26
+ end
27
+
28
+ def draw(viewport)
29
+ if $debug
30
+ @image && @image.draw(
31
+ @object.x - @image.width / 2,
32
+ @object.y + @object.graphics.height / 2 -
33
+ @image.height, 100)
34
+ end
35
+ end
36
+
37
+ def update
38
+ choose_state
39
+ @current_state.update
40
+ end
41
+
42
+ def set_state(state)
43
+ return unless state
44
+ return if state == @current_state
45
+ @last_state_change = Gosu.milliseconds
46
+ @current_state = state
47
+ state.enter
48
+ if $debug
49
+ @image = Gosu::Image.from_text(
50
+ $window, state.class.to_s,
51
+ Gosu.default_font_name, 18)
52
+ end
53
+ end
54
+
55
+ def choose_state
56
+ unless @vision.can_go_forward?
57
+ unless @current_state == @stuck_state
58
+ set_state(@navigating_state)
59
+ end
60
+ end
61
+ # Keep unstucking itself for a while
62
+ change_delay = STATE_CHANGE_DELAY
63
+ if @current_state == @stuck_state
64
+ change_delay *= 5
65
+ end
66
+ now = Gosu.milliseconds
67
+ return unless now - @last_state_change > change_delay
68
+ if @last_location_update.nil?
69
+ @last_location_update = now
70
+ @last_location = @object.location
71
+ end
72
+ if now - @last_location_update > LOCATION_CHECK_DELAY
73
+ unless @last_location.nil? || @current_state.waiting?
74
+ if Utils.distance_between(*@last_location, *@object.location) < 20
75
+ set_state(@stuck_state)
76
+ @stuck_state.stuck_at = @object.location
77
+ return
78
+ end
79
+ end
80
+ @last_location_update = now
81
+ @last_location = @object.location
82
+ end
83
+ if @gun.target
84
+ if @object.health.health > 40
85
+ if @gun.distance_to_target > BulletPhysics::MAX_DIST
86
+ new_state = @chasing_state
87
+ else
88
+ new_state = @fighting_state
89
+ end
90
+ else
91
+ if @fleeing_state.can_flee?
92
+ new_state = @fleeing_state
93
+ else
94
+ new_state = @fighting_state
95
+ end
96
+ end
97
+ else
98
+ new_state = @roaming_state
99
+ end
100
+ set_state(new_state)
101
+ end
102
+ end
@@ -0,0 +1,84 @@
1
+ class TankMotionState
2
+ def initialize(object, vision)
3
+ @object = object
4
+ @vision = vision
5
+ end
6
+
7
+ def enter
8
+ # Override if necessary
9
+ end
10
+
11
+ def change_direction
12
+ # Override
13
+ end
14
+
15
+ def wait_time
16
+ # Override and return a number
17
+ end
18
+
19
+ def drive_time
20
+ # Override and return a number
21
+ end
22
+
23
+ def turn_time
24
+ # Override and return a number
25
+ end
26
+
27
+ def update
28
+ # Override
29
+ end
30
+
31
+ def wait
32
+ @sub_state = :waiting
33
+ @started_waiting = Gosu.milliseconds
34
+ @will_wait_for = wait_time
35
+ @object.throttle_down = false
36
+ end
37
+
38
+ def waiting?
39
+ @sub_state == :waiting
40
+ end
41
+
42
+ def drive
43
+ @sub_state = :driving
44
+ @started_driving = Gosu.milliseconds
45
+ @will_drive_for = drive_time
46
+ @object.throttle_down = true
47
+ end
48
+
49
+ def should_change_direction?
50
+ return true unless @vision.can_go_forward?
51
+ return true unless @changed_direction_at
52
+ Gosu.milliseconds - @changed_direction_at >
53
+ @will_keep_direction_for
54
+ end
55
+
56
+ def substate_expired?
57
+ now = Gosu.milliseconds
58
+ case @sub_state
59
+ when :waiting
60
+ true if now - @started_waiting > @will_wait_for
61
+ when :driving
62
+ true if now - @started_driving > @will_drive_for
63
+ else
64
+ true
65
+ end
66
+ end
67
+
68
+ def on_collision(with)
69
+ change = case rand(0..100)
70
+ when 0..30
71
+ -90
72
+ when 30..60
73
+ 90
74
+ when 60..70
75
+ 135
76
+ when 80..90
77
+ -135
78
+ else
79
+ 180
80
+ end
81
+ @object.physics.change_direction(
82
+ @object.direction + change)
83
+ end
84
+ end
@@ -0,0 +1,34 @@
1
+ class TankNavigatingState < TankMotionState
2
+ def initialize(object, vision)
3
+ @object = object
4
+ @vision = vision
5
+ end
6
+
7
+ def update
8
+ change_direction if should_change_direction?
9
+ drive
10
+ end
11
+
12
+ def change_direction
13
+ closest_free_path = @vision.closest_free_path
14
+ if closest_free_path
15
+ @object.physics.change_direction(
16
+ Utils.angle_between(
17
+ @object.x, @object.y, *closest_free_path))
18
+ end
19
+ @changed_direction_at = Gosu.milliseconds
20
+ @will_keep_direction_for = turn_time
21
+ end
22
+
23
+ def wait_time
24
+ rand(10..100)
25
+ end
26
+
27
+ def drive_time
28
+ rand(1000..2000)
29
+ end
30
+
31
+ def turn_time
32
+ rand(300..1000)
33
+ end
34
+ end
@@ -0,0 +1,83 @@
1
+ class TankRoamingState < TankMotionState
2
+ def initialize(object, vision)
3
+ super
4
+ @object = object
5
+ @vision = vision
6
+ end
7
+
8
+ def update
9
+ change_direction if should_change_direction?
10
+ if substate_expired?
11
+ rand > 0.2 ? drive : wait
12
+ end
13
+ end
14
+
15
+ def required_powerups
16
+ required = []
17
+ health = @object.health.health
18
+ if @object.fire_rate_modifier < 2 && health > 50
19
+ required << FireRatePowerup
20
+ end
21
+ if @object.speed_modifier < 1.5 && health > 50
22
+ required << TankSpeedPowerup
23
+ end
24
+ if health < 100
25
+ required << RepairPowerup
26
+ end
27
+ if health < 190
28
+ required << HealthPowerup
29
+ end
30
+ required
31
+ end
32
+
33
+ def change_direction
34
+ closest_powerup = @vision.closest_powerup(
35
+ *required_powerups)
36
+ if closest_powerup
37
+ @seeking_powerup = true
38
+ angle = Utils.angle_between(
39
+ @object.x, @object.y,
40
+ closest_powerup.x, closest_powerup.y)
41
+ @object.physics.change_direction(
42
+ angle - angle % 45)
43
+ # Go away from inaccessable powerup
44
+ unless @vision.can_go_forward?
45
+ @seeking_powerup = false
46
+ @object.physics.change_direction(
47
+ @object.direction + 180)
48
+ end
49
+ else
50
+ @seeking_powerup = false
51
+ change = case rand(0..100)
52
+ when 0..30
53
+ -45
54
+ when 30..60
55
+ 45
56
+ when 60..80
57
+ 90
58
+ when 80..100
59
+ -90
60
+ end
61
+ @object.physics.change_direction(
62
+ @object.direction + change)
63
+ end
64
+ @changed_direction_at = Gosu.milliseconds
65
+ @will_keep_direction_for = turn_time
66
+ end
67
+
68
+ def wait_time
69
+ rand(50..400)
70
+ end
71
+
72
+ def drive_time
73
+ rand(1000..3000)
74
+ end
75
+
76
+ def turn_time
77
+ if @seeking_powerup
78
+ rand(100..300)
79
+ else
80
+ rand(1000..3000)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,45 @@
1
+ class TankStuckState < TankMotionState
2
+ attr_accessor :stuck_at
3
+ def initialize(object, vision, gun)
4
+ super(object, vision)
5
+ @object = object
6
+ @vision = vision
7
+ @gun = gun
8
+ end
9
+
10
+ def update
11
+ change_direction if should_change_direction?
12
+ drive
13
+ end
14
+
15
+ def change_direction
16
+ closest_free_path = @vision.closest_free_path_away_from(
17
+ @stuck_at)
18
+ if closest_free_path
19
+ @object.physics.change_direction(
20
+ Utils.angle_between(
21
+ @object.x, @object.y, *closest_free_path))
22
+ else
23
+ if @object.health.health > 50 && rand > 0.9
24
+ @object.shoot(*Utils.point_at_distance(
25
+ *@object.location,
26
+ @object.gun_angle,
27
+ 150))
28
+ end
29
+ end
30
+ @changed_direction_at = Gosu.milliseconds
31
+ @will_keep_direction_for = turn_time
32
+ end
33
+
34
+ def wait_time
35
+ rand(10..100)
36
+ end
37
+
38
+ def drive_time
39
+ rand(1000..2000)
40
+ end
41
+
42
+ def turn_time
43
+ rand(1000..2000)
44
+ end
45
+ end