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.
Files changed (55) hide show
  1. data/Makefile +1 -1
  2. data/VERSION +1 -1
  3. data/ceml.gemspec +62 -50
  4. data/guide/guide.html +69 -14
  5. data/guide/guide.md +74 -15
  6. data/guide/guide.pdf +0 -0
  7. data/lib/ceml/driver.rb +0 -181
  8. data/lib/ceml/lang/basic_instruction.rb +49 -0
  9. data/lib/ceml/{casting_statement.rb → lang/casting_statement.rb} +19 -9
  10. data/lib/ceml/{instruction_statements.rb → lang/instruction_statements.rb} +5 -16
  11. data/lib/ceml/{script.rb → lang/script.rb} +53 -43
  12. data/lib/ceml/lang/tt/casting.rb +432 -0
  13. data/lib/ceml/lang/tt/casting.treetop +29 -0
  14. data/lib/ceml/lang/tt/instructions.rb +1130 -0
  15. data/lib/ceml/lang/tt/instructions.treetop +86 -0
  16. data/lib/ceml/lang/tt/lexer.rb +1804 -0
  17. data/lib/ceml/{tt → lang/tt}/lexer.treetop +70 -7
  18. data/lib/ceml/lang/tt/scripts.rb +647 -0
  19. data/lib/ceml/{tt → lang/tt}/scripts.treetop +2 -2
  20. data/lib/ceml/lang.rb +10 -0
  21. data/lib/ceml/models/audition.rb +65 -0
  22. data/lib/ceml/models/bundle.rb +64 -0
  23. data/lib/ceml/models/cast.rb +108 -0
  24. data/lib/ceml/models/castable.rb +81 -0
  25. data/lib/ceml/{incident.rb → models/incident.rb} +63 -15
  26. data/lib/ceml/models/incident_model.rb +100 -0
  27. data/lib/ceml/models/incident_role_slot.rb +16 -0
  28. data/lib/ceml/models/player.rb +80 -0
  29. data/lib/ceml/models/queue.rb +12 -0
  30. data/lib/ceml/models/waiting_room.rb +40 -0
  31. data/lib/ceml/models.rb +16 -0
  32. data/lib/ceml/processor.rb +162 -0
  33. data/lib/ceml.rb +7 -14
  34. data/test/askchain.ceml +6 -0
  35. data/test/compliment.ceml +4 -0
  36. data/test/dialogues/accept.ceml +24 -0
  37. data/test/dialogues/basic_seed.ceml +26 -0
  38. data/test/dialogues/jordan.ceml +121 -0
  39. data/test/helper.rb +44 -39
  40. data/test/jane.ceml +48 -0
  41. data/test/{test_casting.rb → lang/test_casting.rb} +5 -0
  42. data/test/lang/test_instructions.rb +42 -0
  43. data/test/{test_scripts.rb → lang/test_scripts.rb} +3 -2
  44. data/test/sync.ceml +6 -0
  45. data/test/test_castable.rb +20 -0
  46. data/test/test_dialogues.rb +58 -0
  47. data/test/test_incident.rb +64 -127
  48. metadata +54 -30
  49. data/.gitignore +0 -23
  50. data/lib/ceml/confluence.rb +0 -63
  51. data/lib/ceml/role.rb +0 -61
  52. data/lib/ceml/tt/casting.treetop +0 -65
  53. data/lib/ceml/tt/instructions.treetop +0 -91
  54. data/test/test_instructions.rb +0 -27
  55. 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.select{ |p| !p[:released] }
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
- # puts "trying #{@this[:id]}"
39
- next unless instr = seq[pc]
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
- if not rolematch(instr.shift)
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
- next unless send(*instr)
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
- @players.each do |p|
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 @players.all?{ |p| p[:synced] == pc }
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[:released] = x[:tag]
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,12 @@
1
+ module CEML
2
+ class Queue
3
+ def id; 'ceml_q'; end
4
+ include Redis::Objects
5
+ list :calls, :marshal => true
6
+
7
+ def run(klass)
8
+ p = klass.new
9
+ while call = calls.shift; p.send(*call); end
10
+ end
11
+ end
12
+ 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
@@ -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
+