ceml 0.7.13 → 0.8.0
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/Makefile +1 -1
- data/VERSION +1 -1
- data/ceml.gemspec +62 -50
- data/guide/guide.html +69 -14
- data/guide/guide.md +74 -15
- data/guide/guide.pdf +0 -0
- data/lib/ceml/driver.rb +0 -181
- data/lib/ceml/lang/basic_instruction.rb +49 -0
- data/lib/ceml/{casting_statement.rb → lang/casting_statement.rb} +19 -9
- data/lib/ceml/{instruction_statements.rb → lang/instruction_statements.rb} +5 -16
- data/lib/ceml/{script.rb → lang/script.rb} +53 -43
- data/lib/ceml/lang/tt/casting.rb +432 -0
- data/lib/ceml/lang/tt/casting.treetop +29 -0
- data/lib/ceml/lang/tt/instructions.rb +1130 -0
- data/lib/ceml/lang/tt/instructions.treetop +86 -0
- data/lib/ceml/lang/tt/lexer.rb +1804 -0
- data/lib/ceml/{tt → lang/tt}/lexer.treetop +70 -7
- data/lib/ceml/lang/tt/scripts.rb +647 -0
- data/lib/ceml/{tt → lang/tt}/scripts.treetop +2 -2
- data/lib/ceml/lang.rb +10 -0
- data/lib/ceml/models/audition.rb +65 -0
- data/lib/ceml/models/bundle.rb +64 -0
- data/lib/ceml/models/cast.rb +108 -0
- data/lib/ceml/models/castable.rb +81 -0
- data/lib/ceml/{incident.rb → models/incident.rb} +63 -15
- data/lib/ceml/models/incident_model.rb +100 -0
- data/lib/ceml/models/incident_role_slot.rb +16 -0
- data/lib/ceml/models/player.rb +80 -0
- data/lib/ceml/models/queue.rb +12 -0
- data/lib/ceml/models/waiting_room.rb +40 -0
- data/lib/ceml/models.rb +16 -0
- data/lib/ceml/processor.rb +162 -0
- data/lib/ceml.rb +7 -14
- data/test/askchain.ceml +6 -0
- data/test/compliment.ceml +4 -0
- data/test/dialogues/accept.ceml +24 -0
- data/test/dialogues/basic_seed.ceml +26 -0
- data/test/dialogues/jordan.ceml +121 -0
- data/test/helper.rb +44 -39
- data/test/jane.ceml +48 -0
- data/test/{test_casting.rb → lang/test_casting.rb} +5 -0
- data/test/lang/test_instructions.rb +42 -0
- data/test/{test_scripts.rb → lang/test_scripts.rb} +3 -2
- data/test/sync.ceml +6 -0
- data/test/test_castable.rb +20 -0
- data/test/test_dialogues.rb +58 -0
- data/test/test_incident.rb +64 -127
- metadata +54 -30
- data/.gitignore +0 -23
- data/lib/ceml/confluence.rb +0 -63
- data/lib/ceml/role.rb +0 -61
- data/lib/ceml/tt/casting.treetop +0 -65
- data/lib/ceml/tt/instructions.treetop +0 -91
- data/test/test_instructions.rb +0 -27
- data/test/test_release.rb +0 -78
@@ -0,0 +1,64 @@
|
|
1
|
+
module CEML
|
2
|
+
class Bundle < Struct.new(:id)
|
3
|
+
include Redis::Objects
|
4
|
+
value :castables, :marshal => true
|
5
|
+
|
6
|
+
def clear_from_all_rooms(player_id)
|
7
|
+
Audition.new(player_id).clear_from_all_rooms
|
8
|
+
end
|
9
|
+
|
10
|
+
def find_castables(stanza_name = nil)
|
11
|
+
return castables.value unless stanza_name
|
12
|
+
return [castables.value.find{ |c| c.stanza_name == stanza_name }]
|
13
|
+
end
|
14
|
+
|
15
|
+
def all_rooms
|
16
|
+
find_castables.map(&:all_rooms).flatten.uniq
|
17
|
+
end
|
18
|
+
|
19
|
+
def reset
|
20
|
+
all_rooms.each(&:clear)
|
21
|
+
end
|
22
|
+
|
23
|
+
def rooms_for_player(player, stanza_name = nil)
|
24
|
+
roomnames = find_castables(stanza_name).map{ |c| c.waiting_rooms_for_player(player, stanza_name) }.flatten.uniq
|
25
|
+
roomnames.map{ |r| WaitingRoom.new(r) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def join_running_incident?(player, stanza_name = nil)
|
29
|
+
rooms_for_player(player, stanza_name).each do |room|
|
30
|
+
if incident_id = room.audition_for_incidents(player)
|
31
|
+
return incident_id
|
32
|
+
end
|
33
|
+
end
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
def cast_player?(player, stanza_name = nil)
|
38
|
+
incident_id = gen_code
|
39
|
+
find_castables(stanza_name).each do |castable|
|
40
|
+
if cast = castable.cast_player?(incident_id, player, stanza_name)
|
41
|
+
i = IncidentModel.new(incident_id)
|
42
|
+
i.bytecode.value = castable.bytecode
|
43
|
+
i.add_castings(cast.castings)
|
44
|
+
return incident_id
|
45
|
+
end
|
46
|
+
end
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
def absorb?(player, stanza_name = nil)
|
51
|
+
x = join_running_incident?(player, stanza_name)
|
52
|
+
puts "X1: #{x.inspect}"
|
53
|
+
return x if x
|
54
|
+
x = cast_player?(player, stanza_name)
|
55
|
+
puts "X2: #{x.inspect}"
|
56
|
+
x
|
57
|
+
end
|
58
|
+
|
59
|
+
def register_in_rooms(player, stanza_name = nil)
|
60
|
+
Audition.new("#{player[:id]}").list_in_rooms(rooms_for_player(player, stanza_name)) ##{gen_code}:
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module CEML
|
2
|
+
RoleSpec = Struct.new :name, :tagspec, :range
|
3
|
+
|
4
|
+
# this contains the logic of building and approving a valid cast
|
5
|
+
# for an await statement, starting with a seed first_guy
|
6
|
+
class Cast
|
7
|
+
extend Forwardable
|
8
|
+
attr_reader :castings
|
9
|
+
def_delegators :@castable, :type, :timewindow, :radius, :matching, :roles
|
10
|
+
def_delegators :@castings, :[]
|
11
|
+
def initialize(castable, first_guy)
|
12
|
+
@castable = castable
|
13
|
+
@castings = Hash.new{ |h,k| h[k] = Set.new }
|
14
|
+
cast first_guy
|
15
|
+
end
|
16
|
+
|
17
|
+
def room(role)
|
18
|
+
casted_count = @castings[role.name].size
|
19
|
+
[role.range.max - casted_count, 0].max
|
20
|
+
end
|
21
|
+
|
22
|
+
def affinity role, guy
|
23
|
+
casted_count = @castings[role.name].size
|
24
|
+
needed = [role.range.min - casted_count, 0].max
|
25
|
+
allowed = [role.range.max - casted_count, 0].max
|
26
|
+
|
27
|
+
return [-1, -1, -1 ] unless role.tagspec =~ guy and allowed > 0
|
28
|
+
[ role.tagspec.with.size, -needed, -allowed ]
|
29
|
+
end
|
30
|
+
|
31
|
+
def <=>(other)
|
32
|
+
castings <=> other.castings
|
33
|
+
end
|
34
|
+
|
35
|
+
def cast guy
|
36
|
+
return if included? guy or not self =~ guy
|
37
|
+
best_role = roles.max_by{ |role| affinity(role, guy) }
|
38
|
+
return if affinity(best_role, guy)[0] == -1
|
39
|
+
@castings[best_role.name] << guy
|
40
|
+
guy[:roles] = best_role.name.to_sym
|
41
|
+
end
|
42
|
+
|
43
|
+
def included? guy
|
44
|
+
folks.any?{ |fellow| fellow[:id] == guy[:id] }
|
45
|
+
end
|
46
|
+
|
47
|
+
def launchable?
|
48
|
+
case type
|
49
|
+
when :accept
|
50
|
+
roles.any?{ |role| castings[role.name].size >= 1 }
|
51
|
+
when :await
|
52
|
+
roles.all?{ |role| castings[role.name].size >= role.range.min }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def folks
|
57
|
+
castings.values.map(&:to_a).flatten
|
58
|
+
end
|
59
|
+
|
60
|
+
def player_ids
|
61
|
+
folks.map{ |p| p[:id] }
|
62
|
+
end
|
63
|
+
|
64
|
+
def star
|
65
|
+
folks.first
|
66
|
+
end
|
67
|
+
|
68
|
+
def closes_at
|
69
|
+
return nil unless star && timewindow
|
70
|
+
star[:ts] + timewindow
|
71
|
+
end
|
72
|
+
|
73
|
+
def matchings
|
74
|
+
return {} unless star
|
75
|
+
Hash[ *matching.map{ |k| [k, star[:matchables][k]] } ]
|
76
|
+
end
|
77
|
+
|
78
|
+
def circle
|
79
|
+
return nil unless star && radius
|
80
|
+
llr = star[:lat] && star[:lng] && [star[:lat], star[:lng], radius]
|
81
|
+
ll && Circle.new(*llr)
|
82
|
+
end
|
83
|
+
|
84
|
+
def tagspecs
|
85
|
+
roles.map(&:tagspec).uniq
|
86
|
+
end
|
87
|
+
|
88
|
+
def =~(c)
|
89
|
+
(!closes_at or closes_at < Time.unix) and
|
90
|
+
(!circle or circle.contains?(c[:lat], c[:lng])) and
|
91
|
+
(matchings.each{ |k,v| c[:matchables][k] == v }) and
|
92
|
+
(tagspecs.any?{ |ts| ts =~ c })
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class Circle < Struct.new :lat, :lng, :radius
|
97
|
+
def center; Geokit::LatLng(lat, lng); end
|
98
|
+
def contains?(*ll)
|
99
|
+
center.distance_to(Geokit::LatLng(*ll), :meters) <= radius
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Tagspec < Struct.new :with, :without
|
104
|
+
def =~(c)
|
105
|
+
with.all?{ |t| (c[:tags]||[]).include?(t) } and without.all?{ |t| !(c[:tags]||[]).include?(t) }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module CEML
|
2
|
+
class Castable < Struct.new :type, :stanza_name, :matching, :radius, :timewindow, :roles, :bytecode
|
3
|
+
|
4
|
+
def cast_player?(incident_id, player, seeded=false)
|
5
|
+
room_ids = hot_waiting_rooms_given_player(player, seeded)
|
6
|
+
hotties = Audition.from_rooms(room_ids)
|
7
|
+
# puts "hotties are... #{hotties.inspect} from rooms #{room_ids.inspect}"
|
8
|
+
hot_players = hotties.keys.map{ |id| Player.new(id).data.value } + [player]
|
9
|
+
# puts "casting from #{hot_players.inspect}"
|
10
|
+
if cast = cast_from(hot_players)
|
11
|
+
puts "...cast with cast #{cast.player_ids.inspect}"
|
12
|
+
audition_ids = (cast.player_ids & hotties.keys).map{ |id| hotties[id] }
|
13
|
+
puts "consuming #{audition_ids.inspect}"
|
14
|
+
if Audition.consume(audition_ids, room_ids)
|
15
|
+
# post audition signs in waiting rooms for remaining parts
|
16
|
+
with_open_roles(cast) do |idx, role, count|
|
17
|
+
waiting_rooms_to_watch(role, cast).each do |room|
|
18
|
+
room.list_job(incident_id, idx, role.name, count)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
return cast
|
22
|
+
else
|
23
|
+
sleep 0.02
|
24
|
+
return cast_player?(incident_id, player)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
def waiting_rooms_for_player(player, seeded=false)
|
31
|
+
result = []
|
32
|
+
result.concat([*player[:seeded]]) if player[:seeded]
|
33
|
+
result.concat player[:tags] if player[:tags] unless seeded
|
34
|
+
# result << 'generic'
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
def hot_waiting_rooms_given_player(player, seeded=false)
|
39
|
+
rooms = waiting_rooms_for_player(player, seeded)
|
40
|
+
roles.each{ |r| rooms.concat(["#{stanza_name}:#{r.name}", *r.tagspec.with]) }
|
41
|
+
rooms << "#{stanza_name}:*"
|
42
|
+
# rooms << 'generic'
|
43
|
+
rooms.uniq
|
44
|
+
end
|
45
|
+
|
46
|
+
def waiting_rooms_to_watch(role, cast)
|
47
|
+
# skip for now: radius, timewindow, matching, general
|
48
|
+
result = []
|
49
|
+
if stanza_name
|
50
|
+
result << "#{stanza_name}:#{role.name}"
|
51
|
+
result << "#{stanza_name}:*"
|
52
|
+
end
|
53
|
+
if !role.tagspec.with.empty?
|
54
|
+
result.concat role.tagspec.with
|
55
|
+
end
|
56
|
+
# result << 'generic'
|
57
|
+
result.map{ |id| WaitingRoom.new(id) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def all_rooms
|
61
|
+
roles.map{ |r| waiting_rooms_to_watch(r, {}) }.flatten
|
62
|
+
end
|
63
|
+
|
64
|
+
def with_open_roles(cast)
|
65
|
+
roles.each_with_index do |role, i|
|
66
|
+
count = cast.room(role)
|
67
|
+
next unless count > 0
|
68
|
+
yield i, role, count
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# an O(n*^2) alg for now. can do much better
|
73
|
+
def cast_from guys
|
74
|
+
# see if we can build a cast out of them and bid on the casts
|
75
|
+
possible_casts = guys.map{ |guy| Cast.new self, guy }.select(&:star)
|
76
|
+
guys.each{ |guy| possible_casts.each{ |cast| cast.cast guy }}
|
77
|
+
result = possible_casts.detect(&:launchable?)
|
78
|
+
result
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -21,29 +21,50 @@ module CEML
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def rolematch(specified_roles)
|
24
|
-
expanded = roles.map{ |r| r == :agent ? [:agent, :agents] : r }.flatten.concat([:both, :all, :everyone])
|
25
|
-
not (expanded & specified_roles).empty?
|
24
|
+
expanded = roles.map{ |r| r == :agent ? [:agent, :agents] : r }.flatten.concat([:both, :all, :everyone, :them, :either])
|
25
|
+
not (expanded & [*specified_roles]).empty?
|
26
26
|
end
|
27
27
|
|
28
|
+
def log state
|
29
|
+
p = @this
|
30
|
+
instr = seq[pc]
|
31
|
+
guyroles = roles.to_a - [:everyone, :players, :them, :all, :either, :each, :agents, :both]
|
32
|
+
instr ||= []
|
33
|
+
|
34
|
+
puts "[#{id}] #{p[:id]}(#{guyroles}) ##{pc} #{state} -- #{instr[1]}/#{instr[0]} -- #{instr[2].inspect}"
|
35
|
+
end
|
36
|
+
|
37
|
+
|
28
38
|
def run(players, &blk)
|
29
39
|
@players = players
|
30
40
|
@callback = blk
|
31
41
|
|
32
42
|
loop do
|
33
|
-
players = @players
|
34
|
-
# puts "playing with #{players.map{|p|p[:id]}}"
|
43
|
+
players = @players
|
35
44
|
advanced = false
|
36
45
|
players.each do |p|
|
37
46
|
@this = p
|
38
|
-
|
39
|
-
|
47
|
+
instr = seq[pc]
|
48
|
+
unless instr = seq[pc]
|
49
|
+
log 'off schedule'
|
50
|
+
@players.delete(p)
|
51
|
+
cb :released
|
52
|
+
next
|
53
|
+
end
|
40
54
|
instr = instr.dup
|
41
|
-
|
55
|
+
rolespec = instr.shift
|
56
|
+
if not rolematch(rolespec)
|
57
|
+
log "skipping[#{rolespec}]"
|
42
58
|
this[:pc]+=1
|
43
59
|
advanced = true
|
44
60
|
else
|
45
61
|
instr << role_info if instr.first == :start #tmp hack
|
46
|
-
|
62
|
+
if send(*instr)
|
63
|
+
log 'completed'
|
64
|
+
else
|
65
|
+
log 'blocked'
|
66
|
+
next
|
67
|
+
end
|
47
68
|
cb(*instr)
|
48
69
|
this[:pc]+=1
|
49
70
|
advanced = true
|
@@ -67,17 +88,22 @@ module CEML
|
|
67
88
|
end
|
68
89
|
end
|
69
90
|
|
91
|
+
def players_with_role(role)
|
92
|
+
if role
|
93
|
+
@players.select{ |p| p[:roles].include? role }
|
94
|
+
else
|
95
|
+
@players.reject{ |p| p == this }
|
96
|
+
end
|
97
|
+
end
|
70
98
|
|
71
99
|
def expand(role, var)
|
72
100
|
case role
|
73
|
-
when 'his', 'her', 'their', 'my', 'its'; return qs_answers[var]
|
101
|
+
when 'his', 'her', 'their', 'my', 'its', 'your'; return qs_answers[var]
|
74
102
|
when 'world', 'game', 'exercise', 'group'; return (cb :world, var)
|
75
103
|
when 'somebody', 'someone', 'buddy', 'teammate'; role = nil
|
76
104
|
end
|
77
105
|
role = role.to_sym if role
|
78
|
-
|
79
|
-
next if p == this
|
80
|
-
next if role and not p[:roles].include? role
|
106
|
+
players_with_role(role).each do |p|
|
81
107
|
value = (p[:qs_answers]||{})[var] and return value
|
82
108
|
end
|
83
109
|
nil
|
@@ -102,6 +128,16 @@ module CEML
|
|
102
128
|
cb :said, params.merge(:said => x)
|
103
129
|
end
|
104
130
|
|
131
|
+
def seed x
|
132
|
+
x[:rolemap].each do |pair|
|
133
|
+
if rolematch(pair[:from].to_sym)
|
134
|
+
cb :seeded, :target => x[:target], :role => pair[:to]
|
135
|
+
break
|
136
|
+
end
|
137
|
+
end
|
138
|
+
true
|
139
|
+
end
|
140
|
+
|
105
141
|
def start(x); true; end
|
106
142
|
def finish; true; end
|
107
143
|
|
@@ -116,9 +152,9 @@ module CEML
|
|
116
152
|
true
|
117
153
|
end
|
118
154
|
|
119
|
-
def sync
|
155
|
+
def sync q
|
120
156
|
this[:synced] = pc
|
121
|
-
return true if
|
157
|
+
return true if players_with_role(q[:role]).all?{ |p| p[:synced] == pc }
|
122
158
|
end
|
123
159
|
|
124
160
|
def ask_q q
|
@@ -141,6 +177,12 @@ module CEML
|
|
141
177
|
true
|
142
178
|
end
|
143
179
|
|
180
|
+
def pick q
|
181
|
+
choices = q[:value].split(/\s+\-\s+/)
|
182
|
+
qs_answers[q[:key]] ||= choices.sort_by{ rand }.first
|
183
|
+
true
|
184
|
+
end
|
185
|
+
|
144
186
|
def send_msg a
|
145
187
|
text = interpolate(a[:text]) or return false
|
146
188
|
say :message, :msg => text
|
@@ -180,8 +222,14 @@ module CEML
|
|
180
222
|
|
181
223
|
def release x
|
182
224
|
return true if x[:cond] and not expectation(*x[:cond])
|
183
|
-
this
|
225
|
+
@players.delete(this)
|
184
226
|
cb :released, x[:tag]
|
227
|
+
true
|
228
|
+
end
|
229
|
+
|
230
|
+
def replace x
|
231
|
+
return true if x[:cond] and not expectation(*x[:cond])
|
232
|
+
# TODO: implement replace
|
185
233
|
false
|
186
234
|
end
|
187
235
|
|
@@ -0,0 +1,100 @@
|
|
1
|
+
class Hash
|
2
|
+
def like(*syms)
|
3
|
+
syms.inject({}) { |h, k| h[k] = self[k] if self[k]; h }
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
module CEML
|
8
|
+
PLAYER_THREAD_FIELDS = [ :pc, :continue_at, :synced ]
|
9
|
+
# :received, :recognized, :last_answer, :last_answer_recognized
|
10
|
+
|
11
|
+
class IncidentModel < Struct.new(:id)
|
12
|
+
include Redis::Objects
|
13
|
+
lock :running
|
14
|
+
value :bytecode, :marshal => true
|
15
|
+
value :data, :marshal => true
|
16
|
+
hash_key :player_roles, :marshal => true
|
17
|
+
|
18
|
+
def add_castings(castings)
|
19
|
+
castings.each do |rolename, folks|
|
20
|
+
folks.each do |player|
|
21
|
+
Player.new(player[:id]).touch(id)
|
22
|
+
player_roles[player[:id]] = [rolename.to_sym]
|
23
|
+
puts "ADDED(#{id}) #{player[:id]} = #{rolename.to_sym}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def release(player_id)
|
29
|
+
puts "Releasing player #{player_id} from incident #{id}"
|
30
|
+
Player.new(player_id).clear_incident(id)
|
31
|
+
player_roles.delete(player_id)
|
32
|
+
end
|
33
|
+
|
34
|
+
def expire
|
35
|
+
# TODO: remove casting calls from waiting rooms
|
36
|
+
bytecode.clear
|
37
|
+
data.clear
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.run_latest(cb_obj)
|
41
|
+
t = CEML.clock - 1
|
42
|
+
ids = redis.zrangebyscore 'ceml_continue_at', 0, t
|
43
|
+
ids.each{ |id| self.new(id).run(cb_obj) }
|
44
|
+
redis.zremrangebyscore 'ceml_continue_at', 0, t
|
45
|
+
end
|
46
|
+
|
47
|
+
def run(cb_obj)
|
48
|
+
raise unless String === id
|
49
|
+
# puts "RUNNING(#{id})"
|
50
|
+
# running_lock.lock do
|
51
|
+
metadata, player_data = *data.value
|
52
|
+
metadata ||= { :id => id }
|
53
|
+
player_data ||= {}
|
54
|
+
puts "[#{id}] Player data loaded: #{player_data.inspect}"
|
55
|
+
players = []
|
56
|
+
|
57
|
+
puts "[#{id}] Player roles: #{player_roles.all.inspect}"
|
58
|
+
player_roles.each do |player_id, roles|
|
59
|
+
puts "#{id}: #{player_id.inspect} => #{roles.inspect}, #{player_data[player_id].inspect}"
|
60
|
+
player = { :id => player_id, :roles => Set.new(roles) }
|
61
|
+
player[:roles] << :agents << :players << :both << :all << :each << :everyone << :them << :either
|
62
|
+
player.merge! player_data[player_id] if player_data[player_id]
|
63
|
+
p = Player.new(player_id)
|
64
|
+
stored_player = p.data.value
|
65
|
+
msg = p.message.value
|
66
|
+
player.merge! msg if msg
|
67
|
+
player.merge! stored_player if stored_player
|
68
|
+
players << player
|
69
|
+
end
|
70
|
+
|
71
|
+
CEML::Incident.new(bytecode.value, id).run(players) do |player, meth, what|
|
72
|
+
case meth.to_sym when :released, :finish, :replace
|
73
|
+
release(player[:id])
|
74
|
+
end
|
75
|
+
meth = "player_#{meth}"
|
76
|
+
# cb_obj.log "[#{id}] #{meth}: #{player[:id]} #{what.inspect}"
|
77
|
+
if cb_obj.respond_to? meth
|
78
|
+
metadata.update :player => player, :players => players, :id => id
|
79
|
+
result = cb_obj.send(meth, metadata, what)
|
80
|
+
metadata.delete :player
|
81
|
+
metadata.delete :players
|
82
|
+
result
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
players.each do |p|
|
87
|
+
Player.new(p[:id]).message.value = p.like(:received, :recognized, :situation)
|
88
|
+
player_data[p[:id]] = p.like *PLAYER_THREAD_FIELDS
|
89
|
+
end
|
90
|
+
puts "Player data saving: #{player_data.inspect}"
|
91
|
+
data.value = [metadata, player_data]
|
92
|
+
|
93
|
+
if next_run = players.map{ |p| p[:continue_at] }.compact.min
|
94
|
+
redis.zadd 'ceml_continue_at', next_run, id
|
95
|
+
end
|
96
|
+
# end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CEML
|
2
|
+
|
3
|
+
class IncidentRoleSlot < Struct.new(:incident_id, :role, :max)
|
4
|
+
def id; "#{incident_id}:#{role}"; end
|
5
|
+
include Redis::Objects
|
6
|
+
counter :casted
|
7
|
+
|
8
|
+
def reserve_spot!
|
9
|
+
casted.incr{ |val| val <= max }
|
10
|
+
end
|
11
|
+
|
12
|
+
def full?
|
13
|
+
casted.value >= max
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module CEML
|
2
|
+
class Player < Struct.new(:id)
|
3
|
+
include Redis::Objects
|
4
|
+
lock :updating
|
5
|
+
value :data, :marshal => true
|
6
|
+
value :message, :marshal => true
|
7
|
+
sorted_set :current_incidents
|
8
|
+
|
9
|
+
def touch(incident_id)
|
10
|
+
current_incidents[incident_id] = Time.now.to_i
|
11
|
+
end
|
12
|
+
|
13
|
+
def reset
|
14
|
+
Audition.new(id).clear_from_all_rooms
|
15
|
+
data.clear
|
16
|
+
message.clear
|
17
|
+
current_incidents.clear
|
18
|
+
end
|
19
|
+
|
20
|
+
def top_incident_id
|
21
|
+
current_incidents.last
|
22
|
+
end
|
23
|
+
|
24
|
+
def top_incident
|
25
|
+
if iid = top_incident_id
|
26
|
+
IncidentModel.new(iid)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear_incident(id)
|
31
|
+
current_incidents.delete(id)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.update bundle_id, player, cb_obj, &blk
|
35
|
+
player[:bundle_id] = player[:squad_id] = bundle_id
|
36
|
+
new(player[:id].to_s).update player, cb_obj, &blk
|
37
|
+
end
|
38
|
+
|
39
|
+
def clear_answers
|
40
|
+
updating_lock.lock do
|
41
|
+
value = data.value || {}
|
42
|
+
value.delete(:qs_answers)
|
43
|
+
value.delete(:received)
|
44
|
+
data.value = value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
MSG_PARAMS = [:received, :recognized, :situation]
|
49
|
+
|
50
|
+
def split player
|
51
|
+
new_message = player.like(*MSG_PARAMS)
|
52
|
+
MSG_PARAMS.each{ |p| player.delete(p) }
|
53
|
+
return new_message, player
|
54
|
+
end
|
55
|
+
|
56
|
+
def merge_new_player_data player
|
57
|
+
updating_lock.lock do
|
58
|
+
old_value = data.value || {}
|
59
|
+
new_value = old_value.merge player
|
60
|
+
new_value[:qs_answers] = (old_value[:qs_answers]||{}).merge(player[:qs_answers] || {})
|
61
|
+
# puts "SAVING DATA: #{new_value.inspect}"
|
62
|
+
data.value = new_value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def update player, cb_obj
|
67
|
+
player = player.dup
|
68
|
+
# puts "UPDATING player id #{id} with #{player.inspect}"
|
69
|
+
new_message, player = split(player)
|
70
|
+
merge_new_player_data(player)
|
71
|
+
cmd = new_message[:recognized]
|
72
|
+
if cmd and cb_obj.recognize_override(cmd, new_message, player, self)
|
73
|
+
message.delete
|
74
|
+
else
|
75
|
+
message.value = new_message
|
76
|
+
yield player if block_given?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module CEML
|
2
|
+
|
3
|
+
class WaitingRoom < Struct.new(:id)
|
4
|
+
include Redis::Objects
|
5
|
+
set :waiting_auditions
|
6
|
+
set :waiting_incident_roles
|
7
|
+
|
8
|
+
def clear
|
9
|
+
waiting_auditions.each do |audition_id|
|
10
|
+
Audition.new(audition_id).clear_from_all_rooms(id)
|
11
|
+
end
|
12
|
+
waiting_incident_roles.clear
|
13
|
+
end
|
14
|
+
|
15
|
+
def audition_for_incidents(player)
|
16
|
+
# puts "auditioning #{player[:id]} for incidents in room #{id}"
|
17
|
+
waiting_incident_roles.members.each do |key|
|
18
|
+
incident_id, idx, role, count = *key.split(':')
|
19
|
+
# puts "checking against #{incident_id}: #{role}"
|
20
|
+
count = count.to_i
|
21
|
+
role_slot = IncidentRoleSlot.new(incident_id, role, count)
|
22
|
+
next unless role_slot.reserve_spot!
|
23
|
+
waiting_incident_roles.delete(key) if role_slot.full?
|
24
|
+
IncidentModel.new(role_slot.incident_id).add_castings({ role_slot.role => [ player ] })
|
25
|
+
return role_slot.incident_id
|
26
|
+
end
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
|
30
|
+
def list_job(incident_id, idx, rolename, count)
|
31
|
+
waiting_incident_roles << [incident_id, idx, rolename, count].join(':')
|
32
|
+
end
|
33
|
+
|
34
|
+
def add(audition_id)
|
35
|
+
puts "adding #{audition_id} to waiting room #{id.inspect}"
|
36
|
+
waiting_auditions << audition_id
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
data/lib/ceml/models.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'redis/objects'
|
3
|
+
|
4
|
+
Redis::Objects.redis = Redis.new
|
5
|
+
|
6
|
+
require 'ceml/models/cast'
|
7
|
+
require 'ceml/models/castable'
|
8
|
+
require 'ceml/models/incident'
|
9
|
+
require 'ceml/models/audition'
|
10
|
+
require 'ceml/models/incident_model'
|
11
|
+
require 'ceml/models/incident_role_slot'
|
12
|
+
require 'ceml/models/player'
|
13
|
+
require 'ceml/models/waiting_room'
|
14
|
+
require 'ceml/models/queue'
|
15
|
+
require 'ceml/models/bundle'
|
16
|
+
|