rtanque 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. data/.gitignore +22 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +34 -0
  4. data/.travis.yml +11 -0
  5. data/.yardopts +4 -0
  6. data/Gemfile +4 -0
  7. data/Gemfile.ci +6 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +120 -0
  10. data/Rakefile +1 -0
  11. data/bin/rtanque +108 -0
  12. data/lib/rtanque.rb +40 -0
  13. data/lib/rtanque/arena.rb +8 -0
  14. data/lib/rtanque/bot.rb +117 -0
  15. data/lib/rtanque/bot/brain.rb +50 -0
  16. data/lib/rtanque/bot/brain_helper.rb +23 -0
  17. data/lib/rtanque/bot/command.rb +23 -0
  18. data/lib/rtanque/bot/radar.rb +54 -0
  19. data/lib/rtanque/bot/sensors.rb +33 -0
  20. data/lib/rtanque/bot/turret.rb +14 -0
  21. data/lib/rtanque/configuration.rb +46 -0
  22. data/lib/rtanque/explosion.rb +23 -0
  23. data/lib/rtanque/gui.rb +24 -0
  24. data/lib/rtanque/gui/bot.rb +42 -0
  25. data/lib/rtanque/gui/draw_group.rb +30 -0
  26. data/lib/rtanque/gui/explosion.rb +25 -0
  27. data/lib/rtanque/gui/shell.rb +20 -0
  28. data/lib/rtanque/gui/window.rb +51 -0
  29. data/lib/rtanque/heading.rb +172 -0
  30. data/lib/rtanque/match.rb +67 -0
  31. data/lib/rtanque/match/tick_group.rb +50 -0
  32. data/lib/rtanque/movable.rb +51 -0
  33. data/lib/rtanque/normalized_attr.rb +69 -0
  34. data/lib/rtanque/point.rb +140 -0
  35. data/lib/rtanque/runner.rb +88 -0
  36. data/lib/rtanque/shell.rb +40 -0
  37. data/lib/rtanque/version.rb +3 -0
  38. data/resources/images/body.png +0 -0
  39. data/resources/images/bullet.png +0 -0
  40. data/resources/images/explosions/explosion2-1.png +0 -0
  41. data/resources/images/explosions/explosion2-10.png +0 -0
  42. data/resources/images/explosions/explosion2-11.png +0 -0
  43. data/resources/images/explosions/explosion2-12.png +0 -0
  44. data/resources/images/explosions/explosion2-13.png +0 -0
  45. data/resources/images/explosions/explosion2-14.png +0 -0
  46. data/resources/images/explosions/explosion2-15.png +0 -0
  47. data/resources/images/explosions/explosion2-16.png +0 -0
  48. data/resources/images/explosions/explosion2-17.png +0 -0
  49. data/resources/images/explosions/explosion2-18.png +0 -0
  50. data/resources/images/explosions/explosion2-19.png +0 -0
  51. data/resources/images/explosions/explosion2-2.png +0 -0
  52. data/resources/images/explosions/explosion2-20.png +0 -0
  53. data/resources/images/explosions/explosion2-21.png +0 -0
  54. data/resources/images/explosions/explosion2-22.png +0 -0
  55. data/resources/images/explosions/explosion2-23.png +0 -0
  56. data/resources/images/explosions/explosion2-24.png +0 -0
  57. data/resources/images/explosions/explosion2-25.png +0 -0
  58. data/resources/images/explosions/explosion2-26.png +0 -0
  59. data/resources/images/explosions/explosion2-27.png +0 -0
  60. data/resources/images/explosions/explosion2-28.png +0 -0
  61. data/resources/images/explosions/explosion2-29.png +0 -0
  62. data/resources/images/explosions/explosion2-3.png +0 -0
  63. data/resources/images/explosions/explosion2-30.png +0 -0
  64. data/resources/images/explosions/explosion2-31.png +0 -0
  65. data/resources/images/explosions/explosion2-32.png +0 -0
  66. data/resources/images/explosions/explosion2-33.png +0 -0
  67. data/resources/images/explosions/explosion2-34.png +0 -0
  68. data/resources/images/explosions/explosion2-35.png +0 -0
  69. data/resources/images/explosions/explosion2-36.png +0 -0
  70. data/resources/images/explosions/explosion2-37.png +0 -0
  71. data/resources/images/explosions/explosion2-38.png +0 -0
  72. data/resources/images/explosions/explosion2-39.png +0 -0
  73. data/resources/images/explosions/explosion2-4.png +0 -0
  74. data/resources/images/explosions/explosion2-40.png +0 -0
  75. data/resources/images/explosions/explosion2-41.png +0 -0
  76. data/resources/images/explosions/explosion2-42.png +0 -0
  77. data/resources/images/explosions/explosion2-43.png +0 -0
  78. data/resources/images/explosions/explosion2-44.png +0 -0
  79. data/resources/images/explosions/explosion2-45.png +0 -0
  80. data/resources/images/explosions/explosion2-46.png +0 -0
  81. data/resources/images/explosions/explosion2-47.png +0 -0
  82. data/resources/images/explosions/explosion2-48.png +0 -0
  83. data/resources/images/explosions/explosion2-49.png +0 -0
  84. data/resources/images/explosions/explosion2-5.png +0 -0
  85. data/resources/images/explosions/explosion2-50.png +0 -0
  86. data/resources/images/explosions/explosion2-51.png +0 -0
  87. data/resources/images/explosions/explosion2-52.png +0 -0
  88. data/resources/images/explosions/explosion2-53.png +0 -0
  89. data/resources/images/explosions/explosion2-54.png +0 -0
  90. data/resources/images/explosions/explosion2-55.png +0 -0
  91. data/resources/images/explosions/explosion2-56.png +0 -0
  92. data/resources/images/explosions/explosion2-57.png +0 -0
  93. data/resources/images/explosions/explosion2-58.png +0 -0
  94. data/resources/images/explosions/explosion2-59.png +0 -0
  95. data/resources/images/explosions/explosion2-6.png +0 -0
  96. data/resources/images/explosions/explosion2-60.png +0 -0
  97. data/resources/images/explosions/explosion2-61.png +0 -0
  98. data/resources/images/explosions/explosion2-62.png +0 -0
  99. data/resources/images/explosions/explosion2-63.png +0 -0
  100. data/resources/images/explosions/explosion2-64.png +0 -0
  101. data/resources/images/explosions/explosion2-65.png +0 -0
  102. data/resources/images/explosions/explosion2-66.png +0 -0
  103. data/resources/images/explosions/explosion2-67.png +0 -0
  104. data/resources/images/explosions/explosion2-68.png +0 -0
  105. data/resources/images/explosions/explosion2-69.png +0 -0
  106. data/resources/images/explosions/explosion2-7.png +0 -0
  107. data/resources/images/explosions/explosion2-70.png +0 -0
  108. data/resources/images/explosions/explosion2-71.png +0 -0
  109. data/resources/images/explosions/explosion2-8.png +0 -0
  110. data/resources/images/explosions/explosion2-9.png +0 -0
  111. data/resources/images/grass.png +0 -0
  112. data/resources/images/radar.png +0 -0
  113. data/resources/images/turret.png +0 -0
  114. data/rtanque.gemspec +33 -0
  115. data/sample_bots/camper.rb +79 -0
  116. data/sample_bots/keyboard.rb +50 -0
  117. data/sample_bots/seek_and_destroy.rb +51 -0
  118. data/screenshots/battle_1.png +0 -0
  119. data/screenshots/battle_2.png +0 -0
  120. data/spec/rtanque/bot_spec.rb +239 -0
  121. data/spec/rtanque/heading_spec.rb +279 -0
  122. data/spec/rtanque/match_spec.rb +36 -0
  123. data/spec/rtanque/normalized_attr_spec.rb +90 -0
  124. data/spec/rtanque/point_spec.rb +196 -0
  125. data/spec/rtanque/radar_spec.rb +87 -0
  126. data/spec/rtanque/shell_spec.rb +35 -0
  127. data/spec/rtanque_spec.rb +6 -0
  128. data/spec/spec_helper.rb +51 -0
  129. data/templates/bot.erb +11 -0
  130. 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
@@ -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