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,172 @@
|
|
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(Float.send(: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 = self.extract_radians_from_value(radians) % 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 < self ? -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 + self.extract_radians_from_value(other_heading))
|
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 * self.extract_radians_from_value(other_heading))
|
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
|
+
|
162
|
+
protected
|
163
|
+
|
164
|
+
def extract_radians_from_value(value)
|
165
|
+
if value.respond_to?(:radians)
|
166
|
+
value.radians
|
167
|
+
else
|
168
|
+
value.to_f
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module RTanque
|
2
|
+
class Match
|
3
|
+
attr_reader :arena, :bots, :shells, :explosions, :ticks, :max_ticks
|
4
|
+
|
5
|
+
def initialize(arena, max_ticks = nil)
|
6
|
+
@arena = arena
|
7
|
+
@max_ticks = max_ticks
|
8
|
+
@ticks = 0
|
9
|
+
@shells = TickGroup.new
|
10
|
+
@bots = TickGroup.new
|
11
|
+
@explosions = TickGroup.new
|
12
|
+
@bots.pre_tick(&method(:pre_bot_tick))
|
13
|
+
@bots.post_tick(&method(:post_bot_tick))
|
14
|
+
@shells.pre_tick(&method(:pre_shell_tick))
|
15
|
+
@stopped = false
|
16
|
+
end
|
17
|
+
|
18
|
+
def max_ticks_reached?
|
19
|
+
self.max_ticks && self.ticks >= self.max_ticks
|
20
|
+
end
|
21
|
+
|
22
|
+
def finished?
|
23
|
+
@stopped || self.max_ticks_reached? || self.bots.count <= 1
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_bots(*bots)
|
27
|
+
self.bots.add(*bots)
|
28
|
+
end
|
29
|
+
|
30
|
+
def start
|
31
|
+
self.tick until self.finished?
|
32
|
+
end
|
33
|
+
|
34
|
+
def stop
|
35
|
+
@stopped = true
|
36
|
+
end
|
37
|
+
|
38
|
+
def pre_bot_tick(bot)
|
39
|
+
bot.radar.scan(self.bots.all_but(bot))
|
40
|
+
end
|
41
|
+
|
42
|
+
def post_bot_tick(bot)
|
43
|
+
if bot.firing?
|
44
|
+
# shell starts life at the end of the turret
|
45
|
+
shell_position = bot.position.move(bot.turret.heading, RTanque::Bot::Turret::LENGTH)
|
46
|
+
@shells.add(RTanque::Shell.new(bot, shell_position, bot.turret.heading.clone, bot.fire_power))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def pre_shell_tick(shell)
|
51
|
+
shell.hits(self.bots.all_but(shell.bot)) do |origin_bot, bot_hit|
|
52
|
+
damage = (shell.fire_power ** RTanque::Shell::RATIO)
|
53
|
+
bot_hit.reduce_health(damage)
|
54
|
+
if bot_hit.dead?
|
55
|
+
@explosions.add(Explosion.new(bot_hit.position))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def tick
|
61
|
+
self.shells.tick
|
62
|
+
self.bots.tick
|
63
|
+
self.explosions.tick
|
64
|
+
@ticks += 1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
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.reject { |member| to_exclude.include?(member) }
|
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
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module RTanque
|
2
|
+
module NormalizedAttr
|
3
|
+
MAX_DELTA = 1.0 / 0.0 # INFINITY
|
4
|
+
def attr_normalized(attr_name, range, max_delta = MAX_DELTA)
|
5
|
+
@_normalized_attrs ||= {}
|
6
|
+
@_normalized_attrs[attr_name] = AttrContainer.new(range, max_delta)
|
7
|
+
const_set("MAX_#{attr_name.to_s.upcase}", @_normalized_attrs[attr_name].max)
|
8
|
+
const_set("MIN_#{attr_name.to_s.upcase}", @_normalized_attrs[attr_name].min)
|
9
|
+
define_method("normalize_#{attr_name}") do |current_value, new_value|
|
10
|
+
return new_value unless new_value
|
11
|
+
self.class.normalized_attrs(attr_name).normalize(self, current_value, new_value)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def normalized_attrs(attr_name)
|
16
|
+
@_normalized_attrs.fetch(attr_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
class AttrContainer
|
20
|
+
def initialize(range, max_delta = MAX_DELTA)
|
21
|
+
@range = range
|
22
|
+
@max_delta = max_delta
|
23
|
+
end
|
24
|
+
|
25
|
+
def min
|
26
|
+
@range.first
|
27
|
+
end
|
28
|
+
|
29
|
+
def max
|
30
|
+
@range.last
|
31
|
+
end
|
32
|
+
|
33
|
+
def normalize(attached_instance, current_value, new_value)
|
34
|
+
self.enforce_range(self.enforce_delta(attached_instance, current_value, new_value))
|
35
|
+
end
|
36
|
+
|
37
|
+
def max_delta(attached_instance)
|
38
|
+
@max_delta.respond_to?(:call) ? @max_delta.call(attached_instance) : @max_delta
|
39
|
+
end
|
40
|
+
|
41
|
+
def enforce_delta(attached_instance, current_value, new_value)
|
42
|
+
current_delta = self.delta(current_value, new_value)
|
43
|
+
current_max_delta = self.max_delta(attached_instance)
|
44
|
+
if current_delta.abs > current_max_delta
|
45
|
+
current_delta > 0 ? current_value + current_max_delta : current_value - current_max_delta
|
46
|
+
else
|
47
|
+
new_value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def delta(current_value, new_value)
|
52
|
+
if current_value
|
53
|
+
# Heading responds to delta
|
54
|
+
current_value.respond_to?(:delta) ? current_value.delta(new_value) : (new_value - current_value)
|
55
|
+
else
|
56
|
+
0
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def enforce_range(value)
|
61
|
+
if @range.include?(value)
|
62
|
+
value
|
63
|
+
else
|
64
|
+
value > self.max ? self.max : self.min
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module RTanque
|
2
|
+
# A `Point` represents an [x, y] coordinate pair in the {RTanque::Arena}
|
3
|
+
#
|
4
|
+
# ##Usage
|
5
|
+
# @arena = RTanque::Arena.new(100, 100)
|
6
|
+
# # => #<struct RTanque::Arena width=100, height=100>
|
7
|
+
#
|
8
|
+
# @point_one = RTanque::Point.new(0, 1, @arena)
|
9
|
+
# # => #<struct RTanque::Point x=0, y=1, arena=#<struct RTanque::Arena width=100, height=100>>
|
10
|
+
#
|
11
|
+
# @point_one.on_top_wall?
|
12
|
+
# # => false
|
13
|
+
#
|
14
|
+
# @point_one.on_bottom_wall?
|
15
|
+
# # => false
|
16
|
+
#
|
17
|
+
# @point_one.on_right_wall?
|
18
|
+
# # => false
|
19
|
+
#
|
20
|
+
# @point_one.on_left_wall?
|
21
|
+
# # => true
|
22
|
+
#
|
23
|
+
# @point_one.on_wall?
|
24
|
+
# # => true
|
25
|
+
#
|
26
|
+
# @point_two = RTanque::Point.new(100, 1, @arena)
|
27
|
+
# # => #<struct RTanque::Point x=100, y=1, arena=#<struct RTanque::Arena width=100, height=100>>
|
28
|
+
#
|
29
|
+
# @point_two.within_radius?(@point_one, 10)
|
30
|
+
# # => false
|
31
|
+
#
|
32
|
+
# @point_two.within_radius?(@point_one, 100)
|
33
|
+
# # => true
|
34
|
+
#
|
35
|
+
# @point_two.distance(@point_one)
|
36
|
+
# # => 100.0
|
37
|
+
#
|
38
|
+
# @attr_reader [Numeric] x horizontal position (left edge is 0)
|
39
|
+
# @attr_reader [Numeric] y vertical position (bottom edge is 0)
|
40
|
+
# @attr_reader [RTanque::Arena] arena
|
41
|
+
#
|
42
|
+
# @!method distance(other_point) distance to other point
|
43
|
+
# @param [RTanque::Point]
|
44
|
+
# @return [Float]
|
45
|
+
#
|
46
|
+
# @!method heading(other_point) heading to other point
|
47
|
+
# @param [RTanque::Point]
|
48
|
+
# @return [RTanque::Heading]
|
49
|
+
#
|
50
|
+
# @!method on_top_wall?
|
51
|
+
# @return [Boolean]
|
52
|
+
#
|
53
|
+
# @!method on_bottom_wall?
|
54
|
+
# @return [Boolean]
|
55
|
+
#
|
56
|
+
# @!method on_right_wall?
|
57
|
+
# @return [Boolean]
|
58
|
+
#
|
59
|
+
# @!method on_left_wall?
|
60
|
+
# @return [Boolean]
|
61
|
+
#
|
62
|
+
# @!method on_wall?
|
63
|
+
# True if on any wall
|
64
|
+
# @return [Boolean]
|
65
|
+
Point = Struct.new(:x, :y, :arena) do
|
66
|
+
def initialize(*args, &block)
|
67
|
+
super
|
68
|
+
block.call(self) if block
|
69
|
+
self.freeze
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.rand(arena)
|
73
|
+
self.new(Kernel.rand(arena.width), Kernel.rand(arena.height), arena)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.distance(a, b)
|
77
|
+
Math.hypot(a.x - b.x, a.y - b.y)
|
78
|
+
end
|
79
|
+
|
80
|
+
def ==(other_point)
|
81
|
+
self.x == other_point.x && self.y == other_point.y
|
82
|
+
end
|
83
|
+
|
84
|
+
def within_radius?(other_point, radius)
|
85
|
+
self.distance(other_point) <= radius
|
86
|
+
end
|
87
|
+
|
88
|
+
def on_top_wall?
|
89
|
+
self.y >= self.arena.height
|
90
|
+
end
|
91
|
+
|
92
|
+
def on_bottom_wall?
|
93
|
+
self.y <= 0
|
94
|
+
end
|
95
|
+
|
96
|
+
def on_left_wall?
|
97
|
+
self.x <= 0
|
98
|
+
end
|
99
|
+
|
100
|
+
def on_right_wall?
|
101
|
+
self.x >= self.arena.width
|
102
|
+
end
|
103
|
+
|
104
|
+
def on_wall?
|
105
|
+
self.on_top_wall? || self.on_bottom_wall? || self.on_right_wall? || self.on_left_wall?
|
106
|
+
end
|
107
|
+
|
108
|
+
def outside_arena?
|
109
|
+
self.y > self.arena.height || self.y < 0 || self.x > self.arena.width || self.x < 0
|
110
|
+
end
|
111
|
+
|
112
|
+
def move(heading, speed, bound_to_arena = true)
|
113
|
+
# round to avoid floating point errors
|
114
|
+
x = RTanque.round((self.x + (Math.sin(heading) * speed)), 10)
|
115
|
+
y = RTanque.round((self.y + (Math.cos(heading) * speed)), 10)
|
116
|
+
self.class.new(x, y, self.arena) { |point| point.bind_to_arena if bound_to_arena }
|
117
|
+
end
|
118
|
+
|
119
|
+
def bind_to_arena
|
120
|
+
if self.x < 0
|
121
|
+
self.x = 0.0
|
122
|
+
elsif self.x > self.arena.width
|
123
|
+
self.x = self.arena.width.to_f
|
124
|
+
end
|
125
|
+
if self.y < 0
|
126
|
+
self.y = 0.0
|
127
|
+
elsif self.y > self.arena.height
|
128
|
+
self.y = self.arena.height.to_f
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def heading(other_point)
|
133
|
+
Heading.new_between_points(self, other_point)
|
134
|
+
end
|
135
|
+
|
136
|
+
def distance(other_point)
|
137
|
+
self.class.distance(self, other_point)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|