diff --git a/Games/Debris_Dodge/Assets/debris dodge.png b/Games/Debris_Dodge/Assets/debris dodge.png
new file mode 100644
index 0000000000..4084447392
Binary files /dev/null and b/Games/Debris_Dodge/Assets/debris dodge.png differ
diff --git a/Games/Debris_Dodge/Assets/debriss.png b/Games/Debris_Dodge/Assets/debriss.png
new file mode 100644
index 0000000000..f15054948b
Binary files /dev/null and b/Games/Debris_Dodge/Assets/debriss.png differ
diff --git a/Games/Debris_Dodge/Gemfile b/Games/Debris_Dodge/Gemfile
new file mode 100644
index 0000000000..27243f1b8e
--- /dev/null
+++ b/Games/Debris_Dodge/Gemfile
@@ -0,0 +1,4 @@
+source 'https://rubygems.org'
+
+# Specify your gem's dependencies in tank_island.gemspec
+gemspec
diff --git a/Games/Debris_Dodge/README.md b/Games/Debris_Dodge/README.md
new file mode 100644
index 0000000000..1250c52958
--- /dev/null
+++ b/Games/Debris_Dodge/README.md
@@ -0,0 +1,79 @@
+# **Debris Dodge**
+
+---
+
+
+Give you best Hit!
+
+## **Description 📃**
+Tank Island is an open source 2D top down shooter game that was created with Ruby using
+[Gosu](http://www.libgosu.org) game development library while writing
+[this book](https://leanpub.com/developing-games-with-ruby/).
+
+
+## **functionalities 🎮**
+
+Top down 2D shooter game that involves blowing up tanks
+
+
+
+## **How to play? 🕹️**
+
+- `W` `A` `S` `D` moves your tank.
+- Mouse `left click` shoots.
+- `ESC` goes into menu and away from it.
+- `R` respawns your tank.
+- `T` spawns an enemy tank under mouse cursor.
+- `F1` enters debug mode.
+- `F2` toggles profiling
+
+
+
+
+## **Screenshots 📸**
+
+
+
+
+
+
+
+
+
+
+
+
+## Installation
+
+Before installing, make sure you have:
+
+- Ruby installed, preferably through [rbenv](https://github.com/sstephenson/rbenv), not rvm.
+- ImageMagick (`gem install rmagick` should work).
+- Gosu prerequisites for [Mac](https://github.com/jlnr/gosu/wiki/Getting-Started-on-OS-X),
+ [Linux](https://github.com/jlnr/gosu/wiki/Getting-Started-on-Linux) or
+ [Windows](https://github.com/jlnr/gosu/wiki/Getting-Started-on-Windows)
+
+To install it, run
+
+ $ gem install Debris_Dodge
+
+## Starting the game
+
+There are several ways to start the game.
+
+### Running in 800x600 window mode
+
+ $ Debris_Dodge
+
+### Running with custom resolution
+
+ $ w=1600 h=1200 Debris_Dodge
+
+### Running full screen with custom resolution
+
+ $ fs=1 w=1200 h=800 Debris_Dodge
+
+
+
+
+
diff --git a/Games/Debris_Dodge/Rakefile b/Games/Debris_Dodge/Rakefile
new file mode 100644
index 0000000000..809eb5616a
--- /dev/null
+++ b/Games/Debris_Dodge/Rakefile
@@ -0,0 +1,2 @@
+require "bundler/gem_tasks"
+
diff --git a/Games/Debris_Dodge/bin/tank_island b/Games/Debris_Dodge/bin/tank_island
new file mode 100644
index 0000000000..1352ca07a9
--- /dev/null
+++ b/Games/Debris_Dodge/bin/tank_island
@@ -0,0 +1,82 @@
+#!/usr/bin/env ruby
+
+require 'gosu_texture_packer'
+require 'perlin_noise'
+require 'gosu'
+
+root_dir = File.expand_path(File.join(
+ File.dirname(File.dirname(__FILE__)), 'lib'))
+
+%w(
+ game_states/game_state.rb
+ game_states/play_state.rb
+ entities/components/component.rb
+ entities/components/ai/tank_motion_state.rb
+ entities/game_object.rb
+ entities/powerups/powerup.rb
+ entities/box.rb
+ entities/bullet.rb
+ entities/camera.rb
+ entities/components/ai/gun.rb
+ entities/components/ai/tank_chasing_state.rb
+ entities/components/ai/tank_fighting_state.rb
+ entities/components/ai/tank_fleeing_state.rb
+ entities/components/ai/tank_motion_fsm.rb
+ entities/components/ai/tank_motion_state.rb
+ entities/components/ai/tank_navigating_state.rb
+ entities/components/ai/tank_roaming_state.rb
+ entities/components/ai/tank_stuck_state.rb
+ entities/components/ai/vision.rb
+ entities/components/ai_input.rb
+ entities/components/box_graphics.rb
+ entities/components/bullet_graphics.rb
+ entities/components/bullet_physics.rb
+ entities/components/bullet_sounds.rb
+ entities/components/component.rb
+ entities/components/damage_graphics.rb
+ entities/components/explosion_graphics.rb
+ entities/components/explosion_sounds.rb
+ entities/components/health.rb
+ entities/components/player_input.rb
+ entities/components/player_sounds.rb
+ entities/components/powerup_graphics.rb
+ entities/components/powerup_sounds.rb
+ entities/components/tank_graphics.rb
+ entities/components/tank_health.rb
+ entities/components/tank_physics.rb
+ entities/components/tank_sounds.rb
+ entities/components/tree_graphics.rb
+ entities/damage.rb
+ entities/explosion.rb
+ entities/game_object.rb
+ entities/hud.rb
+ entities/map.rb
+ entities/object_pool.rb
+ entities/powerups/fire_rate_powerup.rb
+ entities/powerups/health_powerup.rb
+ entities/powerups/powerup.rb
+ entities/powerups/powerup_respawn_queue.rb
+ entities/powerups/repair_powerup.rb
+ entities/powerups/tank_speed_powerup.rb
+ entities/radar.rb
+ entities/score_display.rb
+ entities/tank.rb
+ entities/tree.rb
+ game_states/demo_state.rb
+ game_states/menu_state.rb
+ game_states/pause_state.rb
+ misc/axis_aligned_bounding_box.rb
+ misc/game_window.rb
+ misc/names.rb
+ misc/quad_tree.rb
+ misc/stats.rb
+ misc/stereo_sample.rb
+ misc/utils.rb
+).each do |f|
+ require File.join(root_dir, f)
+end
+
+$debug = false
+$window = GameWindow.new
+GameState.switch(MenuState.instance)
+$window.show
diff --git a/Games/Debris_Dodge/lib/entities/box.rb b/Games/Debris_Dodge/lib/entities/box.rb
new file mode 100644
index 0000000000..bf70c3db35
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/box.rb
@@ -0,0 +1,28 @@
+class Box < GameObject
+ attr_reader :health, :graphics, :angle
+
+ def initialize(object_pool, x, y)
+ super
+ @graphics = BoxGraphics.new(self)
+ @health = Health.new(self, object_pool, 10, true)
+ @angle = rand(-15..15)
+ end
+
+ def on_collision(object)
+ return unless object.physics.speed > 1.0
+ move(*Utils.point_at_distance(@x, @y, object.direction, 2))
+ @box = nil
+ end
+
+ def box
+ return @box if @box
+ w = @graphics.width / 2
+ h = @graphics.height / 2
+ # Bounding box adjusted to trim shadows
+ @box = [x - w + 4, y - h + 8,
+ x + w , y - h + 8,
+ x + w , y + h,
+ x - w + 4, y + h]
+ @box = Utils.rotate(@angle, @x, @y, *@box)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/bullet.rb b/Games/Debris_Dodge/lib/entities/bullet.rb
new file mode 100644
index 0000000000..93fb0df835
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/bullet.rb
@@ -0,0 +1,26 @@
+class Bullet < GameObject
+ attr_accessor :target_x, :target_y, :source, :speed, :fired_at
+
+ def initialize(object_pool, source_x, source_y, target_x, target_y)
+ super(object_pool, source_x, source_y)
+ @target_x, @target_y = target_x, target_y
+ BulletPhysics.new(self, object_pool)
+ BulletGraphics.new(self)
+ BulletSounds.play(self, object_pool.camera)
+ end
+
+ def box
+ [@x, @y]
+ end
+
+ def explode
+ Explosion.new(object_pool, @x, @y, @source)
+ mark_for_removal
+ end
+
+ def fire(source, speed)
+ @source = source
+ @speed = speed
+ @fired_at = Gosu.milliseconds
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/camera.rb b/Games/Debris_Dodge/lib/entities/camera.rb
new file mode 100644
index 0000000000..06ea2ead0f
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/camera.rb
@@ -0,0 +1,113 @@
+class Camera
+ attr_accessor :x, :y, :zoom
+ attr_reader :target
+
+ def target=(target)
+ @target = target
+ @x, @y = target.x, target.y
+ @zoom = 1
+ end
+
+ def desired_spot
+ if @target.physics.moving?
+ Utils.point_at_distance(
+ @target.x, @target.y,
+ @target.direction,
+ @target.physics.speed.ceil * 25)
+ else
+ [@target.x, @target.y]
+ end
+ end
+
+ def mouse_coords
+ x, y = target_delta_on_screen
+ mouse_x_on_map = @target.x +
+ (x + $window.mouse_x - ($window.width / 2)) / @zoom
+ mouse_y_on_map = @target.y +
+ (y + $window.mouse_y - ($window.height / 2)) / @zoom
+ [mouse_x_on_map, mouse_y_on_map].map(&:round)
+ end
+
+ def update
+ des_x, des_y = desired_spot
+ shift = Utils.adjust_speed(
+ @target.physics.speed).floor *
+ @target.speed_modifier + 1
+ if @x < des_x
+ if des_x - @x < shift
+ @x = des_x
+ else
+ @x += shift
+ end
+ elsif @x > des_x
+ if @x - des_x < shift
+ @x = des_x
+ else
+ @x -= shift
+ end
+ end
+ if @y < des_y
+ if des_y - @y < shift
+ @y = des_y
+ else
+ @y += shift
+ end
+ elsif @y > des_y
+ if @y - des_y < shift
+ @y = des_y
+ else
+ @y -= shift
+ end
+ end
+
+ zoom_delta = @zoom > 0 ? 0.01 : 1.0
+ zoom_delta = Utils.adjust_speed(zoom_delta)
+ if $window.button_down?(Gosu::KbUp)
+ @zoom -= zoom_delta unless @zoom < 0.7
+ elsif $window.button_down?(Gosu::KbDown)
+ @zoom += zoom_delta unless @zoom > 10
+ else
+ target_zoom = @target.physics.speed > 1.1 ? 0.75 : 1.0
+ if @zoom <= (target_zoom - 0.01)
+ @zoom += zoom_delta / 3
+ elsif @zoom > (target_zoom + 0.01)
+ @zoom -= zoom_delta / 3
+ end
+ end
+ end
+
+ def to_s
+ "FPS: #{Gosu.fps}. " <<
+ "#{@x}:#{@y} @ #{'%.2f' % @zoom}. " <<
+ 'WASD to move, arrows to zoom.'
+ end
+
+ def target_delta_on_screen
+ [(@x - @target.x) * @zoom, (@y - @target.y) * @zoom]
+ end
+
+ def draw_crosshair
+ factor = 0.5
+ x = $window.mouse_x
+ y = $window.mouse_y
+ c = crosshair
+ c.draw(x - c.width * factor / 2,
+ y - c.height * factor / 2,
+ 1000, factor, factor)
+ end
+
+ def viewport
+ x0 = @x - ($window.width / 2) / @zoom
+ x1 = @x + ($window.width / 2) / @zoom
+ y0 = @y - ($window.height / 2) / @zoom
+ y1 = @y + ($window.height / 2) / @zoom
+ [x0, x1, y0, y1]
+ end
+
+ private
+
+ def crosshair
+ @crosshair ||= Gosu::Image.new(
+ Utils.media_path('c_dot.png'), false)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/ai/gun.rb b/Games/Debris_Dodge/lib/entities/components/ai/gun.rb
new file mode 100644
index 0000000000..8dec7a30c2
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/ai/gun.rb
@@ -0,0 +1,114 @@
+class AiGun
+ DECISION_DELAY = 300
+ attr_reader :target, :desired_gun_angle
+
+ def initialize(object, vision)
+ @object = object
+ @vision = vision
+ @desired_gun_angle = rand(0..360)
+ @retarget_speed = rand(1..5)
+ @accuracy = rand(0..10)
+ @aggressiveness = rand(1..5)
+ end
+
+ def adjust_angle
+ adjust_desired_angle
+ adjust_gun_angle
+ end
+
+ def update
+ if @vision.in_sight.any?
+ if @vision.closest_tank != @target
+ change_target(@vision.closest_tank)
+ end
+ else
+ @target = nil
+ end
+
+ if @target
+ if (0..30 - rand(0..@accuracy)).include?(
+ (@desired_gun_angle - @object.gun_angle).abs.round)
+ distance = distance_to_target
+ if distance - 50 <= BulletPhysics::MAX_DIST
+ target_x, target_y = Utils.point_at_distance(
+ @object.x, @object.y, @object.gun_angle,
+ distance + 10 - rand(0..@accuracy))
+ if can_make_new_decision? && @object.can_shoot? &&
+ should_shoot?
+ @object.shoot(target_x, target_y)
+ end
+ end
+ end
+ end
+ end
+
+ def draw(viewport)
+ if $debug
+ color = Gosu::Color::BLUE
+ x, y = @object.x, @object.y
+ t_x, t_y = Utils.point_at_distance(x, y, @desired_gun_angle,
+ BulletPhysics::MAX_DIST)
+ $window.draw_line(x, y, color, t_x, t_y, color, 1001)
+ color = Gosu::Color::RED
+ t_x, t_y = Utils.point_at_distance(x, y, @object.gun_angle,
+ BulletPhysics::MAX_DIST)
+ $window.draw_line(x, y, color, t_x, t_y, color, 1000)
+ end
+ end
+
+ def distance_to_target
+ Utils.distance_between(
+ @object.x, @object.y, @target.x, @target.y)
+ end
+
+
+ def should_shoot?
+ rand * @aggressiveness > 0.3
+ end
+
+ def can_make_new_decision?
+ now = Gosu.milliseconds
+ if now - (@last_decision ||= 0) > DECISION_DELAY
+ @last_decision = now
+ true
+ end
+ end
+
+ def adjust_desired_angle
+ @desired_gun_angle = if @target
+ Utils.angle_between(
+ @object.x, @object.y, @target.x, @target.y)
+ else
+ @object.direction
+ end
+ end
+
+ def change_target(new_target)
+ @target = new_target
+ adjust_desired_angle
+ end
+
+ def adjust_gun_angle
+ actual = @object.gun_angle
+ desired = @desired_gun_angle
+ if actual > desired
+ if actual - desired > 180 # 0 -> 360 fix
+ @object.gun_angle = (actual + @retarget_speed) % 360
+ if @object.gun_angle < desired
+ @object.gun_angle = desired # damp
+ end
+ else
+ @object.gun_angle = [actual - @retarget_speed, desired].max
+ end
+ elsif actual < desired
+ if desired - actual > 180 # 360 -> 0 fix
+ @object.gun_angle = (360 + actual - @retarget_speed) % 360
+ if @object.gun_angle > desired
+ @object.gun_angle = desired # damp
+ end
+ else
+ @object.gun_angle = [actual + @retarget_speed, desired].min
+ end
+ end
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/ai/tank_chasing_state.rb b/Games/Debris_Dodge/lib/entities/components/ai/tank_chasing_state.rb
new file mode 100644
index 0000000000..36137754b5
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/ai/tank_chasing_state.rb
@@ -0,0 +1,30 @@
+class TankChasingState < TankMotionState
+ def initialize(object, vision, gun)
+ super(object, vision)
+ @object = object
+ @vision = vision
+ @gun = gun
+ end
+
+ def update
+ change_direction if should_change_direction?
+ drive
+ end
+
+ def change_direction
+ @object.physics.change_direction(
+ @gun.desired_gun_angle -
+ @gun.desired_gun_angle % 45)
+
+ @changed_direction_at = Gosu.milliseconds
+ @will_keep_direction_for = turn_time
+ end
+
+ def drive_time
+ 10000
+ end
+
+ def turn_time
+ rand(300..600)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/ai/tank_fighting_state.rb b/Games/Debris_Dodge/lib/entities/components/ai/tank_fighting_state.rb
new file mode 100644
index 0000000000..f7f9dbb121
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/ai/tank_fighting_state.rb
@@ -0,0 +1,47 @@
+class TankFightingState < TankMotionState
+ def initialize(object, vision)
+ super
+ @object = object
+ @vision = vision
+ end
+
+ def update
+ change_direction if should_change_direction?
+ if substate_expired?
+ rand > 0.1 ? drive : wait
+ end
+ end
+
+ def change_direction
+ change = case rand(0..100)
+ when 0..20
+ -45
+ when 20..40
+ 45
+ when 40..60
+ 90
+ when 60..80
+ -90
+ when 80..90
+ 135
+ when 90..100
+ -135
+ end
+ @object.physics.change_direction(
+ @object.direction + change)
+ @changed_direction_at = Gosu.milliseconds
+ @will_keep_direction_for = turn_time
+ end
+
+ def wait_time
+ rand(50..300)
+ end
+
+ def drive_time
+ rand(5000..10000)
+ end
+
+ def turn_time
+ rand(300..3000)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/ai/tank_fleeing_state.rb b/Games/Debris_Dodge/lib/entities/components/ai/tank_fleeing_state.rb
new file mode 100644
index 0000000000..4dbe5eab0b
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/ai/tank_fleeing_state.rb
@@ -0,0 +1,50 @@
+class TankFleeingState < TankMotionState
+ MAX_FLEE_TIME = 15 * 1000 # 15 seconds
+
+ def initialize(object, vision, gun)
+ super(object, vision)
+ @object = object
+ @vision = vision
+ @gun = gun
+ end
+
+ def can_flee?
+ return true unless @started_fleeing
+ Gosu.milliseconds - @started_fleeing < MAX_FLEE_TIME
+ end
+
+ def enter
+ @started_fleeing ||= Gosu.milliseconds
+ end
+
+ def update
+ change_direction if should_change_direction?
+ drive
+ end
+
+ def change_direction
+ closest_powerup = @vision.closest_powerup(
+ RepairPowerup, HealthPowerup)
+ if closest_powerup
+ angle = Utils.angle_between(
+ @object.x, @object.y,
+ closest_powerup.x, closest_powerup.y)
+ @object.physics.change_direction(
+ angle - angle % 45)
+ else
+ @object.physics.change_direction(
+ 180 + @gun.desired_gun_angle -
+ @gun.desired_gun_angle % 45)
+ end
+ @changed_direction_at = Gosu.milliseconds
+ @will_keep_direction_for = turn_time
+ end
+
+ def drive_time
+ 10000
+ end
+
+ def turn_time
+ rand(300..600)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/ai/tank_motion_fsm.rb b/Games/Debris_Dodge/lib/entities/components/ai/tank_motion_fsm.rb
new file mode 100644
index 0000000000..08fdbfc3ff
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/ai/tank_motion_fsm.rb
@@ -0,0 +1,102 @@
+class TankMotionFSM
+ STATE_CHANGE_DELAY = 500
+ LOCATION_CHECK_DELAY = 5000
+
+ def initialize(object, vision, gun)
+ @object = object
+ @vision = vision
+ @gun = gun
+ @roaming_state = TankRoamingState.new(object, vision)
+ @fighting_state = TankFightingState.new(object, vision)
+ @fleeing_state = TankFleeingState.new(object, vision, gun)
+ @chasing_state = TankChasingState.new(object, vision, gun)
+ @stuck_state = TankStuckState.new(object, vision, gun)
+ @navigating_state = TankNavigatingState.new(object, vision)
+ set_state(@roaming_state)
+ end
+
+ def on_collision(with)
+ @current_state.on_collision(with)
+ end
+
+ def on_damage(amount)
+ if @current_state == @roaming_state
+ set_state(@fighting_state)
+ end
+ end
+
+ def draw(viewport)
+ if $debug
+ @image && @image.draw(
+ @object.x - @image.width / 2,
+ @object.y + @object.graphics.height / 2 -
+ @image.height, 100)
+ end
+ end
+
+ def update
+ choose_state
+ @current_state.update
+ end
+
+ def set_state(state)
+ return unless state
+ return if state == @current_state
+ @last_state_change = Gosu.milliseconds
+ @current_state = state
+ state.enter
+ if $debug
+ @image = Gosu::Image.from_text(
+ $window, state.class.to_s,
+ Gosu.default_font_name, 18)
+ end
+ end
+
+ def choose_state
+ unless @vision.can_go_forward?
+ unless @current_state == @stuck_state
+ set_state(@navigating_state)
+ end
+ end
+ # Keep unstucking itself for a while
+ change_delay = STATE_CHANGE_DELAY
+ if @current_state == @stuck_state
+ change_delay *= 5
+ end
+ now = Gosu.milliseconds
+ return unless now - @last_state_change > change_delay
+ if @last_location_update.nil?
+ @last_location_update = now
+ @last_location = @object.location
+ end
+ if now - @last_location_update > LOCATION_CHECK_DELAY
+ unless @last_location.nil? || @current_state.waiting?
+ if Utils.distance_between(*@last_location, *@object.location) < 20
+ set_state(@stuck_state)
+ @stuck_state.stuck_at = @object.location
+ return
+ end
+ end
+ @last_location_update = now
+ @last_location = @object.location
+ end
+ if @gun.target
+ if @object.health.health > 40
+ if @gun.distance_to_target > BulletPhysics::MAX_DIST
+ new_state = @chasing_state
+ else
+ new_state = @fighting_state
+ end
+ else
+ if @fleeing_state.can_flee?
+ new_state = @fleeing_state
+ else
+ new_state = @fighting_state
+ end
+ end
+ else
+ new_state = @roaming_state
+ end
+ set_state(new_state)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/ai/tank_motion_state.rb b/Games/Debris_Dodge/lib/entities/components/ai/tank_motion_state.rb
new file mode 100644
index 0000000000..e1e65cbeb1
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/ai/tank_motion_state.rb
@@ -0,0 +1,84 @@
+class TankMotionState
+ def initialize(object, vision)
+ @object = object
+ @vision = vision
+ end
+
+ def enter
+ # Override if necessary
+ end
+
+ def change_direction
+ # Override
+ end
+
+ def wait_time
+ # Override and return a number
+ end
+
+ def drive_time
+ # Override and return a number
+ end
+
+ def turn_time
+ # Override and return a number
+ end
+
+ def update
+ # Override
+ end
+
+ def wait
+ @sub_state = :waiting
+ @started_waiting = Gosu.milliseconds
+ @will_wait_for = wait_time
+ @object.throttle_down = false
+ end
+
+ def waiting?
+ @sub_state == :waiting
+ end
+
+ def drive
+ @sub_state = :driving
+ @started_driving = Gosu.milliseconds
+ @will_drive_for = drive_time
+ @object.throttle_down = true
+ end
+
+ def should_change_direction?
+ return true unless @vision.can_go_forward?
+ return true unless @changed_direction_at
+ Gosu.milliseconds - @changed_direction_at >
+ @will_keep_direction_for
+ end
+
+ def substate_expired?
+ now = Gosu.milliseconds
+ case @sub_state
+ when :waiting
+ true if now - @started_waiting > @will_wait_for
+ when :driving
+ true if now - @started_driving > @will_drive_for
+ else
+ true
+ end
+ end
+
+ def on_collision(with)
+ change = case rand(0..100)
+ when 0..30
+ -90
+ when 30..60
+ 90
+ when 60..70
+ 135
+ when 80..90
+ -135
+ else
+ 180
+ end
+ @object.physics.change_direction(
+ @object.direction + change)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/ai/tank_navigating_state.rb b/Games/Debris_Dodge/lib/entities/components/ai/tank_navigating_state.rb
new file mode 100644
index 0000000000..8e888747e7
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/ai/tank_navigating_state.rb
@@ -0,0 +1,34 @@
+class TankNavigatingState < TankMotionState
+ def initialize(object, vision)
+ @object = object
+ @vision = vision
+ end
+
+ def update
+ change_direction if should_change_direction?
+ drive
+ end
+
+ def change_direction
+ closest_free_path = @vision.closest_free_path
+ if closest_free_path
+ @object.physics.change_direction(
+ Utils.angle_between(
+ @object.x, @object.y, *closest_free_path))
+ end
+ @changed_direction_at = Gosu.milliseconds
+ @will_keep_direction_for = turn_time
+ end
+
+ def wait_time
+ rand(10..100)
+ end
+
+ def drive_time
+ rand(1000..2000)
+ end
+
+ def turn_time
+ rand(300..1000)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/ai/tank_roaming_state.rb b/Games/Debris_Dodge/lib/entities/components/ai/tank_roaming_state.rb
new file mode 100644
index 0000000000..1942f809f9
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/ai/tank_roaming_state.rb
@@ -0,0 +1,83 @@
+class TankRoamingState < TankMotionState
+ def initialize(object, vision)
+ super
+ @object = object
+ @vision = vision
+ end
+
+ def update
+ change_direction if should_change_direction?
+ if substate_expired?
+ rand > 0.2 ? drive : wait
+ end
+ end
+
+ def required_powerups
+ required = []
+ health = @object.health.health
+ if @object.fire_rate_modifier < 2 && health > 50
+ required << FireRatePowerup
+ end
+ if @object.speed_modifier < 1.5 && health > 50
+ required << TankSpeedPowerup
+ end
+ if health < 100
+ required << RepairPowerup
+ end
+ if health < 190
+ required << HealthPowerup
+ end
+ required
+ end
+
+ def change_direction
+ closest_powerup = @vision.closest_powerup(
+ *required_powerups)
+ if closest_powerup
+ @seeking_powerup = true
+ angle = Utils.angle_between(
+ @object.x, @object.y,
+ closest_powerup.x, closest_powerup.y)
+ @object.physics.change_direction(
+ angle - angle % 45)
+ # Go away from inaccessable powerup
+ unless @vision.can_go_forward?
+ @seeking_powerup = false
+ @object.physics.change_direction(
+ @object.direction + 180)
+ end
+ else
+ @seeking_powerup = false
+ change = case rand(0..100)
+ when 0..30
+ -45
+ when 30..60
+ 45
+ when 60..80
+ 90
+ when 80..100
+ -90
+ end
+ @object.physics.change_direction(
+ @object.direction + change)
+ end
+ @changed_direction_at = Gosu.milliseconds
+ @will_keep_direction_for = turn_time
+ end
+
+ def wait_time
+ rand(50..400)
+ end
+
+ def drive_time
+ rand(1000..3000)
+ end
+
+ def turn_time
+ if @seeking_powerup
+ rand(100..300)
+ else
+ rand(1000..3000)
+ end
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/ai/tank_stuck_state.rb b/Games/Debris_Dodge/lib/entities/components/ai/tank_stuck_state.rb
new file mode 100644
index 0000000000..58c4ccfc15
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/ai/tank_stuck_state.rb
@@ -0,0 +1,45 @@
+class TankStuckState < TankMotionState
+ attr_accessor :stuck_at
+ def initialize(object, vision, gun)
+ super(object, vision)
+ @object = object
+ @vision = vision
+ @gun = gun
+ end
+
+ def update
+ change_direction if should_change_direction?
+ drive
+ end
+
+ def change_direction
+ closest_free_path = @vision.closest_free_path_away_from(
+ @stuck_at)
+ if closest_free_path
+ @object.physics.change_direction(
+ Utils.angle_between(
+ @object.x, @object.y, *closest_free_path))
+ else
+ if @object.health.health > 50 && rand > 0.9
+ @object.shoot(*Utils.point_at_distance(
+ *@object.location,
+ @object.gun_angle,
+ 150))
+ end
+ end
+ @changed_direction_at = Gosu.milliseconds
+ @will_keep_direction_for = turn_time
+ end
+
+ def wait_time
+ rand(10..100)
+ end
+
+ def drive_time
+ rand(1000..2000)
+ end
+
+ def turn_time
+ rand(1000..2000)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/ai/vision.rb b/Games/Debris_Dodge/lib/entities/components/ai/vision.rb
new file mode 100644
index 0000000000..cd6b9a535a
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/ai/vision.rb
@@ -0,0 +1,109 @@
+class AiVision
+ CACHE_TIMEOUT = 500
+ POWERUP_CACHE_TIMEOUT = 50
+ attr_reader :in_sight
+
+ def initialize(viewer, object_pool, distance)
+ @viewer = viewer
+ @object_pool = object_pool
+ @distance = distance
+ end
+
+ def can_go_forward?
+ in_front = Utils.point_at_distance(
+ *@viewer.location, @viewer.direction, 40)
+ @object_pool.map.can_move_to?(*in_front) &&
+ @object_pool.nearby_point(*in_front, 40, @viewer)
+ .reject { |o| o.is_a? Powerup }.empty?
+ end
+
+ def update
+ @in_sight = @object_pool.nearby(@viewer, @distance)
+ end
+
+ def closest_free_path(away_from = nil)
+ paths = []
+ 5.times do |i|
+ if paths.any?
+ return farthest_from(paths, away_from)
+ end
+ radius = 55 - i * 5
+ range_x = range_y = [-radius, 0, radius]
+ range_x.shuffle.each do |x|
+ range_y.shuffle.each do |y|
+ x = @viewer.x + x
+ y = @viewer.y + y
+ if @object_pool.map.can_move_to?(x, y) &&
+ @object_pool.nearby_point(x, y, radius, @viewer)
+ .reject { |o| o.is_a? Powerup }.empty?
+ if away_from
+ paths << [x, y]
+ else
+ return [x, y]
+ end
+ end
+ end
+ end
+ end
+ false
+ end
+
+ alias :closest_free_path_away_from :closest_free_path
+
+ def closest_tank
+ now = Gosu.milliseconds
+ @closest_tank = nil
+ if now - (@cache_updated_at ||= 0) > CACHE_TIMEOUT
+ @closest_tank = nil
+ @cache_updated_at = now
+ end
+ @closest_tank ||= find_closest_tank
+ end
+
+ def closest_powerup(*suitable)
+ now = Gosu.milliseconds
+ @closest_powerup = nil
+ if now - (@powerup_cache_updated_at ||= 0) > POWERUP_CACHE_TIMEOUT
+ @closest_powerup = nil
+ @powerup_cache_updated_at = now
+ end
+ @closest_powerup ||= find_closest_powerup(*suitable)
+ end
+
+ private
+
+ def farthest_from(paths, away_from)
+ paths.sort do |p1, p2|
+ Utils.distance_between(*p1, *away_from) <=>
+ Utils.distance_between(*p2, *away_from)
+ end.first
+ end
+
+ def find_closest_powerup(*suitable)
+ if suitable.empty?
+ suitable = [FireRatePowerup,
+ HealthPowerup,
+ RepairPowerup,
+ TankSpeedPowerup]
+ end
+ @in_sight.select do |o|
+ suitable.include?(o.class)
+ end.sort do |a, b|
+ x, y = @viewer.x, @viewer.y
+ d1 = Utils.distance_between(x, y, a.x, a.y)
+ d2 = Utils.distance_between(x, y, b.x, b.y)
+ d1 <=> d2
+ end.first
+ end
+
+ def find_closest_tank
+ @in_sight.select do |o|
+ o.class == Tank && !o.health.dead?
+ end.sort do |a, b|
+ x, y = @viewer.x, @viewer.y
+ d1 = Utils.distance_between(x, y, a.x, a.y)
+ d2 = Utils.distance_between(x, y, b.x, b.y)
+ d1 <=> d2
+ end.first
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/ai_input.rb b/Games/Debris_Dodge/lib/entities/components/ai_input.rb
new file mode 100644
index 0000000000..86671b7885
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/ai_input.rb
@@ -0,0 +1,70 @@
+class AiInput < Component
+ # Dark red
+ NAME_COLOR = Gosu::Color.argb(0xeeb10000)
+ UPDATE_RATE = 10 # ms
+ attr_reader :name
+ attr_reader :stats
+
+ def initialize(name, object_pool)
+ super(nil)
+ @object_pool = object_pool
+ @stats = Stats.new(name)
+ @name = name
+ @last_update = Gosu.milliseconds
+ end
+
+ def control(obj)
+ self.object = obj
+ object.components << self
+ @vision = AiVision.new(obj, @object_pool,
+ rand(700..1200))
+ @gun = AiGun.new(obj, @vision)
+ @motion = TankMotionFSM.new(obj, @vision, @gun)
+ end
+
+ def on_collision(with)
+ return if object.health.dead?
+ @motion.on_collision(with)
+ end
+
+ def on_damage(amount)
+ @motion.on_damage(amount)
+ @stats.add_damage(amount)
+ end
+
+ def update
+ return respawn if object.health.dead?
+ @gun.adjust_angle
+ now = Gosu.milliseconds
+ return if now - @last_update < UPDATE_RATE
+ @last_update = now
+ @vision.update
+ @gun.update
+ @motion.update
+ end
+
+ def draw(viewport)
+ @motion.draw(viewport)
+ @gun.draw(viewport)
+ @name_image ||= Gosu::Image.from_text(
+ @name, 20, font: Gosu.default_font_name)
+ @name_image.draw(
+ x - @name_image.width / 2 - 1,
+ y + object.graphics.height / 2, 100,
+ 1, 1, Gosu::Color::WHITE)
+ @name_image.draw(
+ x - @name_image.width / 2,
+ y + object.graphics.height / 2, 100,
+ 1, 1, NAME_COLOR)
+ end
+
+ private
+
+ def respawn
+ if object.health.should_respawn?
+ object.health.restore
+ object.move(*@object_pool.map.spawn_point)
+ PlayerSounds.respawn(object, @object_pool.camera)
+ end
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/box_graphics.rb b/Games/Debris_Dodge/lib/entities/components/box_graphics.rb
new file mode 100644
index 0000000000..e14a2eef79
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/box_graphics.rb
@@ -0,0 +1,39 @@
+class BoxGraphics < Component
+ def initialize(object)
+ super(object)
+ load_sprite
+ end
+
+ def draw(viewport)
+ @box.draw_rot(x, y, 0, object.angle)
+ Utils.mark_corners(object.box) if $debug
+ end
+
+ def height
+ @box.height
+ end
+
+ def width
+ @box.width
+ end
+
+ private
+
+ def load_sprite
+ frame = boxes.frame_list.sample
+ @box = boxes.frame(frame)
+ end
+
+ def center_x
+ @center_x ||= x - width / 2
+ end
+
+ def center_y
+ @center_y ||= y - height / 2
+ end
+
+ def boxes
+ @@boxes ||= Gosu::TexturePacker.load_json(
+ Utils.media_path('boxes_barrels.json'))
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/bullet_graphics.rb b/Games/Debris_Dodge/lib/entities/components/bullet_graphics.rb
new file mode 100644
index 0000000000..1a0d2cc54d
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/bullet_graphics.rb
@@ -0,0 +1,13 @@
+class BulletGraphics < Component
+ def draw(viewport)
+ image.draw(x - 8, y - 8, 1)
+ Utils.mark_corners(object.box) if $debug
+ end
+
+ private
+
+ def image
+ @@bullet ||= Gosu::Image.new(
+ Utils.media_path('bullet.png'), false)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/bullet_physics.rb b/Games/Debris_Dodge/lib/entities/components/bullet_physics.rb
new file mode 100644
index 0000000000..71d399bfff
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/bullet_physics.rb
@@ -0,0 +1,65 @@
+class BulletPhysics < Component
+ START_DIST = 20
+ MAX_DIST = 500
+
+ def initialize(game_object, object_pool)
+ super(game_object)
+ x, y = point_at_distance(START_DIST)
+ object.move(x, y)
+ @object_pool = object_pool
+ if trajectory_length > MAX_DIST
+ object.target_x, object.target_y = point_at_distance(MAX_DIST)
+ end
+ end
+
+ def update
+ fly_speed = Utils.adjust_speed(object.speed)
+ now = Gosu.milliseconds
+ @last_update ||= object.fired_at
+ fly_distance = (now - @last_update) * 0.001 * fly_speed
+ object.move(*point_at_distance(fly_distance))
+ @last_update = now
+ check_hit
+ object.explode if arrived?
+ end
+
+ def trajectory_length
+ Utils.distance_between(object.target_x, object.target_y, x, y)
+ end
+
+ def point_at_distance(distance)
+ if distance > trajectory_length
+ return [object.target_x, object.target_y]
+ end
+ distance_factor = distance.to_f / trajectory_length
+ p_x = x + (object.target_x - x) * distance_factor
+ p_y = y + (object.target_y - y) * distance_factor
+ [p_x, p_y]
+ end
+
+ private
+
+ def check_hit
+ @object_pool.nearby(object, 50).each do |obj|
+ next if obj == object.source # Don't hit source tank
+ if obj.class == Tree
+ if Utils.distance_between(x, y, obj.x, obj.y) < 10
+ return do_hit(obj) if obj.respond_to?(:health)
+ end
+ elsif Utils.point_in_poly(x, y, *obj.box)
+ # Direct hit - extra damage
+ return do_hit(obj) if obj.respond_to?(:health)
+ end
+ end
+ end
+
+ def do_hit(obj)
+ obj.health.inflict_damage(20, object.source)
+ object.target_x = x
+ object.target_y = y
+ end
+
+ def arrived?
+ x == object.target_x && y == object.target_y
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/bullet_sounds.rb b/Games/Debris_Dodge/lib/entities/components/bullet_sounds.rb
new file mode 100644
index 0000000000..a79ea40f00
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/bullet_sounds.rb
@@ -0,0 +1,15 @@
+class BulletSounds
+ class << self
+ def play(object, camera)
+ volume, pan = Utils.volume_and_pan(object, camera)
+ sound.play(object.object_id, pan, volume)
+ end
+
+ private
+
+ def sound
+ @@sound ||= StereoSample.new(
+ $window, Utils.media_path('fire.ogg'))
+ end
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/component.rb b/Games/Debris_Dodge/lib/entities/components/component.rb
new file mode 100644
index 0000000000..83e20e8833
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/component.rb
@@ -0,0 +1,32 @@
+class Component
+ attr_reader :object # better performance
+
+ def initialize(game_object = nil)
+ self.object = game_object
+ end
+
+ def update
+ # override
+ end
+
+ def draw(viewport)
+ # override
+ end
+
+ protected
+
+ def object=(obj)
+ if obj
+ @object = obj
+ obj.components << self
+ end
+ end
+
+ def x
+ @object.x
+ end
+
+ def y
+ @object.y
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/damage_graphics.rb b/Games/Debris_Dodge/lib/entities/components/damage_graphics.rb
new file mode 100644
index 0000000000..1c9ca84f91
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/damage_graphics.rb
@@ -0,0 +1,21 @@
+class DamageGraphics < Component
+ def initialize(object_pool)
+ super
+ @image = images.sample
+ @angle = rand(0..360)
+ end
+
+ def draw(viewport)
+ @image.draw_rot(x, y, 0, @angle)
+ end
+
+ private
+
+ def images
+ @@images ||= (1..4).map do |i|
+ Gosu::Image.new(
+ Utils.media_path("damage#{i}.png"), false
+ )
+ end
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/explosion_graphics.rb b/Games/Debris_Dodge/lib/entities/components/explosion_graphics.rb
new file mode 100644
index 0000000000..efbff0cfc6
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/explosion_graphics.rb
@@ -0,0 +1,43 @@
+class ExplosionGraphics < Component
+ FRAME_DELAY = 16.66 # ms
+
+ def initialize(game_object)
+ super
+ @current_frame = 0
+ end
+
+ def draw(viewport)
+ image = current_frame
+ image.draw(
+ x - image.width / 2 + 3,
+ y - image.height / 2 - 35,
+ 20)
+ end
+
+ def update
+ now = Gosu.milliseconds
+ delta = now - (@last_frame ||= now)
+ if delta > FRAME_DELAY
+ @last_frame = now
+ end
+ @current_frame += (delta / FRAME_DELAY).floor
+ object.mark_for_removal if done?
+ end
+
+ private
+
+ def current_frame
+ animation[@current_frame % animation.size]
+ end
+
+ def done?
+ @done ||= @current_frame >= animation.size
+ end
+
+ def animation
+ @@animation ||=
+ Gosu::Image.load_tiles(
+ $window, Utils.media_path('explosion.png'),
+ 128, 128, false)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/explosion_sounds.rb b/Games/Debris_Dodge/lib/entities/components/explosion_sounds.rb
new file mode 100644
index 0000000000..871fa58b2c
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/explosion_sounds.rb
@@ -0,0 +1,16 @@
+class ExplosionSounds
+ class << self
+ def play(object, camera)
+ volume, pan = Utils.volume_and_pan(object, camera)
+ sound.play(object.object_id, pan, volume)
+ end
+
+ private
+
+ def sound
+ @@sound ||= StereoSample.new(
+ $window, Utils.media_path('explosion.ogg'))
+ end
+ end
+end
+
diff --git a/Games/Debris_Dodge/lib/entities/components/health.rb b/Games/Debris_Dodge/lib/entities/components/health.rb
new file mode 100644
index 0000000000..a116742b2e
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/health.rb
@@ -0,0 +1,87 @@
+class Health < Component
+ attr_accessor :health
+
+ def initialize(object, object_pool, health, explodes)
+ super(object)
+ @explodes = explodes
+ @object_pool = object_pool
+ @initial_health = @health = health
+ @health_updated = true
+ end
+
+ def restore
+ @health = @initial_health
+ @health_updated = true
+ end
+
+ def increase(amount)
+ @health = [@health + 25, @initial_health * 2].min
+ @health_updated = true
+ end
+
+ def damaged?
+ @health < @initial_health
+ end
+
+ def dead?
+ @health < 1
+ end
+
+ def update
+ update_image
+ end
+
+ def inflict_damage(amount, cause)
+ if @health > 0
+ @health_updated = true
+ if object.respond_to?(:input)
+ object.input.stats.add_damage(amount)
+ # Don't count damage to trees and boxes
+ if cause.respond_to?(:input) && cause != object
+ cause.input.stats.add_damage_dealt(amount)
+ end
+ end
+ @health = [@health - amount.to_i, 0].max
+ after_death(cause) if dead?
+ end
+ end
+
+ def draw(viewport)
+ return unless draw?
+ @image && @image.draw(
+ x - @image.width / 2,
+ y - object.graphics.height / 2 -
+ @image.height, 100)
+ end
+
+ protected
+
+ def draw?
+ $debug
+ end
+
+ def update_image
+ return unless draw?
+ if @health_updated
+ text = @health.to_s
+ font_size = 18
+ @image = Gosu::Image.from_text(
+ text,
+ font_size, font: Gosu.default_font_name)
+ @health_updated = false
+ end
+ end
+
+ def after_death(cause)
+ if @explodes
+ Thread.new do
+ sleep(rand(0.1..0.3))
+ Explosion.new(@object_pool, x, y, cause)
+ sleep 0.3
+ object.mark_for_removal
+ end
+ else
+ object.mark_for_removal
+ end
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/player_input.rb b/Games/Debris_Dodge/lib/entities/components/player_input.rb
new file mode 100644
index 0000000000..471838da1f
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/player_input.rb
@@ -0,0 +1,100 @@
+class PlayerInput < Component
+ # Dark green
+ NAME_COLOR = Gosu::Color.argb(0xee084408)
+ attr_reader :stats
+
+ def initialize(name, camera, object_pool)
+ super(nil)
+ @name = name
+ @stats = Stats.new(name)
+ @camera = camera
+ @object_pool = object_pool
+ end
+
+ def control(obj)
+ self.object = obj
+ obj.components << self
+ end
+
+ def on_collision(with)
+ end
+
+ def on_damage(amount)
+ @stats.add_damage(amount)
+ end
+
+ def update
+ return respawn if object.health.dead?
+ d_x, d_y = @camera.target_delta_on_screen
+ atan = Math.atan2(($window.width / 2) - d_x - $window.mouse_x,
+ ($window.height / 2) - d_y - $window.mouse_y)
+ object.gun_angle = -atan * 180 / Math::PI
+ motion_buttons = [Gosu::KbW, Gosu::KbS, Gosu::KbA, Gosu::KbD]
+
+ if any_button_down?(*motion_buttons)
+ object.throttle_down = true
+ object.physics.change_direction(
+ change_angle(object.direction, *motion_buttons))
+ else
+ object.throttle_down = false
+ end
+
+ if Utils.button_down?(Gosu::MsLeft)
+ object.shoot(*@camera.mouse_coords)
+ end
+ end
+
+ def draw(viewport)
+ @name_image ||= Gosu::Image.from_text(
+ @name, 20, font: Gosu.default_font_name)
+ @name_image.draw(
+ x - @name_image.width / 2 - 1,
+ y + object.graphics.height / 2, 100,
+ 1, 1, Gosu::Color::WHITE)
+ @name_image.draw(
+ x - @name_image.width / 2,
+ y + object.graphics.height / 2, 100,
+ 1, 1, NAME_COLOR)
+ end
+
+ private
+
+ def respawn
+ if object.health.should_respawn?
+ object.health.restore
+ object.move(*@object_pool.map.spawn_point)
+ @camera.x, @camera.y = x, y
+ PlayerSounds.respawn(object, @camera)
+ end
+ end
+
+ def any_button_down?(*buttons)
+ buttons.each do |b|
+ return true if Utils.button_down?(b)
+ end
+ false
+ end
+
+ def change_angle(previous_angle, up, down, right, left)
+ if Utils.button_down?(up)
+ angle = 0.0
+ angle += 45.0 if Utils.button_down?(left)
+ angle -= 45.0 if Utils.button_down?(right)
+ elsif Utils.button_down?(down)
+ angle = 180.0
+ angle -= 45.0 if Utils.button_down?(left)
+ angle += 45.0 if Utils.button_down?(right)
+ elsif Utils.button_down?(left)
+ angle = 90.0
+ angle += 45.0 if Utils.button_down?(up)
+ angle -= 45.0 if Utils.button_down?(down)
+ elsif Utils.button_down?(right)
+ angle = 270.0
+ angle -= 45.0 if Utils.button_down?(up)
+ angle += 45.0 if Utils.button_down?(down)
+ end
+ angle = (angle + 360) % 360 if angle && angle < 0
+ (angle || previous_angle)
+ end
+
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/player_sounds.rb b/Games/Debris_Dodge/lib/entities/components/player_sounds.rb
new file mode 100644
index 0000000000..3c5d17bd3b
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/player_sounds.rb
@@ -0,0 +1,16 @@
+class PlayerSounds
+ class << self
+ def respawn(object, camera)
+ volume, pan = Utils.volume_and_pan(object, camera)
+ respawn_sound.play(object.object_id, pan, volume * 0.5)
+ end
+
+ private
+
+ def respawn_sound
+ @@respawn ||= StereoSample.new(
+ $window, Utils.media_path('respawn.wav'))
+ end
+ end
+end
+
diff --git a/Games/Debris_Dodge/lib/entities/components/powerup_graphics.rb b/Games/Debris_Dodge/lib/entities/components/powerup_graphics.rb
new file mode 100644
index 0000000000..808a2262ca
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/powerup_graphics.rb
@@ -0,0 +1,22 @@
+class PowerupGraphics < Component
+ def initialize(object, type)
+ super(object)
+ @type = type
+ end
+
+ def draw(viewport)
+ image.draw(x - 12, y - 12, 1)
+ Utils.mark_corners(object.box) if $debug
+ end
+
+ private
+
+ def image
+ @image ||= images.frame("#{@type}.png")
+ end
+
+ def images
+ @@images ||= Gosu::TexturePacker.load_json(
+ Utils.media_path('pickups.json'))
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/powerup_sounds.rb b/Games/Debris_Dodge/lib/entities/components/powerup_sounds.rb
new file mode 100644
index 0000000000..509272247a
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/powerup_sounds.rb
@@ -0,0 +1,15 @@
+class PowerupSounds
+ class << self
+ def play(object, camera)
+ volume, pan = Utils.volume_and_pan(object, camera)
+ sound.play(object.object_id, pan, volume)
+ end
+
+ private
+
+ def sound
+ @@sound ||= StereoSample.new(
+ $window, Utils.media_path('powerup.ogg'))
+ end
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/tank_graphics.rb b/Games/Debris_Dodge/lib/entities/components/tank_graphics.rb
new file mode 100644
index 0000000000..09ed2ceacf
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/tank_graphics.rb
@@ -0,0 +1,46 @@
+class TankGraphics < Component
+ def initialize(game_object)
+ super(game_object)
+ @body_normal = units.frame('tank1_body.png')
+ @shadow_normal = units.frame('tank1_body_shadow.png')
+ @gun_normal = units.frame('tank1_dualgun.png')
+ @body_dead = units.frame('tank1_body_destroyed.png')
+ @shadow_dead = units.frame('tank1_body_destroyed_shadow.png')
+ @gun_dead = nil
+ update
+ end
+
+ def update
+ if object && object.health.dead?
+ @body = @body_dead
+ @gun = @gun_dead
+ @shadow = @shadow_dead
+ else
+ @body = @body_normal
+ @gun = @gun_normal
+ @shadow = @shadow_normal
+ end
+ end
+
+ def draw(viewport)
+ @shadow.draw_rot(x - 1, y - 1, 0, object.direction)
+ @body.draw_rot(x, y, 1, object.direction)
+ @gun.draw_rot(x, y, 2, object.gun_angle) if @gun
+ Utils.mark_corners(object.box) if $debug
+ end
+
+ def width
+ @body.width
+ end
+
+ def height
+ @body.height
+ end
+
+ private
+
+ def units
+ @@units = Gosu::TexturePacker.load_json(
+ Utils.media_path('ground_units.json'), :precise)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/tank_health.rb b/Games/Debris_Dodge/lib/entities/components/tank_health.rb
new file mode 100644
index 0000000000..4da7fa8e46
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/tank_health.rb
@@ -0,0 +1,32 @@
+class TankHealth < Health
+ RESPAWN_DELAY = 5000
+ attr_accessor :health
+
+ def initialize(object, object_pool)
+ super(object, object_pool, 100, true)
+ end
+
+ def should_respawn?
+ if @death_time
+ Gosu.milliseconds - @death_time > RESPAWN_DELAY
+ end
+ end
+
+ protected
+
+ def draw?
+ true
+ end
+
+ def after_death(cause)
+ @death_time = Gosu.milliseconds
+ object.reset_modifiers
+ object.input.stats.add_death
+ kill = object != cause ? 1 : -1
+ cause.input.stats.add_kill(kill)
+ Thread.new do
+ sleep(rand(0.1..0.3))
+ Explosion.new(@object_pool, x, y, cause)
+ end
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/tank_physics.rb b/Games/Debris_Dodge/lib/entities/components/tank_physics.rb
new file mode 100644
index 0000000000..072a790585
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/tank_physics.rb
@@ -0,0 +1,179 @@
+class TankPhysics < Component
+ attr_accessor :speed, :in_collision, :collides_with
+
+ def initialize(game_object, object_pool)
+ super(game_object)
+ @object_pool = object_pool
+ @map = object_pool.map
+ @speed = 0.0
+ end
+
+ def can_move_to?(x, y)
+ old_x, old_y = object.x, object.y
+ object.move(x, y)
+ return false unless @map.can_move_to?(x, y)
+ @object_pool.nearby(object, 100).each do |obj|
+ next if obj.class == Bullet && obj.source == object
+ if collides_with_poly?(obj.box)
+ if obj.is_a? Powerup
+ obj.on_collision(object)
+ else
+ @collides_with = obj
+ # Allow to get unstuck
+ old_distance = Utils.distance_between(
+ obj.x, obj.y, old_x, old_y)
+ new_distance = Utils.distance_between(
+ obj.x, obj.y, x, y)
+ return false if new_distance < old_distance
+ end
+ else
+ @collides_with = nil
+ end
+ end
+ true
+ ensure
+ object.move(old_x, old_y)
+ end
+
+ def change_direction(new_direction)
+ change = (new_direction - object.direction + 360) % 360
+ change = 360 - change if change > 180
+ if change > 90
+ @speed = 0
+ elsif change > 45
+ @speed *= 0.33
+ elsif change > 0
+ @speed *= 0.66
+ end
+ object.direction = new_direction % 360
+ end
+
+ def moving?
+ @speed > 0
+ end
+
+ def box_height
+ @box_height ||= object.graphics.height
+ end
+
+ def box_width
+ @box_width ||= object.graphics.width
+ end
+
+ # Tank box looks like H. Vertices:
+ # 1 2 5 6
+ # 3 4
+ #
+ # 10 9
+ # 12 11 8 7
+ def box
+ w = box_width / 2 - 1
+ h = box_height / 2 - 1
+ tw = 8 # track width
+ fd = 8 # front depth
+ rd = 6 # rear depth
+ Utils.rotate(object.direction, x, y,
+ x + w, y + h, #1
+ x + w - tw, y + h, #2
+ x + w - tw, y + h - fd, #3
+
+ x - w + tw, y + h - fd, #4
+ x - w + tw, y + h, #5
+ x - w, y + h, #6
+
+ x - w, y - h, #7
+ x - w + tw, y - h, #8
+ x - w + tw, y - h + rd, #9
+
+ x + w - tw, y - h + rd, #10
+ x + w - tw, y - h, #11
+ x + w, y - h, #12
+ )
+ end
+
+ def update
+ if object.throttle_down && !object.health.dead?
+ accelerate
+ else
+ decelerate
+ end
+ if @speed > 0
+ new_x, new_y = x, y
+ speed = apply_movement_penalty(@speed)
+ shift = Utils.adjust_speed(speed) * object.speed_modifier
+ case @object.direction.to_i
+ when 0
+ new_y -= shift
+ when 45
+ new_x += shift
+ new_y -= shift
+ when 90
+ new_x += shift
+ when 135
+ new_x += shift
+ new_y += shift
+ when 180
+ new_y += shift
+ when 225
+ new_y += shift
+ new_x -= shift
+ when 270
+ new_x -= shift
+ when 315
+ new_x -= shift
+ new_y -= shift
+ end
+ if can_move_to?(new_x, new_y)
+ object.move(new_x, new_y)
+ @in_collision = false
+ else
+ object.on_collision(@collides_with)
+ @speed = 0.0
+ @in_collision = true
+ end
+ end
+ end
+
+ private
+
+ def apply_movement_penalty(speed)
+ speed * (1.0 - @map.movement_penalty(x, y))
+ end
+
+ def accelerate
+ @speed += 0.08 if @speed < 5
+ end
+
+ def decelerate
+ if @speed > 0
+ @speed = [@speed - 0.5, 0].max
+ elsif @speed < 0
+ @speed = [@speed + 0.5, 0].min
+ end
+ damp_speed
+ end
+
+ def damp_speed
+ @speed = 0 if @speed < 0.01
+ end
+
+ def collides_with_poly?(poly)
+ if poly
+ if poly.size == 2
+ px, py = poly
+ return Utils.point_in_poly(px, py, *box)
+ end
+ poly.each_slice(2) do |x, y|
+ return true if Utils.point_in_poly(x, y, *box)
+ end
+ box.each_slice(2) do |x, y|
+ return true if Utils.point_in_poly(x, y, *poly)
+ end
+ end
+ false
+ end
+
+ def collides_with_point?(x, y)
+ Utils.point_in_poly(x, y, box)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/tank_sounds.rb b/Games/Debris_Dodge/lib/entities/components/tank_sounds.rb
new file mode 100644
index 0000000000..dfeb6a7978
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/tank_sounds.rb
@@ -0,0 +1,43 @@
+class TankSounds < Component
+ def initialize(object, object_pool)
+ super(object)
+ @object_pool = object_pool
+ end
+
+ def update
+ id = object.object_id
+ if object.physics.moving?
+ move_volume = Utils.volume(
+ object, @object_pool.camera)
+ pan = Utils.pan(object, @object_pool.camera)
+ if driving_sound.paused?(id)
+ driving_sound.resume(id)
+ elsif driving_sound.stopped?(id)
+ driving_sound.play(id, pan, 0.5, 1, true)
+ end
+ driving_sound.volume_and_pan(id, move_volume * 0.5, pan)
+ else
+ if driving_sound.playing?(id)
+ driving_sound.pause(id)
+ end
+ end
+ end
+
+ def collide
+ vol, pan = Utils.volume_and_pan(
+ object, @object_pool.camera)
+ crash_sound.play(self.object_id, pan, vol, 1, false)
+ end
+
+ private
+
+ def driving_sound
+ @@driving_sound ||= StereoSample.new(
+ $window, Utils.media_path('tank_driving.ogg'))
+ end
+
+ def crash_sound
+ @@crash_sound ||= StereoSample.new(
+ $window, Utils.media_path('metal_interaction2.wav'))
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/components/tree_graphics.rb b/Games/Debris_Dodge/lib/entities/components/tree_graphics.rb
new file mode 100644
index 0000000000..9be245255a
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/components/tree_graphics.rb
@@ -0,0 +1,69 @@
+class TreeGraphics < Component
+ SHAKE_TIME = 100
+ SHAKE_COOLDOWN = 200
+ SHAKE_DISTANCE = [2, 1, 0, -1, -2, -1, 0, 1, 0, -1, 0]
+ def initialize(object, seed)
+ super(object)
+ load_sprite(seed)
+ end
+
+ def shake(direction)
+ now = Gosu.milliseconds
+ return if @shake_start &&
+ now - @shake_start < SHAKE_TIME + SHAKE_COOLDOWN
+ @shake_start = now
+ @shake_direction = direction
+ @shaking = true
+ end
+
+ def adjust_shake(x, y, shaking_for)
+ elapsed = [shaking_for, SHAKE_TIME].min / SHAKE_TIME.to_f
+ frame = ((SHAKE_DISTANCE.length - 1) * elapsed).floor
+ distance = SHAKE_DISTANCE[frame]
+ Utils.point_at_distance(x, y, @shake_direction, distance)
+ end
+
+ def draw(viewport)
+ if @shaking
+ shaking_for = Gosu.milliseconds - @shake_start
+ shaking_x, shaking_y = adjust_shake(
+ center_x, center_y, shaking_for)
+ @tree.draw(shaking_x, shaking_y, 5)
+ if shaking_for >= SHAKE_TIME
+ @shaking = false
+ end
+ else
+ @tree.draw(center_x, center_y, 5)
+ end
+ Utils.mark_corners(object.box) if $debug
+ end
+
+ def height
+ @tree.height
+ end
+
+ def width
+ @tree.width
+ end
+
+ private
+
+ def load_sprite(seed)
+ frame_list = trees.frame_list
+ frame = frame_list[(frame_list.size * seed).round]
+ @tree = trees.frame(frame)
+ end
+
+ def center_x
+ @center_x ||= x - @tree.width / 2
+ end
+
+ def center_y
+ @center_y ||= y - @tree.height / 2
+ end
+
+ def trees
+ @@trees ||= Gosu::TexturePacker.load_json(
+ Utils.media_path('trees_packed.json'))
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/damage.rb b/Games/Debris_Dodge/lib/entities/damage.rb
new file mode 100644
index 0000000000..a501ae6ae7
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/damage.rb
@@ -0,0 +1,26 @@
+class Damage < GameObject
+ MAX_INSTANCES = 300
+ @@instances = []
+
+ def initialize(object_pool, x, y)
+ super
+ DamageGraphics.new(self)
+ track(self)
+ end
+
+ def effect?
+ true
+ end
+
+ private
+
+ def track(instance)
+ if @@instances.size < MAX_INSTANCES
+ @@instances << instance
+ else
+ out = @@instances.shift
+ out.mark_for_removal
+ @@instances << instance
+ end
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/explosion.rb b/Games/Debris_Dodge/lib/entities/explosion.rb
new file mode 100644
index 0000000000..772c38351e
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/explosion.rb
@@ -0,0 +1,34 @@
+class Explosion < GameObject
+
+ def initialize(object_pool, x, y, source)
+ super(object_pool, x, y)
+ @source = source
+ @object_pool = object_pool
+ if @object_pool.map.can_move_to?(x, y)
+ Damage.new(@object_pool, x, y)
+ end
+ ExplosionGraphics.new(self)
+ ExplosionSounds.play(self, object_pool.camera)
+ inflict_damage
+ end
+
+ def effect?
+ true
+ end
+
+ def mark_for_removal
+ super
+ end
+
+ private
+
+ def inflict_damage
+ object_pool.nearby(self, 100).each do |obj|
+ if obj.respond_to?(:health)
+ obj.health.inflict_damage(
+ Math.sqrt(3 * 100 - Utils.distance_between(
+ obj.x, obj.y, @x, @y)), @source)
+ end
+ end
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/game_object.rb b/Games/Debris_Dodge/lib/entities/game_object.rb
new file mode 100644
index 0000000000..05fa774bab
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/game_object.rb
@@ -0,0 +1,54 @@
+class GameObject
+ attr_reader :x, :y, :location, :components
+ def initialize(object_pool, x, y)
+ @x, @y = x, y
+ @location = [x, y]
+ @components = []
+ @object_pool = object_pool
+ @object_pool.add(self)
+ end
+
+ def move(new_x, new_y)
+ return if new_x == @x && new_y == @y
+ @object_pool.tree_remove(self)
+ @x = new_x
+ @y = new_y
+ @location = [new_x, new_y]
+ @object_pool.tree_insert(self)
+ end
+
+ def update
+ @components.map(&:update)
+ end
+
+ def draw(viewport)
+ @components.each { |c| c.draw(viewport) }
+ end
+
+ def removable?
+ @removable
+ end
+
+ def mark_for_removal
+ @removable = true
+ end
+
+ def on_collision(object)
+ end
+
+ def effect?
+ false
+ end
+
+ def box
+ end
+
+ def collide
+ end
+
+ protected
+
+ def object_pool
+ @object_pool
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/hud.rb b/Games/Debris_Dodge/lib/entities/hud.rb
new file mode 100644
index 0000000000..6f3f1965f2
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/hud.rb
@@ -0,0 +1,79 @@
+class HUD
+ attr_accessor :active
+ def initialize(object_pool, tank)
+ @object_pool = object_pool
+ @tank = tank
+ @radar = Radar.new(@object_pool, tank)
+ end
+
+ def player=(tank)
+ @tank = tank
+ @radar.target = tank
+ end
+
+ def update
+ @radar.update
+ end
+
+ def health_image
+ if @health.nil? || @tank.health.health != @health
+ @health = @tank.health.health
+ @health_image = Gosu::Image.from_text(
+ "Health: #{@health}", 20, font: Utils.main_font)
+ end
+ @health_image
+ end
+
+ def stats_image
+ stats = @tank.input.stats
+ if @stats_image.nil? || stats.changed_at <= Gosu.milliseconds
+ @stats_image = Gosu::Image.from_text(
+ "Kills: #{stats.kills}", 20, font: Utils.main_font)
+ end
+ @stats_image
+ end
+
+ def fire_rate_image
+ if @tank.fire_rate_modifier > 1
+ if @fire_rate != @tank.fire_rate_modifier
+ @fire_rate = @tank.fire_rate_modifier
+ @fire_rate_image = Gosu::Image.from_text(
+ "Fire rate: #{@fire_rate.round(2)}X",
+ 20, font: Utils.main_font)
+ end
+ else
+ @fire_rate_image = nil
+ end
+ @fire_rate_image
+ end
+
+ def speed_image
+ if @tank.speed_modifier > 1
+ if @speed != @tank.speed_modifier
+ @speed = @tank.speed_modifier
+ @speed_image = Gosu::Image.from_text(
+ "Speed: #{@speed.round(2)}X",
+ 20, font: Utils.main_font)
+ end
+ else
+ @speed_image = nil
+ end
+ @speed_image
+ end
+
+ def draw
+ if @active
+ @object_pool.camera.draw_crosshair
+ end
+ @radar.draw
+ offset = 20
+ health_image.draw(20, offset, 1000)
+ stats_image.draw(20, offset += 30, 1000)
+ if fire_rate_image
+ fire_rate_image.draw(20, offset += 30, 1000)
+ end
+ if speed_image
+ speed_image.draw(20, offset += 30, 1000)
+ end
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/map.rb b/Games/Debris_Dodge/lib/entities/map.rb
new file mode 100644
index 0000000000..4a55676cbe
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/map.rb
@@ -0,0 +1,183 @@
+class Map
+ MAP_WIDTH = 30
+ MAP_HEIGHT = 30
+ TILE_SIZE = 128
+
+ def self.bounding_box
+ center = [MAP_WIDTH * TILE_SIZE / 2,
+ MAP_HEIGHT * TILE_SIZE / 2]
+ half_dimension = [MAP_WIDTH * TILE_SIZE,
+ MAP_HEIGHT * TILE_SIZE]
+ AxisAlignedBoundingBox.new(center, half_dimension)
+ end
+
+ def initialize(object_pool)
+ load_tiles
+ @object_pool = object_pool
+ object_pool.map = self
+ @map = generate_map
+ generate_trees
+ generate_boxes
+ generate_powerups
+ end
+
+ def spawn_points(max)
+ @spawn_points = (0..max).map do
+ find_spawn_point
+ end
+ @spawn_points_pointer = 0
+ end
+
+ def spawn_point
+ @spawn_points[(@spawn_points_pointer += 1) % @spawn_points.size]
+ end
+
+ def can_move_to?(x, y)
+ tile = tile_at(x, y)
+ tile && tile != @water
+ end
+
+ def movement_penalty(x, y)
+ tile = tile_at(x, y)
+ case tile
+ when @sand
+ 0.33
+ else
+ 0
+ end
+ end
+
+ def draw(viewport)
+ viewport = viewport.map { |p| p / TILE_SIZE }
+ x0, x1, y0, y1 = viewport.map(&:to_i)
+ (x0-1..x1).each do |x|
+ (y0-1..y1).each do |y|
+ row = @map[x]
+ map_x = x * TILE_SIZE
+ map_y = y * TILE_SIZE
+ if row
+ tile = @map[x][y]
+ if tile
+ tile.draw(map_x, map_y, 0)
+ else
+ @water.draw(map_x, map_y, 0)
+ end
+ else
+ @water.draw(map_x, map_y, 0)
+ end
+ end
+ end
+ end
+
+ private
+
+ def tile_at(x, y)
+ t_x = ((x / TILE_SIZE) % TILE_SIZE).floor
+ t_y = ((y / TILE_SIZE) % TILE_SIZE).floor
+ row = @map[t_x]
+ row ? row[t_y] : @water
+ end
+
+ def load_tiles
+ tiles = Gosu::Image.load_tiles(
+ $window, Utils.media_path('ground.png'),
+ 128, 128, true)
+ @sand = tiles[0]
+ @grass = tiles[8]
+ @water = Gosu::Image.new(
+ Utils.media_path('water.png'), tileable: true)
+ end
+
+ def generate_map
+ noises = Perlin::Noise.new(2)
+ contrast = Perlin::Curve.contrast(
+ Perlin::Curve::CUBIC, 2)
+ map = {}
+ MAP_WIDTH.times do |x|
+ map[x] = {}
+ MAP_HEIGHT.times do |y|
+ n = noises[x * 0.1, y * 0.1]
+ n = contrast.call(n)
+ map[x][y] = choose_tile(n)
+ end
+ end
+ map
+ end
+
+ def generate_trees
+ noises = Perlin::Noise.new(2)
+ contrast = Perlin::Curve.contrast(
+ Perlin::Curve::CUBIC, 2)
+ trees = 0
+ target_trees = rand(1500..1500)
+ while trees < target_trees do
+ x = rand(0..MAP_WIDTH * TILE_SIZE)
+ y = rand(0..MAP_HEIGHT * TILE_SIZE)
+ n = noises[x * 0.001, y * 0.001]
+ n = contrast.call(n)
+ if tile_at(x, y) == @grass && n > 0.5
+ Tree.new(@object_pool, x, y, n * 2 - 1)
+ trees += 1
+ end
+ end
+ end
+
+ def generate_boxes
+ boxes = 0
+ target_boxes = rand(50..200)
+ while boxes < target_boxes do
+ x = rand(0..MAP_WIDTH * TILE_SIZE)
+ y = rand(0..MAP_HEIGHT * TILE_SIZE)
+ if tile_at(x, y) != @water
+ Box.new(@object_pool, x, y)
+ boxes += 1
+ end
+ end
+ end
+
+ def generate_powerups
+ pups = 0
+ target_pups = rand(20..30)
+ while pups < target_pups do
+ x = rand(0..MAP_WIDTH * TILE_SIZE)
+ y = rand(0..MAP_HEIGHT * TILE_SIZE)
+ if tile_at(x, y) != @water &&
+ @object_pool.nearby_point(x, y, 150).empty?
+ random_powerup.new(@object_pool, x, y)
+ pups += 1
+ end
+ end
+ end
+
+ def random_powerup
+ [HealthPowerup,
+ RepairPowerup,
+ FireRatePowerup,
+ TankSpeedPowerup].sample
+ end
+
+ def choose_tile(val)
+ case val
+ when 0.0..0.3 # 30% chance
+ @water
+ when 0.3..0.5 # 20% chance, water edges
+ @sand
+ else # 50% chance
+ @grass
+ end
+ end
+
+ private
+
+ def find_spawn_point
+ while true
+ x = rand(0..MAP_WIDTH * TILE_SIZE)
+ y = rand(0..MAP_HEIGHT * TILE_SIZE)
+ if can_move_to?(x, y) &&
+ @object_pool.nearby_point(x, y, 150).empty?
+ return [x, y]
+ end
+ end
+ end
+
+end
diff --git a/Games/Debris_Dodge/lib/entities/object_pool.rb b/Games/Debris_Dodge/lib/entities/object_pool.rb
new file mode 100644
index 0000000000..016a7db96b
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/object_pool.rb
@@ -0,0 +1,59 @@
+class ObjectPool
+ attr_accessor :map, :camera, :objects, :powerup_respawn_queue
+
+ def size
+ @objects.size
+ end
+
+ def initialize(box)
+ @tree = QuadTree.new(box)
+ @powerup_respawn_queue = PowerupRespawnQueue.new
+ @objects = []
+ end
+
+ def add(object)
+ @objects << object
+ @tree.insert(object)
+ end
+
+ def tree_remove(object)
+ @tree.remove(object)
+ end
+
+ def tree_insert(object)
+ @tree.insert(object)
+ end
+
+ def update_all
+ @objects.each(&:update)
+ @objects.reject! do |o|
+ if o.removable?
+ @tree.remove(o)
+ true
+ end
+ end
+ @powerup_respawn_queue.respawn(self)
+ end
+
+ def nearby_point(cx, cy, max_distance, object = nil)
+ hx, hy = cx + max_distance, cy + max_distance
+ # Fast, rough results
+ results = @tree.query_range(
+ AxisAlignedBoundingBox.new([cx, cy], [hx, hy]))
+ # Sift through to select fine-grained results
+ results.select do |o|
+ o != object &&
+ Utils.distance_between(
+ o.x, o.y, cx, cy) <= max_distance
+ end
+ end
+
+ def nearby(object, max_distance)
+ cx, cy = object.location
+ nearby_point(cx, cy, max_distance, object)
+ end
+
+ def query_range(box)
+ @tree.query_range(box)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/powerups/fire_rate_powerup.rb b/Games/Debris_Dodge/lib/entities/powerups/fire_rate_powerup.rb
new file mode 100644
index 0000000000..76cadb4d50
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/powerups/fire_rate_powerup.rb
@@ -0,0 +1,14 @@
+class FireRatePowerup < Powerup
+ def pickup(object)
+ if object.class == Tank
+ if object.fire_rate_modifier < 2
+ object.fire_rate_modifier += 0.25
+ end
+ true
+ end
+ end
+
+ def graphics
+ :straight_gun
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/powerups/health_powerup.rb b/Games/Debris_Dodge/lib/entities/powerups/health_powerup.rb
new file mode 100644
index 0000000000..c73cdbe9c3
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/powerups/health_powerup.rb
@@ -0,0 +1,12 @@
+class HealthPowerup < Powerup
+ def pickup(object)
+ if object.class == Tank
+ object.health.increase(25)
+ true
+ end
+ end
+
+ def graphics
+ :life_up
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/powerups/powerup.rb b/Games/Debris_Dodge/lib/entities/powerups/powerup.rb
new file mode 100644
index 0000000000..edd2305ff7
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/powerups/powerup.rb
@@ -0,0 +1,35 @@
+class Powerup < GameObject
+ def initialize(object_pool, x, y)
+ super
+ PowerupGraphics.new(self, graphics)
+ end
+
+ def box
+ [x - 8, y - 8,
+ x + 8, y - 8,
+ x + 8, y + 8,
+ x - 8, y + 8]
+ end
+
+ def on_collision(object)
+ if pickup(object)
+ PowerupSounds.play(object, object_pool.camera)
+ remove
+ end
+ end
+
+ def pickup(object)
+ # override and implement application
+ end
+
+ def remove
+ object_pool.powerup_respawn_queue.enqueue(
+ respawn_delay,
+ self.class, x, y)
+ mark_for_removal
+ end
+
+ def respawn_delay
+ 30
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/powerups/powerup_respawn_queue.rb b/Games/Debris_Dodge/lib/entities/powerups/powerup_respawn_queue.rb
new file mode 100644
index 0000000000..b8f89d6691
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/powerups/powerup_respawn_queue.rb
@@ -0,0 +1,23 @@
+class PowerupRespawnQueue
+ RESPAWN_DELAY = 1000
+ def initialize
+ @respawn_queue = {}
+ @last_respawn = Gosu.milliseconds
+ end
+
+ def enqueue(delay_seconds, type, x, y)
+ respawn_at = Gosu.milliseconds + delay_seconds * 1000
+ @respawn_queue[respawn_at.to_i] = [type, x, y]
+ end
+
+ def respawn(object_pool)
+ now = Gosu.milliseconds
+ return if now - @last_respawn < RESPAWN_DELAY
+ @respawn_queue.keys.each do |k|
+ next if k > now # not yet
+ type, x, y = @respawn_queue.delete(k)
+ type.new(object_pool, x, y)
+ end
+ @last_respawn = now
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/powerups/repair_powerup.rb b/Games/Debris_Dodge/lib/entities/powerups/repair_powerup.rb
new file mode 100644
index 0000000000..fc3136d8f7
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/powerups/repair_powerup.rb
@@ -0,0 +1,14 @@
+class RepairPowerup < Powerup
+ def pickup(object)
+ if object.class == Tank
+ if object.health.health < 100
+ object.health.restore
+ end
+ true
+ end
+ end
+
+ def graphics
+ :repair
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/powerups/tank_speed_powerup.rb b/Games/Debris_Dodge/lib/entities/powerups/tank_speed_powerup.rb
new file mode 100644
index 0000000000..c3e2f917f6
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/powerups/tank_speed_powerup.rb
@@ -0,0 +1,14 @@
+class TankSpeedPowerup < Powerup
+ def pickup(object)
+ if object.class == Tank
+ if object.speed_modifier < 1.5
+ object.speed_modifier += 0.10
+ end
+ true
+ end
+ end
+
+ def graphics
+ :wingman
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/radar.rb b/Games/Debris_Dodge/lib/entities/radar.rb
new file mode 100644
index 0000000000..96d7018a10
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/radar.rb
@@ -0,0 +1,62 @@
+class Radar
+ UPDATE_FREQUENCY = 1000
+ WIDTH = 150
+ HEIGHT = 100
+ PADDING = 10
+ # Black with 33% transparency
+ BACKGROUND = Gosu::Color.new(255 * 0.33, 0, 0, 0)
+ attr_accessor :target
+
+ def initialize(object_pool, target)
+ @object_pool = object_pool
+ @target = target
+ @last_update = 0
+ end
+
+ def update
+ if Gosu.milliseconds - @last_update > UPDATE_FREQUENCY
+ @nearby = nil
+ end
+ @nearby ||= @object_pool.nearby(@target, 2000).select do |o|
+ o.class == Tank && !o.health.dead?
+ end
+ end
+
+ def draw
+ x1, x2, y1, y2 = radar_coords
+ $window.draw_quad(
+ x1, y1, BACKGROUND,
+ x2, y1, BACKGROUND,
+ x2, y2, BACKGROUND,
+ x1, y2, BACKGROUND,
+ 200)
+ draw_tank(@target, Gosu::Color::GREEN)
+ @nearby && @nearby.each do |t|
+ draw_tank(t, Gosu::Color::RED)
+ end
+ end
+
+ private
+
+ def draw_tank(tank, color)
+ x1, x2, y1, y2 = radar_coords
+ tx = x1 + WIDTH / 2 + (tank.x - @target.x) / 20
+ ty = y1 + HEIGHT / 2 + (tank.y - @target.y) / 20
+ if (x1..x2).include?(tx) && (y1..y2).include?(ty)
+ $window.draw_quad(
+ tx - 2, ty - 2, color,
+ tx + 2, ty - 2, color,
+ tx + 2, ty + 2, color,
+ tx - 2, ty + 2, color,
+ 300)
+ end
+ end
+
+ def radar_coords
+ x1 = $window.width - WIDTH - PADDING
+ x2 = $window.width - PADDING
+ y1 = $window.height - HEIGHT - PADDING
+ y2 = $window.height - PADDING
+ [x1, x2, y1, y2]
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/score_display.rb b/Games/Debris_Dodge/lib/entities/score_display.rb
new file mode 100644
index 0000000000..cca560d0f6
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/score_display.rb
@@ -0,0 +1,35 @@
+class ScoreDisplay
+ def initialize(object_pool, font_size=30)
+ @font_size = font_size
+ tanks = object_pool.objects.select do |o|
+ o.class == Tank
+ end
+ stats = tanks.map(&:input).map(&:stats)
+ stats.sort! do |stat1, stat2|
+ stat2.kills <=> stat1.kills
+ end
+ create_stats_image(stats)
+ end
+
+ def create_stats_image(stats)
+ text = stats.map do |stat|
+ "#{stat.kills}: #{stat.name} "
+ end.join("\n")
+ @stats_image = Gosu::Image.from_text(
+ $window, text, Utils.main_font, @font_size)
+ end
+
+ def draw
+ @stats_image.draw(
+ $window.width / 2 - @stats_image.width / 2,
+ $window.height / 4 + 30,
+ 1000)
+ end
+
+ def draw_top_right
+ @stats_image.draw(
+ $window.width - @stats_image.width - 20,
+ 20,
+ 1000)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/entities/tank.rb b/Games/Debris_Dodge/lib/entities/tank.rb
new file mode 100644
index 0000000000..3e0fbf685b
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/tank.rb
@@ -0,0 +1,64 @@
+class Tank < GameObject
+ SHOOT_DELAY = 500
+ attr_accessor :throttle_down, :direction,
+ :gun_angle, :sounds, :physics, :graphics, :health, :input,
+ :fire_rate_modifier, :speed_modifier
+
+ def initialize(object_pool, input)
+ x, y = object_pool.map.spawn_point
+ super(object_pool, x, y)
+ @input = input
+ @input.control(self)
+ @physics = TankPhysics.new(self, object_pool)
+ @sounds = TankSounds.new(self, object_pool)
+ @health = TankHealth.new(self, object_pool)
+ @graphics = TankGraphics.new(self)
+ @direction = rand(0..7) * 45
+ @gun_angle = rand(0..360)
+ reset_modifiers
+ end
+
+ def box
+ @physics.box
+ end
+
+ def on_collision(object)
+ return unless object
+ # Avoid recursion
+ if object.class == Tank
+ # Inform AI about hit
+ object.input.on_collision(object)
+ else
+ # Call only on non-tanks to avoid recursion
+ object.on_collision(self)
+ end
+ # Bullets should not slow Tanks down
+ if object.class != Bullet
+ @sounds.collide if @physics.speed > 1
+ end
+ end
+
+ def shoot(target_x, target_y)
+ if can_shoot?
+ @last_shot = Gosu.milliseconds
+ Bullet.new(object_pool, @x, @y, target_x, target_y).fire(
+ self, 1500)
+ input.stats.add_shot
+ end
+ end
+
+ def can_shoot?
+ Gosu.milliseconds - (@last_shot || 0) >
+ (SHOOT_DELAY / @fire_rate_modifier)
+ end
+
+ def reset_modifiers
+ @fire_rate_modifier = 1
+ @speed_modifier = 1
+ end
+
+ def to_s
+ "Tank [#{@health.health}@#{@x}:#{@y}@#{@physics.speed.round(2)}px/tick]#{@input.stats}"
+ end
+
+end
diff --git a/Games/Debris_Dodge/lib/entities/tree.rb b/Games/Debris_Dodge/lib/entities/tree.rb
new file mode 100644
index 0000000000..07635300d1
--- /dev/null
+++ b/Games/Debris_Dodge/lib/entities/tree.rb
@@ -0,0 +1,18 @@
+class Tree < GameObject
+ attr_reader :health, :graphics
+
+ def initialize(object_pool, x, y, seed)
+ super(object_pool, x, y)
+ @graphics = TreeGraphics.new(self, seed)
+ @health = Health.new(self, object_pool, 30, false)
+ @angle = rand(-15..15)
+ end
+
+ def on_collision(object)
+ @graphics.shake(object.direction)
+ end
+
+ def box
+ [@x, @y]
+ end
+end
diff --git a/Games/Debris_Dodge/lib/game_states/demo_state.rb b/Games/Debris_Dodge/lib/game_states/demo_state.rb
new file mode 100644
index 0000000000..13b9587661
--- /dev/null
+++ b/Games/Debris_Dodge/lib/game_states/demo_state.rb
@@ -0,0 +1,49 @@
+class DemoState < PlayState
+ attr_accessor :tank
+
+ def enter
+ # Prevent reactivating HUD
+ end
+
+ def update
+ super
+ @score_display = ScoreDisplay.new(
+ object_pool, 20)
+ end
+
+ def draw
+ super
+ @score_display.draw_top_right
+ end
+
+ def button_down(id)
+ super
+ if id == Gosu::KbSpace
+ target_tank = @tanks.reject do |t|
+ t == @camera.target
+ end.sample
+ switch_to_tank(target_tank)
+ end
+ end
+
+ private
+
+ def create_tanks(amount)
+ @map.spawn_points(amount * 3)
+ @tanks = []
+ amount.times do |i|
+ @tanks << Tank.new(@object_pool, AiInput.new(
+ @names.random, @object_pool))
+ end
+ target_tank = @tanks.sample
+ @hud = HUD.new(@object_pool, target_tank)
+ @hud.active = false
+ switch_to_tank(target_tank)
+ end
+
+ def switch_to_tank(tank)
+ @camera.target = tank
+ @hud.player = tank
+ self.tank = tank
+ end
+end
diff --git a/Games/Debris_Dodge/lib/game_states/game_state.rb b/Games/Debris_Dodge/lib/game_states/game_state.rb
new file mode 100644
index 0000000000..bb6f83eee0
--- /dev/null
+++ b/Games/Debris_Dodge/lib/game_states/game_state.rb
@@ -0,0 +1,27 @@
+class GameState
+
+ def self.switch(new_state)
+ $window.state && $window.state.leave
+ $window.state = new_state
+ new_state.enter
+ end
+
+ def enter
+ end
+
+ def leave
+ end
+
+ def draw
+ end
+
+ def update
+ end
+
+ def needs_redraw?
+ true
+ end
+
+ def button_down(id)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/game_states/menu_state.rb b/Games/Debris_Dodge/lib/game_states/menu_state.rb
new file mode 100644
index 0000000000..294e7d4c06
--- /dev/null
+++ b/Games/Debris_Dodge/lib/game_states/menu_state.rb
@@ -0,0 +1,62 @@
+require 'singleton'
+class MenuState < GameState
+ include Singleton
+ attr_accessor :play_state
+
+ def initialize
+ @message = Gosu::Image.from_text(
+ "Tank Island",
+ 60,
+ font: Utils.title_font
+ )
+ end
+
+ def enter
+ music.play(true)
+ music.volume = 1
+ end
+
+ def leave
+ music.volume = 0
+ music.stop
+ end
+
+ def music
+ @@music ||= Gosu::Song.new(
+ Utils.media_path('menu_music.ogg'))
+ end
+
+ def update
+ text = "Q: Quit\nN: New Game\nD: Demo"
+ text << "\nC: Continue" if @play_state
+ @info = Gosu::Image.from_text(
+ text,
+ 30, options = {font: Utils.main_font})
+ end
+
+ def draw
+ @message.draw(
+ $window.width / 2 - @message.width / 2,
+ $window.height / 2 - @message.height / 2,
+ 10)
+ @info.draw(
+ $window.width / 2 - @info.width / 2,
+ $window.height / 2 - @info.height / 2 + 100,
+ 10)
+ end
+
+ def button_down(id)
+ $window.close if id == Gosu::KbQ
+ if id == Gosu::KbC && @play_state
+ GameState.switch(@play_state)
+ end
+ if id == Gosu::KbN
+ @play_state = PlayState.new
+ GameState.switch(@play_state)
+ end
+ if id == Gosu::KbD
+ @play_state = DemoState.new
+ GameState.switch(@play_state)
+ end
+ end
+end
diff --git a/Games/Debris_Dodge/lib/game_states/pause_state.rb b/Games/Debris_Dodge/lib/game_states/pause_state.rb
new file mode 100644
index 0000000000..8a8078792d
--- /dev/null
+++ b/Games/Debris_Dodge/lib/game_states/pause_state.rb
@@ -0,0 +1,61 @@
+require 'singleton'
+class PauseState < GameState
+ include Singleton
+ attr_accessor :play_state
+
+ def initialize
+ @message = Gosu::Image.from_text(
+ $window, "Game Paused",
+ Utils.title_font, 60)
+ end
+
+ def enter
+ music.play(true)
+ music.volume = 1
+ @score_display = ScoreDisplay.new(@play_state.object_pool)
+ @mouse_coords = [$window.mouse_x, $window.mouse_y]
+ end
+
+ def leave
+ music.volume = 0
+ music.stop
+ $window.mouse_x, $window.mouse_y = @mouse_coords
+ end
+
+ def music
+ @@music ||= Gosu::Song.new(
+ $window, Utils.media_path('menu_music.ogg'))
+ end
+
+ def draw
+ @play_state.draw
+ @message.draw(
+ $window.width / 2 - @message.width / 2,
+ $window.height / 4 - @message.height - 50,
+ 1000)
+ info.draw(
+ $window.width / 2 - info.width / 2,
+ $window.height / 4 - info.height,
+ 1000)
+ @score_display.draw
+ end
+
+ def info
+ @info ||= Gosu::Image.from_text(
+ $window, 'Q: Quit to Main Menu',
+ Utils.main_font, 30)
+ end
+
+ def button_down(id)
+ if id == Gosu::KbQ
+ MenuState.instance.play_state = @play_state
+ GameState.switch(MenuState.instance)
+ end
+ if id == Gosu::KbC && @play_state
+ GameState.switch(@play_state)
+ end
+ if id == Gosu::KbEscape
+ GameState.switch(@play_state)
+ end
+ end
+end
diff --git a/Games/Debris_Dodge/lib/game_states/play_state.rb b/Games/Debris_Dodge/lib/game_states/play_state.rb
new file mode 100644
index 0000000000..89100befe5
--- /dev/null
+++ b/Games/Debris_Dodge/lib/game_states/play_state.rb
@@ -0,0 +1,119 @@
+class PlayState < GameState
+ attr_accessor :update_interval, :object_pool, :tank
+
+ def initialize
+ # http://www.paulandstorm.com/wha/clown-names/
+ @names = Names.new(
+ Utils.media_path('names.txt'))
+ @object_pool = ObjectPool.new(Map.bounding_box)
+ @map = Map.new(@object_pool)
+ @camera = Camera.new
+ @object_pool.camera = @camera
+ create_tanks(7)
+ end
+
+ def update
+ StereoSample.cleanup
+ @object_pool.update_all
+ @camera.update
+ @hud.update
+ update_caption
+ end
+
+ def draw
+ cam_x = @camera.x
+ cam_y = @camera.y
+ off_x = $window.width / 2 - cam_x
+ off_y = $window.height / 2 - cam_y
+ viewport = @camera.viewport
+ x1, x2, y1, y2 = viewport
+ box = AxisAlignedBoundingBox.new(
+ [x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2],
+ [x1 - Map::TILE_SIZE, y1 - Map::TILE_SIZE])
+ $window.translate(off_x, off_y) do
+ zoom = @camera.zoom
+ $window.scale(zoom, zoom, cam_x, cam_y) do
+ @map.draw(viewport)
+ @object_pool.query_range(box).map do |o|
+ o.draw(viewport)
+ end
+ end
+ end
+ @hud.draw
+ end
+
+ def button_down(id)
+ if id == Gosu::KbEscape
+ pause = PauseState.instance
+ pause.play_state = self
+ GameState.switch(pause)
+ end
+ if id == Gosu::KbT
+ t = Tank.new(@object_pool,
+ AiInput.new(@names.random, @object_pool))
+ t.move(*@camera.mouse_coords)
+ end
+ if id == Gosu::KbF1
+ $debug = !$debug
+ end
+ if id == Gosu::KbF2
+ toggle_profiling
+ end
+ if id == Gosu::KbR
+ @tank.mark_for_removal
+ @tank = Tank.new(@object_pool,
+ PlayerInput.new('Player', @camera, @object_pool))
+ @camera.target = @tank
+ @hud.player = @tank
+ end
+ end
+
+ def leave
+ StereoSample.stop_all
+ if @profiling_now
+ toggle_profiling
+ end
+ @hud.active = false
+ end
+
+ def enter
+ @hud.active = true
+ end
+
+ private
+
+ def create_tanks(amount)
+ @map.spawn_points(amount * 3)
+ @tank = Tank.new(@object_pool,
+ PlayerInput.new('Player', @camera, @object_pool))
+ amount.times do |i|
+ Tank.new(@object_pool, AiInput.new(
+ @names.random, @object_pool))
+ end
+ @camera.target = @tank
+ @hud = HUD.new(@object_pool, @tank)
+ end
+
+ def toggle_profiling
+ require 'ruby-prof' unless defined?(RubyProf)
+ if @profiling_now
+ result = RubyProf.stop
+ printer = RubyProf::FlatPrinter.new(result)
+ printer.print(STDOUT, min_percent: 0.01)
+ @profiling_now = false
+ else
+ RubyProf.start
+ @profiling_now = true
+ end
+ end
+
+ def update_caption
+ now = Gosu.milliseconds
+ if now - (@caption_updated_at || 0) > 1000
+ $window.caption = 'Tank Island. ' <<
+ "[FPS: #{Gosu.fps}. " <<
+ "Tank @ #{@tank.x.round}:#{@tank.y.round}]"
+ @caption_updated_at = now
+ end
+ end
+end
diff --git a/Games/Debris_Dodge/lib/misc/axis_aligned_bounding_box.rb b/Games/Debris_Dodge/lib/misc/axis_aligned_bounding_box.rb
new file mode 100644
index 0000000000..70bc9e30d1
--- /dev/null
+++ b/Games/Debris_Dodge/lib/misc/axis_aligned_bounding_box.rb
@@ -0,0 +1,33 @@
+class AxisAlignedBoundingBox
+ attr_reader :center, :half_dimension
+ def initialize(center, half_dimension)
+ @center = center
+ @half_dimension = half_dimension
+ @dhx = (@half_dimension[0] - @center[0]).abs
+ @dhy = (@half_dimension[1] - @center[1]).abs
+ end
+
+ def contains?(point)
+ return false unless (@center[0] + @dhx) >= point[0]
+ return false unless (@center[0] - @dhx) <= point[0]
+ return false unless (@center[1] + @dhy) >= point[1]
+ return false unless (@center[1] - @dhy) <= point[1]
+ true
+ end
+
+ def intersects?(other)
+ ocx, ocy = other.center
+ ohx, ohy = other.half_dimension
+ odhx = (ohx - ocx).abs
+ return false unless (@center[0] + @dhx) >= (ocx - odhx)
+ return false unless (@center[0] - @dhx) <= (ocx + odhx)
+ odhy = (ohy - ocy).abs
+ return false unless (@center[1] + @dhy) >= (ocy - odhy)
+ return false unless (@center[1] - @dhy) <= (ocy + odhy)
+ true
+ end
+
+ def to_s
+ "c: #{@center}, h: #{@half_dimension}"
+ end
+end
diff --git a/Games/Debris_Dodge/lib/misc/game_window.rb b/Games/Debris_Dodge/lib/misc/game_window.rb
new file mode 100644
index 0000000000..504061cf4a
--- /dev/null
+++ b/Games/Debris_Dodge/lib/misc/game_window.rb
@@ -0,0 +1,30 @@
+class GameWindow < Gosu::Window
+ attr_accessor :state
+
+ def initialize
+ super((ENV['w'] || 800).to_i,
+ (ENV['h'] || 600).to_i,
+ (ENV['fs'] ? true : false))
+ end
+
+ def update
+ Utils.track_update_interval
+ @state.update
+ end
+
+ def draw
+ @state.draw
+ end
+
+ def needs_redraw?
+ @state.needs_redraw?
+ end
+
+ def needs_cursor?
+ Utils.update_interval > 200
+ end
+
+ def button_down(id)
+ @state.button_down(id)
+ end
+end
diff --git a/Games/Debris_Dodge/lib/misc/names.rb b/Games/Debris_Dodge/lib/misc/names.rb
new file mode 100644
index 0000000000..09d2825521
--- /dev/null
+++ b/Games/Debris_Dodge/lib/misc/names.rb
@@ -0,0 +1,13 @@
+class Names
+ def initialize(file)
+ @names = File.read(file).split("\n").reject do |n|
+ n.size > 12
+ end
+ end
+
+ def random
+ name = @names.sample
+ @names.delete(name)
+ name
+ end
+end
diff --git a/Games/Debris_Dodge/lib/misc/quad_tree.rb b/Games/Debris_Dodge/lib/misc/quad_tree.rb
new file mode 100644
index 0000000000..6a119aee01
--- /dev/null
+++ b/Games/Debris_Dodge/lib/misc/quad_tree.rb
@@ -0,0 +1,91 @@
+class QuadTree
+ NODE_CAPACITY = 12
+ attr_accessor :ne, :nw, :se, :sw, :objects
+
+ def initialize(boundary)
+ @boundary = boundary
+ @objects = []
+ end
+
+ def insert(game_object)
+ return false unless @boundary.contains?(
+ game_object.location)
+
+ if @objects.size < NODE_CAPACITY
+ @objects << game_object
+ return true
+ end
+
+ subdivide unless @nw
+
+ return true if @nw.insert(game_object)
+ return true if @ne.insert(game_object)
+ return true if @sw.insert(game_object)
+ return true if @se.insert(game_object)
+
+ # should never happen
+ raise "Failed to insert #{game_object}"
+ end
+
+ def remove(game_object)
+ return false unless @boundary.contains?(
+ game_object.location)
+ if @objects.delete(game_object)
+ return true
+ end
+ return false unless @nw
+ return true if @nw.remove(game_object)
+ return true if @ne.remove(game_object)
+ return true if @sw.remove(game_object)
+ return true if @se.remove(game_object)
+ false
+ end
+
+ def query_range(range)
+ result = []
+ unless @boundary.intersects?(range)
+ return result
+ end
+
+ @objects.each do |o|
+ if range.contains?(o.location)
+ result << o
+ end
+ end
+
+ # Not subdivided
+ return result unless @ne
+
+ result += @nw.query_range(range)
+ result += @ne.query_range(range)
+ result += @sw.query_range(range)
+ result += @se.query_range(range)
+
+ result
+ end
+
+ private
+
+ def subdivide
+ cx, cy = @boundary.center
+ hx, hy = @boundary.half_dimension
+ hhx = (cx - hx).abs / 2.0
+ hhy = (cy - hy).abs / 2.0
+ @nw = QuadTree.new(
+ AxisAlignedBoundingBox.new(
+ [cx - hhx, cy - hhy],
+ [cx, cy]))
+ @ne = QuadTree.new(
+ AxisAlignedBoundingBox.new(
+ [cx + hhx, cy - hhy],
+ [cx, cy]))
+ @sw = QuadTree.new(
+ AxisAlignedBoundingBox.new(
+ [cx - hhx, cy + hhy],
+ [cx, cy]))
+ @se = QuadTree.new(
+ AxisAlignedBoundingBox.new(
+ [cx + hhx, cy + hhy],
+ [cx, cy]))
+ end
+end
diff --git a/Games/Debris_Dodge/lib/misc/stats.rb b/Games/Debris_Dodge/lib/misc/stats.rb
new file mode 100644
index 0000000000..394b584991
--- /dev/null
+++ b/Games/Debris_Dodge/lib/misc/stats.rb
@@ -0,0 +1,55 @@
+class Stats
+ attr_reader :name, :kills, :deaths, :shots, :changed_at
+ def initialize(name)
+ @name = name
+ @kills = @deaths = @shots = @damage = @damage_dealt = 0
+ changed
+ end
+
+ def add_kill(amount = 1)
+ @kills += amount
+ changed
+ end
+
+ def add_death
+ @deaths += 1
+ changed
+ end
+
+ def add_shot
+ @shots += 1
+ changed
+ end
+
+ def add_damage(amount)
+ @damage += amount
+ changed
+ end
+
+ def damage
+ @damage.round
+ end
+
+ def add_damage_dealt(amount)
+ @damage_dealt += amount
+ changed
+ end
+
+ def damage_dealt
+ @damage_dealt.round
+ end
+
+ def to_s
+ "[kills: #{@kills}, " \
+ "deaths: #{@deaths}, " \
+ "shots: #{@shots}, " \
+ "damage: #{damage}, " \
+ "damage_dealt: #{damage_dealt}]"
+ end
+
+ private
+
+ def changed
+ @changed_at = Gosu.milliseconds
+ end
+end
diff --git a/Games/Debris_Dodge/lib/misc/stereo_sample.rb b/Games/Debris_Dodge/lib/misc/stereo_sample.rb
new file mode 100644
index 0000000000..211f6ca2f0
--- /dev/null
+++ b/Games/Debris_Dodge/lib/misc/stereo_sample.rb
@@ -0,0 +1,96 @@
+class StereoSample
+ MAX_POLIPHONY = 16
+ @@all_instances = []
+
+ def self.register_instances(instances)
+ @@all_instances << instances
+ end
+
+ def self.cleanup
+ @@all_instances.each do |instances|
+ instances.each do |key, instance|
+ unless instance.playing? || instance.paused?
+ instances.delete(key)
+ end
+ end
+ end
+ end
+
+ def self.stop_all
+ @@all_instances.each do |instances|
+ instances.each do |key, instance|
+ if instance.playing?
+ instance.stop
+ end
+ end
+ end
+ end
+
+ def initialize(window, sound_l, sound_r = sound_l)
+ @sound_l = Gosu::Sample.new(sound_l)
+ # Use same sample in mono -> stereo
+ if sound_l == sound_r
+ @sound_r = @sound_l
+ else
+ @sound_r = Gosu::Sample.new(sound_r)
+ end
+ @instances = {}
+ self.class.register_instances(@instances)
+ end
+
+ def paused?(id = :default)
+ i = @instances["#{id}_l"]
+ i && i.paused?
+ end
+
+ def playing?(id = :default)
+ i = @instances["#{id}_l"]
+ i && i.playing?
+ end
+
+ def stopped?(id = :default)
+ @instances["#{id}_l"].nil?
+ end
+
+ def play(id = :default, pan = 0,
+ volume = 1, speed = 1, looping = false)
+ if @instances.size > MAX_POLIPHONY
+ return
+ end
+ @instances["#{id}_l"] = @sound_l.play_pan(
+ -0.2, 0, speed, looping)
+ @instances["#{id}_r"] = @sound_r.play_pan(
+ 0.2, 0, speed, looping)
+ volume_and_pan(id, volume, pan)
+ end
+
+ def pause(id = :default)
+ @instances["#{id}_l"].pause
+ @instances["#{id}_r"].pause
+ end
+
+ def resume(id = :default)
+ @instances["#{id}_l"].resume
+ @instances["#{id}_r"].resume
+ end
+
+ def stop
+ @instances.delete("#{id}_l").stop
+ @instances.delete("#{id}_r").stop
+ end
+
+ def volume_and_pan(id, volume, pan)
+ return unless @instances["#{id}_l"]
+ if pan > 0
+ pan_l = 1 - pan * 2
+ pan_r = 1
+ else
+ pan_l = 1
+ pan_r = 1 + pan * 2
+ end
+ pan_l *= volume
+ pan_r *= volume
+ @instances["#{id}_l"].volume = [pan_l, 0.05].max
+ @instances["#{id}_r"].volume = [pan_r, 0.05].max
+ end
+end
diff --git a/Games/Debris_Dodge/lib/misc/utils.rb b/Games/Debris_Dodge/lib/misc/utils.rb
new file mode 100644
index 0000000000..88800da504
--- /dev/null
+++ b/Games/Debris_Dodge/lib/misc/utils.rb
@@ -0,0 +1,145 @@
+module Utils
+ HEARING_DISTANCE = 1000.0
+ DEBUG_COLORS = [
+ Gosu::Color::RED,
+ Gosu::Color::BLUE,
+ Gosu::Color::YELLOW,
+ Gosu::Color::WHITE
+ ]
+ def self.media_path(file)
+ File.join(File.dirname(File.dirname(File.dirname(
+ __FILE__))), 'media', file)
+ end
+
+ def self.track_update_interval
+ now = Gosu.milliseconds
+ @update_interval = (now - (@last_update ||= 0)).to_f
+ @last_update = now
+ end
+
+ def self.title_font
+ media_path('top_secret.ttf')
+ end
+
+ def self.main_font
+ media_path('armalite_rifle.ttf')
+ end
+
+ def self.update_interval
+ @update_interval ||= $window.update_interval
+ end
+
+ def self.adjust_speed(speed)
+ speed * update_interval / 33.33
+ end
+
+ def self.button_down?(button)
+ @buttons ||= {}
+ now = Gosu.milliseconds
+ now = now - (now % 150)
+ if $window.button_down?(button)
+ @buttons[button] = now
+ true
+ elsif @buttons[button]
+ if now == @buttons[button]
+ true
+ else
+ @buttons.delete(button)
+ false
+ end
+ end
+ end
+
+ def self.rotate(angle, around_x, around_y, *points)
+ result = []
+ angle = angle * Math::PI / 180.0
+ points.each_slice(2) do |x, y|
+ r_x = Math.cos(angle) * (around_x - x) -
+ Math.sin(angle) * (around_y - y) + around_x
+ r_y = Math.sin(angle) * (around_x - x) +
+ Math.cos(angle) * (around_y - y) + around_y
+ result << r_x
+ result << r_y
+ end
+ result
+ end
+
+ # http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
+ def self.point_in_poly(testx, testy, *poly)
+ nvert = poly.size / 2 # Number of vertices in poly
+ vertx = []
+ verty = []
+ poly.each_slice(2) do |x, y|
+ vertx << x
+ verty << y
+ end
+ inside = false
+ j = nvert - 1
+ (0..nvert - 1).each do |i|
+ if (((verty[i] > testy) != (verty[j] > testy)) &&
+ (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) /
+ (verty[j] - verty[i]) + vertx[i]))
+ inside = !inside
+ end
+ j = i
+ end
+ inside
+ end
+
+ def self.distance_between(x1, y1, x2, y2)
+ dx = x1 - x2
+ dy = y1 - y2
+ Math.sqrt(dx * dx + dy * dy)
+ end
+
+ def self.angle_between(x, y, target_x, target_y)
+ dx = target_x - x
+ dy = target_y - y
+ (180 - Math.atan2(dx, dy) * 180 / Math::PI) + 360 % 360
+ end
+
+ def self.point_at_distance(source_x, source_y, angle, distance)
+ angle = (90 - angle) * Math::PI / 180
+ x = source_x + Math.cos(angle) * distance
+ y = source_y - Math.sin(angle) * distance
+ [x, y]
+ end
+
+ def self.mark_corners(box)
+ i = 0
+ box.each_slice(2) do |x, y|
+ color = DEBUG_COLORS[i]
+ $window.draw_triangle(
+ x - 3, y - 3, color,
+ x, y, color,
+ x + 3, y - 3, color,
+ 100)
+ i = (i + 1) % 4
+ end
+ end
+
+ def self.volume(object, camera)
+ return 1 if object == camera.target
+ distance = Utils.distance_between(
+ camera.target.x, camera.target.y,
+ object.x, object.y)
+ distance = [(HEARING_DISTANCE - distance), 0].max
+ distance / HEARING_DISTANCE
+ end
+
+ def self.pan(object, camera)
+ return 0 if object == camera.target
+ pan = object.x - camera.target.x
+ sig = pan > 0 ? 1 : -1
+ pan = (pan % HEARING_DISTANCE) / HEARING_DISTANCE
+ if sig > 0
+ pan
+ else
+ -1 + pan
+ end
+ end
+
+ def self.volume_and_pan(object, camera)
+ [volume(object, camera), pan(object, camera)]
+ end
+end
diff --git a/Games/Debris_Dodge/media/armalite_rifle.ttf b/Games/Debris_Dodge/media/armalite_rifle.ttf
new file mode 100644
index 0000000000..06619a3aef
Binary files /dev/null and b/Games/Debris_Dodge/media/armalite_rifle.ttf differ
diff --git a/Games/Debris_Dodge/media/boxes_barrels.json b/Games/Debris_Dodge/media/boxes_barrels.json
new file mode 100644
index 0000000000..f74563a202
--- /dev/null
+++ b/Games/Debris_Dodge/media/boxes_barrels.json
@@ -0,0 +1,60 @@
+{"frames": {
+
+"barrel_side.png":
+{
+ "frame": {"x":2,"y":2,"w":30,"h":36},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":36},
+ "sourceSize": {"w":30,"h":36}
+},
+"barrel_top.png":
+{
+ "frame": {"x":34,"y":2,"w":30,"h":32},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":32},
+ "sourceSize": {"w":30,"h":32}
+},
+"box.png":
+{
+ "frame": {"x":42,"y":40,"w":50,"h":50},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":50,"h":50},
+ "sourceSize": {"w":50,"h":50}
+},
+"box_2x1.png":
+{
+ "frame": {"x":2,"y":176,"w":80,"h":53},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":80,"h":53},
+ "sourceSize": {"w":80,"h":53}
+},
+"box_2x2.png":
+{
+ "frame": {"x":2,"y":92,"w":80,"h":82},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":80,"h":82},
+ "sourceSize": {"w":80,"h":82}
+},
+"box_small.png":
+{
+ "frame": {"x":2,"y":40,"w":38,"h":40},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":38,"h":40},
+ "sourceSize": {"w":38,"h":40}
+}},
+"meta": {
+ "app": "http://www.codeandweb.com/texturepacker ",
+ "version": "1.0",
+ "image": "boxes_barrels.png",
+ "format": "RGBA8888",
+ "size": {"w":94,"h":231},
+ "scale": "1",
+ "smartupdate": "$TexturePacker:SmartUpdate:e1f8dd3e75d56379964832e4e1fe08fd:ca59b503f7b878a09d84d41cbeb53a15:d01cf1bf6125b2a1524959777ff4451f$"
+}
+}
diff --git a/Games/Debris_Dodge/media/boxes_barrels.png b/Games/Debris_Dodge/media/boxes_barrels.png
new file mode 100644
index 0000000000..89a94f1286
Binary files /dev/null and b/Games/Debris_Dodge/media/boxes_barrels.png differ
diff --git a/Games/Debris_Dodge/media/bullet.png b/Games/Debris_Dodge/media/bullet.png
new file mode 100644
index 0000000000..a8016083e9
Binary files /dev/null and b/Games/Debris_Dodge/media/bullet.png differ
diff --git a/Games/Debris_Dodge/media/c_dot.png b/Games/Debris_Dodge/media/c_dot.png
new file mode 100644
index 0000000000..c848f78c53
Binary files /dev/null and b/Games/Debris_Dodge/media/c_dot.png differ
diff --git a/Games/Debris_Dodge/media/country_field.png b/Games/Debris_Dodge/media/country_field.png
new file mode 100644
index 0000000000..4a2dffdb18
Binary files /dev/null and b/Games/Debris_Dodge/media/country_field.png differ
diff --git a/Games/Debris_Dodge/media/crash.ogg b/Games/Debris_Dodge/media/crash.ogg
new file mode 100644
index 0000000000..8a9bd8cd39
Binary files /dev/null and b/Games/Debris_Dodge/media/crash.ogg differ
diff --git a/Games/Debris_Dodge/media/damage1.png b/Games/Debris_Dodge/media/damage1.png
new file mode 100644
index 0000000000..371c304e66
Binary files /dev/null and b/Games/Debris_Dodge/media/damage1.png differ
diff --git a/Games/Debris_Dodge/media/damage2.png b/Games/Debris_Dodge/media/damage2.png
new file mode 100644
index 0000000000..5ca6de4f34
Binary files /dev/null and b/Games/Debris_Dodge/media/damage2.png differ
diff --git a/Games/Debris_Dodge/media/damage3.png b/Games/Debris_Dodge/media/damage3.png
new file mode 100644
index 0000000000..b676c0df70
Binary files /dev/null and b/Games/Debris_Dodge/media/damage3.png differ
diff --git a/Games/Debris_Dodge/media/damage4.png b/Games/Debris_Dodge/media/damage4.png
new file mode 100644
index 0000000000..b748883b92
Binary files /dev/null and b/Games/Debris_Dodge/media/damage4.png differ
diff --git a/Games/Debris_Dodge/media/decor.json b/Games/Debris_Dodge/media/decor.json
new file mode 100644
index 0000000000..e5a59aaee4
--- /dev/null
+++ b/Games/Debris_Dodge/media/decor.json
@@ -0,0 +1,516 @@
+{"frames": {
+
+"aircraft_1d_destroyed.png":
+{
+ "frame": {"x":451,"y":102,"w":57,"h":42},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":57,"h":42},
+ "sourceSize": {"w":57,"h":42}
+},
+"aircraft_2d_destroyed.png":
+{
+ "frame": {"x":2,"y":680,"w":63,"h":47},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":63,"h":47},
+ "sourceSize": {"w":63,"h":47}
+},
+"aircraft_3d_destroyed.png":
+{
+ "frame": {"x":368,"y":125,"w":81,"h":55},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":81,"h":55},
+ "sourceSize": {"w":81,"h":55}
+},
+"aircraft_3e_destroyed.png":
+{
+ "frame": {"x":368,"y":68,"w":81,"h":55},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":81,"h":55},
+ "sourceSize": {"w":81,"h":55}
+},
+"barrels_1.png":
+{
+ "frame": {"x":430,"y":738,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"barrels_2.png":
+{
+ "frame": {"x":364,"y":680,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"bush_1.png":
+{
+ "frame": {"x":102,"y":820,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"bush_big.png":
+{
+ "frame": {"x":368,"y":182,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"crates_1.png":
+{
+ "frame": {"x":168,"y":794,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"crates_2.png":
+{
+ "frame": {"x":102,"y":754,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"crates_3.png":
+{
+ "frame": {"x":331,"y":812,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"crates_4.png":
+{
+ "frame": {"x":265,"y":760,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"dirt_001.png":
+{
+ "frame": {"x":2,"y":480,"w":88,"h":68},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":88,"h":68},
+ "sourceSize": {"w":88,"h":68}
+},
+"dirt_002.png":
+{
+ "frame": {"x":2,"y":550,"w":88,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":88,"h":64},
+ "sourceSize": {"w":88,"h":64}
+},
+"dirt_grass_hor_01.png":
+{
+ "frame": {"x":2,"y":272,"w":128,"h":104},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":104},
+ "sourceSize": {"w":128,"h":104}
+},
+"dirt_grass_hor_02.png":
+{
+ "frame": {"x":2,"y":166,"w":128,"h":104},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":104},
+ "sourceSize": {"w":128,"h":104}
+},
+"dirt_grass_vert.png":
+{
+ "frame": {"x":132,"y":36,"w":104,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":104,"h":128},
+ "sourceSize": {"w":104,"h":128}
+},
+"dirt_road_hor.png":
+{
+ "frame": {"x":2,"y":2,"w":256,"h":32},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":256,"h":32},
+ "sourceSize": {"w":256,"h":32}
+},
+"dirt_road_vert.png":
+{
+ "frame": {"x":2,"y":738,"w":32,"h":256},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":32,"h":256},
+ "sourceSize": {"w":32,"h":256}
+},
+"grass_01.png":
+{
+ "frame": {"x":238,"y":68,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"grass_02.png":
+{
+ "frame": {"x":2,"y":36,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"road_1.png":
+{
+ "frame": {"x":234,"y":532,"w":64,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":128},
+ "sourceSize": {"w":64,"h":128}
+},
+"road_2.png":
+{
+ "frame": {"x":430,"y":608,"w":64,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":128},
+ "sourceSize": {"w":64,"h":128}
+},
+"road_3.png":
+{
+ "frame": {"x":364,"y":486,"w":64,"h":192},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":192},
+ "sourceSize": {"w":64,"h":192}
+},
+"road_4.png":
+{
+ "frame": {"x":432,"y":414,"w":64,"h":192},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":192},
+ "sourceSize": {"w":64,"h":192}
+},
+"road_5.png":
+{
+ "frame": {"x":260,"y":2,"w":192,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":192,"h":64},
+ "sourceSize": {"w":192,"h":64}
+},
+"road_asphalt_clean_hor.png":
+{
+ "frame": {"x":364,"y":312,"w":128,"h":100},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":100},
+ "sourceSize": {"w":128,"h":100}
+},
+"road_asphalt_clean_to_damaged_hor.png":
+{
+ "frame": {"x":234,"y":300,"w":128,"h":100},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":100},
+ "sourceSize": {"w":128,"h":100}
+},
+"road_asphalt_clean_to_damaged_vert.png":
+{
+ "frame": {"x":132,"y":426,"w":100,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":100,"h":128},
+ "sourceSize": {"w":100,"h":128}
+},
+"road_asphalt_clean_vert.png":
+{
+ "frame": {"x":234,"y":402,"w":100,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":100,"h":128},
+ "sourceSize": {"w":100,"h":128}
+},
+"road_asphalt_damaged_hor.png":
+{
+ "frame": {"x":2,"y":378,"w":128,"h":100},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":100},
+ "sourceSize": {"w":128,"h":100}
+},
+"road_asphalt_damaged_to_clean_hor.png":
+{
+ "frame": {"x":234,"y":198,"w":128,"h":100},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":100},
+ "sourceSize": {"w":128,"h":100}
+},
+"road_asphalt_damaged_to_clean_vert.png":
+{
+ "frame": {"x":132,"y":296,"w":100,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":100,"h":128},
+ "sourceSize": {"w":100,"h":128}
+},
+"road_asphalt_damaged_vert.png":
+{
+ "frame": {"x":132,"y":166,"w":100,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":100,"h":128},
+ "sourceSize": {"w":100,"h":128}
+},
+"road_corner_1.png":
+{
+ "frame": {"x":199,"y":728,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"road_corner_2.png":
+{
+ "frame": {"x":133,"y":688,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"road_corner_3.png":
+{
+ "frame": {"x":67,"y":688,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"road_corner_4.png":
+{
+ "frame": {"x":36,"y":760,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"rock_010.png":
+{
+ "frame": {"x":364,"y":414,"w":66,"h":70},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":66,"h":70},
+ "sourceSize": {"w":66,"h":70}
+},
+"rock_1.png":
+{
+ "frame": {"x":36,"y":936,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"rock_2.png":
+{
+ "frame": {"x":36,"y":870,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"rock_3.png":
+{
+ "frame": {"x":428,"y":804,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"rock_4.png":
+{
+ "frame": {"x":362,"y":746,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"rock_5.png":
+{
+ "frame": {"x":296,"y":694,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"rock_6.png":
+{
+ "frame": {"x":230,"y":662,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"rock_7.png":
+{
+ "frame": {"x":164,"y":622,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"rock_8.png":
+{
+ "frame": {"x":98,"y":622,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"rock_9.png":
+{
+ "frame": {"x":128,"y":556,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":64,"h":64}
+},
+"tank1_body_destroyed.png":
+{
+ "frame": {"x":92,"y":480,"w":34,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":34,"h":48},
+ "sourceSize": {"w":34,"h":48}
+},
+"tank1b_body_destroyed.png":
+{
+ "frame": {"x":92,"y":530,"w":34,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":34,"h":48},
+ "sourceSize": {"w":34,"h":48}
+},
+"tank2_body_destroyed.png":
+{
+ "frame": {"x":454,"y":2,"w":42,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":42,"h":48},
+ "sourceSize": {"w":42,"h":48}
+},
+"tank2b_body_destroyed.png":
+{
+ "frame": {"x":454,"y":52,"w":42,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":42,"h":48},
+ "sourceSize": {"w":42,"h":48}
+},
+"tank3_body_destroyed.png":
+{
+ "frame": {"x":336,"y":525,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3b_body_destroyed.png":
+{
+ "frame": {"x":336,"y":484,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3c_body_destroyed.png":
+{
+ "frame": {"x":336,"y":443,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3d_body_destroyed.png":
+{
+ "frame": {"x":336,"y":402,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"truck1_destroyed.png":
+{
+ "frame": {"x":2,"y":616,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck1b_destroyed.png":
+{
+ "frame": {"x":332,"y":630,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck1c_destroyed.png":
+{
+ "frame": {"x":300,"y":596,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck2_destroyed.png":
+{
+ "frame": {"x":332,"y":566,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck2b_destroyed.png":
+{
+ "frame": {"x":300,"y":532,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck2c_destroyed.png":
+{
+ "frame": {"x":66,"y":616,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck3_destroyed.png":
+{
+ "frame": {"x":34,"y":616,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+}},
+"meta": {
+ "app": "http://www.texturepacker.com",
+ "version": "1.0",
+ "image": "decor.png",
+ "format": "RGBA8888",
+ "size": {"w":512,"h":1024},
+ "scale": "1",
+ "smartupdate": "$TexturePacker:SmartUpdate:2e6b6964f24c7abfaa85a804e2dc1b05$"
+}
+}
diff --git a/Games/Debris_Dodge/media/decor.png b/Games/Debris_Dodge/media/decor.png
new file mode 100644
index 0000000000..9c385f8870
Binary files /dev/null and b/Games/Debris_Dodge/media/decor.png differ
diff --git a/Games/Debris_Dodge/media/decor.psd b/Games/Debris_Dodge/media/decor.psd
new file mode 100644
index 0000000000..ba2766e4d6
Binary files /dev/null and b/Games/Debris_Dodge/media/decor.psd differ
diff --git a/Games/Debris_Dodge/media/explosion.ogg b/Games/Debris_Dodge/media/explosion.ogg
new file mode 100644
index 0000000000..04d6799b97
Binary files /dev/null and b/Games/Debris_Dodge/media/explosion.ogg differ
diff --git a/Games/Debris_Dodge/media/explosion.png b/Games/Debris_Dodge/media/explosion.png
new file mode 100644
index 0000000000..42fa4425f9
Binary files /dev/null and b/Games/Debris_Dodge/media/explosion.png differ
diff --git a/Games/Debris_Dodge/media/fire.ogg b/Games/Debris_Dodge/media/fire.ogg
new file mode 100644
index 0000000000..0c82c760df
Binary files /dev/null and b/Games/Debris_Dodge/media/fire.ogg differ
diff --git a/Games/Debris_Dodge/media/ground.json b/Games/Debris_Dodge/media/ground.json
new file mode 100644
index 0000000000..1193be2e7a
--- /dev/null
+++ b/Games/Debris_Dodge/media/ground.json
@@ -0,0 +1,492 @@
+{"frames": {
+
+"beach_bl.png":
+{
+ "frame": {"x":896,"y":640,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_bl_grass.png":
+{
+ "frame": {"x":768,"y":640,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_bm_01.png":
+{
+ "frame": {"x":640,"y":896,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_bm_01_grass.png":
+{
+ "frame": {"x":640,"y":768,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_bm_02.png":
+{
+ "frame": {"x":640,"y":640,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_bm_02_grass.png":
+{
+ "frame": {"x":896,"y":512,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_bm_03.png":
+{
+ "frame": {"x":768,"y":512,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_bm_03_grass.png":
+{
+ "frame": {"x":640,"y":512,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_bm_04.png":
+{
+ "frame": {"x":512,"y":896,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_bm_04_grass.png":
+{
+ "frame": {"x":512,"y":768,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_bm_05.png":
+{
+ "frame": {"x":512,"y":640,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_bm_05_grass.png":
+{
+ "frame": {"x":512,"y":512,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_br.png":
+{
+ "frame": {"x":896,"y":384,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_br_grass.png":
+{
+ "frame": {"x":768,"y":384,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_l_up_diagonal.png":
+{
+ "frame": {"x":640,"y":384,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_l_up_diagonal_grass.png":
+{
+ "frame": {"x":512,"y":384,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_l_up_diagonal_neighbour.png":
+{
+ "frame": {"x":384,"y":896,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_l_up_diagonal_neighbour_grass.png":
+{
+ "frame": {"x":384,"y":768,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_lm_01.png":
+{
+ "frame": {"x":384,"y":640,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_lm_01_grass.png":
+{
+ "frame": {"x":384,"y":512,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_lm_02.png":
+{
+ "frame": {"x":384,"y":384,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_lm_02_grass.png":
+{
+ "frame": {"x":896,"y":256,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_lm_03.png":
+{
+ "frame": {"x":768,"y":256,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_lm_03_grass.png":
+{
+ "frame": {"x":640,"y":256,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_lm_04.png":
+{
+ "frame": {"x":512,"y":256,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_lm_04_grass.png":
+{
+ "frame": {"x":384,"y":256,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_r_diagonal_neighbour.png":
+{
+ "frame": {"x":256,"y":896,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_r_diagonal_neighbour_grass.png":
+{
+ "frame": {"x":256,"y":768,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_r_down_diagonal.png":
+{
+ "frame": {"x":256,"y":640,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_r_down_diagonal_grass.png":
+{
+ "frame": {"x":256,"y":512,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_r_down_diagonal_neighbour.png":
+{
+ "frame": {"x":256,"y":384,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_r_down_diagonal_neighbour_grass.png":
+{
+ "frame": {"x":256,"y":256,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_r_up_diagonal.png":
+{
+ "frame": {"x":896,"y":128,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_r_up_diagonal_grass.png":
+{
+ "frame": {"x":768,"y":128,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_rm_01-22.png":
+{
+ "frame": {"x":256,"y":128,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_rm_01.png":
+{
+ "frame": {"x":640,"y":128,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_rm_01_grass-22.png":
+{
+ "frame": {"x":384,"y":128,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_rm_01_grass.png":
+{
+ "frame": {"x":512,"y":128,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_rm_02.png":
+{
+ "frame": {"x":128,"y":896,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_rm_02_grass.png":
+{
+ "frame": {"x":128,"y":768,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_rm_03.png":
+{
+ "frame": {"x":128,"y":640,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_rm_03_grass.png":
+{
+ "frame": {"x":128,"y":512,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_rm_04.png":
+{
+ "frame": {"x":128,"y":384,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_rm_04_grass.png":
+{
+ "frame": {"x":128,"y":256,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_rm_05.png":
+{
+ "frame": {"x":128,"y":128,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_rm_05_grass.png":
+{
+ "frame": {"x":896,"y":0,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_tl.png":
+{
+ "frame": {"x":768,"y":0,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_tl_grass.png":
+{
+ "frame": {"x":640,"y":0,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_tm_01.png":
+{
+ "frame": {"x":512,"y":0,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_tm_01_grass.png":
+{
+ "frame": {"x":384,"y":0,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_tm_02.png":
+{
+ "frame": {"x":256,"y":0,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_tm_02_grass.png":
+{
+ "frame": {"x":128,"y":0,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_tm_03.png":
+{
+ "frame": {"x":0,"y":896,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_tm_03_grass.png":
+{
+ "frame": {"x":0,"y":768,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_tm_04.png":
+{
+ "frame": {"x":0,"y":640,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_tm_04_grass.png":
+{
+ "frame": {"x":0,"y":512,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_tr.png":
+{
+ "frame": {"x":0,"y":384,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"beach_tr_grass.png":
+{
+ "frame": {"x":0,"y":256,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"grass.png":
+{
+ "frame": {"x":0,"y":128,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+},
+"sand.png":
+{
+ "frame": {"x":0,"y":0,"w":128,"h":128},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
+ "sourceSize": {"w":128,"h":128}
+}},
+"meta": {
+ "app": "http://www.texturepacker.com",
+ "version": "1.0",
+ "image": "ground.png",
+ "format": "RGBA8888",
+ "size": {"w":1024,"h":1024},
+ "scale": "1",
+ "smartupdate": "$TexturePacker:SmartUpdate:a4e7956c80b1dabce22dc97d34f9811b$"
+}
+}
diff --git a/Games/Debris_Dodge/media/ground.png b/Games/Debris_Dodge/media/ground.png
new file mode 100644
index 0000000000..536a5ceb08
Binary files /dev/null and b/Games/Debris_Dodge/media/ground.png differ
diff --git a/Games/Debris_Dodge/media/ground_units.json b/Games/Debris_Dodge/media/ground_units.json
new file mode 100644
index 0000000000..a3bcc73653
--- /dev/null
+++ b/Games/Debris_Dodge/media/ground_units.json
@@ -0,0 +1,900 @@
+{"frames": {
+
+"tank1_body.png":
+{
+ "frame": {"x":278,"y":338,"w":34,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":34,"h":48},
+ "sourceSize": {"w":34,"h":48}
+},
+"tank1_body_1b.png":
+{
+ "frame": {"x":314,"y":382,"w":34,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":34,"h":48},
+ "sourceSize": {"w":34,"h":48}
+},
+"tank1_body_destroyed.png":
+{
+ "frame": {"x":322,"y":332,"w":34,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":34,"h":48},
+ "sourceSize": {"w":34,"h":48}
+},
+"tank1_body_destroyed_shadow.png":
+{
+ "frame": {"x":438,"y":346,"w":34,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":34,"h":48},
+ "sourceSize": {"w":34,"h":48}
+},
+"tank1_body_hit.png":
+{
+ "frame": {"x":366,"y":302,"w":34,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":34,"h":48},
+ "sourceSize": {"w":34,"h":48}
+},
+"tank1_body_shadow.png":
+{
+ "frame": {"x":402,"y":302,"w":34,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":34,"h":48},
+ "sourceSize": {"w":34,"h":48}
+},
+"tank1_dualgun.png":
+{
+ "frame": {"x":336,"y":244,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank1_dualgun_hit.png":
+{
+ "frame": {"x":234,"y":196,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank1_gun.png":
+{
+ "frame": {"x":176,"y":196,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank1_gun_destroyed-02.png":
+{
+ "frame": {"x":60,"y":196,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank1_gun_destroyed.png":
+{
+ "frame": {"x":118,"y":196,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank1_gun_hit.png":
+{
+ "frame": {"x":2,"y":196,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank1_track.png":
+{
+ "frame": {"x":394,"y":352,"w":34,"h":28},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":34,"h":28},
+ "sourceSize": {"w":34,"h":28}
+},
+"tank1b_body.png":
+{
+ "frame": {"x":358,"y":352,"w":34,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":34,"h":48},
+ "sourceSize": {"w":34,"h":48}
+},
+"tank1b_body_destroyed.png":
+{
+ "frame": {"x":474,"y":346,"w":34,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":34,"h":48},
+ "sourceSize": {"w":34,"h":48}
+},
+"tank1b_body_destroyed_shadow.png":
+{
+ "frame": {"x":438,"y":346,"w":34,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":34,"h":48},
+ "sourceSize": {"w":34,"h":48}
+},
+"tank1b_body_shadow.png":
+{
+ "frame": {"x":402,"y":302,"w":34,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":34,"h":48},
+ "sourceSize": {"w":34,"h":48}
+},
+"tank1b_dualgun.png":
+{
+ "frame": {"x":176,"y":254,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank1b_dualgun_hit.png":
+{
+ "frame": {"x":118,"y":254,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank1b_gun.png":
+{
+ "frame": {"x":60,"y":254,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank1b_gun_destroyed-02.png":
+{
+ "frame": {"x":452,"y":288,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank1b_gun_destroyed.png":
+{
+ "frame": {"x":2,"y":254,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank1b_gun_hit.png":
+{
+ "frame": {"x":394,"y":244,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank1b_track.png":
+{
+ "frame": {"x":394,"y":382,"w":34,"h":28},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":34,"h":28},
+ "sourceSize": {"w":34,"h":28}
+},
+"tank2_body.png":
+{
+ "frame": {"x":292,"y":138,"w":42,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":42,"h":48},
+ "sourceSize": {"w":42,"h":48}
+},
+"tank2_body_destroyed.png":
+{
+ "frame": {"x":458,"y":180,"w":42,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":42,"h":48},
+ "sourceSize": {"w":42,"h":48}
+},
+"tank2_body_destroyed_shadow.png":
+{
+ "frame": {"x":234,"y":254,"w":42,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":42,"h":48},
+ "sourceSize": {"w":42,"h":48}
+},
+"tank2_body_hit.png":
+{
+ "frame": {"x":458,"y":130,"w":42,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":42,"h":48},
+ "sourceSize": {"w":42,"h":48}
+},
+"tank2_body_shadow.png":
+{
+ "frame": {"x":292,"y":188,"w":42,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":42,"h":48},
+ "sourceSize": {"w":42,"h":48}
+},
+"tank2_dualgun.png":
+{
+ "frame": {"x":342,"y":2,"w":66,"h":66},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":66,"h":66},
+ "sourceSize": {"w":66,"h":66}
+},
+"tank2_dualgun_destroyed.png":
+{
+ "frame": {"x":274,"y":2,"w":66,"h":66},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":66,"h":66},
+ "sourceSize": {"w":66,"h":66}
+},
+"tank2_dualgun_hit.png":
+{
+ "frame": {"x":206,"y":2,"w":66,"h":66},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":66,"h":66},
+ "sourceSize": {"w":66,"h":66}
+},
+"tank2_gun.png":
+{
+ "frame": {"x":138,"y":2,"w":66,"h":66},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":66,"h":66},
+ "sourceSize": {"w":66,"h":66}
+},
+"tank2_gun_destroyed.png":
+{
+ "frame": {"x":70,"y":2,"w":66,"h":66},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":66,"h":66},
+ "sourceSize": {"w":66,"h":66}
+},
+"tank2_gun_hit.png":
+{
+ "frame": {"x":2,"y":2,"w":66,"h":66},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":66,"h":66},
+ "sourceSize": {"w":66,"h":66}
+},
+"tank2_track.png":
+{
+ "frame": {"x":322,"y":302,"w":42,"h":28},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":42,"h":28},
+ "sourceSize": {"w":42,"h":28}
+},
+"tank2b_body.png":
+{
+ "frame": {"x":234,"y":304,"w":42,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":42,"h":48},
+ "sourceSize": {"w":42,"h":48}
+},
+"tank2b_body_destroyed.png":
+{
+ "frame": {"x":278,"y":288,"w":42,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":42,"h":48},
+ "sourceSize": {"w":42,"h":48}
+},
+"tank2b_body_destroyed_shadow.png":
+{
+ "frame": {"x":234,"y":254,"w":42,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":42,"h":48},
+ "sourceSize": {"w":42,"h":48}
+},
+"tank2b_body_hit.png":
+{
+ "frame": {"x":292,"y":238,"w":42,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":42,"h":48},
+ "sourceSize": {"w":42,"h":48}
+},
+"tank2b_body_shadow.png":
+{
+ "frame": {"x":292,"y":188,"w":42,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":42,"h":48},
+ "sourceSize": {"w":42,"h":48}
+},
+"tank2b_dualgun.png":
+{
+ "frame": {"x":274,"y":70,"w":66,"h":66},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":66,"h":66},
+ "sourceSize": {"w":66,"h":66}
+},
+"tank2b_dualgun_destroyed.png":
+{
+ "frame": {"x":206,"y":70,"w":66,"h":66},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":66,"h":66},
+ "sourceSize": {"w":66,"h":66}
+},
+"tank2b_dualgun_hit.png":
+{
+ "frame": {"x":138,"y":70,"w":66,"h":66},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":66,"h":66},
+ "sourceSize": {"w":66,"h":66}
+},
+"tank2b_gun.png":
+{
+ "frame": {"x":70,"y":70,"w":66,"h":66},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":66,"h":66},
+ "sourceSize": {"w":66,"h":66}
+},
+"tank2b_gun_destroyed.png":
+{
+ "frame": {"x":2,"y":70,"w":66,"h":66},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":66,"h":66},
+ "sourceSize": {"w":66,"h":66}
+},
+"tank2b_gun_hit.png":
+{
+ "frame": {"x":410,"y":2,"w":66,"h":66},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":66,"h":66},
+ "sourceSize": {"w":66,"h":66}
+},
+"tank3_body.png":
+{
+ "frame": {"x":58,"y":481,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3_body_destroyed.png":
+{
+ "frame": {"x":30,"y":481,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3_body_destroyed_shadow.png":
+{
+ "frame": {"x":2,"y":481,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3_body_hit.png":
+{
+ "frame": {"x":198,"y":440,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3_body_shadow.png":
+{
+ "frame": {"x":170,"y":440,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3_gun.png":
+{
+ "frame": {"x":452,"y":230,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank3_gun_destroyed.png":
+{
+ "frame": {"x":394,"y":186,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank3_gun_hit.png":
+{
+ "frame": {"x":336,"y":186,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank3_track.png":
+{
+ "frame": {"x":290,"y":478,"w":22,"h":28},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":22,"h":28},
+ "sourceSize": {"w":22,"h":28}
+},
+"tank3b_body.png":
+{
+ "frame": {"x":142,"y":440,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3b_body_destroyed.png":
+{
+ "frame": {"x":114,"y":440,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3b_body_hit.png":
+{
+ "frame": {"x":86,"y":440,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3b_gun.png":
+{
+ "frame": {"x":234,"y":138,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank3b_gun_destroyed.png":
+{
+ "frame": {"x":176,"y":138,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank3b_gun_hit.png":
+{
+ "frame": {"x":118,"y":138,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank3b_track.png":
+{
+ "frame": {"x":290,"y":448,"w":22,"h":28},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":22,"h":28},
+ "sourceSize": {"w":22,"h":28}
+},
+"tank3c_body.png":
+{
+ "frame": {"x":58,"y":440,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3c_body_destroyed.png":
+{
+ "frame": {"x":30,"y":440,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3c_body_hit.png":
+{
+ "frame": {"x":2,"y":440,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3c_gun.png":
+{
+ "frame": {"x":60,"y":138,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank3c_gun_destroyed.png":
+{
+ "frame": {"x":2,"y":138,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank3c_gun_hit.png":
+{
+ "frame": {"x":400,"y":128,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank3c_track.png":
+{
+ "frame": {"x":290,"y":418,"w":22,"h":28},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":22,"h":28},
+ "sourceSize": {"w":22,"h":28}
+},
+"tank3d_body.png":
+{
+ "frame": {"x":370,"y":540,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3d_body_destroyed.png":
+{
+ "frame": {"x":342,"y":530,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3d_body_hit.png":
+{
+ "frame": {"x":314,"y":496,"w":26,"h":39},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":26,"h":39},
+ "sourceSize": {"w":26,"h":39}
+},
+"tank3d_gun.png":
+{
+ "frame": {"x":342,"y":128,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank3d_gun_destroyed.png":
+{
+ "frame": {"x":400,"y":70,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank3d_gun_hit.png":
+{
+ "frame": {"x":342,"y":70,"w":56,"h":56},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":56},
+ "sourceSize": {"w":56,"h":56}
+},
+"tank3d_track.png":
+{
+ "frame": {"x":290,"y":388,"w":22,"h":28},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":22,"h":28},
+ "sourceSize": {"w":22,"h":28}
+},
+"truck1_body.png":
+{
+ "frame": {"x":258,"y":452,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck1_body_hit.png":
+{
+ "frame": {"x":226,"y":418,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck1_body_shadow.png":
+{
+ "frame": {"x":194,"y":376,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck1_destroyed.png":
+{
+ "frame": {"x":162,"y":376,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck1_destroyed_shadow.png":
+{
+ "frame": {"x":130,"y":376,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck1b_body.png":
+{
+ "frame": {"x":98,"y":376,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck1b_body_hit.png":
+{
+ "frame": {"x":66,"y":376,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck1b_destroyed.png":
+{
+ "frame": {"x":34,"y":376,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck1c_body.png":
+{
+ "frame": {"x":2,"y":376,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck1c_body_hit.png":
+{
+ "frame": {"x":474,"y":524,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck1c_destroyed.png":
+{
+ "frame": {"x":442,"y":524,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck2_body.png":
+{
+ "frame": {"x":410,"y":524,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck2_body_hit.png":
+{
+ "frame": {"x":378,"y":476,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck2_body_shadow.png":
+{
+ "frame": {"x":346,"y":466,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck2_destroyed.png":
+{
+ "frame": {"x":314,"y":432,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck2_destroyed_shadow.png":
+{
+ "frame": {"x":258,"y":388,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck2b_body.png":
+{
+ "frame": {"x":226,"y":354,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck2b_body_hit.png":
+{
+ "frame": {"x":194,"y":312,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck2b_destroyed.png":
+{
+ "frame": {"x":162,"y":312,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck2c_body.png":
+{
+ "frame": {"x":130,"y":312,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck2c_body_hit.png":
+{
+ "frame": {"x":98,"y":312,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck2c_destroyed.png":
+{
+ "frame": {"x":66,"y":312,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck3_body.png":
+{
+ "frame": {"x":34,"y":312,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck3_body_hit.png":
+{
+ "frame": {"x":2,"y":312,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck3_body_shadow.png":
+{
+ "frame": {"x":478,"y":460,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck3_destroyed.png":
+{
+ "frame": {"x":446,"y":460,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck3_destroyed_shadow.png":
+{
+ "frame": {"x":414,"y":460,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck3b_body.png":
+{
+ "frame": {"x":462,"y":396,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck3b_body_hit.png":
+{
+ "frame": {"x":382,"y":412,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck3b_destroyed.png":
+{
+ "frame": {"x":430,"y":396,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck3c_body.png":
+{
+ "frame": {"x":350,"y":402,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck3c_body_hit.png":
+{
+ "frame": {"x":478,"y":66,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck3c_destroyed.png":
+{
+ "frame": {"x":478,"y":2,"w":30,"h":62},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":30,"h":62},
+ "sourceSize": {"w":30,"h":62}
+},
+"truck_track.png":
+{
+ "frame": {"x":86,"y":481,"w":24,"h":23},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":24,"h":23},
+ "sourceSize": {"w":24,"h":23}
+}},
+"meta": {
+ "app": "http://www.texturepacker.com",
+ "version": "1.0",
+ "image": "ground_units.png",
+ "format": "RGBA8888",
+ "size": {"w":512,"h":1024},
+ "scale": "1",
+ "smartupdate": "$TexturePacker:SmartUpdate:0da088f23b1454869c6ff25aabebeda2$"
+}
+}
diff --git a/Games/Debris_Dodge/media/ground_units.png b/Games/Debris_Dodge/media/ground_units.png
new file mode 100644
index 0000000000..c9c32a48a4
Binary files /dev/null and b/Games/Debris_Dodge/media/ground_units.png differ
diff --git a/Games/Debris_Dodge/media/menu_music.ogg b/Games/Debris_Dodge/media/menu_music.ogg
new file mode 100644
index 0000000000..d3706a6a10
Binary files /dev/null and b/Games/Debris_Dodge/media/menu_music.ogg differ
diff --git a/Games/Debris_Dodge/media/metal_interaction2.wav b/Games/Debris_Dodge/media/metal_interaction2.wav
new file mode 100644
index 0000000000..8519c81c36
Binary files /dev/null and b/Games/Debris_Dodge/media/metal_interaction2.wav differ
diff --git a/Games/Debris_Dodge/media/names.txt b/Games/Debris_Dodge/media/names.txt
new file mode 100644
index 0000000000..bae56c1746
--- /dev/null
+++ b/Games/Debris_Dodge/media/names.txt
@@ -0,0 +1,279 @@
+Strippy
+Boffo
+Buffo
+Drips
+Porno
+Mentos
+Felches
+Grout
+Oboe
+Bumbo
+Salvo
+Drowny
+Viagra
+Acetone
+Zarathustra
+Styptic
+Recidivo
+Unctuous
+Reflux
+Maalox
+Scurvy
+Rickets
+Noriega
+Polyp
+Garbo
+Tantric
+Phalanx
+Bondage
+Asbestos
+Trenton
+Aspic
+Marfin
+Merkin
+Dropsy
+Ponzi
+Garrotte
+Calphalon
+Juvie
+Schmoey Schmoe-head
+Nixon
+Droppo
+Lesions
+Boner
+Clowny
+Clooney
+Frowny
+Enfilade
+Mojo
+Ploop
+Contusions
+Animus
+Pooey Poo-head
+Weiner
+Bac-o’s
+Buckminster Fuller
+Domey
+Subjunctive Predicate
+Pestilence
+Fury
+Ninja
+Platano
+Melons
+Nutout
+Dangles
+Tipsy
+Steerage
+Dunkirk
+Antietam
+Chins
+Genuflection
+Kreplach
+Pringles
+Shingles
+Viscera
+Viggo
+Kielbasa
+Sterno
+Salieri
+Flaubert
+Gaspachio
+Chagganooga
+Nougat
+Lumps
+Chewie
+Wookie
+Kotter
+Dracula
+Han Solo
+Jabba
+Lando
+Alone
+Felony
+Diaspora
+Perspicacity
+Tapenade
+Detritus
+Jaundice
+Sbarro
+Osmosis
+Achtung!
+Stabbo
+Oleo
+Tampopo
+Nagano
+Synergy
+Effluvia
+Leg Warmers
+Roboto
+Prozac
+Eukenuba
+Retsin
+Rogaine
+Ethel Merman
+Topical Crème
+Ointment
+Cold Compress
+Ace Bandage
+Bourbon
+Bowels
+Brundlefly
+Platanos
+My People
+Mo’ Broccoli
+Borracho
+Smuts
+Nehi
+Olestra
+Greedo
+Coolio
+Consiglieri
+Dinner Bell
+Maligno
+Angelos
+Parabola
+Velcro
+Nubbins
+Scuppers
+Kippers
+Flautas
+Blintzes
+Decoupage
+Sprockets
+Lupins
+Gorditas
+Funyuns
+Manicles
+Drano
+Alcoa
+Spores
+Spleens
+Placenta
+Melanoma
+Pathos
+Explosivo
+Feces
+Epoxy
+Ruminant
+Calamari
+Toffifay
+Proletariat
+Cop Rock
+Tunisia
+Cabal
+Glasnost
+Lanolin
+Surfeit
+Pillages
+Acela
+Wads
+Bulges
+Apshai
+Zork
+Socratic Method
+Stoma
+Bulldog
+Bilbo
+Stephen McNulty
+Ball in
+Schyler
+Santyl
+Fungible
+Fergie
+Verizon
+Magma
+Clown
+Mature Content
+Humpy
+Flabby
+Meniscus
+Mellotron
+M.C. Escher
+Morbidly Obese
+Fluffer
+Strychnine
+Tolstoy
+String Theory
+Mastication
+Corpuscle
+Animatronic Abraham Lincoln
+Arrears
+No Lo Contendere
+Top Copy is Yours
+Wankel
+Peristalsis
+Next Stop Friendship Heights
+Pompatus
+T’aint
+Repetitive Motion Disorder
+Post-traumatic Stress Disorder
+Aphasia
+Enlarged Prostate
+Irritable Bowel Syndrome
+Feline Distemper
+Riboflavin
+Flintstones Chewable Vitamins
+Metamucils
+Premature Ejaculation
+Erection Lasting More Than 4 Hours
+The
+Quadratic Equation
+Stephen Hawking
+Hello Larry
+Mildly Amusing
+Scary to Children
+Harmful If Swallowed
+Do Not Take Internally
+Courtesy Flush
+Chuck Norris
+Collect all Four
+Buckaroo Bonsai Reference
+Your Mom Just Died
+Jonathan Coulton
+Axis of Evil
+Crystal Pepsi
+Dr. Bob
+FEMA
+Broken Levee
+Rhyming Couplet
+Iambic Pentameter
+Reforestation
+Eustachian Tube
+Trachea
+Duodenum
+Relapse
+Dressing on the Side, Please,
+High Fructose Corn Syrup
+High Fiber Diet
+Load Bearing Wall
+Partially Hydrogenated
+Reconstituted Meat Slurry
+30,000 lbs of Bananas
+Open-source Coding
+Network Administrator
+Subversive Meme
+4th Grade Education
+Conscientious Objector
+Monkeydogs
+Irregular Heartbeat
+Asystole
+Peak Oil
+SEPTA IRA
+Diversified Stock Portfolio
+No-load Funds
+0% APR for 12 Months
+Zero-Sum Game
+Nitrogen-fixing Crop Rotation
+Frame-off Restoration
+Acid Bath
+Knights Templar
+Mary Magdalene
+Unexpected Plot Twist
+Kennesaw Mountain Landis
+Unintended Consequences
+Odd-lot Purchases
+Martyrs
+Consenting Adults
+Store-brand Taco Seasoning
+Penultimate
+It’s Not a Balloon It’s an Airship
diff --git a/Games/Debris_Dodge/media/pickups.json b/Games/Debris_Dodge/media/pickups.json
new file mode 100644
index 0000000000..c3cdd6acba
--- /dev/null
+++ b/Games/Debris_Dodge/media/pickups.json
@@ -0,0 +1,68 @@
+{"frames": {
+
+"coin.png":
+{
+ "frame": {"x":2,"y":80,"w":25,"h":24},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":25,"h":24},
+ "sourceSize": {"w":25,"h":24}
+},
+"diagonal_gun.png":
+{
+ "frame": {"x":29,"y":54,"w":25,"h":24},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":25,"h":24},
+ "sourceSize": {"w":25,"h":24}
+},
+"life_up.png":
+{
+ "frame": {"x":2,"y":54,"w":25,"h":24},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":25,"h":24},
+ "sourceSize": {"w":25,"h":24}
+},
+"missile.png":
+{
+ "frame": {"x":29,"y":28,"w":25,"h":24},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":25,"h":24},
+ "sourceSize": {"w":25,"h":24}
+},
+"repair.png":
+{
+ "frame": {"x":2,"y":28,"w":25,"h":24},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":25,"h":24},
+ "sourceSize": {"w":25,"h":24}
+},
+"straight_gun.png":
+{
+ "frame": {"x":29,"y":2,"w":25,"h":24},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":25,"h":24},
+ "sourceSize": {"w":25,"h":24}
+},
+"wingman.png":
+{
+ "frame": {"x":2,"y":2,"w":25,"h":24},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":25,"h":24},
+ "sourceSize": {"w":25,"h":24}
+}},
+"meta": {
+ "app": "http://www.codeandweb.com/texturepacker ",
+ "version": "1.0",
+ "image": "pickups.png",
+ "format": "RGBA8888",
+ "size": {"w":64,"h":128},
+ "scale": "0.5",
+ "smartupdate": "$TexturePacker:SmartUpdate:5bccec5fb5decb53145ba22504d88edd:1/1$"
+}
+}
diff --git a/Games/Debris_Dodge/media/pickups.png b/Games/Debris_Dodge/media/pickups.png
new file mode 100644
index 0000000000..4cdd27e732
Binary files /dev/null and b/Games/Debris_Dodge/media/pickups.png differ
diff --git a/Games/Debris_Dodge/media/top_secret.ttf b/Games/Debris_Dodge/media/top_secret.ttf
new file mode 100644
index 0000000000..fa8644287f
Binary files /dev/null and b/Games/Debris_Dodge/media/top_secret.ttf differ
diff --git a/Games/Debris_Dodge/media/trees.png b/Games/Debris_Dodge/media/trees.png
new file mode 100644
index 0000000000..1d461258f2
Binary files /dev/null and b/Games/Debris_Dodge/media/trees.png differ
diff --git a/Games/Debris_Dodge/media/trees_packed.json b/Games/Debris_Dodge/media/trees_packed.json
new file mode 100644
index 0000000000..be7906b52e
--- /dev/null
+++ b/Games/Debris_Dodge/media/trees_packed.json
@@ -0,0 +1,388 @@
+{"frames": {
+
+"tree_01.png":
+{
+ "frame": {"x":280,"y":183,"w":62,"h":66},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":0,"w":62,"h":66},
+ "sourceSize": {"w":68,"h":68}
+},
+"tree_02.png":
+{
+ "frame": {"x":125,"y":3,"w":40,"h":38},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":2,"w":40,"h":38},
+ "sourceSize": {"w":44,"h":42}
+},
+"tree_03.png":
+{
+ "frame": {"x":451,"y":115,"w":66,"h":64},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":66,"h":64},
+ "sourceSize": {"w":68,"h":68}
+},
+"tree_04.png":
+{
+ "frame": {"x":383,"y":115,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":68,"h":68}
+},
+"tree_05.png":
+{
+ "frame": {"x":217,"y":55,"w":54,"h":52},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":54,"h":52},
+ "sourceSize": {"w":54,"h":54}
+},
+"tree_06.png":
+{
+ "frame": {"x":3,"y":55,"w":46,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":46,"h":48},
+ "sourceSize": {"w":46,"h":48}
+},
+"tree_07.png":
+{
+ "frame": {"x":443,"y":55,"w":52,"h":56},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":0,"w":52,"h":56},
+ "sourceSize": {"w":56,"h":58}
+},
+"tree_08.png":
+{
+ "frame": {"x":163,"y":55,"w":50,"h":50},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":1,"w":50,"h":50},
+ "sourceSize": {"w":50,"h":52}
+},
+"tree_09.png":
+{
+ "frame": {"x":39,"y":3,"w":38,"h":38},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":38,"h":38},
+ "sourceSize": {"w":40,"h":40}
+},
+"tree_10.png":
+{
+ "frame": {"x":3,"y":183,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":66,"h":66}
+},
+"tree_11.png":
+{
+ "frame": {"x":365,"y":3,"w":47,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":2,"w":47,"h":48},
+ "sourceSize": {"w":47,"h":52}
+},
+"tree_12.png":
+{
+ "frame": {"x":416,"y":3,"w":46,"h":48},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":46,"h":48},
+ "sourceSize": {"w":46,"h":48}
+},
+"tree_13.png":
+{
+ "frame": {"x":3,"y":3,"w":32,"h":32},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
+ "sourceSize": {"w":32,"h":32}
+},
+"tree_14.png":
+{
+ "frame": {"x":215,"y":3,"w":46,"h":44},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":46,"h":44},
+ "sourceSize": {"w":46,"h":44}
+},
+"tree_15.png":
+{
+ "frame": {"x":71,"y":183,"w":64,"h":64},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":64,"h":64},
+ "sourceSize": {"w":66,"h":66}
+},
+"tree_16.png":
+{
+ "frame": {"x":169,"y":3,"w":42,"h":42},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":1,"w":42,"h":42},
+ "sourceSize": {"w":48,"h":46}
+},
+"tree_17.png":
+{
+ "frame": {"x":385,"y":55,"w":54,"h":54},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":2,"w":54,"h":54},
+ "sourceSize": {"w":54,"h":58}
+},
+"tree_18.png":
+{
+ "frame": {"x":139,"y":183,"w":68,"h":64},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":68,"h":64},
+ "sourceSize": {"w":70,"h":64}
+},
+"tree_19.png":
+{
+ "frame": {"x":67,"y":115,"w":56,"h":58},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":56,"h":58},
+ "sourceSize": {"w":56,"h":60}
+},
+"tree_20.png":
+{
+ "frame": {"x":275,"y":55,"w":48,"h":52},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":1,"w":48,"h":52},
+ "sourceSize": {"w":52,"h":54}
+},
+"tree_21.png":
+{
+ "frame": {"x":3,"y":115,"w":60,"h":56},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":60,"h":56},
+ "sourceSize": {"w":64,"h":60}
+},
+"tree_22.png":
+{
+ "frame": {"x":327,"y":55,"w":54,"h":54},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":2,"w":54,"h":54},
+ "sourceSize": {"w":54,"h":60}
+},
+"tree_23.png":
+{
+ "frame": {"x":346,"y":183,"w":62,"h":66},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":62,"h":66},
+ "sourceSize": {"w":64,"h":68}
+},
+"tree_24.png":
+{
+ "frame": {"x":81,"y":3,"w":40,"h":38},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":1,"w":40,"h":38},
+ "sourceSize": {"w":40,"h":42}
+},
+"tree_25.png":
+{
+ "frame": {"x":255,"y":115,"w":58,"h":62},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":58,"h":62},
+ "sourceSize": {"w":60,"h":62}
+},
+"tree_26.png":
+{
+ "frame": {"x":466,"y":3,"w":48,"h":48},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":48,"h":48},
+ "sourceSize": {"w":50,"h":50}
+},
+"tree_27.png":
+{
+ "frame": {"x":127,"y":115,"w":56,"h":60},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":1,"w":56,"h":60},
+ "sourceSize": {"w":60,"h":62}
+},
+"tree_28.png":
+{
+ "frame": {"x":3,"y":257,"w":66,"h":70},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":66,"h":70},
+ "sourceSize": {"w":70,"h":70}
+},
+"tree_29.png":
+{
+ "frame": {"x":111,"y":55,"w":48,"h":50},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":1,"w":48,"h":50},
+ "sourceSize": {"w":52,"h":52}
+},
+"tree_30.png":
+{
+ "frame": {"x":317,"y":115,"w":62,"h":62},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":2,"w":62,"h":62},
+ "sourceSize": {"w":66,"h":66}
+},
+"tree_38.png":
+{
+ "frame": {"x":407,"y":341,"w":110,"h":114},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":3,"w":110,"h":114},
+ "sourceSize": {"w":110,"h":120}
+},
+"tree_39.png":
+{
+ "frame": {"x":301,"y":341,"w":102,"h":114},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":1,"w":102,"h":114},
+ "sourceSize": {"w":106,"h":120}
+},
+"tree_40.png":
+{
+ "frame": {"x":211,"y":183,"w":65,"h":65},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":65,"h":65},
+ "sourceSize": {"w":67,"h":65}
+},
+"tree_41.png":
+{
+ "frame": {"x":151,"y":257,"w":74,"h":72},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":74,"h":72},
+ "sourceSize": {"w":82,"h":76}
+},
+"tree_42.png":
+{
+ "frame": {"x":73,"y":257,"w":74,"h":70},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":2,"w":74,"h":70},
+ "sourceSize": {"w":78,"h":76}
+},
+"tree_43.png":
+{
+ "frame": {"x":385,"y":257,"w":78,"h":78},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":1,"w":78,"h":78},
+ "sourceSize": {"w":84,"h":82}
+},
+"tree_44.png":
+{
+ "frame": {"x":195,"y":341,"w":102,"h":108},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":2,"w":102,"h":108},
+ "sourceSize": {"w":110,"h":112}
+},
+"tree_46.png":
+{
+ "frame": {"x":467,"y":257,"w":70,"h":80},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":8,"w":70,"h":80},
+ "sourceSize": {"w":76,"h":90}
+},
+"tree_47.png":
+{
+ "frame": {"x":53,"y":55,"w":54,"h":50},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":2,"w":54,"h":50},
+ "sourceSize": {"w":60,"h":56}
+},
+"tree_48.png":
+{
+ "frame": {"x":265,"y":3,"w":46,"h":46},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":6,"y":3,"w":46,"h":46},
+ "sourceSize": {"w":60,"h":56}
+},
+"tree_49.png":
+{
+ "frame": {"x":315,"y":3,"w":46,"h":46},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":6,"y":5,"w":46,"h":46},
+ "sourceSize": {"w":60,"h":56}
+},
+"tree_50.png":
+{
+ "frame": {"x":412,"y":183,"w":68,"h":70},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":2,"w":68,"h":70},
+ "sourceSize": {"w":70,"h":76}
+},
+"tree_51.png":
+{
+ "frame": {"x":229,"y":257,"w":74,"h":74},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":4,"y":1,"w":74,"h":74},
+ "sourceSize": {"w":86,"h":78}
+},
+"tree_52.png":
+{
+ "frame": {"x":187,"y":115,"w":64,"h":62},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":4,"w":64,"h":62},
+ "sourceSize": {"w":68,"h":68}
+},
+"tree_53.png":
+{
+ "frame": {"x":3,"y":341,"w":94,"h":82},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":5,"w":94,"h":82},
+ "sourceSize": {"w":94,"h":98}
+},
+"tree_54.png":
+{
+ "frame": {"x":101,"y":341,"w":90,"h":90},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":0,"w":90,"h":90},
+ "sourceSize": {"w":94,"h":90}
+},
+"tree_55.png":
+{
+ "frame": {"x":307,"y":257,"w":74,"h":78},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":2,"w":74,"h":78},
+ "sourceSize": {"w":76,"h":82}
+}},
+"meta": {
+ "app": "http://www.codeandweb.com/texturepacker ",
+ "version": "1.0",
+ "image": "trees_packed.png",
+ "format": "RGBA8888",
+ "size": {"w":540,"h":458},
+ "scale": "1",
+ "smartupdate": "$TexturePacker:SmartUpdate:d8b078a9b489ef94c8641390211543b9:adf8ffbc3d0a646442fdb1855a36ac0e:a7653fb3592c74bd7bf4b33200c676f1$"
+}
+}
diff --git a/Games/Debris_Dodge/media/trees_packed.png b/Games/Debris_Dodge/media/trees_packed.png
new file mode 100644
index 0000000000..24001c4505
Binary files /dev/null and b/Games/Debris_Dodge/media/trees_packed.png differ
diff --git a/Games/Debris_Dodge/media/water.png b/Games/Debris_Dodge/media/water.png
new file mode 100644
index 0000000000..7c4934cb0d
Binary files /dev/null and b/Games/Debris_Dodge/media/water.png differ
diff --git a/Games/Debris_Dodge/spec/misc/aabb_spec.rb b/Games/Debris_Dodge/spec/misc/aabb_spec.rb
new file mode 100644
index 0000000000..1ba5e3bcc3
--- /dev/null
+++ b/Games/Debris_Dodge/spec/misc/aabb_spec.rb
@@ -0,0 +1,85 @@
+require_relative '../../lib/misc/axis_aligned_bounding_box'
+describe AxisAlignedBoundingBox do
+
+ let(:center) { [5, 5] }
+ let(:half_dimension) { [10, 10] }
+ let(:box) { AxisAlignedBoundingBox.new(
+ center, half_dimension) }
+
+ let(:point1) { [4, 7] }
+ let(:point2) { [0, 0] }
+ let(:point3) { [10, 10] }
+ let(:point4) { [0, 10] }
+ let(:point5) { [10, 0] }
+
+ let(:point6) { [-2, 7] }
+ let(:point7) { [0, 11] }
+ let(:point8) { [11, 11] }
+ let(:point9) { [0, 11] }
+ let(:point10) { [11, 0] }
+ let(:point11) { [3, 15] }
+ let(:point12) { [11, 4] }
+ let(:point13) { [1, -4] }
+ let(:point14) { [-1, -4] }
+
+ describe '#contains?' do
+ it 'detects containing point' do
+ expect(box.contains?(point1)).to be_truthy
+ expect(box.contains?(point2)).to be_truthy
+ expect(box.contains?(point3)).to be_truthy
+ expect(box.contains?(point4)).to be_truthy
+ expect(box.contains?(point5)).to be_truthy
+ end
+
+ it 'does not detect points out of bounds' do
+ expect(box.contains?(point6)).to be_falsy
+ expect(box.contains?(point8)).to be_falsy
+ expect(box.contains?(point7)).to be_falsy
+ expect(box.contains?(point9)).to be_falsy
+ expect(box.contains?(point10)).to be_falsy
+ expect(box.contains?(point11)).to be_falsy
+ expect(box.contains?(point12)).to be_falsy
+ expect(box.contains?(point13)).to be_falsy
+ expect(box.contains?(point14)).to be_falsy
+ end
+ end
+
+ describe '#intersects?' do
+ # center within box
+ let(:box1) { AxisAlignedBoundingBox.new(
+ [8, 8], [10, 12]) }
+ # center out of boundaries
+ let(:box2) { AxisAlignedBoundingBox.new(
+ [12, 12], [15, 15]) }
+ let(:box2_1) { AxisAlignedBoundingBox.new(
+ [-5, -5], [1, 1]) }
+ # touching corners
+ let(:box3) { AxisAlignedBoundingBox.new(
+ [-1, -1], [0, 0]) }
+ # out of bounds
+ let(:box4) { AxisAlignedBoundingBox.new(
+ [15, 15], [17, 17]) }
+ let(:box5) { AxisAlignedBoundingBox.new(
+ [-5, -5], [-1, -1]) }
+ let(:box6) { AxisAlignedBoundingBox.new(
+ [5, 12], [6, 13]) }
+
+
+ it 'intersects with itself' do
+ expect(box.intersects?(box)).to be_truthy
+ end
+
+ it 'detects intersecting boxes' do
+ expect(box.intersects?(box1)).to be_truthy
+ expect(box.intersects?(box2)).to be_truthy
+ expect(box.intersects?(box2_1)).to be_truthy
+ expect(box.intersects?(box3)).to be_truthy
+ end
+
+ it 'does not detect interesections out of bounds' do
+ expect(box.intersects?(box4)).to be_falsy
+ expect(box.intersects?(box5)).to be_falsy
+ expect(box.intersects?(box6)).to be_falsy
+ end
+ end
+end
diff --git a/Games/Debris_Dodge/spec/misc/quad_tree_spec.rb b/Games/Debris_Dodge/spec/misc/quad_tree_spec.rb
new file mode 100644
index 0000000000..d28af31ffc
--- /dev/null
+++ b/Games/Debris_Dodge/spec/misc/quad_tree_spec.rb
@@ -0,0 +1,137 @@
+class GameObject
+ attr_accessor :x, :y
+ def initialize(x, y)
+ @x, @y = x, y
+ end
+
+ def location
+ [@x, @y]
+ end
+end
+
+require_relative '../../lib/misc/axis_aligned_bounding_box'
+require_relative '../../lib/misc/quad_tree'
+
+describe QuadTree do
+ let(:box) { AxisAlignedBoundingBox.new(
+ [5, 5], [10, 10]) }
+ let(:tree) { QuadTree.new(box) }
+
+ let(:location) { [5, 5] }
+ let(:object) { GameObject.new(*location) }
+
+ describe '#insert' do
+ subject { tree.insert(object) }
+
+ context 'around zero coordinates' do
+ let(:box) { AxisAlignedBoundingBox.new(
+ [0, 0], [10, 10]) }
+ let(:location) { [0, 0] }
+
+ it 'inserts in the middle' do
+ expect(subject).to be_truthy
+ end
+ end
+
+ context 'object in center' do
+ it 'gets inserted' do
+ expect(subject).to be_truthy
+ end
+ end
+
+ context 'object in corner' do
+ let(:location) { [10, 10] }
+ it 'gets inserted' do
+ expect(subject).to be_truthy
+ end
+ end
+
+ context 'object out of bounds' do
+ let(:location) { [11, 11] }
+ it 'does not get inserted' do
+ expect(subject).to be_falsy
+ end
+ end
+
+ context 'several objects' do
+ subject { tree.query_range(box) }
+ context 'to different locations' do
+ before do
+ 50.times do |i|
+ tree.insert(
+ GameObject.new(
+ rand(0..10),
+ rand(0..10)))
+ end
+ end
+ it 'inserts all' do
+ expect(subject.size).to eq 50
+ end
+ end
+
+ context 'to same location' do
+ before { 5.times { tree.insert(object.dup) } }
+
+ it 'still inserts all' do
+ expect(subject.size).to eq 5
+ end
+ end
+ end
+ end
+
+ describe '#query_range' do
+ let(:query_params) { [[5, 5], [10, 10]] }
+ let(:query_box) { AxisAlignedBoundingBox.new(*query_params) }
+ subject { tree.query_range(query_box) }
+
+ context 'empty tree' do
+ it 'returns empty array' do
+ should be_empty
+ end
+ end
+
+ context 'single object' do
+ before { tree.insert(object) }
+
+ context 'is found' do
+ let(:query_params) do
+ [object.location, object.location.map { |c| c + 1 }]
+ end
+ end
+ end
+ end
+
+ describe '#remove' do
+ context 'single object' do
+ before do
+ tree.insert(object)
+ tree.remove(object)
+ end
+ subject { tree.query_range(box) }
+
+ it 'removes it' do
+ expect(subject.size).to equal(0)
+ end
+ end
+
+ context 'all objects' do
+ before do
+ 50.times do
+ tree.insert(
+ GameObject.new(
+ rand(0..10),
+ rand(0..10)))
+ end
+ end
+ subject { tree.query_range(box) }
+
+ it 'removes it' do
+ expect(subject.size).to equal(50)
+ subject.each do |ob|
+ expect(tree.remove(ob)).to be_truthy
+ end
+ expect(tree.query_range(box).size).to equal(0)
+ end
+ end
+ end
+end
diff --git a/Games/Debris_Dodge/tank_island.gemspec b/Games/Debris_Dodge/tank_island.gemspec
new file mode 100644
index 0000000000..086617cadd
--- /dev/null
+++ b/Games/Debris_Dodge/tank_island.gemspec
@@ -0,0 +1,30 @@
+# coding: utf-8
+lib = File.expand_path('../lib', __FILE__)
+$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
+
+Gem::Specification.new do |spec|
+ spec.name = "tank_island"
+ spec.version = '1.0.5'
+ spec.authors = ["Tomas Varaneckas"]
+ spec.email = ["tomas.varaneckas@gmail.com"]
+ spec.summary = %q{Top down 2D shooter game that involves blowing up tanks}
+ spec.description = <<-EOS
+ This is a game built with Gosu library while writing "Developing Games With Ruby" book.
+ You can get the book at https://leanpub.com/developing-games-with-ruby
+ EOS
+ spec.homepage = "https://leanpub.com/developing-games-with-ruby"
+ spec.license = "MIT"
+
+ spec.files = `git ls-files -z`.split("\x0")
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
+ spec.require_paths = ["lib"]
+
+ spec.add_development_dependency "bundler", "~> 1.6"
+ spec.add_development_dependency "rake", "~> 10.0"
+
+ spec.add_runtime_dependency 'gosu', "~> 0.15.2"
+ spec.add_runtime_dependency 'rmagick'
+ spec.add_runtime_dependency 'gosu_texture_packer'
+ spec.add_runtime_dependency 'perlin_noise'
+end