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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +58 -0
- data/Rakefile +2 -0
- data/bin/tank_island +30 -0
- data/lib/entities/box.rb +28 -0
- data/lib/entities/bullet.rb +26 -0
- data/lib/entities/camera.rb +113 -0
- data/lib/entities/components/ai/gun.rb +114 -0
- data/lib/entities/components/ai/tank_chasing_state.rb +30 -0
- data/lib/entities/components/ai/tank_fighting_state.rb +47 -0
- data/lib/entities/components/ai/tank_fleeing_state.rb +50 -0
- data/lib/entities/components/ai/tank_motion_fsm.rb +102 -0
- data/lib/entities/components/ai/tank_motion_state.rb +84 -0
- data/lib/entities/components/ai/tank_navigating_state.rb +34 -0
- data/lib/entities/components/ai/tank_roaming_state.rb +83 -0
- data/lib/entities/components/ai/tank_stuck_state.rb +45 -0
- data/lib/entities/components/ai/vision.rb +109 -0
- data/lib/entities/components/ai_input.rb +70 -0
- data/lib/entities/components/box_graphics.rb +39 -0
- data/lib/entities/components/bullet_graphics.rb +13 -0
- data/lib/entities/components/bullet_physics.rb +65 -0
- data/lib/entities/components/bullet_sounds.rb +15 -0
- data/lib/entities/components/component.rb +32 -0
- data/lib/entities/components/damage_graphics.rb +20 -0
- data/lib/entities/components/explosion_graphics.rb +43 -0
- data/lib/entities/components/explosion_sounds.rb +16 -0
- data/lib/entities/components/health.rb +87 -0
- data/lib/entities/components/player_input.rb +100 -0
- data/lib/entities/components/player_sounds.rb +16 -0
- data/lib/entities/components/powerup_graphics.rb +22 -0
- data/lib/entities/components/powerup_sounds.rb +15 -0
- data/lib/entities/components/tank_graphics.rb +46 -0
- data/lib/entities/components/tank_health.rb +32 -0
- data/lib/entities/components/tank_physics.rb +179 -0
- data/lib/entities/components/tank_sounds.rb +43 -0
- data/lib/entities/components/tree_graphics.rb +69 -0
- data/lib/entities/damage.rb +26 -0
- data/lib/entities/explosion.rb +34 -0
- data/lib/entities/game_object.rb +54 -0
- data/lib/entities/hud.rb +79 -0
- data/lib/entities/map.rb +183 -0
- data/lib/entities/object_pool.rb +59 -0
- data/lib/entities/powerups/fire_rate_powerup.rb +14 -0
- data/lib/entities/powerups/health_powerup.rb +12 -0
- data/lib/entities/powerups/powerup.rb +35 -0
- data/lib/entities/powerups/powerup_respawn_queue.rb +23 -0
- data/lib/entities/powerups/repair_powerup.rb +14 -0
- data/lib/entities/powerups/tank_speed_powerup.rb +14 -0
- data/lib/entities/radar.rb +62 -0
- data/lib/entities/score_display.rb +35 -0
- data/lib/entities/tank.rb +64 -0
- data/lib/entities/tree.rb +18 -0
- data/lib/game_states/demo_state.rb +49 -0
- data/lib/game_states/game_state.rb +27 -0
- data/lib/game_states/menu_state.rb +60 -0
- data/lib/game_states/pause_state.rb +61 -0
- data/lib/game_states/play_state.rb +119 -0
- data/lib/misc/axis_aligned_bounding_box.rb +33 -0
- data/lib/misc/game_window.rb +30 -0
- data/lib/misc/names.rb +13 -0
- data/lib/misc/quad_tree.rb +91 -0
- data/lib/misc/stats.rb +55 -0
- data/lib/misc/stereo_sample.rb +96 -0
- data/lib/misc/utils.rb +145 -0
- data/media/armalite_rifle.ttf +0 -0
- data/media/boxes_barrels.json +60 -0
- data/media/boxes_barrels.png +0 -0
- data/media/bullet.png +0 -0
- data/media/c_dot.png +0 -0
- data/media/country_field.png +0 -0
- data/media/crash.ogg +0 -0
- data/media/damage1.png +0 -0
- data/media/damage2.png +0 -0
- data/media/damage3.png +0 -0
- data/media/damage4.png +0 -0
- data/media/decor.json +516 -0
- data/media/decor.png +0 -0
- data/media/decor.psd +0 -0
- data/media/explosion.mp3 +0 -0
- data/media/explosion.png +0 -0
- data/media/fire.mp3 +0 -0
- data/media/ground.json +492 -0
- data/media/ground.png +0 -0
- data/media/ground_units.json +900 -0
- data/media/ground_units.png +0 -0
- data/media/menu_music.mp3 +0 -0
- data/media/metal_interaction2.wav +0 -0
- data/media/names.txt +279 -0
- data/media/pickups.json +68 -0
- data/media/pickups.png +0 -0
- data/media/powerup.mp3 +0 -0
- data/media/respawn.wav +0 -0
- data/media/tank_driving.mp3 +0 -0
- data/media/top_secret.ttf +0 -0
- data/media/trees.png +0 -0
- data/media/trees_packed.json +388 -0
- data/media/trees_packed.png +0 -0
- data/media/water.png +0 -0
- data/spec/misc/aabb_spec.rb +85 -0
- data/spec/misc/quad_tree_spec.rb +137 -0
- data/tank_island.gemspec +29 -0
- 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
         |