colstrom-rtanque 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,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 = (self.x + (Math.sin(heading) * speed)).round(10)
|
|
115
|
+
y = (self.y + (Math.cos(heading) * speed)).round(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
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Add the working directory so that loading of bots works as expected
|
|
2
|
+
$LOAD_PATH << Dir.pwd
|
|
3
|
+
# Add the gem root dir so that sample_bots can be loaded
|
|
4
|
+
$LOAD_PATH << File.expand_path('../../../', __FILE__)
|
|
5
|
+
|
|
6
|
+
module RTanque
|
|
7
|
+
# Runner manages running an {RTanque::Match}
|
|
8
|
+
class Runner
|
|
9
|
+
LoadError = Class.new(::LoadError)
|
|
10
|
+
attr_reader :match
|
|
11
|
+
|
|
12
|
+
# @param [Integer] width
|
|
13
|
+
# @param [Integer] height
|
|
14
|
+
# @param [*match_args] args provided to {RTanque::Match#initialize}
|
|
15
|
+
def initialize(width, height, *match_args)
|
|
16
|
+
@match = RTanque::Match.new(RTanque::Arena.new(width, height), *match_args)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Attempts to load given {RTanque::Bot::Brain} given its path
|
|
20
|
+
# @param [String] brain_path
|
|
21
|
+
# @raise [RTanque::Runner::LoadError] if brain could not be loaded
|
|
22
|
+
def add_brain_path(brain_path)
|
|
23
|
+
parsed_path = self.parse_brain_path(brain_path)
|
|
24
|
+
bots = parsed_path.multiplier.times.map { self.new_bots_from_brain_path(parsed_path.path) }.flatten
|
|
25
|
+
self.match.add_bots(bots)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Starts the match
|
|
29
|
+
# @param [Boolean] gui if false, runs headless match
|
|
30
|
+
def start(gui = true)
|
|
31
|
+
if gui
|
|
32
|
+
require 'rtanque/gui'
|
|
33
|
+
window = RTanque::Gui::Window.new(self.match)
|
|
34
|
+
trap(:INT) { window.close }
|
|
35
|
+
window.show
|
|
36
|
+
else
|
|
37
|
+
trap(:INT) { self.match.stop }
|
|
38
|
+
self.match.start
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
protected
|
|
43
|
+
|
|
44
|
+
def new_bots_from_brain_path(brain_path)
|
|
45
|
+
self.fetch_brain_klasses(brain_path).map do |brain_klass|
|
|
46
|
+
RTanque::Bot.new_random_location(self.match.arena, brain_klass)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def fetch_brain_klasses(brain_path)
|
|
51
|
+
@load_brain_klass_cache ||= Hash.new do |cache, path|
|
|
52
|
+
cache[path] = self.get_diff_in_object_space(RTanque::Bot::Brain) {
|
|
53
|
+
begin
|
|
54
|
+
require(path)
|
|
55
|
+
rescue ::LoadError => e
|
|
56
|
+
raise LoadError, e.message
|
|
57
|
+
end
|
|
58
|
+
}
|
|
59
|
+
raise LoadError, "No class of type #{RTanque::Bot::Brain} found in #{path}" if cache[path].empty?
|
|
60
|
+
cache[path]
|
|
61
|
+
end
|
|
62
|
+
@load_brain_klass_cache[brain_path]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def get_diff_in_object_space(klass)
|
|
66
|
+
current_object_space = self.get_descendants_of_class(klass)
|
|
67
|
+
yield
|
|
68
|
+
self.get_descendants_of_class(klass) - current_object_space
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def get_descendants_of_class(klass)
|
|
72
|
+
::ObjectSpace.each_object(::Class).select {|k| k < klass }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
BRAIN_PATH_PARSER = /\A(.+?)\:[x|X](\d+)\z/
|
|
76
|
+
# @!visibility private
|
|
77
|
+
ParsedBrainPath = Struct.new(:path, :multiplier)
|
|
78
|
+
def parse_brain_path(brain_path)
|
|
79
|
+
path = brain_path.gsub('\.rb$', '')
|
|
80
|
+
multiplier = 1
|
|
81
|
+
brain_path.match(BRAIN_PATH_PARSER) { |m|
|
|
82
|
+
path = m[1]
|
|
83
|
+
multiplier = m[2].to_i
|
|
84
|
+
}
|
|
85
|
+
ParsedBrainPath.new(path, multiplier)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module RTanque
|
|
2
|
+
class Shell
|
|
3
|
+
include Movable
|
|
4
|
+
RATIO = Configuration.shell.ratio
|
|
5
|
+
SHELL_SPEED_FACTOR = Configuration.shell.speed_factor
|
|
6
|
+
attr_reader :bot, :arena, :fire_power
|
|
7
|
+
|
|
8
|
+
def self.speed fire_power
|
|
9
|
+
fire_power * SHELL_SPEED_FACTOR
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(bot, position, heading, fire_power)
|
|
13
|
+
@bot = bot
|
|
14
|
+
@arena = bot.arena
|
|
15
|
+
@fire_power = fire_power
|
|
16
|
+
self.position = position
|
|
17
|
+
self.heading = heading
|
|
18
|
+
self.speed = self.class.speed(fire_power) # TODO: add bot's relative speed in this heading
|
|
19
|
+
@dead = false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def bound_to_arena
|
|
23
|
+
false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def dead?
|
|
27
|
+
@dead ||= self.position.outside_arena?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def dead!
|
|
31
|
+
@dead = true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def hits(bots, &on_hit)
|
|
35
|
+
bots.each do |hit_bot|
|
|
36
|
+
if hit_bot.position.within_radius?(self.position, Bot::RADIUS)
|
|
37
|
+
self.dead!
|
|
38
|
+
on_hit.call(self.bot, hit_bot) if on_hit
|
|
39
|
+
break
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|