ceml 0.7.13 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|