monkeymusic 0.0.4 → 0.0.5
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 +8 -8
- data/bin/monkeymusic-generate +2 -2
- data/levels/bomberman2.rb +13 -15
- data/levels/demolevel.rb +23 -0
- data/levels/testlevel.rb +7 -6
- data/lib/monkey_music/game.rb +14 -10
- data/lib/monkey_music/level.rb +6 -3
- data/lib/monkey_music/level_loader.rb +5 -4
- data/lib/monkey_music/metadata/album.rb +15 -0
- data/lib/monkey_music/metadata/artist.rb +13 -0
- data/lib/monkey_music/metadata/track.rb +22 -0
- data/lib/monkey_music/player.rb +88 -20
- data/lib/monkey_music/runner.rb +8 -8
- data/lib/monkey_music/ui/{browser_ui.rb → browser.rb} +6 -7
- data/lib/monkey_music/ui/{console_ui.rb → console.rb} +5 -4
- data/lib/monkey_music/units/track.rb +17 -7
- data/lib/monkey_music/units/user.rb +3 -5
- data/lib/monkey_music.rb +6 -2
- data/lib/monkey_music_generate/recommendation_loader.rb +25 -64
- data/lib/monkey_music_generate/runner.rb +100 -0
- data/lib/monkey_music_generate/score_system.rb +24 -9
- data/lib/monkey_music_generate/toplist_loader.rb +41 -25
- data/lib/monkey_music_generate.rb +1 -1
- data/users/synth.rb +8 -4
- data/users/synth.yaml +846 -1335
- metadata +9 -5
- data/lib/monkey_music_generate/generator.rb +0 -98
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MTBjY2IwY2ZiYTBkOGU3ZWExYzM1NmRiZjU3OGE4N2NkMTY4Y2FjYw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MDZiMDI1YjFmMmRmOWZmYjI3NDlhNzNjNjlhZWFkZTk3ZDM4ZGIxNQ==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZGIzYmE3NDExNGFjYzUxMmZlNzAzMTJkNDk3NjA2OTk2ZTlkNWFjZDk1OWY4
|
10
|
+
MmY4NTgxMTBlOTI2ZTIwNmUyYjk5YjE0NzIzYWViMzJiN2FjMjZjYmY3Yzhh
|
11
|
+
OTE0NDg1ZjEwMGJjMWYxY2VjYTE3OWExMDIwYjBhYjNhNzA0Yjg=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MDZiMTRlOGE3MWE4NGJjNGI2YWNlOTcyODM5ZDM0YjM5NzBhZmJiOWZlNWNh
|
14
|
+
ZmQ4YWVkZDI2OGE4YmNmNjM3OWM1MDE3ZjBiMmJmYTk1N2RjNWE0MTgyYjJl
|
15
|
+
ZWVmNjJlMGEyMTRlMGMwYjNkM2ZiNTMzMmI0NjE3OGJmODQxZGI=
|
data/bin/monkeymusic-generate
CHANGED
data/levels/bomberman2.rb
CHANGED
@@ -1,20 +1,18 @@
|
|
1
1
|
carrying_capacity 5
|
2
|
-
|
3
|
-
|
2
|
+
turn_limit 500
|
3
|
+
time_limit 20000
|
4
4
|
|
5
|
-
legend(
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
"#" => Wall,
|
15
|
-
})
|
5
|
+
legend "1" => Monkey.player(1),
|
6
|
+
"2" => Monkey.player(2),
|
7
|
+
"X" => Track.worth(-2),
|
8
|
+
"x" => Track.worth(-1),
|
9
|
+
"l" => Track.worth(1),
|
10
|
+
"t" => Track.worth(2),
|
11
|
+
"T" => Track.worth(3),
|
12
|
+
"U" => User,
|
13
|
+
"#" => Wall
|
16
14
|
|
17
|
-
layout <<
|
15
|
+
layout <<EOS
|
18
16
|
Xl 1 U 2 lX
|
19
17
|
t ll ll t
|
20
18
|
###t ########### t###
|
@@ -28,4 +26,4 @@ t ll ll t
|
|
28
26
|
### # # # # # # # ###
|
29
27
|
#t # # # t#
|
30
28
|
#l###l#t#t#t#t#l###l#
|
31
|
-
|
29
|
+
EOS
|
data/levels/demolevel.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
carrying_capacity 3
|
2
|
+
metadata_requests_per_turn 2
|
3
|
+
max_turns 100
|
4
|
+
|
5
|
+
legend({
|
6
|
+
"1" => Monkey.player(1),
|
7
|
+
"2" => Monkey.player(2),
|
8
|
+
"X" => Track.worth(-2),
|
9
|
+
"x" => Track.worth(-1),
|
10
|
+
"l" => Track.worth(1),
|
11
|
+
"t" => Track.worth(2),
|
12
|
+
"T" => Track.worth(3),
|
13
|
+
"U" => User,
|
14
|
+
"#" => Wall,
|
15
|
+
})
|
16
|
+
|
17
|
+
layout <<LAYOUT
|
18
|
+
....t..t...
|
19
|
+
.#.......#.
|
20
|
+
#..#1U2#..#
|
21
|
+
.#X.....X#.
|
22
|
+
.....t.....
|
23
|
+
LAYOUT
|
data/levels/testlevel.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
carrying_capacity 3
|
2
|
-
|
3
|
-
|
2
|
+
turn_limit 100
|
3
|
+
time_limit 300
|
4
4
|
|
5
5
|
legend({
|
6
6
|
"1" => Monkey.player(1),
|
@@ -11,18 +11,19 @@ legend({
|
|
11
11
|
"t" => Track.worth(2),
|
12
12
|
"T" => Track.worth(3),
|
13
13
|
"U" => User,
|
14
|
+
"W" => Wall,
|
14
15
|
})
|
15
16
|
|
16
17
|
layout <<LAYOUT
|
17
18
|
...........
|
18
|
-
|
19
|
-
|
19
|
+
.WW.....WW.
|
20
|
+
.Wx.....xW.
|
20
21
|
.......T...
|
21
22
|
1..........
|
22
23
|
...t......U
|
23
24
|
2..........
|
24
25
|
.......l...
|
25
|
-
|
26
|
-
|
26
|
+
.WX.....xW.
|
27
|
+
.WW.....WW.
|
27
28
|
...........
|
28
29
|
LAYOUT
|
data/lib/monkey_music/game.rb
CHANGED
@@ -12,24 +12,28 @@ module MonkeyMusic
|
|
12
12
|
|
13
13
|
def start
|
14
14
|
@ui.update(@level)
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
# Send initial state to players
|
16
|
+
init_threads = []
|
17
|
+
@players.each do |p|
|
18
|
+
init_threads << Thread.new { p.init! }
|
19
|
+
end
|
20
|
+
init_threads.each(&:join)
|
21
|
+
# Start the game
|
22
|
+
@level.turn_limit.times do |turn|
|
23
|
+
turn += 1
|
24
|
+
break if @level.complete?
|
19
25
|
# Query players for moves
|
20
26
|
query_threads = []
|
21
|
-
|
27
|
+
turn_time = Benchmark.realtime do
|
22
28
|
@players.each do |p|
|
23
|
-
query_threads << Thread.new
|
24
|
-
p.query_move!
|
25
|
-
end
|
29
|
+
query_threads << Thread.new { p.query! turn }
|
26
30
|
end
|
27
31
|
query_threads.each(&:join)
|
28
32
|
end
|
29
33
|
# Move players in random order
|
30
34
|
@players.shuffle.each { |p| p.move! }
|
31
|
-
|
32
|
-
@ui.update(@level,
|
35
|
+
## Update ui
|
36
|
+
@ui.update(@level, turn, turn_time)
|
33
37
|
end
|
34
38
|
end
|
35
39
|
|
data/lib/monkey_music/level.rb
CHANGED
@@ -2,9 +2,8 @@ require 'json'
|
|
2
2
|
|
3
3
|
module MonkeyMusic
|
4
4
|
class Level
|
5
|
-
attr_accessor :width, :height, :
|
6
|
-
attr_reader :players
|
7
|
-
attr_reader :user
|
5
|
+
attr_accessor :width, :height, :turn_limit, :time_limit
|
6
|
+
attr_reader :players, :user, :units
|
8
7
|
|
9
8
|
def initialize(players, user)
|
10
9
|
@players = players
|
@@ -33,6 +32,10 @@ module MonkeyMusic
|
|
33
32
|
(@players.detect { |p| p.monkey.carrying.count > 0 }).nil?
|
34
33
|
end
|
35
34
|
|
35
|
+
def tracks
|
36
|
+
@units.select {|u| u.kind_of? Track }
|
37
|
+
end
|
38
|
+
|
36
39
|
def remove(unit)
|
37
40
|
@units.reject! { |u| u == unit }
|
38
41
|
end
|
@@ -8,12 +8,13 @@ module MonkeyMusic
|
|
8
8
|
@legend = {}
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
12
|
-
@level.
|
11
|
+
def turn_limit(turn_limit)
|
12
|
+
@level.turn_limit = turn_limit
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
16
|
-
@level.
|
15
|
+
def time_limit(time_limit)
|
16
|
+
@level.time_limit = time_limit
|
17
|
+
@level.players.each {|p| p.remaining_time = time_limit }
|
17
18
|
end
|
18
19
|
|
19
20
|
def carrying_capacity(capacity)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module MonkeyMusic::Metadata
|
2
|
+
class Album
|
3
|
+
attr_reader :name, :artist, :year
|
4
|
+
|
5
|
+
def initialize(args = {})
|
6
|
+
@name = args[:name]
|
7
|
+
@artist = args[:artist]
|
8
|
+
@year = args[:year]
|
9
|
+
end
|
10
|
+
|
11
|
+
def serialize
|
12
|
+
"#{@name},#{@artist},#{@year}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module MonkeyMusic::Metadata
|
2
|
+
class Track
|
3
|
+
attr_reader :uri, :name, :artist, :album, :year, :multiplier, :value
|
4
|
+
|
5
|
+
def initialize(args = {})
|
6
|
+
@name = args[:name]
|
7
|
+
@artist = args[:artist]
|
8
|
+
@album = args[:album]
|
9
|
+
@year = args[:year]
|
10
|
+
@uri = args[:uri]
|
11
|
+
end
|
12
|
+
|
13
|
+
def set_value(multiplier, value)
|
14
|
+
@multiplier = multiplier
|
15
|
+
@value = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def serialize
|
19
|
+
"#{@name},#{@artist},#{@album},#{@year}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/monkey_music/player.rb
CHANGED
@@ -1,42 +1,110 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
|
1
3
|
module MonkeyMusic
|
2
4
|
class Player
|
3
|
-
attr_accessor :monkey, :level
|
5
|
+
attr_accessor :monkey, :level, :has_boost, :remaining_time
|
4
6
|
|
5
7
|
def initialize(file)
|
6
8
|
@file = file
|
7
|
-
@monkey = Monkey.new
|
9
|
+
@monkey = Monkey.new
|
10
|
+
@has_boost = true
|
11
|
+
@penalty = 0
|
12
|
+
@moves = []
|
13
|
+
@queries = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def init!
|
17
|
+
IO.popen(@file, "r+") {|io| io.puts initial_output }
|
8
18
|
end
|
9
19
|
|
10
|
-
def
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
20
|
+
def query!(turn)
|
21
|
+
if @penalty > 0
|
22
|
+
@penalty -= 1
|
23
|
+
@remaining_time = @monkey.level.time_limit if @penalty < 1
|
24
|
+
else
|
25
|
+
IO.popen(@file, "r+") do |io|
|
26
|
+
io.puts turn_output(turn)
|
27
|
+
@remaining_time -= (Benchmark.realtime { @input = io.gets } * 1000).round
|
28
|
+
parse!(@input) if @input
|
29
|
+
io.puts response_to(@queries) unless @queries.empty?
|
30
|
+
@queries = []
|
31
|
+
end
|
32
|
+
@penalty = 5 if @remaining_time < 0
|
15
33
|
end
|
16
34
|
end
|
17
35
|
|
18
|
-
def
|
19
|
-
|
36
|
+
def initial_output
|
37
|
+
level = @monkey.level
|
38
|
+
user = @monkey.level.user
|
39
|
+
[ "INIT",
|
40
|
+
@monkey.id,
|
41
|
+
level.width,
|
42
|
+
level.height,
|
43
|
+
level.turn_limit,
|
44
|
+
user.toplists[:top_tracks].length,
|
45
|
+
user.toplists[:top_tracks].map(&:serialize).join("\n"),
|
46
|
+
user.toplists[:top_albums].length,
|
47
|
+
user.toplists[:top_albums].map(&:serialize).join("\n"),
|
48
|
+
user.toplists[:top_artists].length,
|
49
|
+
user.toplists[:top_artists].map(&:serialize).join("\n"),
|
50
|
+
user.toplists[:disliked_artists].length,
|
51
|
+
user.toplists[:disliked_artists].map(&:serialize).join("\n"),
|
52
|
+
].join("\n")
|
53
|
+
end
|
54
|
+
|
55
|
+
def turn_output(turn)
|
56
|
+
[ "TURN",
|
57
|
+
turn,
|
20
58
|
@monkey.remaining_capacity,
|
21
|
-
@
|
22
|
-
@monkey.level.width,
|
23
|
-
@monkey.level.height,
|
59
|
+
@remaining_time,
|
24
60
|
@monkey.level.serialize,
|
25
61
|
].join("\n")
|
26
62
|
end
|
27
63
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
64
|
+
def parse!(s)
|
65
|
+
@tokens = s.chomp.split(",")
|
66
|
+
parse_next_token!
|
67
|
+
end
|
68
|
+
|
69
|
+
def parse_next_token!
|
70
|
+
token = @tokens.shift
|
71
|
+
if /^[NWES]$/.match(token) then @moves << parse_move(token)
|
72
|
+
elsif /^spotify:track:/.match(token) then @queries << token
|
73
|
+
elsif "B" == token then boost!
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def boost!
|
78
|
+
return unless @has_boost
|
79
|
+
@has_boost = false
|
80
|
+
[3, @tokens.length].min.times { parse_next_token! }
|
81
|
+
end
|
82
|
+
|
83
|
+
def response_to(queries)
|
84
|
+
tracks = []
|
85
|
+
queries.each do |uri|
|
86
|
+
tracks << @monkey.level.tracks.find {|t| t.uri == uri }
|
87
|
+
end
|
88
|
+
lines = []
|
89
|
+
lines << tracks.length
|
90
|
+
tracks.each do |track|
|
91
|
+
lines << "#{track.uri},#{track.metadata.serialize}"
|
92
|
+
end
|
93
|
+
lines.join("\n")
|
94
|
+
end
|
95
|
+
|
96
|
+
def parse_move(move)
|
97
|
+
case move
|
98
|
+
when "N" then :north
|
99
|
+
when "W" then :west
|
100
|
+
when "E" then :east
|
101
|
+
when "S" then :south
|
34
102
|
end
|
35
103
|
end
|
36
104
|
|
37
105
|
def move!
|
38
|
-
@monkey.move!
|
39
|
-
@
|
106
|
+
@moves.each {|move| @monkey.move! move }
|
107
|
+
@moves = []
|
40
108
|
end
|
41
109
|
|
42
110
|
def to_s
|
data/lib/monkey_music/runner.rb
CHANGED
@@ -3,7 +3,7 @@ require 'optparse'
|
|
3
3
|
module MonkeyMusic
|
4
4
|
class Runner
|
5
5
|
|
6
|
-
@@default_level = '
|
6
|
+
@@default_level = 'bomberman2.rb'
|
7
7
|
@@default_user = 'synth.yaml'
|
8
8
|
|
9
9
|
def initialize(arguments)
|
@@ -43,9 +43,9 @@ module MonkeyMusic
|
|
43
43
|
print "Using browser UI. Press the enter key to start game. "
|
44
44
|
gets
|
45
45
|
puts "Starting game..."
|
46
|
-
ui =
|
46
|
+
ui = UI::Browser.new(@delay)
|
47
47
|
else
|
48
|
-
ui =
|
48
|
+
ui = UI::Console.new(@delay)
|
49
49
|
end
|
50
50
|
## Start game
|
51
51
|
@game = Game.new(level, @players, ui)
|
@@ -97,14 +97,14 @@ module MonkeyMusic
|
|
97
97
|
@delay = delay
|
98
98
|
end
|
99
99
|
|
100
|
-
opts.on('-b', '--browser-ui',
|
101
|
-
'View the game through the browser instead of the console.') do |password|
|
102
|
-
|
103
|
-
end
|
100
|
+
#opts.on('-b', '--browser-ui',
|
101
|
+
#'View the game through the browser instead of the console.') do |password|
|
102
|
+
#@browser_ui = true
|
103
|
+
#end
|
104
104
|
|
105
105
|
opts.on('-v', '--version',
|
106
106
|
'Show the current version.') do |password|
|
107
|
-
puts '0.0.
|
107
|
+
puts '0.0.5' # TODO: Find out how to extract version from GemSpec
|
108
108
|
exit
|
109
109
|
end
|
110
110
|
|
@@ -1,12 +1,11 @@
|
|
1
1
|
require 'em-websocket'
|
2
2
|
require 'json'
|
3
|
-
require 'rack'
|
4
3
|
|
5
|
-
module MonkeyMusic
|
6
|
-
class
|
4
|
+
module MonkeyMusic::UI
|
5
|
+
class Browser
|
7
6
|
|
8
|
-
def initialize(delay)
|
9
|
-
@delay = delay
|
7
|
+
def initialize(delay = 1)
|
8
|
+
@delay = delay
|
10
9
|
puts "Initializing websockets..."
|
11
10
|
Thread.new {
|
12
11
|
EM.run {
|
@@ -27,9 +26,9 @@ module MonkeyMusic
|
|
27
26
|
puts "Websockets initialized!"
|
28
27
|
end
|
29
28
|
|
30
|
-
def update(level)
|
29
|
+
def update(level, query_time = 0)
|
31
30
|
@ws.send(level.as_json) if @ws
|
32
|
-
sleep
|
31
|
+
sleep 1
|
33
32
|
end
|
34
33
|
|
35
34
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
module MonkeyMusic
|
2
|
-
class
|
1
|
+
module MonkeyMusic::UI
|
2
|
+
class Console
|
3
3
|
|
4
4
|
def initialize(delay)
|
5
5
|
@delay = delay || 1
|
@@ -12,7 +12,7 @@ module MonkeyMusic
|
|
12
12
|
puts "\n"*5
|
13
13
|
end
|
14
14
|
|
15
|
-
def update(level, query_time = 0)
|
15
|
+
def update(level, turn = 0, query_time = 0)
|
16
16
|
# Clear screen
|
17
17
|
puts "\e[H\e[2J"
|
18
18
|
# Level
|
@@ -21,9 +21,10 @@ module MonkeyMusic
|
|
21
21
|
puts "\n"
|
22
22
|
level.players.each do |player|
|
23
23
|
monkey = player.monkey
|
24
|
-
puts "---"
|
24
|
+
puts "--- Turn: #{turn}/#{level.turn_limit} ---"
|
25
25
|
print "#{monkey.name} | "
|
26
26
|
print "Score: #{monkey.score} | "
|
27
|
+
print "Time: #{player.remaining_time} | "
|
27
28
|
puts "Capacity: #{monkey.remaining_capacity}"
|
28
29
|
monkey.carrying.each { |t| puts "#{t.value}p: #{t.name}" }
|
29
30
|
print("\n"*monkey.remaining_capacity)
|
@@ -1,22 +1,32 @@
|
|
1
1
|
module MonkeyMusic
|
2
2
|
class Track < Base
|
3
|
-
attr_accessor :
|
4
|
-
:value, :multiplier
|
3
|
+
attr_accessor :metadata
|
5
4
|
|
6
5
|
def self.worth(n)
|
7
6
|
Class.new Track do @worth = n end
|
8
7
|
end
|
9
8
|
|
10
9
|
def self.from_user(user)
|
11
|
-
|
10
|
+
track = Track.new
|
11
|
+
track.metadata = if @worth
|
12
12
|
user.recommend!(@worth) || user.recommendations.sample
|
13
13
|
else
|
14
14
|
user.recommendations.sample
|
15
15
|
end
|
16
|
+
track
|
17
|
+
end
|
18
|
+
|
19
|
+
# Delegate to metadata
|
20
|
+
def method_missing(method, *args, &block)
|
21
|
+
if @metadata.respond_to?(method)
|
22
|
+
@metadata.send(method, *args, &block)
|
23
|
+
else
|
24
|
+
raise NoMethodError
|
25
|
+
end
|
16
26
|
end
|
17
27
|
|
18
28
|
def serialize
|
19
|
-
@uri
|
29
|
+
@metadata.uri
|
20
30
|
end
|
21
31
|
|
22
32
|
def to_json(options = {})
|
@@ -24,9 +34,9 @@ module MonkeyMusic
|
|
24
34
|
:x => @x,
|
25
35
|
:y => @y,
|
26
36
|
:type => self.class.name.split('::').last,
|
27
|
-
:name => @name,
|
28
|
-
:multiplier => @multiplier,
|
29
|
-
:value => @value,
|
37
|
+
:name => @metadata.name,
|
38
|
+
:multiplier => @metadata.multiplier,
|
39
|
+
:value => @metadata.value,
|
30
40
|
}.to_json
|
31
41
|
end
|
32
42
|
end
|
@@ -21,14 +21,12 @@ module MonkeyMusic
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def dump
|
24
|
-
YAML::dump
|
25
|
-
|
26
|
-
:recommendations => @recommendations
|
27
|
-
})
|
24
|
+
YAML::dump :toplists => @toplists,
|
25
|
+
:recommendations => @recommendations
|
28
26
|
end
|
29
27
|
|
30
28
|
def load_from_file(file)
|
31
|
-
data = YAML::load(IO.read
|
29
|
+
data = YAML::load(IO.read file)
|
32
30
|
@toplists = data[:toplists]
|
33
31
|
@recommendations = data[:recommendations]
|
34
32
|
@remaining_recommendations = @recommendations
|
data/lib/monkey_music.rb
CHANGED
@@ -8,8 +8,12 @@ require 'monkey_music/player'
|
|
8
8
|
require 'monkey_music/level'
|
9
9
|
require 'monkey_music/level_loader'
|
10
10
|
|
11
|
-
require 'monkey_music/ui/
|
12
|
-
require 'monkey_music/ui/
|
11
|
+
require 'monkey_music/ui/console'
|
12
|
+
require 'monkey_music/ui/browser'
|
13
|
+
|
14
|
+
require 'monkey_music/metadata/track'
|
15
|
+
require 'monkey_music/metadata/album'
|
16
|
+
require 'monkey_music/metadata/artist'
|
13
17
|
|
14
18
|
require 'monkey_music/units/base'
|
15
19
|
require 'monkey_music/units/monkey'
|