colstrom-rtanque 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +10 -0
- data/.yardopts +4 -0
- data/Gemfile +4 -0
- data/Gemfile.ci +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +168 -0
- data/Rakefile +1 -0
- data/bin/rtanque +117 -0
- data/lib/rtanque.rb +31 -0
- data/lib/rtanque/arena.rb +8 -0
- data/lib/rtanque/bot.rb +117 -0
- data/lib/rtanque/bot/brain.rb +50 -0
- data/lib/rtanque/bot/brain_helper.rb +23 -0
- data/lib/rtanque/bot/command.rb +23 -0
- data/lib/rtanque/bot/radar.rb +54 -0
- data/lib/rtanque/bot/sensors.rb +37 -0
- data/lib/rtanque/bot/turret.rb +14 -0
- data/lib/rtanque/configuration.rb +47 -0
- data/lib/rtanque/explosion.rb +23 -0
- data/lib/rtanque/gui.rb +25 -0
- data/lib/rtanque/gui/bot.rb +71 -0
- data/lib/rtanque/gui/bot/health_color_calculator.rb +37 -0
- data/lib/rtanque/gui/draw_group.rb +30 -0
- data/lib/rtanque/gui/explosion.rb +25 -0
- data/lib/rtanque/gui/shell.rb +31 -0
- data/lib/rtanque/gui/window.rb +64 -0
- data/lib/rtanque/heading.rb +162 -0
- data/lib/rtanque/match.rb +73 -0
- data/lib/rtanque/match/tick_group.rb +50 -0
- data/lib/rtanque/movable.rb +51 -0
- data/lib/rtanque/normalized_attr.rb +69 -0
- data/lib/rtanque/point.rb +140 -0
- data/lib/rtanque/runner.rb +88 -0
- data/lib/rtanque/shell.rb +44 -0
- data/lib/rtanque/version.rb +3 -0
- data/resources/images/body.png +0 -0
- data/resources/images/bullet.png +0 -0
- data/resources/images/explosions/explosion2-1.png +0 -0
- data/resources/images/explosions/explosion2-10.png +0 -0
- data/resources/images/explosions/explosion2-11.png +0 -0
- data/resources/images/explosions/explosion2-12.png +0 -0
- data/resources/images/explosions/explosion2-13.png +0 -0
- data/resources/images/explosions/explosion2-14.png +0 -0
- data/resources/images/explosions/explosion2-15.png +0 -0
- data/resources/images/explosions/explosion2-16.png +0 -0
- data/resources/images/explosions/explosion2-17.png +0 -0
- data/resources/images/explosions/explosion2-18.png +0 -0
- data/resources/images/explosions/explosion2-19.png +0 -0
- data/resources/images/explosions/explosion2-2.png +0 -0
- data/resources/images/explosions/explosion2-20.png +0 -0
- data/resources/images/explosions/explosion2-21.png +0 -0
- data/resources/images/explosions/explosion2-22.png +0 -0
- data/resources/images/explosions/explosion2-23.png +0 -0
- data/resources/images/explosions/explosion2-24.png +0 -0
- data/resources/images/explosions/explosion2-25.png +0 -0
- data/resources/images/explosions/explosion2-26.png +0 -0
- data/resources/images/explosions/explosion2-27.png +0 -0
- data/resources/images/explosions/explosion2-28.png +0 -0
- data/resources/images/explosions/explosion2-29.png +0 -0
- data/resources/images/explosions/explosion2-3.png +0 -0
- data/resources/images/explosions/explosion2-30.png +0 -0
- data/resources/images/explosions/explosion2-31.png +0 -0
- data/resources/images/explosions/explosion2-32.png +0 -0
- data/resources/images/explosions/explosion2-33.png +0 -0
- data/resources/images/explosions/explosion2-34.png +0 -0
- data/resources/images/explosions/explosion2-35.png +0 -0
- data/resources/images/explosions/explosion2-36.png +0 -0
- data/resources/images/explosions/explosion2-37.png +0 -0
- data/resources/images/explosions/explosion2-38.png +0 -0
- data/resources/images/explosions/explosion2-39.png +0 -0
- data/resources/images/explosions/explosion2-4.png +0 -0
- data/resources/images/explosions/explosion2-40.png +0 -0
- data/resources/images/explosions/explosion2-41.png +0 -0
- data/resources/images/explosions/explosion2-42.png +0 -0
- data/resources/images/explosions/explosion2-43.png +0 -0
- data/resources/images/explosions/explosion2-44.png +0 -0
- data/resources/images/explosions/explosion2-45.png +0 -0
- data/resources/images/explosions/explosion2-46.png +0 -0
- data/resources/images/explosions/explosion2-47.png +0 -0
- data/resources/images/explosions/explosion2-48.png +0 -0
- data/resources/images/explosions/explosion2-49.png +0 -0
- data/resources/images/explosions/explosion2-5.png +0 -0
- data/resources/images/explosions/explosion2-50.png +0 -0
- data/resources/images/explosions/explosion2-51.png +0 -0
- data/resources/images/explosions/explosion2-52.png +0 -0
- data/resources/images/explosions/explosion2-53.png +0 -0
- data/resources/images/explosions/explosion2-54.png +0 -0
- data/resources/images/explosions/explosion2-55.png +0 -0
- data/resources/images/explosions/explosion2-56.png +0 -0
- data/resources/images/explosions/explosion2-57.png +0 -0
- data/resources/images/explosions/explosion2-58.png +0 -0
- data/resources/images/explosions/explosion2-59.png +0 -0
- data/resources/images/explosions/explosion2-6.png +0 -0
- data/resources/images/explosions/explosion2-60.png +0 -0
- data/resources/images/explosions/explosion2-61.png +0 -0
- data/resources/images/explosions/explosion2-62.png +0 -0
- data/resources/images/explosions/explosion2-63.png +0 -0
- data/resources/images/explosions/explosion2-64.png +0 -0
- data/resources/images/explosions/explosion2-65.png +0 -0
- data/resources/images/explosions/explosion2-66.png +0 -0
- data/resources/images/explosions/explosion2-67.png +0 -0
- data/resources/images/explosions/explosion2-68.png +0 -0
- data/resources/images/explosions/explosion2-69.png +0 -0
- data/resources/images/explosions/explosion2-7.png +0 -0
- data/resources/images/explosions/explosion2-70.png +0 -0
- data/resources/images/explosions/explosion2-71.png +0 -0
- data/resources/images/explosions/explosion2-8.png +0 -0
- data/resources/images/explosions/explosion2-9.png +0 -0
- data/resources/images/grass.png +0 -0
- data/resources/images/radar.png +0 -0
- data/resources/images/turret.png +0 -0
- data/rtanque.gemspec +34 -0
- data/sample_bots/camper.rb +79 -0
- data/sample_bots/keyboard.rb +50 -0
- data/sample_bots/seek_and_destroy.rb +53 -0
- data/screenshots/battle_1.png +0 -0
- data/screenshots/battle_2.png +0 -0
- data/spec/rtanque/bot_spec.rb +239 -0
- data/spec/rtanque/heading_spec.rb +279 -0
- data/spec/rtanque/match_spec.rb +46 -0
- data/spec/rtanque/normalized_attr_spec.rb +90 -0
- data/spec/rtanque/point_spec.rb +196 -0
- data/spec/rtanque/radar_spec.rb +87 -0
- data/spec/rtanque/shell_spec.rb +35 -0
- data/spec/rtanque_spec.rb +34 -0
- data/spec/spec_helper.rb +51 -0
- data/templates/bot.erb +17 -0
- metadata +315 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
module RTanque
|
2
|
+
class Bot
|
3
|
+
# Commands the associated {RTanque::Bot}. This class should be inherited from and {NAME} and {#tick!} overridden.
|
4
|
+
#
|
5
|
+
# See {RTanque::Bot::BrainHelper} for a useful mixin
|
6
|
+
#
|
7
|
+
# Sample bots:
|
8
|
+
#
|
9
|
+
# * {file:sample_bots/seek_and_destroy.rb SeekAndDestroy}
|
10
|
+
# * {file:sample_bots/camper.rb Camper}
|
11
|
+
# * {file:sample_bots/keyboard.rb Keyboard} Special bot controlled by the keyboard
|
12
|
+
#
|
13
|
+
class Brain
|
14
|
+
# Bot's display name
|
15
|
+
# @!parse NAME = 'bot name'
|
16
|
+
|
17
|
+
# @!attribute [r] sensors
|
18
|
+
# @return [RTanque::Bot::Sensors]
|
19
|
+
# @!attribute [r] command
|
20
|
+
# @return [RTanque::Bot::Command]
|
21
|
+
attr_accessor :sensors, :command
|
22
|
+
# @return [RTanque::Arena]
|
23
|
+
attr_reader :arena
|
24
|
+
|
25
|
+
# @!visibility private
|
26
|
+
def initialize(arena)
|
27
|
+
@arena = arena
|
28
|
+
end
|
29
|
+
|
30
|
+
# @!visibility private
|
31
|
+
def tick(sensors)
|
32
|
+
self.sensors = sensors
|
33
|
+
RTanque::Bot::Command.new.tap do |empty_command|
|
34
|
+
self.command = empty_command
|
35
|
+
self.tick!
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Main logic goes here
|
40
|
+
#
|
41
|
+
# Get input from {#sensors}. See {RTanque::Bot::Sensors}
|
42
|
+
#
|
43
|
+
# Give output to {#command}. See {RTanque::Bot::Command}
|
44
|
+
# @abstract
|
45
|
+
def tick!
|
46
|
+
# Sweet bot logic
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module RTanque
|
2
|
+
class Bot
|
3
|
+
# Some helpful constants and methods for use as mixin in {RTanque::Bot::Brain}
|
4
|
+
module BrainHelper
|
5
|
+
BOT_RADIUS = Bot::RADIUS
|
6
|
+
MAX_FIRE_POWER = Bot::MAX_FIRE_POWER
|
7
|
+
MIN_FIRE_POWER = Bot::MIN_FIRE_POWER
|
8
|
+
MAX_HEALTH = Bot::MAX_HEALTH
|
9
|
+
MAX_BOT_SPEED = Bot::MAX_SPEED
|
10
|
+
MAX_BOT_ROTATION = Configuration.bot.turn_step
|
11
|
+
MAX_TURRET_ROTATION = Configuration.turret.turn_step
|
12
|
+
MAX_RADAR_ROTATION = Configuration.radar.turn_step
|
13
|
+
|
14
|
+
# Run block every 'num_of_ticks'
|
15
|
+
# @param [Integer] num_of_ticks tick interval at which to execute block
|
16
|
+
# @yield
|
17
|
+
# @return [void]
|
18
|
+
def at_tick_interval(num_of_ticks)
|
19
|
+
yield if sensors.ticks % num_of_ticks == 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module RTanque
|
2
|
+
class Bot
|
3
|
+
# Command provide output from the {RTanque::Bot::Brain} about the current state of the {RTanque::Match}
|
4
|
+
#
|
5
|
+
# They are made available to {RTanque::Bot::Brain} via {RTanque::Bot::Brain#command}
|
6
|
+
#
|
7
|
+
# All values are bound. Setting an out-of-bounds value will result in it being set to the max/min allowed value.
|
8
|
+
#
|
9
|
+
# @attr_writer [Float] speed
|
10
|
+
# @attr_writer [Float, RTanque::Heading] heading
|
11
|
+
# @attr_writer [Float, RTanque::Heading] radar_heading
|
12
|
+
# @attr_writer [Float, RTanque::Heading] turret_heading
|
13
|
+
# @attr_writer [Float, nil] fire_power sets firing power. Setting to nil will stop firing. See {#fire}
|
14
|
+
#
|
15
|
+
# @param [Float] power alias to {#fire_power=}
|
16
|
+
# @!method fire(power)
|
17
|
+
Command = Struct.new(:speed, :heading, :radar_heading, :turret_heading, :fire_power) do
|
18
|
+
def fire(power = 3)
|
19
|
+
self.fire_power = power
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module RTanque
|
2
|
+
class Bot
|
3
|
+
class Radar
|
4
|
+
include Enumerable
|
5
|
+
include Movable
|
6
|
+
extend NormalizedAttr
|
7
|
+
VISION_RANGE = Configuration.radar.vision
|
8
|
+
attr_normalized(:heading, Heading::FULL_RANGE, Configuration.radar.turn_step)
|
9
|
+
|
10
|
+
# A Reflection is the information obtained for a bot detected by {RTanque::Bot::Radar}
|
11
|
+
#
|
12
|
+
# @attr_reader [RTanque::Heading] heading
|
13
|
+
# @attr_reader [Float] distance
|
14
|
+
# @attr_reader [String] name
|
15
|
+
Reflection = Struct.new(:heading, :distance, :name) do
|
16
|
+
def self.new_from_points(from_position, to_position, &tap)
|
17
|
+
self.new(from_position.heading(to_position), from_position.distance(to_position)).tap(&tap)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(bot, heading)
|
22
|
+
@bot = bot
|
23
|
+
@heading = heading
|
24
|
+
@reflections = []
|
25
|
+
end
|
26
|
+
|
27
|
+
def position
|
28
|
+
@bot.position
|
29
|
+
end
|
30
|
+
|
31
|
+
def each(&block)
|
32
|
+
@reflections.each(&block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def empty?
|
36
|
+
self.count == 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def scan(bots)
|
40
|
+
@reflections.clear
|
41
|
+
bots.each do |other_bot|
|
42
|
+
if self.can_detect?(other_bot)
|
43
|
+
@reflections << Reflection.new_from_points(self.position, other_bot.position) { |reflection| reflection.name = other_bot.name }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def can_detect?(other_bot)
|
50
|
+
VISION_RANGE.include?(Heading.delta_between_points(self.position, self.heading, other_bot.position))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module RTanque
|
2
|
+
class Bot
|
3
|
+
# Sensors provide input to the {RTanque::Bot::Brain} about the current state of the {RTanque::Match}
|
4
|
+
#
|
5
|
+
# They are made available to {RTanque::Bot::Brain} via {RTanque::Bot::Brain#sensors}
|
6
|
+
#
|
7
|
+
# @attr_reader [Integer] ticks number of ticks, starts at 0
|
8
|
+
# @attr_reader [Float] health health of bot. if == 0, dead
|
9
|
+
# @attr_reader [Float] gun_energy energy of cannon. if < 0, cannot fire
|
10
|
+
# @attr_reader [Float] speed
|
11
|
+
# @attr_reader [RTanque::Point] position
|
12
|
+
# @attr_reader [RTanque::Heading] heading
|
13
|
+
# @attr_reader [RTanque::Heading] radar_heading
|
14
|
+
# @attr_reader [RTanque::Heading] turret_heading
|
15
|
+
# @attr_reader [Enumerator] radar enumerates all bots scanned by the radar, yielding {RTanque::Bot::Radar::Reflection}
|
16
|
+
Sensors = Struct.new(:ticks, :health, :speed, :position, :heading, :radar, :radar_heading, :turret_heading, :gun_energy) do
|
17
|
+
def initialize(*args, &block)
|
18
|
+
super(*args)
|
19
|
+
block.call(self) if block
|
20
|
+
self.freeze
|
21
|
+
end
|
22
|
+
|
23
|
+
def button_down?(button_id)
|
24
|
+
if self.class.const_defined?(:Gosu)
|
25
|
+
button_id = Gosu::Window.char_to_button_id(button_id) unless button_id.kind_of?(Integer)
|
26
|
+
@gui_window && @gui_window.button_down?(button_id)
|
27
|
+
else
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def gui_window=(gui_window)
|
33
|
+
@gui_window = gui_window
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module RTanque
|
2
|
+
class Bot
|
3
|
+
class Turret
|
4
|
+
include Movable
|
5
|
+
extend NormalizedAttr
|
6
|
+
LENGTH = Configuration.turret.length
|
7
|
+
attr_normalized(:heading, Heading::FULL_RANGE, Configuration.turret.turn_step)
|
8
|
+
|
9
|
+
def initialize(heading)
|
10
|
+
@heading = heading
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'configuration'
|
2
|
+
|
3
|
+
module RTanque
|
4
|
+
one_degree = (Math::PI / 180.0)
|
5
|
+
|
6
|
+
# @!visibility private
|
7
|
+
Configuration = ::Configuration.for('default') do
|
8
|
+
raise_brain_tick_errors true
|
9
|
+
quit_when_finished true
|
10
|
+
|
11
|
+
bot do
|
12
|
+
radius 19
|
13
|
+
health_reduction_on_exception 2
|
14
|
+
health 0..100
|
15
|
+
speed -3..3
|
16
|
+
speed_step 0.05
|
17
|
+
turn_step one_degree * 1.5
|
18
|
+
fire_power 1..5
|
19
|
+
gun_energy_max 10
|
20
|
+
gun_energy_factor 10
|
21
|
+
end
|
22
|
+
turret do
|
23
|
+
length 28
|
24
|
+
turn_step (one_degree * 2.0)
|
25
|
+
end
|
26
|
+
radar do
|
27
|
+
turn_step 0.05
|
28
|
+
vision -(one_degree * 10.0)..(one_degree * 10.0)
|
29
|
+
end
|
30
|
+
shell do
|
31
|
+
speed_factor 4.5
|
32
|
+
ratio 1.5 # used by Bot#adjust_fire_power and to calculate damage done by shell to bot
|
33
|
+
end
|
34
|
+
explosion do
|
35
|
+
life_span 70 * 1 # should be multiple of the number of frames in the explosion animation
|
36
|
+
end
|
37
|
+
gui do
|
38
|
+
update_interval 16.666666 # in milliseconds. 16.666666 == 60 FPS
|
39
|
+
fonts do
|
40
|
+
small 16
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
def Configuration.config(&block)
|
45
|
+
::Configuration::DSL.evaluate(self, &block)
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module RTanque
|
2
|
+
class Explosion
|
3
|
+
include Movable
|
4
|
+
attr_reader :position
|
5
|
+
LIFE_SPAN = Configuration.explosion.life_span # ticks
|
6
|
+
def initialize(position)
|
7
|
+
@position = position
|
8
|
+
@ticks = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def percent_dead
|
12
|
+
@ticks / LIFE_SPAN.to_f
|
13
|
+
end
|
14
|
+
|
15
|
+
def tick
|
16
|
+
@ticks += 1
|
17
|
+
end
|
18
|
+
|
19
|
+
def dead?
|
20
|
+
@ticks > LIFE_SPAN
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/rtanque/gui.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module RTanque
|
2
|
+
module Gui
|
3
|
+
RESOURCE_DIR = File.expand_path('../../../resources', __FILE__)
|
4
|
+
def self.resource_path(*components)
|
5
|
+
File.join(RESOURCE_DIR, *components)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ZOrder
|
9
|
+
BACKGROUND = -1
|
10
|
+
BOT_HEALTH = 5
|
11
|
+
BOT_NAME = 6
|
12
|
+
BOT_BODY = 7
|
13
|
+
BOT_TURRET = 8
|
14
|
+
BOT_RADAR = 9
|
15
|
+
SHELL = 10
|
16
|
+
EXPLOSION = 20
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rtanque/gui/draw_group'
|
22
|
+
require 'rtanque/gui/window'
|
23
|
+
require 'rtanque/gui/bot'
|
24
|
+
require 'rtanque/gui/shell'
|
25
|
+
require 'rtanque/gui/explosion'
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'gosu'
|
2
|
+
require 'texplay'
|
3
|
+
|
4
|
+
require 'rtanque/gui/bot/health_color_calculator'
|
5
|
+
|
6
|
+
module RTanque
|
7
|
+
module Gui
|
8
|
+
class Bot
|
9
|
+
attr_reader :bot
|
10
|
+
|
11
|
+
HEALTH_BAR_HEIGHT = 3
|
12
|
+
HEALTH_BAR_WIDTH = 100
|
13
|
+
|
14
|
+
def initialize(window, bot)
|
15
|
+
@window = window
|
16
|
+
@bot = bot
|
17
|
+
@body_image = Gosu::Image.new(@window, Gui.resource_path("images/body.png"))
|
18
|
+
@turret_image = Gosu::Image.new(@window, Gui.resource_path("images/turret.png"))
|
19
|
+
@radar_image = Gosu::Image.new(@window, Gui.resource_path("images/radar.png"))
|
20
|
+
@score_bar_image = TexPlay.create_blank_image(@window, HEALTH_BAR_WIDTH, HEALTH_BAR_HEIGHT)
|
21
|
+
@name_font = Gosu::Font.new(@window, Window::FONT_NAME, Window::SMALL_FONT_SIZE)
|
22
|
+
@x_factor = 1
|
23
|
+
@y_factor = 1
|
24
|
+
end
|
25
|
+
|
26
|
+
def draw
|
27
|
+
position = [@bot.position.x, @window.height - @bot.position.y]
|
28
|
+
draw_bot(position)
|
29
|
+
draw_name(position)
|
30
|
+
draw_health(position)
|
31
|
+
end
|
32
|
+
|
33
|
+
def grow(factor = 2, step = 0.002)
|
34
|
+
@x_factor += step unless @x_factor > factor
|
35
|
+
@y_factor += step unless @y_factor > factor
|
36
|
+
end
|
37
|
+
|
38
|
+
def draw_bot(position)
|
39
|
+
@body_image.draw_rot(position[0], position[1], ZOrder::BOT_BODY, Gosu.radians_to_degrees(@bot.heading.to_f), 0.5, 0.5, @x_factor, @y_factor)
|
40
|
+
@turret_image.draw_rot(position[0], position[1], ZOrder::BOT_TURRET, Gosu.radians_to_degrees(@bot.turret.heading.to_f), 0.5, 0.5, @x_factor, @y_factor)
|
41
|
+
@radar_image.draw_rot(position[0], position[1], ZOrder::BOT_RADAR, Gosu.radians_to_degrees(@bot.radar.heading.to_f), 0.5, 0.5, @x_factor, @y_factor)
|
42
|
+
end
|
43
|
+
|
44
|
+
def draw_name(position)
|
45
|
+
x,y = *position
|
46
|
+
@name_font.draw_rel(self.bot.name, x, y + (RTanque::Bot::RADIUS * @y_factor) + Window::SMALL_FONT_SIZE.to_i, ZOrder::BOT_NAME, 0.5, 0.5, @x_factor, @y_factor)
|
47
|
+
end
|
48
|
+
|
49
|
+
def draw_health(position)
|
50
|
+
x,y = *position
|
51
|
+
x_health = health.round(0)
|
52
|
+
health_color = color_for_health
|
53
|
+
@score_bar_image.paint {
|
54
|
+
rect 0, 0, HEALTH_BAR_WIDTH, HEALTH_BAR_HEIGHT, :color => [0,0,0,0], :fill => true
|
55
|
+
rect 0, 0, x_health, HEALTH_BAR_HEIGHT, :color => health_color, :fill => true
|
56
|
+
}
|
57
|
+
@score_bar_image.draw(x - (HEALTH_BAR_WIDTH/2) * @x_factor, y + (5 + RTanque::Bot::RADIUS) * @y_factor, ZOrder::BOT_HEALTH, @x_factor, @y_factor)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def color_for_health
|
63
|
+
HealthColorCalculator.new(health).color_as_rgb
|
64
|
+
end
|
65
|
+
|
66
|
+
def health
|
67
|
+
self.bot.health
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module RTanque
|
2
|
+
module Gui
|
3
|
+
class Bot
|
4
|
+
class HealthColorCalculator
|
5
|
+
|
6
|
+
# different health-clors as RGB values
|
7
|
+
FULL_HEALTH_COLOR = [ 74, 190, 74].map { |v| v/255.0 }
|
8
|
+
MEDIUM_HEALTH_COLOR = [255, 190, 0].map { |v| v/255.0 }
|
9
|
+
LOW_HEALTH_COLOR = [220, 0, 0].map { |v| v/255.0 }
|
10
|
+
|
11
|
+
attr_reader :health
|
12
|
+
|
13
|
+
def initialize(health)
|
14
|
+
@health = health
|
15
|
+
end
|
16
|
+
|
17
|
+
def color_as_rgb
|
18
|
+
if health > 50
|
19
|
+
percentage = ((100 - health) / 50)
|
20
|
+
color_between FULL_HEALTH_COLOR, MEDIUM_HEALTH_COLOR, percentage
|
21
|
+
else
|
22
|
+
percentage = ((50 - health) / 50)
|
23
|
+
color_between MEDIUM_HEALTH_COLOR, LOW_HEALTH_COLOR, percentage
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def color_between(color_a, color_b, percentage)
|
28
|
+
[
|
29
|
+
(color_b[0] - color_a[0]) * percentage + color_a[0],
|
30
|
+
(color_b[1] - color_a[1]) * percentage + color_a[1],
|
31
|
+
(color_b[2] - color_a[2]) * percentage + color_a[2]
|
32
|
+
]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module RTanque
|
2
|
+
module Gui
|
3
|
+
class DrawGroup
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize(tick_group, &create_drawable)
|
7
|
+
@tick_group = tick_group
|
8
|
+
@mapped_drawables = Hash.new do |h, tickable|
|
9
|
+
h[tickable] = create_drawable.call(tickable)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def each(&block)
|
14
|
+
@tick_group.each do |tickable|
|
15
|
+
if tickable.dead?
|
16
|
+
@mapped_drawables.delete(tickable)
|
17
|
+
else
|
18
|
+
block.call(@mapped_drawables[tickable]) # This invokes @mapped_drawables's block if tickable not already in the hash
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def draw
|
24
|
+
self.each do |drawable|
|
25
|
+
drawable.draw
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|