gosu_android 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/LICENSE +19 -0
  2. data/README.md +35 -0
  3. data/bin/gosu_android +11 -0
  4. data/examples/arkanoid.rb +105 -0
  5. data/examples/pong_activity.rb +99 -0
  6. data/examples/test-game.rb +114 -0
  7. data/lib/gosu.java.jar +0 -0
  8. data/lib/gosu_android.rb +1 -0
  9. data/lib/gosu_android/audio/audio.rb +159 -0
  10. data/lib/gosu_android/commands/base.rb +200 -0
  11. data/lib/gosu_android/description.rb +5 -0
  12. data/lib/gosu_android/graphics/bitmap.rb +12 -0
  13. data/lib/gosu_android/graphics/bitmapUtils.rb +51 -0
  14. data/lib/gosu_android/graphics/blockAllocator.rb +107 -0
  15. data/lib/gosu_android/graphics/color.rb +27 -0
  16. data/lib/gosu_android/graphics/common.rb +21 -0
  17. data/lib/gosu_android/graphics/drawOp.rb +6 -0
  18. data/lib/gosu_android/graphics/drawOpQueue.rb +39 -0
  19. data/lib/gosu_android/graphics/font.rb +61 -0
  20. data/lib/gosu_android/graphics/graphics.rb +227 -0
  21. data/lib/gosu_android/graphics/graphicsBase.rb +27 -0
  22. data/lib/gosu_android/graphics/image.rb +151 -0
  23. data/lib/gosu_android/graphics/imageData.rb +23 -0
  24. data/lib/gosu_android/graphics/largeImageData.rb +116 -0
  25. data/lib/gosu_android/graphics/renderState.rb +5 -0
  26. data/lib/gosu_android/graphics/texChunk.rb +68 -0
  27. data/lib/gosu_android/graphics/texture.rb +86 -0
  28. data/lib/gosu_android/input/buttons.rb +128 -0
  29. data/lib/gosu_android/input/input.rb +120 -0
  30. data/lib/gosu_android/main-window.rb +314 -0
  31. data/lib/gosu_android/math.rb +21 -0
  32. data/lib/gosu_android/physics/physicsManager.rb +57 -0
  33. data/lib/gosu_android/physics/physicsObject.rb +113 -0
  34. data/lib/gosu_android/requires.rb +40 -0
  35. data/lib/gosu_android/timing.rb +8 -0
  36. data/lib/gosu_android/version.rb +3 -0
  37. data/res/drawable-nodpi/ball.png +0 -0
  38. data/res/drawable-nodpi/bar.png +0 -0
  39. data/res/drawable-nodpi/bar_hor.png +0 -0
  40. data/res/drawable-nodpi/character_atlas8.png +0 -0
  41. data/res/drawable-nodpi/ship.png +0 -0
  42. data/res/drawable-nodpi/space.png +0 -0
  43. data/res/drawable-nodpi/star.png +0 -0
  44. data/res/raw/beep.wav +0 -0
  45. data/res/raw/chriss_onac_tempo_red.mp3 +0 -0
  46. metadata +127 -0
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Garoe Dorta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ Gosu-Android
2
+ ============
3
+ A Gosu implementation for Adroid devices.
4
+
5
+ Installation
6
+ -----------
7
+ Sadly right now you need to manually copy every file to every ruboto proyect you want to use it.
8
+
9
+ - Download the sources
10
+ - Copy the `lib` folder and the `gosu.rb` to the `proyect_name/src/` folder inside your [ruboto](http://github.com/ruboto/ruboto) proyect.
11
+ - Copy the `gosu.java.jar` in the `proyect_name/libs/` folder.
12
+ - Place the `res` files in the `proyect_name/res` folder.
13
+ - It is very important that you do not copy the `java` files in your proyect folder, they are included for developers only.
14
+
15
+
16
+ General Information
17
+ -------------------
18
+ * This is still an early effort, so there are a number of features that had not yet been added.
19
+ * There are some known bugs that I hope to fix soon.
20
+ * In its current status there are some small changes to Gosu Window initialization, check examples.
21
+ * A new object with some basic physics has been added.
22
+
23
+ Troubleshooting
24
+ -------------------
25
+ * If you're using Ruboto 0.10.4 or earlier, you may get an error when trying to require the gosu libraries: `(SystemStackError) stack level too deep` in `require 'gosu'`. If this happens:
26
+ * Replace `require 'gosu'` with `with_large_stack { require 'gosu' }`. If it still doesn't work:
27
+ * Try `with_large_stack(256)
28
+ require 'gosu'
29
+ end`. If it still doesn't work, try again with `512` instead of `256`.
30
+ * Alternatively, update to the latest Ruboto (0.11 or better).
31
+ * Relevant Ruboto issues:
32
+ * https://github.com/ruboto/ruboto/issues/359
33
+ * https://github.com/ruboto/ruboto/issues/375
34
+ * When using several audio files double check that all have the same codification or you could get: `E/MediaPlayer(16127): Unable to to create media player`
35
+ * http://forums.pragprog.com/forums/152/topics/9144
data/bin/gosu_android ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'gosu_android/commands/base'
5
+ rescue RuntimeError
6
+ puts $!.message
7
+ exit 1
8
+ end
9
+
10
+ # Run Base, which will handle actual commands
11
+ Gosu::Commands::Base.main
@@ -0,0 +1,105 @@
1
+ require 'gosu_android'
2
+
3
+ class GameWindow < Gosu::Window
4
+ def initialize
5
+ super 600, 480, false, 30
6
+ self.caption = "Gosu Arkanoid"
7
+ self.physics_manager.gravity_y = 0
8
+ @score = 0
9
+ @song = Gosu::Song.new(self, Ruboto::R::raw::chriss_onac_tempo_red)
10
+ @beep = Gosu::Sample.new(self, Ruboto::R::raw::beep)
11
+ @p1x = 0
12
+
13
+ @ball = Gosu::Square.new(self, Ruboto::R::drawable::ball, 100, 200, 0, 50, 20, 100, 100)
14
+
15
+ @player_x = 300
16
+ @player_y = 473
17
+ @size = 110
18
+ # 25 is to compesate square size of the ball
19
+ @size2 = @size/2 - 25
20
+ @player = Gosu::Plane.new(self,Ruboto::R::drawable::bar_hor, [@player_x, @player_y], [@player_x + @size, @player_y], 0)
21
+
22
+ #Left plane
23
+ @p1 = Gosu::Plane.new(self, Ruboto::R::drawable::bar, [0, 0], [0, 480], 0)
24
+ #Top plane
25
+ @p2 = Gosu::Plane.new(self, Ruboto::R::drawable::bar, [600, 0], [0, 0], 0)
26
+ #Right plane
27
+ @p3 = Gosu::Plane.new(self,Ruboto::R::drawable::bar, [600, 480], [600, 0], 0)
28
+
29
+ @blocks = []
30
+ @blocks_position = []
31
+ block_x = 150
32
+ block_y = 120
33
+ img = Ruboto::R::drawable::bar_hor
34
+ 2.times do |i|
35
+ 3.times do |j|
36
+ @blocks.push Gosu::Plane.new(self, img, [block_x + (@size + 30)*i , block_y + 30*j ], [block_x + (@size + 30)*(i + 1), block_y + 30*j ], 0)
37
+ @blocks_position.push [block_x + (@size + 30)*i, block_y + 30*j]
38
+ end
39
+ end
40
+
41
+ 6.times do |i|
42
+ self.apply_physics @blocks[i]
43
+ end
44
+
45
+ self.apply_physics @ball
46
+ self.apply_physics @player
47
+ self.apply_physics @p1
48
+ self.apply_physics @p2
49
+ self.apply_physics @p3
50
+ @font = Gosu::Font.new(self, Gosu::default_font_name, 20)
51
+ @song.play true
52
+ end
53
+
54
+ def update
55
+ if @ball.center[1] > 480
56
+ @ball.position[1] = 200
57
+ @ball.velocity[1] = -@ball.velocity[0]
58
+ @beep.play
59
+ end
60
+ end
61
+
62
+ def object_collided( x, y, other_object )
63
+ if(@blocks.include? other_object )
64
+ @score += 1
65
+ self.stop_physics other_object
66
+ @blocks_position.delete_at(@blocks.index other_object)
67
+ @blocks.delete other_object
68
+ end
69
+ @beep.play
70
+ end
71
+
72
+ def touch_moved(touch)
73
+ touch.y = touch.y - @size2
74
+ @player_x = touch.x
75
+ @player.bottom_limit[0] = @player_x
76
+ @player.top_limit[0] = @player_x + @size
77
+ end
78
+
79
+ def draw
80
+ @blocks.each_index do |i|
81
+ @blocks[i].draw(@blocks_position[i][0], @blocks_position[i][1], 0)
82
+ end
83
+
84
+ @ball.draw
85
+ @player.draw(@player_x, @player_y, 0)
86
+ @font.draw("Score: #{@score}", 10, 10, 3, 1.0, 1.0, 0xffffff00)
87
+ end
88
+
89
+ end
90
+
91
+ class GosuActivity
92
+ def on_create(bundle)
93
+ super(bundle)
94
+ Gosu::AndroidInitializer.instance.start(self)
95
+ rescue Exception => e
96
+ puts "#{ e } (#{ e.class } #{e.message} #{e.backtrace.inspect} )!"
97
+ end
98
+
99
+ def on_ready
100
+ window = GameWindow.new
101
+ window.show
102
+ rescue Exception => e
103
+ puts "#{ e } (#{ e.class } #{e.message} #{e.backtrace.inspect} )!"
104
+ end
105
+ end
@@ -0,0 +1,99 @@
1
+ require 'gosu_android'
2
+
3
+ class GameWindow < Gosu::Window
4
+ def initialize
5
+ super 600, 480, false, 50
6
+ self.caption = "Gosu Pong Game"
7
+ self.physics_manager.gravity_y = 0
8
+ @p1score = 0
9
+ @p2score = 0
10
+ @song = Gosu::Song.new(self, Ruboto::R::raw::chriss_onac_tempo_red)
11
+ @beep = Gosu::Sample.new(self, Ruboto::R::raw::beep)
12
+ @p1x = 0
13
+ # 25 is to compesate square size of the ball
14
+ @p1y = 250 + 25
15
+ @p3x = 593
16
+ @p3y = 100 + 25
17
+ @size = 110
18
+ @size2 = @size/2 - 25
19
+ @squ = Gosu::Square.new(self, Ruboto::R::drawable::ball, 100, 200, 0, 50, 20, 100, 100)
20
+ #Left plane
21
+ @p1 = Gosu::Plane.new(self, Ruboto::R::drawable::bar, [@p1x,@p1y], [@p1x, @p1y + @size] ,0)
22
+ #Top plane
23
+ @p2 = Gosu::Plane.new(self, Ruboto::R::drawable::bar, [600,0], [0,0], 0 )
24
+ #Right plane
25
+ @p3 = Gosu::Plane.new(self,Ruboto::R::drawable::bar, [@p3x,@p3y + @size], [@p3x,@p3y], 0)
26
+ #Bottom plane
27
+ @p4 = Gosu::Plane.new(self,Ruboto::R::drawable::bar, [0,480], [600,480], 0)
28
+ self.apply_physics @squ
29
+ self.apply_physics @p1
30
+ self.apply_physics @p2
31
+ self.apply_physics @p3
32
+ self.apply_physics @p4
33
+ @font = Gosu::Font.new(self, Gosu::default_font_name, 20)
34
+ @song.play true
35
+ end
36
+
37
+ def update
38
+ if @squ.center[0] < 0
39
+ #Player 1 lost
40
+ @p1score += 1
41
+ #Reset the ball
42
+ @squ.position[0] = 300
43
+ @squ.velocity[0] = -@squ.velocity[0]
44
+ @beep.play
45
+ else
46
+ if @squ.center[0] > 600
47
+ #Player2 lost
48
+ @p2score += 1
49
+ #Reset the ball
50
+ @squ.position[0] = 300
51
+ @squ.velocity[0] = -@squ.velocity[0]
52
+ @beep.play
53
+ end
54
+ end
55
+ end
56
+
57
+ def object_collided( x, y, other_object )
58
+ @beep.play
59
+ end
60
+
61
+ def touch_moved(touch)
62
+ touch.y = touch.y - @size2
63
+ if touch.x < 300
64
+ #Player1
65
+ @p1y = touch.y
66
+ @p1.bottom_limit[1] = @p1y + @size
67
+ @p1.top_limit[1] = @p1y
68
+ else
69
+ #Player2
70
+ @p3y = touch.y
71
+ @p3.bottom_limit[1] = @p3y + @size
72
+ @p3.top_limit[1] = @p3y
73
+ end
74
+ end
75
+
76
+ def draw
77
+ @squ.draw
78
+ @p1.draw(@p1x,@p1y,0)
79
+ @p3.draw(@p3x,@p3y,0)
80
+ @font.draw("Score: A #{@p1score} B #{@p2score} ", 10, 10, 3, 1.0, 1.0, 0xffffff00)
81
+ end
82
+
83
+ end
84
+
85
+ class GosuActivity
86
+ def on_create(bundle)
87
+ super(bundle)
88
+ Gosu::AndroidInitializer.instance.start(self)
89
+ rescue Exception => e
90
+ puts "#{ e } (#{ e.class } #{e.message} #{e.backtrace.inspect} )!"
91
+ end
92
+
93
+ def on_ready
94
+ window = GameWindow.new
95
+ window.show
96
+ rescue Exception => e
97
+ puts "#{ e } (#{ e.class } #{e.message} #{e.backtrace.inspect} )!"
98
+ end
99
+ end
@@ -0,0 +1,114 @@
1
+ require 'gosu_android'
2
+
3
+ module ZOrder
4
+ Background, Stars, Player, UI = *0..3
5
+ end
6
+
7
+ class Player
8
+ attr_reader :score
9
+
10
+ def initialize(window)
11
+ @image = Gosu::Image.new(window, "/mnt/sdcard/jruby/media/Starfighter.bmp", false)
12
+ @beep = Gosu::Sample.new(window, "/mnt/sdcard/jruby/media/Beep.wav")
13
+ @x = @y = @vel_x = @vel_y = @angle = 0.0
14
+ @score = 0
15
+ end
16
+
17
+ def warp(x, y)
18
+ @x, @y = x, y
19
+ end
20
+
21
+ def draw
22
+ @image.draw_rot(@x, @y, ZOrder::Player, @angle)
23
+ end
24
+
25
+ def collect_stars(stars)
26
+ stars.reject! do |star|
27
+ if Gosu::distance(@x, @y, star.x, star.y) < 35 then
28
+ @score += 10
29
+ @beep.play
30
+ true
31
+ else
32
+ false
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ class Star
39
+ attr_reader :x, :y
40
+
41
+ def initialize(animation)
42
+ @animation = animation
43
+ @color = Gosu::Color.new(0xff000000)
44
+ @color.red = rand(256 - 40) + 40
45
+ @color.green = rand(256 - 40) + 40
46
+ @color.blue = rand(256 - 40) + 40
47
+ @x = rand * 640
48
+ @y = rand * 480
49
+ end
50
+
51
+ def draw
52
+ img = @animation[Gosu::milliseconds / 100 % @animation.size]
53
+ img.draw(@x - img.width / 2.0, @y - img.height / 2.0,
54
+ ZOrder::Stars, 1, 1, @color, Gosu::AM_ADD)
55
+ end
56
+ end
57
+
58
+ class GameWindow < Gosu::Window
59
+ def initialize
60
+ super(640, 480, false)
61
+ self.caption = "Gosu Tutorial Game"
62
+
63
+ #@background_image = Gosu::Image.new(self, "media/Space.png", true)
64
+
65
+ @player = Player.new(self)
66
+ @player.warp(320, 240)
67
+
68
+ @star_anim = Gosu::Image::load_tiles(self, "/mnt/sdcard/jruby/media/Star.png", 25, 25, false)
69
+ @stars = Array.new
70
+
71
+ #@font = Gosu::Font.new(self, Gosu::default_font_name, 20)
72
+ end
73
+
74
+ def update
75
+ @player.collect_stars(@stars)
76
+ #Normally 25 stars
77
+ if rand(100) < 4 and @stars.size < 5 then
78
+ @stars.push(Star.new(@star_anim))
79
+ end
80
+ end
81
+
82
+ def touch_moved(touch)
83
+ @player.warp(touch.x, touch.y)
84
+ end
85
+
86
+ def draw
87
+ #@background_image.draw(0, 0, ZOrder::Background)
88
+ @player.draw
89
+ @stars.each { |star| star.draw }
90
+ #@font.draw("Score: #{@player.score}", 10, 10, ZOrder::UI, 1.0, 1.0, 0xffffff00)
91
+ end
92
+
93
+ def button_down(id)
94
+ if id == Gosu::KbEscape then
95
+ close
96
+ end
97
+ end
98
+ end
99
+
100
+ class TestGameActivity
101
+ def on_create(bundle)
102
+ super(bundle)
103
+ Gosu::AndroidInitializer.instance.start(self)
104
+ rescue Exception => e
105
+ puts "#{ e } (#{ e.class } #{e.message} #{e.backtrace.inspect} )!"
106
+ end
107
+
108
+ def on_ready
109
+ window = GameWindow.new
110
+ window.show
111
+ rescue Exception => e
112
+ puts "#{ e } (#{ e.class } #{e.message} #{e.backtrace.inspect} )!"
113
+ end
114
+ end
data/lib/gosu.java.jar ADDED
Binary file
@@ -0,0 +1 @@
1
+ require 'gosu_android/main-window'
@@ -0,0 +1,159 @@
1
+ require 'gosu_android/requires'
2
+
3
+ module Gosu
4
+ MAX_SAMPLES = 10
5
+ class SampleInstance
6
+ def initialize
7
+
8
+ end
9
+ end
10
+
11
+ class AudioFocusListener
12
+ def onAudioFocusChange focusChange
13
+ puts "In focus change #{focusChange}"
14
+ end
15
+
16
+ def toString
17
+ self.class.to_s
18
+ end
19
+ end
20
+
21
+ #TODO ManageAudioFocus, when app loses, stop song
22
+ #TODO Raise a warning is the file could not be loaded
23
+ class Sample
24
+
25
+ #Constructs a sample that can be played on the specified audio
26
+ #system and loads the sample from a file.
27
+ def initialize(window, filename)
28
+ @window = window
29
+ #Set finalize
30
+ ObjectSpace.define_finalizer(self, self.class.method(:finalize).to_proc)
31
+ if not defined? @@pool
32
+ @@pool = JavaImports::SoundPool.new(MAX_SAMPLES, JavaImports::AudioManager::STREAM_MUSIC, 0)
33
+ end
34
+
35
+ if(filename.class == Fixnum )
36
+ @id = @@pool.load( @window.activity.getApplicationContext, filename, 1 )
37
+ else
38
+ @id = @@pool.load(filename, 1)
39
+ end
40
+ end
41
+
42
+ #Plays the sample without panning.
43
+ #\param volume Can be anything from 0.0 (silence) to 1.0 (full
44
+ #volume).
45
+ #\param speed Playback speed is only limited by the underlying audio library,
46
+ #and can accept very high or low values. Use 1.0 for
47
+ #normal playback speed.
48
+ def play(volume = 1, speed = 1, looping = false)
49
+ if looping == false
50
+ @stream_id = @@pool.play(@id, volume, volume, 1, 0, 1.0)
51
+ else
52
+ @stream_id = @@pool.play(@id, volume, volume, 1, 1, 1.0)
53
+ end
54
+ end
55
+
56
+ #TODO Pan is not supported so it is ignored
57
+ def play_pan(pan = 0, volume = 1, speed = 1, looping = false)
58
+ if looping == false
59
+ @stream_id = @@pool.play(@id, volume, volume, 1, 0, 1.0)
60
+ else
61
+ @stream_id = @@pool.play(@id, volume, volume, 1, 1, 1.0)
62
+ end
63
+ end
64
+
65
+ def Sample.finalize(id)
66
+ @@pool.unload(@id)
67
+ end
68
+
69
+ end
70
+
71
+ #TODO Error on playing several songs, bear in mind mediaplayer states
72
+ #FIXME Set listener for when the data finished loading asynchronously
73
+ # add some checks play is reached before that
74
+ class Song
75
+ attr_reader :current_song
76
+ def initialize(window, filename)
77
+ @window = window
78
+ if not defined? @@media_player
79
+ @@media_player = JavaImports::MediaPlayer.new
80
+ @@audio_focus_listener = AudioFocusListener.new
81
+ context = @window.activity.getApplicationContext
82
+ @@audio_manager = context.getSystemService(Context::AUDIO_SERVICE)
83
+ focus = @@audio_manager.requestAudioFocus(@@audio_focus_listener, JavaImports::AudioManager::STREAM_MUSIC, JavaImports::AudioManager::AUDIOFOCUS_GAIN)
84
+ else
85
+ @@media_player.reset
86
+ focus = @@audio_manager.requestAudioFocus(@@audio_focus_listener, JavaImports::AudioManager::STREAM_MUSIC, JavaImports::AudioManager::AUDIOFOCUS_GAIN)
87
+ end
88
+
89
+ if filename.class == Fixnum
90
+ afd = @window.activity.getApplicationContext.getResources.openRawResourceFd(filename)
91
+ filename = afd.getFileDescriptor
92
+ end
93
+
94
+ @@media_player.on_prepared_listener = (proc{media_player_ready})
95
+ @@media_player.setDataSource filename
96
+ @@media_player.prepareAsync
97
+ @player_ready = false
98
+ @window.media_player = @@media_player
99
+ @playing = false
100
+ @file_name = filename
101
+
102
+ if not defined? @@current_song
103
+ @@current_song = 0
104
+ end
105
+ end
106
+
107
+ def media_player_ready
108
+ @player_ready = true
109
+ #Song should be playing but media player was not ready
110
+ #so start playing now
111
+ if @playing
112
+ @@media_player.start
113
+ end
114
+ end
115
+
116
+ #Starts or resumes playback of the song. This will stop all other
117
+ #songs and set the current song to this object.
118
+ def play(looping = false)
119
+ @@media_player.setLooping(looping)
120
+ if @player_ready
121
+ @@media_player.start
122
+ end
123
+ @@current_song = @file_name
124
+ @playing = true
125
+ end
126
+
127
+ #Pauses playback of the song. It is not considered being played.
128
+ #currentSong will stay the same.
129
+ def pause
130
+ if @player_ready
131
+ @@media_player.pause
132
+ end
133
+ @playing = false
134
+ end
135
+
136
+ #Returns true if the song is the current song, but in paused
137
+ #mode.
138
+ def paused?
139
+ not @playing and @@current_song == @file_name
140
+ end
141
+
142
+ #Returns true if the song is currently playing.
143
+ def playing?
144
+ @playing and @@current_song == @file_name
145
+ end
146
+
147
+ #Stops playback of this song if it is currently played or paused.
148
+ #Afterwards, current_song will return 0.
149
+ def stop
150
+ if @player_ready
151
+ @@media_player.pause
152
+ @@media_player.stop
153
+ end
154
+ @@current_song = 0
155
+ end
156
+
157
+ end
158
+
159
+ end