monkeymusic 0.0.1
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/Gemfile +6 -0
- data/Gemfile.lock +31 -0
- data/LICENSE +21 -0
- data/README.markdown +38 -0
- data/bin/monkeymusic +5 -0
- data/levels/testlevel.rb +28 -0
- data/levels/testlevel_maze.rb +24 -0
- data/levels/the_last_crusade.rb +21 -0
- data/levels/travelling_salesmonkeys.rb +0 -0
- data/lib/monkey_music/game.rb +27 -0
- data/lib/monkey_music/level.rb +91 -0
- data/lib/monkey_music/level_loader.rb +64 -0
- data/lib/monkey_music/player.rb +41 -0
- data/lib/monkey_music/recommendation_loader.rb +91 -0
- data/lib/monkey_music/runner.rb +169 -0
- data/lib/monkey_music/score_system.rb +37 -0
- data/lib/monkey_music/toplist_loader.rb +68 -0
- data/lib/monkey_music/ui/browser_ui.rb +36 -0
- data/lib/monkey_music/ui/console_ui.rb +31 -0
- data/lib/monkey_music/units/base.rb +71 -0
- data/lib/monkey_music/units/monkey.rb +85 -0
- data/lib/monkey_music/units/track.rb +33 -0
- data/lib/monkey_music/units/user.rb +38 -0
- data/lib/monkey_music/units/wall.rb +4 -0
- data/lib/monkey_music.rb +20 -0
- data/users/synth.rb +7 -0
- data/users/synth.yaml +1682 -0
- metadata +136 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
module MonkeyMusic
|
2
|
+
class ScoreSystem
|
3
|
+
|
4
|
+
def evaluate_user_recommendations!(user)
|
5
|
+
@user = user
|
6
|
+
@user.recommendations.each { |track| evaluate!(track) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def evaluate!(track)
|
10
|
+
multiplier = 0
|
11
|
+
if @user.toplists[:top_tracks].include? track.name
|
12
|
+
multiplier = -1
|
13
|
+
elsif @user.toplists[:disliked].include? track.artist
|
14
|
+
multiplier = -2
|
15
|
+
else
|
16
|
+
if @user.toplists[:top_albums].include? track.album
|
17
|
+
multiplier += 1
|
18
|
+
end
|
19
|
+
if @user.toplists[:top_artists].include? track.artist
|
20
|
+
multiplier += 1
|
21
|
+
end
|
22
|
+
if @user.toplists[:top_track_albums].include? track.album
|
23
|
+
multiplier += 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
track.multiplier = multiplier
|
27
|
+
track.value = 4**multiplier.abs * sign(multiplier)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def sign(num)
|
33
|
+
num < 0 ? -1 : 1
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'hallon'
|
2
|
+
|
3
|
+
module MonkeyMusic
|
4
|
+
class ToplistLoader
|
5
|
+
attr_reader :loaded_toplists
|
6
|
+
|
7
|
+
def initialize(toplist_file)
|
8
|
+
@toplist_file = IO.read(toplist_file)
|
9
|
+
@toplists = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def load_for_user!(user)
|
13
|
+
@user = user
|
14
|
+
# Load toplists URI:s from file
|
15
|
+
self.instance_eval(@toplist_file)
|
16
|
+
# Load toplist contents from libspotify
|
17
|
+
load_toplists!
|
18
|
+
load_top_track_albums!
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# User toplist DSL
|
24
|
+
|
25
|
+
def toplist(type, uri)
|
26
|
+
@toplists[type] = uri
|
27
|
+
end
|
28
|
+
|
29
|
+
# Toplist loading
|
30
|
+
|
31
|
+
def load_toplists!
|
32
|
+
@loaded_toplists = {}
|
33
|
+
@toplists.each do |type, uri|
|
34
|
+
@loaded_toplists[type] = load_from_list(type, uri)
|
35
|
+
@user.toplists[type] = parse_toplist(@loaded_toplists[type])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def load_top_track_albums!
|
40
|
+
@loaded_toplists[:top_track_albums] =
|
41
|
+
@loaded_toplists[:top_tracks].map(&:album).each(&:load)
|
42
|
+
@user.toplists[:top_track_albums] =
|
43
|
+
parse_toplist(@loaded_toplists[:top_track_albums])
|
44
|
+
end
|
45
|
+
|
46
|
+
def load_from_list(type, uri)
|
47
|
+
playlist = Hallon::Playlist.new(uri).load
|
48
|
+
tracks = playlist.tracks.to_a
|
49
|
+
tracks.each(&:load)
|
50
|
+
if type == :top_tracks
|
51
|
+
tracks
|
52
|
+
elsif type == :top_artists || type == :disliked
|
53
|
+
tracks.map(&:artist).each(&:load)
|
54
|
+
elsif type == :top_albums
|
55
|
+
tracks.map(&:album).each(&:load)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_toplist(list)
|
60
|
+
result = []
|
61
|
+
list.each do |item|
|
62
|
+
result << item.name
|
63
|
+
end
|
64
|
+
result
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'em-websocket'
|
2
|
+
require 'json'
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
module MonkeyMusic
|
6
|
+
class BrowserUI
|
7
|
+
|
8
|
+
def initialize(delay)
|
9
|
+
@delay = delay || 1
|
10
|
+
puts "Initializing websockets..."
|
11
|
+
Thread.new {
|
12
|
+
EM.run {
|
13
|
+
EM::WebSocket.run(:host => "0.0.0.0", :port => 3000) do |ws|
|
14
|
+
@ws = ws
|
15
|
+
ws.onopen do |handshake|
|
16
|
+
end
|
17
|
+
|
18
|
+
ws.onclose do
|
19
|
+
end
|
20
|
+
|
21
|
+
ws.onmessage do |msg|
|
22
|
+
end
|
23
|
+
end
|
24
|
+
}
|
25
|
+
}
|
26
|
+
sleep 1 # TODO: Asynchronous acknowledge that ui is ready
|
27
|
+
puts "Websockets initialized!"
|
28
|
+
end
|
29
|
+
|
30
|
+
def update(level)
|
31
|
+
@ws.send(level.as_json) if @ws
|
32
|
+
sleep @delay
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module MonkeyMusic
|
2
|
+
class ConsoleUI
|
3
|
+
|
4
|
+
def initialize(delay)
|
5
|
+
@delay = delay || 1
|
6
|
+
end
|
7
|
+
|
8
|
+
def msg(msg)
|
9
|
+
puts "\e[H\e[2J"
|
10
|
+
puts "\n"*5
|
11
|
+
puts " "*10 + msg
|
12
|
+
puts "\n"*5
|
13
|
+
end
|
14
|
+
|
15
|
+
def update(level)
|
16
|
+
# Clear screen
|
17
|
+
puts "\e[H\e[2J"
|
18
|
+
# Level
|
19
|
+
puts level.to_s
|
20
|
+
# Score
|
21
|
+
puts "\n"
|
22
|
+
puts "="*10
|
23
|
+
puts "\n"
|
24
|
+
level.players.each do |player|
|
25
|
+
puts "#{player.monkey.name}: #{player.monkey.score}"
|
26
|
+
puts "{ #{"0"*player.monkey.carrying.count}#{"_"*player.monkey.remaining_capacity} }\n"
|
27
|
+
end
|
28
|
+
sleep @delay
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module MonkeyMusic
|
2
|
+
class Base
|
3
|
+
attr_accessor :x, :y, :character
|
4
|
+
attr_reader :level, :id
|
5
|
+
|
6
|
+
@@curr_id = 0
|
7
|
+
|
8
|
+
def assign_id
|
9
|
+
@id = @@curr_id
|
10
|
+
@@curr_id += 1
|
11
|
+
end
|
12
|
+
|
13
|
+
def place!(level, x, y)
|
14
|
+
@level = level
|
15
|
+
@x, @y = x, y
|
16
|
+
end
|
17
|
+
|
18
|
+
def move!
|
19
|
+
# To be overriden
|
20
|
+
end
|
21
|
+
|
22
|
+
def at?(x, y)
|
23
|
+
@x == x && @y == y
|
24
|
+
end
|
25
|
+
|
26
|
+
def distance_to(unit)
|
27
|
+
(@x - unit.x).abs + (@y - unit.y).abs
|
28
|
+
end
|
29
|
+
|
30
|
+
def pos
|
31
|
+
[@x, @y]
|
32
|
+
end
|
33
|
+
|
34
|
+
def direction_of(unit)
|
35
|
+
if (@x - unit.x).abs > (@y - unit.y).abs
|
36
|
+
unit.x > @x ? :east : :west
|
37
|
+
else
|
38
|
+
unit.y > @y ? :south : :north
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
@character
|
44
|
+
end
|
45
|
+
|
46
|
+
# As shown to player
|
47
|
+
def serialize
|
48
|
+
to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_json(options = {})
|
52
|
+
{ :id => @id,
|
53
|
+
:x => @x,
|
54
|
+
:y => @y,
|
55
|
+
:type => self.class.name.split('::').last
|
56
|
+
}.to_json
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def translate(x, y, direction)
|
62
|
+
case direction
|
63
|
+
when :north then [x, y - 1]
|
64
|
+
when :south then [x, y + 1]
|
65
|
+
when :east then [x + 1, y ]
|
66
|
+
when :west then [x - 1, y ]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module MonkeyMusic
|
2
|
+
class Monkey < Base
|
3
|
+
attr_accessor :name, :score, :capacity, :character
|
4
|
+
attr_reader :carrying, :facing
|
5
|
+
|
6
|
+
def self.player(number)
|
7
|
+
Class.new Monkey do @player_index = number - 1 end
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.from_players(players)
|
11
|
+
@player_index &&
|
12
|
+
players[@player_index] &&
|
13
|
+
players[@player_index].monkey
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@score = 0
|
18
|
+
@capacity = 1
|
19
|
+
@carrying = []
|
20
|
+
@delivered = []
|
21
|
+
@facing = [:west, :east].sample
|
22
|
+
end
|
23
|
+
|
24
|
+
def move!(direction)
|
25
|
+
target = translate(@x, @y, direction)
|
26
|
+
if target_unit = @level.at(*target)
|
27
|
+
# Interact with unit
|
28
|
+
interact_with!(target_unit)
|
29
|
+
else
|
30
|
+
# Face the right direction
|
31
|
+
@facing = case direction
|
32
|
+
when :west then :west
|
33
|
+
when :east then :east
|
34
|
+
else @facing
|
35
|
+
end
|
36
|
+
# Perform move
|
37
|
+
@x, @y = *target if @level.accessible?(*target)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def interact_with!(unit)
|
42
|
+
case unit
|
43
|
+
when Track then pick_up!(unit)
|
44
|
+
when User then deliver!
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def pick_up!(unit)
|
49
|
+
if @carrying.count < @capacity
|
50
|
+
@level.remove(unit)
|
51
|
+
@carrying << unit
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def deliver!
|
56
|
+
@delivered += @carrying
|
57
|
+
@score += tally(@carrying)
|
58
|
+
@carrying = []
|
59
|
+
end
|
60
|
+
|
61
|
+
def tally(tracks)
|
62
|
+
score = 0
|
63
|
+
tracks.each do |track|
|
64
|
+
score += track.value
|
65
|
+
end
|
66
|
+
score
|
67
|
+
end
|
68
|
+
|
69
|
+
def remaining_capacity
|
70
|
+
(@capacity - carrying.count) || 0
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_json(options = {})
|
74
|
+
{ :id => @id,
|
75
|
+
:x => @x,
|
76
|
+
:y => @y,
|
77
|
+
:facing => @facing,
|
78
|
+
:type => self.class.name.split('::').last,
|
79
|
+
:name => @name,
|
80
|
+
:score => @score,
|
81
|
+
}.to_json
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module MonkeyMusic
|
2
|
+
class Track < Base
|
3
|
+
attr_accessor :uri, :name, :artist, :album, :popularity, :year,
|
4
|
+
:value, :multiplier
|
5
|
+
|
6
|
+
def self.worth(n)
|
7
|
+
Class.new Track do @worth = n end
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.from_user(user)
|
11
|
+
if @worth
|
12
|
+
user.recommend!(@worth) || user.recommendations.sample
|
13
|
+
else
|
14
|
+
user.recommendations.sample
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def serialize
|
19
|
+
@uri
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_json(options = {})
|
23
|
+
{ :id => @id,
|
24
|
+
:x => @x,
|
25
|
+
:y => @y,
|
26
|
+
:type => self.class.name.split('::').last,
|
27
|
+
:name => @name,
|
28
|
+
:multiplier => @multiplier,
|
29
|
+
:value => @value,
|
30
|
+
}.to_json
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module MonkeyMusic
|
4
|
+
class User < Base
|
5
|
+
attr_accessor :toplists, :recommendations
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@toplists = {}
|
9
|
+
@recommendations = @remaining_recommendations = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def recommend!(worth)
|
13
|
+
index = @remaining_recommendations.index do |r|
|
14
|
+
r.multiplier == worth
|
15
|
+
end
|
16
|
+
if index
|
17
|
+
@remaining_recommendations.delete_at(index)
|
18
|
+
else
|
19
|
+
@recommendations.find { |r| r.multiplier == worth }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def dump
|
24
|
+
YAML::dump({
|
25
|
+
:toplists => @toplists,
|
26
|
+
:recommendations => @recommendations
|
27
|
+
})
|
28
|
+
end
|
29
|
+
|
30
|
+
def load_from_file(file)
|
31
|
+
data = YAML::load(IO.read(file))
|
32
|
+
@toplists = data[:toplists]
|
33
|
+
@recommendations = data[:recommendations]
|
34
|
+
@remaining_recommendations = @recommendations
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/monkey_music.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$: << File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
require 'monkey_music/runner'
|
6
|
+
require 'monkey_music/game'
|
7
|
+
require 'monkey_music/player'
|
8
|
+
require 'monkey_music/toplist_loader'
|
9
|
+
require 'monkey_music/recommendation_loader'
|
10
|
+
require 'monkey_music/score_system'
|
11
|
+
require 'monkey_music/level'
|
12
|
+
require 'monkey_music/level_loader'
|
13
|
+
|
14
|
+
require 'monkey_music/ui/console_ui'
|
15
|
+
require 'monkey_music/ui/browser_ui'
|
16
|
+
|
17
|
+
require 'monkey_music/units/base'
|
18
|
+
require 'monkey_music/units/monkey'
|
19
|
+
require 'monkey_music/units/track'
|
20
|
+
require 'monkey_music/units/user'
|
data/users/synth.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
toplist :top_tracks, "spotify:user:poscar%21:playlist:4yIhuHviCmTYjcFqunPxsa"
|
2
|
+
|
3
|
+
toplist :top_artists, "spotify:user:poscar%21:playlist:6MjfrIbeCKQMVCU9JQNf1u"
|
4
|
+
|
5
|
+
toplist :top_albums, "spotify:user:poscar%21:playlist:1ZXPeNHa8YYvY0VzxRdv3q"
|
6
|
+
|
7
|
+
toplist :disliked, "spotify:user:poscar%21:playlist:5IEx3cooSMgrtftvw5B9Ug"
|