gamebox 0.0.6 → 0.0.7
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.
- data/History.txt +14 -0
- data/Manifest.txt +40 -37
- data/README.txt +2 -2
- data/Rakefile +27 -20
- data/TODO.txt +8 -8
- data/bin/gamebox +7 -1
- data/docs/getting_started.rdoc +2 -5
- data/docs/logo.png +0 -0
- data/lib/gamebox.rb +12 -1
- data/lib/gamebox/actor.rb +34 -9
- data/lib/gamebox/actor_factory.rb +25 -14
- data/lib/gamebox/actor_view.rb +6 -6
- data/lib/gamebox/{logo.rb → actors/logo.rb} +0 -0
- data/lib/gamebox/{score.rb → actors/score.rb} +2 -1
- data/lib/gamebox/{svg_actor.rb → actors/svg_actor.rb} +3 -4
- data/lib/gamebox/ai/polaris.rb +2 -0
- data/lib/gamebox/behavior.rb +3 -0
- data/lib/gamebox/{animated.rb → behaviors/animated.rb} +13 -5
- data/lib/gamebox/{graphical.rb → behaviors/graphical.rb} +11 -1
- data/lib/gamebox/{layered.rb → behaviors/layered.rb} +14 -0
- data/lib/gamebox/{physical.rb → behaviors/physical.rb} +41 -14
- data/lib/gamebox/{updatable.rb → behaviors/updatable.rb} +1 -1
- data/lib/gamebox/{templates/template_app → data}/config/objects.yml +4 -2
- data/lib/gamebox/data/graphics/logo.png +0 -0
- data/lib/gamebox/event_compat.rb +285 -0
- data/lib/gamebox/ftor.rb +2 -0
- data/lib/gamebox/gamebox_application.rb +12 -1
- data/lib/gamebox/generators/actor_generator.rb +2 -2
- data/lib/gamebox/generators/view_generator.rb +42 -0
- data/lib/gamebox/input_manager.rb +88 -21
- data/lib/gamebox/{aliasing.rb → lib/aliasing.rb} +0 -0
- data/lib/gamebox/{templates/template_app/lib → lib}/diy.rb +0 -0
- data/lib/gamebox/{inflections.rb → lib/inflections.rb} +0 -0
- data/lib/gamebox/{inflector.rb → lib/inflector.rb} +2 -2
- data/lib/gamebox/{linked_list.rb → lib/linked_list.rb} +0 -0
- data/lib/gamebox/{metaclass.rb → lib/metaclass.rb} +0 -0
- data/lib/gamebox/lib/numbers_ext.rb +3 -0
- data/lib/gamebox/{platform.rb → lib/platform.rb} +0 -0
- data/lib/gamebox/{publisher_ext.rb → lib/publisher_ext.rb} +0 -0
- data/lib/gamebox/{sorted_list.rb → lib/sorted_list.rb} +0 -0
- data/lib/gamebox/lib/surface_ext.rb +76 -0
- data/lib/gamebox/physical_director.rb +1 -1
- data/lib/gamebox/{physical_level.rb → physical_stage.rb} +28 -18
- data/lib/gamebox/physics.rb +2 -2
- data/lib/gamebox/resource_manager.rb +77 -8
- data/lib/gamebox/sound_manager.rb +15 -17
- data/lib/gamebox/stage.rb +143 -0
- data/lib/gamebox/stage_manager.rb +110 -0
- data/lib/gamebox/svg_document.rb +1 -1
- data/lib/gamebox/tasks/gamebox_tasks.rb +1 -1
- data/lib/gamebox/templates/{test_actor.erb → actor_spec.erb} +2 -2
- data/lib/gamebox/templates/actor_view.erb +8 -0
- data/lib/gamebox/templates/actor_view_spec.erb +10 -0
- data/lib/gamebox/templates/template_app/Rakefile +4 -4
- data/lib/gamebox/templates/template_app/config/game.yml +1 -0
- data/lib/gamebox/templates/template_app/config/stage_config.yml +2 -0
- data/lib/gamebox/templates/template_app/script/generate +2 -1
- data/lib/gamebox/templates/template_app/{test → spec}/helper.rb +0 -0
- data/lib/gamebox/templates/template_app/src/{demo_level.rb → demo_stage.rb} +5 -3
- data/lib/gamebox/templates/template_app/src/game.rb +4 -6
- data/lib/gamebox/version.rb +3 -2
- data/lib/gamebox/viewport.rb +18 -9
- data/lib/gamebox/{graphical_actor_view.rb → views/graphical_actor_view.rb} +13 -1
- data/lib/gamebox/wrapped_screen.rb +2 -1
- data/spec/actor_spec.rb +57 -0
- data/{test/test_animated.rb → spec/animated_spec.rb} +5 -6
- data/spec/helper.rb +24 -0
- data/{test/test_line_of_site.rb → spec/line_of_site_spec.rb} +1 -1
- data/{test/test_physical.rb → spec/physical_spec.rb} +3 -4
- data/{test/test_polaris.rb → spec/polaris_spec.rb} +1 -1
- data/{test/test_viewport.rb → spec/viewport_spec.rb} +11 -8
- metadata +53 -50
- data/docs/gamebox04_big.png +0 -0
- data/lib/gamebox/level.rb +0 -65
- data/lib/gamebox/mode.rb +0 -123
- data/lib/gamebox/mode_manager.rb +0 -80
- data/lib/gamebox/numbers_ext.rb +0 -3
- data/lib/gamebox/surface_ext.rb +0 -37
- data/lib/gamebox/templates/template_app/config/mode_level_config.yml +0 -3
- data/test/helper.rb +0 -26
- data/test/test_actor.rb +0 -36
data/lib/gamebox/ftor.rb
ADDED
@@ -4,6 +4,8 @@ $: << "#{File.dirname(__FILE__)}/../config"
|
|
4
4
|
require 'rubygems'
|
5
5
|
require 'rubygame'
|
6
6
|
include Rubygame
|
7
|
+
include Rubygame::Events
|
8
|
+
require 'event_compat'
|
7
9
|
require 'surface_ext'
|
8
10
|
|
9
11
|
require "environment"
|
@@ -23,7 +25,16 @@ class GameboxApp
|
|
23
25
|
end
|
24
26
|
|
25
27
|
def initialize
|
26
|
-
|
28
|
+
gamebox_objects = YAML.load(File.read(GAMEBOX_PATH + 'data/config/objects.yml'))
|
29
|
+
|
30
|
+
game_objects_file = APP_ROOT + '/config/objects.yml'
|
31
|
+
game_specific_objects = {}
|
32
|
+
if File.exist? game_objects_file
|
33
|
+
game_specific_objects = YAML.load(File.read(game_objects_file))
|
34
|
+
end
|
35
|
+
objects = gamebox_objects.merge! game_specific_objects
|
36
|
+
|
37
|
+
@context = DIY::Context.from_yaml(YAML.dump(objects))
|
27
38
|
end
|
28
39
|
|
29
40
|
def setup
|
@@ -28,13 +28,13 @@ class ActorGenerator
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def build_test_actor
|
31
|
-
template_file = File.open(File.join(GAMEBOX_PATH,'templates','
|
31
|
+
template_file = File.open(File.join(GAMEBOX_PATH,'templates','actor_spec.erb'))
|
32
32
|
template_contents = template_file.readlines.join
|
33
33
|
actor_template = ERB.new(template_contents)
|
34
34
|
|
35
35
|
result = actor_template.result @bind
|
36
36
|
|
37
|
-
out_file = File.join(APP_ROOT,'
|
37
|
+
out_file = File.join(APP_ROOT,'spec',Inflector.underscore(@actor_name)+"_spec.rb")
|
38
38
|
raise "File exists [#{out_file}]" if File.exists? out_file
|
39
39
|
File.open(out_file,"w+") do |f|
|
40
40
|
f.write result
|
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
class ViewGenerator
|
3
|
+
require 'erb'
|
4
|
+
def generate(*args)
|
5
|
+
|
6
|
+
@actor_view_name = ARGV[1]
|
7
|
+
@actor_view_name += "View" unless @actor_view_name.end_with? "View"
|
8
|
+
@behaviors = ARGV[2]
|
9
|
+
@bind = binding
|
10
|
+
|
11
|
+
build_actor_view
|
12
|
+
build_test_actor_view
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_actor_view
|
16
|
+
template_file = File.open(File.join(GAMEBOX_PATH,'templates','actor_view.erb'))
|
17
|
+
template_contents = template_file.readlines.join
|
18
|
+
actor_view_template = ERB.new(template_contents)
|
19
|
+
|
20
|
+
result = actor_view_template.result @bind
|
21
|
+
|
22
|
+
out_file = File.join(APP_ROOT,'src',Inflector.underscore(@actor_view_name)+".rb")
|
23
|
+
raise "File exists [#{out_file}]" if File.exists? out_file
|
24
|
+
File.open(out_file,"w+") do |f|
|
25
|
+
f.write result
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_test_actor_view
|
30
|
+
template_file = File.open(File.join(GAMEBOX_PATH,'templates','actor_view_spec.erb'))
|
31
|
+
template_contents = template_file.readlines.join
|
32
|
+
actor_view_template = ERB.new(template_contents)
|
33
|
+
|
34
|
+
result = actor_view_template.result @bind
|
35
|
+
|
36
|
+
out_file = File.join(APP_ROOT,'spec',Inflector.underscore(@actor_view_name)+"_spec.rb")
|
37
|
+
raise "File exists [#{out_file}]" if File.exists? out_file
|
38
|
+
File.open(out_file,"w+") do |f|
|
39
|
+
f.write result
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -1,26 +1,52 @@
|
|
1
1
|
require 'publisher'
|
2
|
+
|
3
|
+
# InputManager handles the pumping of SDL for events and distributing of said events.
|
4
|
+
# You can gain access to these events by registering for all events,
|
5
|
+
# or just the ones you care about.
|
6
|
+
# All events:
|
7
|
+
# input_manager.when :event_received do |evt|
|
8
|
+
# ...
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# Some events:
|
12
|
+
# input_manager.reg KeyPressed, :space do
|
13
|
+
# ...
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# Don't forget to unreg for these things between stages,
|
17
|
+
# since the InputManager is shared across stages.
|
2
18
|
class InputManager
|
3
19
|
extend Publisher
|
4
20
|
can_fire :key_up, :event_received
|
5
21
|
|
22
|
+
|
23
|
+
# lookup map for mouse button clicks
|
6
24
|
MOUSE_BUTTON_LOOKUP = {
|
7
|
-
|
8
|
-
|
9
|
-
|
25
|
+
MOUSE_LEFT => :left,
|
26
|
+
MOUSE_MIDDLE => :middle,
|
27
|
+
MOUSE_RIGHT => :right,
|
10
28
|
}
|
11
29
|
|
12
|
-
|
13
|
-
|
14
|
-
|
30
|
+
constructor :config_manager
|
31
|
+
|
32
|
+
# Sets up the clock and main event loop. You should never call this method,
|
33
|
+
# as this class should be initialized by diy.
|
34
|
+
def setup
|
15
35
|
@queue = EventQueue.new
|
36
|
+
@queue.enable_new_style_events
|
16
37
|
@queue.ignore = [
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
38
|
+
InputFocusGained,
|
39
|
+
InputFocusLost,
|
40
|
+
MouseFocusGained,
|
41
|
+
MouseFocusLost,
|
42
|
+
WindowMinimized,
|
43
|
+
WindowUnminimized,
|
44
|
+
JoystickAxisMoved,
|
45
|
+
JoystickBallMoved,
|
46
|
+
JoystickButtonPressed,
|
47
|
+
JoystickButtonReleased,
|
48
|
+
JoystickHatMoved,
|
49
|
+
WindowResized
|
24
50
|
]
|
25
51
|
|
26
52
|
@clock = Clock.new do |c|
|
@@ -31,39 +57,49 @@ class InputManager
|
|
31
57
|
end
|
32
58
|
end
|
33
59
|
|
60
|
+
@auto_quit = @config_manager[:auto_quit]
|
61
|
+
|
34
62
|
@hooks = {}
|
35
63
|
@non_id_hooks = {}
|
36
64
|
end
|
37
65
|
|
66
|
+
# Sets the target framerate for the game.
|
67
|
+
# This setting controls how lock Clock#tick will delay.
|
38
68
|
def framerate=(frame_rate)
|
39
69
|
@clock.target_framerate = frame_rate
|
40
70
|
end
|
41
71
|
|
72
|
+
# Returns the target framerate.
|
42
73
|
def framerate
|
43
74
|
@clock.target_framerate
|
44
75
|
end
|
45
76
|
|
77
|
+
# This is where the queue gets pumped. This gets called from your game application.
|
46
78
|
def main_loop(game)
|
47
79
|
catch(:rubygame_quit) do
|
48
80
|
loop do
|
49
81
|
# add magic hooks
|
50
82
|
@queue.each do |event|
|
51
83
|
case event
|
52
|
-
when
|
84
|
+
when KeyPressed
|
53
85
|
case event.key
|
54
|
-
when
|
86
|
+
when :f
|
55
87
|
puts "Framerate:#{@clock.framerate}"
|
56
|
-
when
|
57
|
-
throw :rubygame_quit
|
88
|
+
when @auto_quit
|
89
|
+
throw :rubygame_quit
|
58
90
|
end
|
59
|
-
when
|
91
|
+
when QuitRequested
|
60
92
|
throw :rubygame_quit
|
61
93
|
end
|
62
94
|
fire :event_received, event
|
63
95
|
|
64
96
|
event_hooks = @hooks[event.class]
|
65
97
|
id = event.key if event.respond_to? :key
|
66
|
-
|
98
|
+
|
99
|
+
if event.respond_to? :button
|
100
|
+
id ||= (MOUSE_BUTTON_LOOKUP[event.button] or event.button)
|
101
|
+
end
|
102
|
+
|
67
103
|
unless id.nil?
|
68
104
|
event_action_hooks = event_hooks[id] if event_hooks
|
69
105
|
if event_action_hooks
|
@@ -86,7 +122,22 @@ class InputManager
|
|
86
122
|
end
|
87
123
|
end
|
88
124
|
|
125
|
+
# registers a block to be called when matching events are pulled from the SDL queue.
|
126
|
+
# ie
|
127
|
+
# input_manager.register_hook KeyPressed do |evt|
|
128
|
+
# # will be called on every key press
|
129
|
+
# end
|
130
|
+
# input_manager.register_hook KeyPressed, K_SPACE do |evt|
|
131
|
+
# # will be called on every spacebar key press
|
132
|
+
# end
|
89
133
|
def register_hook(event_class, *event_ids, &block)
|
134
|
+
return unless block_given?
|
135
|
+
listener = eval("self", block.binding)
|
136
|
+
_register_hook listener, event_class, *event_ids, &block
|
137
|
+
end
|
138
|
+
alias reg register_hook
|
139
|
+
|
140
|
+
def _register_hook(listener, event_class, *event_ids, &block)
|
90
141
|
return unless block_given?
|
91
142
|
@hooks[event_class] ||= {}
|
92
143
|
for event_id in event_ids
|
@@ -97,13 +148,15 @@ class InputManager
|
|
97
148
|
if event_ids.empty?
|
98
149
|
@non_id_hooks[event_class] << block
|
99
150
|
end
|
100
|
-
listener = eval("self", block.binding)
|
101
151
|
listener.when :remove_me do
|
102
152
|
unregister_hook event_class, *event_ids, &block
|
103
153
|
end
|
104
154
|
end
|
105
|
-
alias reg register_hook
|
106
155
|
|
156
|
+
# unregisters a block to be called when matching events are pulled from the SDL queue.
|
157
|
+
# ie
|
158
|
+
# input_manager.unregister_hook KeyPressed, :space, registered_block
|
159
|
+
# also see InputManager#clear_hooks for clearing many hooks
|
107
160
|
def unregister_hook(event_class, *event_ids, &block)
|
108
161
|
@hooks[event_class] ||= {}
|
109
162
|
for event_id in event_ids
|
@@ -117,6 +170,9 @@ class InputManager
|
|
117
170
|
end
|
118
171
|
alias unreg unregister_hook
|
119
172
|
|
173
|
+
|
174
|
+
# removes all blocks that are in the scope of listener's instance.
|
175
|
+
# clears all listeners if listener is nil
|
120
176
|
def clear_hooks(listener=nil)
|
121
177
|
if listener
|
122
178
|
for event_klass, id_listeners in @hooks
|
@@ -137,4 +193,15 @@ class InputManager
|
|
137
193
|
@non_id_hooks = {}
|
138
194
|
end
|
139
195
|
end
|
196
|
+
|
197
|
+
# autohook a boolean to be set to true while a key is pressed
|
198
|
+
def while_key_pressed(key, target, accessor)
|
199
|
+
_register_hook target, KeyPressed, key do
|
200
|
+
target.send accessor.to_s+"=", true
|
201
|
+
end
|
202
|
+
_register_hook target, KeyReleased, key do
|
203
|
+
target.send accessor.to_s+"=", false
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
140
207
|
end
|
File without changes
|
File without changes
|
File without changes
|
@@ -200,7 +200,7 @@ module Inflector
|
|
200
200
|
class_name_in_module.to_s.gsub(/^.*::/, '')
|
201
201
|
end
|
202
202
|
|
203
|
-
# Create the name of a table like Rails does for
|
203
|
+
# Create the name of a table like Rails does for stagels to table names. This method
|
204
204
|
# uses the pluralize method on the last word in the string.
|
205
205
|
#
|
206
206
|
# Examples
|
@@ -211,7 +211,7 @@ module Inflector
|
|
211
211
|
pluralize(underscore(class_name))
|
212
212
|
end
|
213
213
|
|
214
|
-
# Create a class name from a table name like Rails does for table names to
|
214
|
+
# Create a class name from a table name like Rails does for table names to stagels.
|
215
215
|
# Note that this returns a string and not a Class. (To convert to an actual class
|
216
216
|
# follow classify with constantize.)
|
217
217
|
#
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'ftor'
|
2
|
+
include Rubygame::Color
|
3
|
+
module Rubygame
|
4
|
+
class Surface
|
5
|
+
def draw_line_s(point1, point2, color, thickness)
|
6
|
+
half_thickness = thickness/2.0
|
7
|
+
x1 = point1[0]
|
8
|
+
y1 = point1[1]
|
9
|
+
x2 = point2[0]
|
10
|
+
y2 = point2[1]
|
11
|
+
|
12
|
+
point1_vector = Ftor.new x1, y1
|
13
|
+
point2_vector = Ftor.new x2, y2
|
14
|
+
|
15
|
+
line_vector = point2_vector-point1_vector
|
16
|
+
perp_vector = line_vector.normal.unit
|
17
|
+
|
18
|
+
points = []
|
19
|
+
pvt = perp_vector*half_thickness
|
20
|
+
poly_point1 = Ftor.new(x1,y1)+pvt
|
21
|
+
poly_point2 = Ftor.new(x2,y2)+pvt
|
22
|
+
poly_point3 = Ftor.new(x2,y2)-pvt
|
23
|
+
poly_point4 = Ftor.new(x1,y1)-pvt
|
24
|
+
|
25
|
+
points << [poly_point1.x,poly_point1.y]
|
26
|
+
points << [poly_point2.x,poly_point2.y]
|
27
|
+
points << [poly_point3.x,poly_point3.y]
|
28
|
+
points << [poly_point4.x,poly_point4.y]
|
29
|
+
points << [poly_point1.x,poly_point1.y]
|
30
|
+
|
31
|
+
draw_polygon_s points, color
|
32
|
+
draw_circle_s [x1,y1], half_thickness, color
|
33
|
+
draw_circle_s [x2,y2], half_thickness, color
|
34
|
+
end
|
35
|
+
|
36
|
+
# BY IPPA
|
37
|
+
def fill_gradient(user_options = {})
|
38
|
+
# merge arguments from methodcall Over default options
|
39
|
+
default_options = {
|
40
|
+
:from_color => :black,
|
41
|
+
:to_color => :white,
|
42
|
+
:thickness => 10,
|
43
|
+
:orientation => :down,
|
44
|
+
:rect => Rect.new([0, 0, self.width, self.height])
|
45
|
+
}
|
46
|
+
options = default_options.merge(user_options)
|
47
|
+
|
48
|
+
# typecast color and rect arguments
|
49
|
+
from_color = (options[:from_color].is_a? ColorRGB)? options[:from_color] : Color[options[:from_color]]
|
50
|
+
to_color = (options[:to_color].is_a? ColorRGB)? options[:to_color] : Color[options[:to_color]]
|
51
|
+
rect = (options[:rect].is_a? Rect)? options[:rect] : Rect.new(:rect)
|
52
|
+
|
53
|
+
length = (options[:orientation] == :vertical) ? rect.height : rect.width
|
54
|
+
weight_step = 1.0 / (length.to_f / options[:thickness].to_f)
|
55
|
+
weight = 0.0
|
56
|
+
x = rect.x
|
57
|
+
y = rect.y
|
58
|
+
|
59
|
+
while weight < 1.0
|
60
|
+
color = to_color.average(from_color, weight)
|
61
|
+
|
62
|
+
if options[:orientation] == :vertical
|
63
|
+
self.draw_box_s([x, y], [rect.width, y + options[:thickness]], color)
|
64
|
+
y += options[:thickness]
|
65
|
+
else
|
66
|
+
self.draw_box_s([x, y],[x + options[:thickness], rect.height], color)
|
67
|
+
x += options[:thickness]
|
68
|
+
end
|
69
|
+
|
70
|
+
weight += weight_step
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end # module Rubygame
|
@@ -1,29 +1,26 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
#
|
4
|
-
require '
|
1
|
+
# Stage represent on level of game play. Some games will likely have only one
|
2
|
+
# stage. Stage is responsible for loading its background, props, and directors.
|
3
|
+
# PhysicalStage adds a physics space to the Stage
|
4
|
+
require 'stage'
|
5
5
|
require 'physics'
|
6
6
|
require 'physical_director'
|
7
|
-
class
|
7
|
+
class PhysicalStage < Stage
|
8
8
|
|
9
9
|
attr_accessor :space
|
10
10
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
@sound_manager = sound_manager
|
18
|
-
@input_manager = input_manager
|
19
|
-
@viewport = viewport
|
20
|
-
@opts = opts
|
21
|
-
|
11
|
+
def setup
|
12
|
+
super
|
13
|
+
setup_space
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup_space
|
22
17
|
@space = Space.new
|
23
18
|
@space.iterations = 20
|
24
|
-
@space.elastic_iterations =
|
19
|
+
@space.elastic_iterations = 5
|
20
|
+
end
|
25
21
|
|
26
|
-
|
22
|
+
def create_director
|
23
|
+
PhysicalDirector.new
|
27
24
|
end
|
28
25
|
|
29
26
|
PHYSICS_STEP = 25.0
|
@@ -87,4 +84,17 @@ class PhysicalLevel < Level
|
|
87
84
|
end
|
88
85
|
end
|
89
86
|
|
87
|
+
# Find any / all objects who's bounding box currently contains
|
88
|
+
# the passed in screen position. Requires a block as this sets
|
89
|
+
# a callback all the way down in Chipmunk and could be called
|
90
|
+
# later in the future.
|
91
|
+
#
|
92
|
+
# This block is called on each actor found
|
93
|
+
def pick(x, y, &block)
|
94
|
+
@space.shape_point_query(vec2(x, y)) do |found|
|
95
|
+
actor = @director.find_physical_obj(found)
|
96
|
+
block.call(actor)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
90
100
|
end
|