rtanque 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.rvmrc +34 -0
- data/.travis.yml +11 -0
- data/.yardopts +4 -0
- data/Gemfile +4 -0
- data/Gemfile.ci +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +120 -0
- data/Rakefile +1 -0
- data/bin/rtanque +108 -0
- data/lib/rtanque.rb +40 -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 +33 -0
- data/lib/rtanque/bot/turret.rb +14 -0
- data/lib/rtanque/configuration.rb +46 -0
- data/lib/rtanque/explosion.rb +23 -0
- data/lib/rtanque/gui.rb +24 -0
- data/lib/rtanque/gui/bot.rb +42 -0
- data/lib/rtanque/gui/draw_group.rb +30 -0
- data/lib/rtanque/gui/explosion.rb +25 -0
- data/lib/rtanque/gui/shell.rb +20 -0
- data/lib/rtanque/gui/window.rb +51 -0
- data/lib/rtanque/heading.rb +172 -0
- data/lib/rtanque/match.rb +67 -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 +40 -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 +33 -0
- data/sample_bots/camper.rb +79 -0
- data/sample_bots/keyboard.rb +50 -0
- data/sample_bots/seek_and_destroy.rb +51 -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 +36 -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 +6 -0
- data/spec/spec_helper.rb +51 -0
- data/templates/bot.erb +11 -0
- metadata +310 -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,33 @@
|
|
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
|
+
button_id = Gosu::Window.char_to_button_id(button_id) unless button_id.kind_of?(Integer)
|
25
|
+
@gui_window && @gui_window.button_down?(button_id)
|
26
|
+
end
|
27
|
+
|
28
|
+
def gui_window=(gui_window)
|
29
|
+
@gui_window = gui_window
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
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,46 @@
|
|
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
|
+
|
10
|
+
bot do
|
11
|
+
radius 19
|
12
|
+
health_reduction_on_exception 2
|
13
|
+
health 0..100
|
14
|
+
speed -3..3
|
15
|
+
speed_step 0.05
|
16
|
+
turn_step one_degree * 1.5
|
17
|
+
fire_power 1..5
|
18
|
+
gun_energy_max 10
|
19
|
+
gun_energy_factor 10
|
20
|
+
end
|
21
|
+
turret do
|
22
|
+
length 28
|
23
|
+
turn_step (one_degree * 2.0)
|
24
|
+
end
|
25
|
+
radar do
|
26
|
+
turn_step 0.05
|
27
|
+
vision -(one_degree * 10.0)..(one_degree * 10.0)
|
28
|
+
end
|
29
|
+
shell do
|
30
|
+
speed_factor 4.5
|
31
|
+
ratio 1.5 # used by Bot#adjust_fire_power and to calculate damage done by shell to bot
|
32
|
+
end
|
33
|
+
explosion do
|
34
|
+
life_span 70 * 1 # should be multiple of the number of frames in the explosion animation
|
35
|
+
end
|
36
|
+
gui do
|
37
|
+
update_interval 16.666666 # in milliseconds. 16.666666 == 60 FPS
|
38
|
+
fonts do
|
39
|
+
small 16
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
def Configuration.config(&block)
|
44
|
+
::Configuration::DSL.evaluate(self, &block)
|
45
|
+
end
|
46
|
+
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,24 @@
|
|
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_NAME = 0
|
11
|
+
BOT_BODY = 1
|
12
|
+
BOT_TURRET = 2
|
13
|
+
BOT_RADAR = 3
|
14
|
+
SHELL = 4
|
15
|
+
EXPLOSION = 10
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rtanque/gui/draw_group'
|
21
|
+
require 'rtanque/gui/window'
|
22
|
+
require 'rtanque/gui/bot'
|
23
|
+
require 'rtanque/gui/shell'
|
24
|
+
require 'rtanque/gui/explosion'
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'gosu'
|
2
|
+
|
3
|
+
module RTanque
|
4
|
+
module Gui
|
5
|
+
class Bot
|
6
|
+
attr_reader :bot
|
7
|
+
|
8
|
+
def initialize(window, bot)
|
9
|
+
@window = window
|
10
|
+
@bot = bot
|
11
|
+
@body_image = Gosu::Image.new(@window, Gui.resource_path("images/body.png"))
|
12
|
+
@turret_image = Gosu::Image.new(@window, Gui.resource_path("images/turret.png"))
|
13
|
+
@radar_image = Gosu::Image.new(@window, Gui.resource_path("images/radar.png"))
|
14
|
+
@name_font = Gosu::Font.new(@window, Window::FONT_NAME, Window::SMALL_FONT_SIZE)
|
15
|
+
@x_factor = 1
|
16
|
+
@y_factor = 1
|
17
|
+
end
|
18
|
+
|
19
|
+
def draw
|
20
|
+
position = [@bot.position.x, @window.height - @bot.position.y]
|
21
|
+
draw_bot(position)
|
22
|
+
draw_name(position)
|
23
|
+
end
|
24
|
+
|
25
|
+
def grow(factor = 2, step = 0.002)
|
26
|
+
@x_factor += step unless @x_factor > factor
|
27
|
+
@y_factor += step unless @y_factor > factor
|
28
|
+
end
|
29
|
+
|
30
|
+
def draw_bot(position)
|
31
|
+
@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)
|
32
|
+
@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)
|
33
|
+
@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)
|
34
|
+
end
|
35
|
+
|
36
|
+
def draw_name(position)
|
37
|
+
x,y = *position
|
38
|
+
@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)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
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
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'gosu'
|
2
|
+
|
3
|
+
module RTanque
|
4
|
+
module Gui
|
5
|
+
class Explosion
|
6
|
+
FRAMES = (1..71)
|
7
|
+
|
8
|
+
def initialize(window, explosion)
|
9
|
+
@window = window
|
10
|
+
@explosion = explosion
|
11
|
+
@position = [explosion.position.x, window.height - explosion.position.y]
|
12
|
+
@explosion_images = FRAMES.map { |i| Gosu::Image.new(@window, Gui.resource_path("images/explosions/explosion2-#{i}.png")) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def draw
|
16
|
+
frame.draw_rot(@position[0], @position[1], 5, ZOrder::EXPLOSION)
|
17
|
+
end
|
18
|
+
|
19
|
+
def frame
|
20
|
+
@frames_length ||= @explosion_images.length - 1
|
21
|
+
@explosion_images[(@explosion.percent_dead * @frames_length).floor]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'gosu'
|
2
|
+
|
3
|
+
module RTanque
|
4
|
+
module Gui
|
5
|
+
class Shell
|
6
|
+
attr_reader :shell
|
7
|
+
|
8
|
+
def initialize(window, shell)
|
9
|
+
@window = window
|
10
|
+
@shell = shell
|
11
|
+
@shell_image = Gosu::Image.new(@window, Gui.resource_path("images/bullet.png"))
|
12
|
+
end
|
13
|
+
|
14
|
+
def draw
|
15
|
+
position = [self.shell.position.x, @window.height - self.shell.position.y]
|
16
|
+
@shell_image.draw_rot(position[0], position[1], ZOrder::SHELL, 0, 0.5, 0.5)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'gosu'
|
2
|
+
|
3
|
+
module RTanque
|
4
|
+
module Gui
|
5
|
+
class Window < Gosu::Window
|
6
|
+
UPDATE_INTERVAL = Configuration.gui.update_interval
|
7
|
+
FONT_NAME = Gosu::default_font_name
|
8
|
+
SMALL_FONT_SIZE = Configuration.gui.fonts.small
|
9
|
+
|
10
|
+
attr_reader :gui_bots, :gui_shells, :gui_explosions
|
11
|
+
|
12
|
+
def initialize(match)
|
13
|
+
@match = match
|
14
|
+
@arena = match.arena
|
15
|
+
match.bots.each { |bot| bot.gui_window = self }
|
16
|
+
@gui_bots = DrawGroup.new(match.bots) do |bot|
|
17
|
+
RTanque::Gui::Bot.new(self, bot)
|
18
|
+
end
|
19
|
+
@gui_shells = DrawGroup.new(match.shells) do |shell|
|
20
|
+
RTanque::Gui::Shell.new(self, shell)
|
21
|
+
end
|
22
|
+
@gui_explosions = DrawGroup.new(match.explosions) do |explosion|
|
23
|
+
RTanque::Gui::Explosion.new(self, explosion)
|
24
|
+
end
|
25
|
+
# Fullscreen: https://github.com/jlnr/gosu/issues/159#issuecomment-12473172
|
26
|
+
super(@arena.width, @arena.height, false, UPDATE_INTERVAL)
|
27
|
+
self.caption = self.class.name.split('::').first
|
28
|
+
@background = Gosu::Image.new(self, Gui.resource_path("images/grass.png"))
|
29
|
+
end
|
30
|
+
|
31
|
+
def update
|
32
|
+
@match.tick
|
33
|
+
end
|
34
|
+
|
35
|
+
def draw
|
36
|
+
self.close if button_down?(Gosu::Button::KbEscape)
|
37
|
+
@background.draw(0, 0, ZOrder::BACKGROUND)
|
38
|
+
if @match.finished?
|
39
|
+
self.gui_bots.each { |bot| bot.grow(2) }
|
40
|
+
end
|
41
|
+
self.draw_drawables
|
42
|
+
end
|
43
|
+
|
44
|
+
def draw_drawables
|
45
|
+
self.gui_bots.draw
|
46
|
+
self.gui_shells.draw
|
47
|
+
self.gui_explosions.draw
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|