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,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,31 @@
|
|
1
|
+
require 'gosu'
|
2
|
+
|
3
|
+
module RTanque
|
4
|
+
module Gui
|
5
|
+
class Shell
|
6
|
+
attr_reader :shell
|
7
|
+
|
8
|
+
DEBUG = ENV["DEBUG_SHELLS"]
|
9
|
+
|
10
|
+
def initialize(window, shell)
|
11
|
+
@window = window
|
12
|
+
@shell = shell
|
13
|
+
@x0 = shell.position.x
|
14
|
+
@y0 = @window.height - shell.position.y
|
15
|
+
@shell_image = Gosu::Image.new(@window, Gui.resource_path("images/bullet.png"))
|
16
|
+
end
|
17
|
+
|
18
|
+
def draw
|
19
|
+
position = [self.shell.position.x, @window.height - self.shell.position.y]
|
20
|
+
@shell_image.draw_rot(position[0], position[1], ZOrder::SHELL, 0, 0.5, 0.5)
|
21
|
+
|
22
|
+
if DEBUG then
|
23
|
+
white = Gosu::Color::WHITE
|
24
|
+
pos = shell.position
|
25
|
+
x1, y1 = pos.x, @window.height - pos.y
|
26
|
+
@window.draw_line @x0, @y0, white, x1, y1, white, ZOrder::SHELL
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,64 @@
|
|
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
|
+
|
30
|
+
@draw_procs = []
|
31
|
+
end
|
32
|
+
|
33
|
+
def update
|
34
|
+
@match.tick
|
35
|
+
end
|
36
|
+
|
37
|
+
def draw
|
38
|
+
self.close if button_down?(Gosu::Button::KbEscape)
|
39
|
+
@background.draw(0, 0, ZOrder::BACKGROUND)
|
40
|
+
if @match.finished?
|
41
|
+
self.close if Configuration.quit_when_finished
|
42
|
+
self.gui_bots.each { |bot| bot.grow(2) }
|
43
|
+
end
|
44
|
+
self.draw_drawables
|
45
|
+
end
|
46
|
+
|
47
|
+
def draw_drawables
|
48
|
+
self.gui_bots.draw
|
49
|
+
self.gui_shells.draw
|
50
|
+
self.gui_explosions.draw
|
51
|
+
|
52
|
+
@draw_procs.each do |proc|
|
53
|
+
proc.call self
|
54
|
+
end
|
55
|
+
|
56
|
+
@draw_procs.clear
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_draw_proc &b
|
60
|
+
@draw_procs << b
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module RTanque
|
3
|
+
# A Heading represents an angle. Basically a wrapper around `Float` bound to `(0..Math::PI * 2)`
|
4
|
+
#
|
5
|
+
# 0.0 == `RTanque::Heading::NORTH` is 'up'
|
6
|
+
#
|
7
|
+
# ##Basic Usage
|
8
|
+
# RTanque::Heading.new(Math::PI)
|
9
|
+
# # => <RTanque::Heading: 1.0rad 180.0deg>
|
10
|
+
#
|
11
|
+
# RTanque::Heading.new(Math::PI) + RTanque::Heading.new(Math::PI)
|
12
|
+
# # => <RTanque::Heading: 0.0rad 0.0deg>
|
13
|
+
#
|
14
|
+
# RTanque::Heading.new(Math::PI / 2.0) + Math::PI
|
15
|
+
# # => <RTanque::Heading: 1.5rad 270.0deg>
|
16
|
+
#
|
17
|
+
# RTanque::Heading.new(0.0) == 0
|
18
|
+
# # => true
|
19
|
+
#
|
20
|
+
# ##Utility Methods
|
21
|
+
# RTanque::Heading.new_from_degrees(180.0)
|
22
|
+
# # => <RTanque::Heading: 1.0rad 180.0deg>
|
23
|
+
#
|
24
|
+
# RTanque::Heading.new(Math::PI).to_degrees
|
25
|
+
# # => 180.0
|
26
|
+
#
|
27
|
+
# RTanque::Heading.new_between_points(RTanque::Point.new(0,0), RTanque::Point.new(2,3))
|
28
|
+
# # => <RTanque::Heading: 0.1871670418109988rad 33.690067525979785deg>
|
29
|
+
#
|
30
|
+
# RTanque::Heading.new_from_degrees(1).delta(RTanque::Heading.new_from_degrees(359))
|
31
|
+
# # => -0.034906585039886195
|
32
|
+
class Heading < Numeric
|
33
|
+
FULL_ANGLE = Math::PI * 2.0
|
34
|
+
HALF_ANGLE = Math::PI
|
35
|
+
EIGHTH_ANGLE = Math::PI / 4.0
|
36
|
+
ONE_DEGREE = FULL_ANGLE / 360.0
|
37
|
+
FULL_RANGE = (0..FULL_ANGLE)
|
38
|
+
|
39
|
+
NORTH = N = 0.0
|
40
|
+
NORTH_EAST = NE = 1.0 * EIGHTH_ANGLE
|
41
|
+
EAST = E = 2.0 * EIGHTH_ANGLE
|
42
|
+
SOUTH_EAST = SE = 3.0 * EIGHTH_ANGLE
|
43
|
+
SOUTH = S = 4.0 * EIGHTH_ANGLE
|
44
|
+
SOUTH_WEST = SW = 5.0 * EIGHTH_ANGLE
|
45
|
+
WEST = W = 6.0 * EIGHTH_ANGLE
|
46
|
+
NORTH_WEST = NW = 7.0 * EIGHTH_ANGLE
|
47
|
+
|
48
|
+
def self.new_from_degrees(degrees)
|
49
|
+
self.new((degrees / 180.0) * Math::PI)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.new_between_points(from_point, to_point)
|
53
|
+
self.new(from_point == to_point ? 0.0 : Math.atan2(to_point.x - from_point.x, to_point.y - from_point.y))
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.delta_between_points(from_point, from_point_heading, to_point)
|
57
|
+
rel_heading = self.new_between_points(from_point, to_point)
|
58
|
+
self.new(from_point_heading).delta(rel_heading)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.rand
|
62
|
+
self.new(Kernel.rand * FULL_ANGLE)
|
63
|
+
end
|
64
|
+
|
65
|
+
attr_reader :radians
|
66
|
+
|
67
|
+
# Creates a new RTanque::Heading
|
68
|
+
# @param [#to_f] radians degree to wrap (in radians)
|
69
|
+
def initialize(radians = NORTH)
|
70
|
+
@radians = radians.to_f % FULL_ANGLE
|
71
|
+
@memoized = {} # allow memoization since @some_var ||= x doesn't work when frozen
|
72
|
+
self.freeze
|
73
|
+
end
|
74
|
+
|
75
|
+
# difference between `self` and `to` respecting negative angles
|
76
|
+
# @param [#to_f] to
|
77
|
+
# @return [Float]
|
78
|
+
def delta(to)
|
79
|
+
diff = (to.to_f - self.to_f).abs % FULL_ANGLE
|
80
|
+
diff = -(FULL_ANGLE - diff) if diff > Math::PI
|
81
|
+
to.to_f < self.to_f ? -diff : diff
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [RTanque::Heading]
|
85
|
+
def clone
|
86
|
+
self.class.new(self.radians)
|
87
|
+
end
|
88
|
+
|
89
|
+
# @param [#to_f] other_heading
|
90
|
+
# @return [Boolean]
|
91
|
+
def ==(other_heading)
|
92
|
+
self.to_f == other_heading.to_f
|
93
|
+
end
|
94
|
+
|
95
|
+
# continue with Numeric's pattern
|
96
|
+
# @param [#to_f] other_heading
|
97
|
+
# @return [Boolean]
|
98
|
+
def eql?(other_heading)
|
99
|
+
other_heading.instance_of?(self.class) && self.==(other_heading)
|
100
|
+
end
|
101
|
+
|
102
|
+
# @param [#to_f] other_heading
|
103
|
+
# @return [Boolean]
|
104
|
+
def <=>(other_heading)
|
105
|
+
self.to_f <=> other_heading.to_f
|
106
|
+
end
|
107
|
+
|
108
|
+
# @param [#to_f] other_heading
|
109
|
+
# @return [RTanque::Heading]
|
110
|
+
def +(other_heading)
|
111
|
+
self.class.new(self.radians + other_heading.to_f)
|
112
|
+
end
|
113
|
+
|
114
|
+
# @param [#to_f] other_heading
|
115
|
+
# @return [RTanque::Heading]
|
116
|
+
def -(other_heading)
|
117
|
+
self.+(-other_heading)
|
118
|
+
end
|
119
|
+
|
120
|
+
# @param [#to_f] other_heading
|
121
|
+
# @return [RTanque::Heading]
|
122
|
+
def *(other_heading)
|
123
|
+
self.class.new(self.radians * other_heading.to_f)
|
124
|
+
end
|
125
|
+
|
126
|
+
# @param [#to_f] other_heading
|
127
|
+
# @return [RTanque::Heading]
|
128
|
+
def /(other_heading)
|
129
|
+
self.*(1.0 / other_heading)
|
130
|
+
end
|
131
|
+
|
132
|
+
# unary operator
|
133
|
+
# @return [RTanque::Heading]
|
134
|
+
def +@
|
135
|
+
self.class.new(+self.radians)
|
136
|
+
end
|
137
|
+
|
138
|
+
# unary operator
|
139
|
+
# @return [RTanque::Heading]
|
140
|
+
def -@
|
141
|
+
self.class.new(-self.radians)
|
142
|
+
end
|
143
|
+
|
144
|
+
def to_s
|
145
|
+
self.to_f
|
146
|
+
end
|
147
|
+
|
148
|
+
def inspect
|
149
|
+
"<#{self.class.name}: #{self.radians}rad #{self.to_degrees}deg>"
|
150
|
+
end
|
151
|
+
|
152
|
+
# @return [Float]
|
153
|
+
def to_f
|
154
|
+
self.radians
|
155
|
+
end
|
156
|
+
|
157
|
+
# @return [Float]
|
158
|
+
def to_degrees
|
159
|
+
@memoized[:to_degrees] ||= (self.radians * 180.0) / Math::PI
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module RTanque
|
2
|
+
class Match
|
3
|
+
attr_reader :arena, :bots, :shells, :explosions, :ticks, :max_ticks, :teams
|
4
|
+
|
5
|
+
def initialize(arena, max_ticks = nil, teams = false)
|
6
|
+
@arena = arena
|
7
|
+
@max_ticks = max_ticks
|
8
|
+
@teams = teams
|
9
|
+
@ticks = 0
|
10
|
+
@shells = TickGroup.new
|
11
|
+
@bots = TickGroup.new
|
12
|
+
@explosions = TickGroup.new
|
13
|
+
@bots.pre_tick(&method(:pre_bot_tick))
|
14
|
+
@bots.post_tick(&method(:post_bot_tick))
|
15
|
+
@shells.pre_tick(&method(:pre_shell_tick))
|
16
|
+
@stopped = false
|
17
|
+
end
|
18
|
+
|
19
|
+
def teams=(bool)
|
20
|
+
@teams = bool
|
21
|
+
end
|
22
|
+
|
23
|
+
def max_ticks_reached?
|
24
|
+
self.max_ticks && self.ticks >= self.max_ticks
|
25
|
+
end
|
26
|
+
|
27
|
+
def finished?
|
28
|
+
@stopped || self.max_ticks_reached? || self.bots.count <= 1 ||
|
29
|
+
(self.teams && self.bots.map(&:name).uniq.size == 1)
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_bots(*bots)
|
33
|
+
self.bots.add(*bots)
|
34
|
+
end
|
35
|
+
|
36
|
+
def start
|
37
|
+
self.tick until self.finished?
|
38
|
+
end
|
39
|
+
|
40
|
+
def stop
|
41
|
+
@stopped = true
|
42
|
+
end
|
43
|
+
|
44
|
+
def pre_bot_tick(bot)
|
45
|
+
bot.radar.scan(self.bots.all_but(bot))
|
46
|
+
end
|
47
|
+
|
48
|
+
def post_bot_tick(bot)
|
49
|
+
if bot.firing?
|
50
|
+
# shell starts life at the end of the turret
|
51
|
+
shell_position = bot.position.move(bot.turret.heading, RTanque::Bot::Turret::LENGTH)
|
52
|
+
@shells.add(RTanque::Shell.new(bot, shell_position, bot.turret.heading.clone, bot.fire_power))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def pre_shell_tick(shell)
|
57
|
+
shell.hits(self.bots.all_but(shell.bot)) do |origin_bot, bot_hit|
|
58
|
+
damage = (shell.fire_power**RTanque::Shell::RATIO)
|
59
|
+
bot_hit.reduce_health(damage)
|
60
|
+
if bot_hit.dead?
|
61
|
+
@explosions.add(Explosion.new(bot_hit.position))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def tick
|
67
|
+
self.shells.tick
|
68
|
+
self.bots.tick
|
69
|
+
self.explosions.tick
|
70
|
+
@ticks += 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module RTanque
|
2
|
+
class Match
|
3
|
+
class TickGroup
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@members = []
|
8
|
+
@pre_tick = nil
|
9
|
+
@post_tick = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
@members.each(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def all_but(*to_exclude)
|
17
|
+
self.to_a - to_exclude
|
18
|
+
end
|
19
|
+
|
20
|
+
def delete_if(&block)
|
21
|
+
@members.delete_if(&block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def add(*members)
|
25
|
+
@members += members.flatten
|
26
|
+
end
|
27
|
+
|
28
|
+
def pre_tick(&block)
|
29
|
+
@pre_tick = block
|
30
|
+
end
|
31
|
+
|
32
|
+
def post_tick(&block)
|
33
|
+
@post_tick = block
|
34
|
+
end
|
35
|
+
|
36
|
+
def tick
|
37
|
+
self.delete_if do |member|
|
38
|
+
if member.dead?
|
39
|
+
true
|
40
|
+
else
|
41
|
+
@pre_tick.call(member) if @pre_tick
|
42
|
+
member.tick
|
43
|
+
@post_tick.call(member) if @post_tick
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module RTanque
|
2
|
+
module Movable
|
3
|
+
def tick
|
4
|
+
update_position
|
5
|
+
end
|
6
|
+
|
7
|
+
def dead?
|
8
|
+
false # should overwrite
|
9
|
+
end
|
10
|
+
|
11
|
+
def arena
|
12
|
+
@arena
|
13
|
+
end
|
14
|
+
|
15
|
+
def arena=(val)
|
16
|
+
@arena = val
|
17
|
+
end
|
18
|
+
|
19
|
+
def position
|
20
|
+
@position
|
21
|
+
end
|
22
|
+
|
23
|
+
def position=(val)
|
24
|
+
@position = val
|
25
|
+
end
|
26
|
+
|
27
|
+
def bound_to_arena
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def update_position
|
32
|
+
@position = @position.move(self.heading, self.speed, self.bound_to_arena)
|
33
|
+
end
|
34
|
+
|
35
|
+
def heading
|
36
|
+
@heading
|
37
|
+
end
|
38
|
+
|
39
|
+
def heading=(val)
|
40
|
+
@heading = Heading.new(val) if val
|
41
|
+
end
|
42
|
+
|
43
|
+
def speed
|
44
|
+
@speed
|
45
|
+
end
|
46
|
+
|
47
|
+
def speed=(val)
|
48
|
+
@speed = val if val
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|