tank_island 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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