class DCI::Context

Public Class Methods

[](*args) click to toggle source

A short way for ContextSubclass.new(players_and_extra_args).run(extra_args)

# File lib/drsi/dci/context.rb, line 22
def [](*args)
  new(*args).run
end
inherited(subklass) click to toggle source

Every subclass of Context has is own class and instance method roles defined. The instance method delegates value to the class.

# File lib/drsi/dci/context.rb, line 11
def inherited(subklass)
  subklass.class_eval do
    @roles ||= {}
    def self.roles; @roles end
    def roles; self.class.roles end
    private :roles
    assign_unplay_roles_within_instance_methods!
  end
end
new(args={}) click to toggle source

Instances of a defined subclass of Context are initialized checking first that all subclass defined roles are provided in the creation invocation raising an error if any of them is missing. Once the previous check is met, every object playing in the context instance is associated to the stated role. Non players args are associated to instance_variables and readers defined.

# File lib/drsi/dci/context.rb, line 117
def initialize(args={})
  check_all_roles_provided_in!(args)
  players, noplayers = args.partition {|key, *| roles.has_key?(key)}.map {|group| Hash[*group.flatten(1)]}
  @_players = players
  @settings = noplayers
end
roles() click to toggle source
# File lib/drsi/dci/context.rb, line 14
def self.roles; @roles end

Private Class Methods

assign_unplay_roles_within_instance_method!(methodname) click to toggle source

Wraps the given klass's methodname to assign/un-assign roles to player objects before and after actual method execution.

# File lib/drsi/dci/context.rb, line 96
def assign_unplay_roles_within_instance_method!(methodname)
  class_eval do
    method_object = instance_method(methodname)
    define_method(methodname) do |*args, &block|
      # puts "Context: #{self} - in method #{methodname} - #{do_play_unplay_p} assigning_roles"
      extending_ticker = players_play_role!
      method_object.bind(self).call(*args, &block).tap do
        # puts "Context: #{self} - in method #{methodname} - #{do_play_unplay_p} un-assigning_roles"
        players_unplay_role!(extending_ticker)
      end
    end
  end
end
assign_unplay_roles_within_instance_methods!() click to toggle source

Wraps every existing public/protected instance_method of klass (not superclasses methods) to assign roles to player objects before the execution and to un-assign roles from player objects at the end of execution just before returning control. Also inject code to magically do the same to every new method defined in klass.

# File lib/drsi/dci/context.rb, line 81
def assign_unplay_roles_within_instance_methods!
  instance_methods(false).each do |existing_methodname|
    assign_unplay_roles_within_instance_method!(existing_methodname)
  end
  def self.method_added(methodname)
    if not @context_internals and public_method_defined?(methodname)
      @context_internals = true
      assign_unplay_roles_within_instance_method!(methodname)
      @context_internals = false
    end
  end
end
create_role_from(key, &block) click to toggle source

Adds a new entry to the roles accumulator hash.

# File lib/drsi/dci/context.rb, line 41
def create_role_from(key, &block)
  roles.merge!(key => create_role_module_from(key, &block))
end
create_role_module_from(rolekey, &block) click to toggle source

Defines and return a new subclass of DCI::Role named after the given rolekey and with body the given block.

# File lib/drsi/dci/context.rb, line 46
def create_role_module_from(rolekey, &block)
  new_mod_name = rolekey.to_s.split(/\_+/).map(&:capitalize).join('')
  const_set(new_mod_name, Module.new(&block))
  const_get(new_mod_name).tap {|mod| mod.send(:extend, ::DCI::Role)}
end
define_mate_roleplayers_readers_after_newrole(new_rolekey) click to toggle source

After a new role is defined, you've got to create a reader method for this new role in the rest of context roles, and viceverse: create a reader method in the new role mod for each of the other roles in the context. This method does exactly this.

# File lib/drsi/dci/context.rb, line 61
def define_mate_roleplayers_readers_after_newrole(new_rolekey)
  new_role_mod = roles[new_rolekey]
  mate_roles   = mate_roles_of(new_rolekey)
  mate_roles.each do |mate_rolekey, mate_role_mod|
    mate_role_mod.send(:add_role_reader_for!, new_rolekey)
    new_role_mod.send(:add_role_reader_for!,  mate_rolekey)
  end
end
define_reader_for_role(rolekey) click to toggle source

Defines a private reader to allow a context instance access to the roleplayer object associated to the given rolekey.

# File lib/drsi/dci/context.rb, line 53
def define_reader_for_role(rolekey)
  private
  attr_reader rolekey
end
mate_roles_of(rolekey) click to toggle source

For a give role key, returns a hash with the rest of the roles (pair :rolekey => role_mod) in the context it belongs to.

# File lib/drsi/dci/context.rb, line 71
def mate_roles_of(rolekey)
  roles.dup.tap do |roles|
    roles.delete(rolekey)
  end
end
method_added(methodname) click to toggle source
# File lib/drsi/dci/context.rb, line 85
def self.method_added(methodname)
  if not @context_internals and public_method_defined?(methodname)
    @context_internals = true
    assign_unplay_roles_within_instance_method!(methodname)
    @context_internals = false
  end
end
role(rolekey, &block) click to toggle source

The macro role is defined to allow a subclass of Context to define roles in its definition body. Every new role is added to the hash of roles in that Context subclass. A reader to access the object playing the new role is also defined and available in every instance of the context subclass. Also, readers to allow each other role access are defined.

# File lib/drsi/dci/context.rb, line 33
def role(rolekey, &block)
  raise "role name must be a symbol" unless rolekey.is_a?(Symbol)
  create_role_from(rolekey, &block)
  define_reader_for_role(rolekey)
  define_mate_roleplayers_readers_after_newrole(rolekey)
end

Private Instance Methods

assign_role_to_player!(rolekey, player, extending_ticker:{}) click to toggle source

Associates a role to an intended player:

- The player object is 'extended' with the methods of the role to play.
- The player get access to the context it is playing.
- The player get access to the rest of players in its context through instance methods named after their role keys.
- This context instance get access to this new role player through an instance method named after the role key.
# File lib/drsi/dci/context.rb, line 168
def assign_role_to_player!(rolekey, player, extending_ticker:{})
  role_mod = roles[rolekey]
  # puts "  Context: #{self} - assigning role #{rolekey} to #{player}"
  ::DCI::Multiplayer(player).each do |roleplayer|
    if player_already_playing_role_in_this_context?(roleplayer, rolekey)
      extending_ticker.merge!("#{roleplayer.object_id}_#{rolekey}" => false)
    else
      extending_ticker.merge!("#{roleplayer.object_id}_#{rolekey}" => true)
      roleplayer.__play_role!(rolekey, role_mod, self)
    end
  end
  instance_variable_set(:"@#{rolekey}", player)
end
check_all_roles_provided_in!(players={}) click to toggle source

Checks there is a player for each role. Raises and error message in case of missing roles.

# File lib/drsi/dci/context.rb, line 138
def check_all_roles_provided_in!(players={})
  missing_rolekeys = missing_roles(players)
  raise "missing roles #{missing_rolekeys}" unless missing_rolekeys.empty?
end
missing_roles(players={}) click to toggle source

The list of roles with no player provided

# File lib/drsi/dci/context.rb, line 144
def missing_roles(players={})
  (roles.keys - players.keys)
end
player_already_playing_role_in_this_context?(player, rolekey) click to toggle source
# File lib/drsi/dci/context.rb, line 148
def player_already_playing_role_in_this_context?(player, rolekey)
  context = player.send(:__context)
  return false unless player.send(:__rolekey) == rolekey
  context == self or context.class == self.class
end
players_play_role!() click to toggle source

Associates every role to the intended player.

# File lib/drsi/dci/context.rb, line 155
def players_play_role!
  extending_ticker = {}
  roles.keys.each do |rolekey|
    assign_role_to_player!(rolekey, @_players[rolekey], extending_ticker: extending_ticker)
  end
  extending_ticker
end
players_unplay_role!(extending_ticker) click to toggle source

Disassociates every role from the playing object.

# File lib/drsi/dci/context.rb, line 183
def players_unplay_role!(extending_ticker)
  roles.keys.each do |rolekey|
    ::DCI::Multiplayer(@_players[rolekey]).each do |roleplayer|
      # puts "  Context: #{self} - un-assigning role #{rolekey} to #{roleplayer}"
      roleplayer.__unplay_last_role! if extending_ticker["#{roleplayer.object_id}_#{rolekey}"]
    end
    # 'instance_variable_set(:"@#{rolekey}", nil)
  end
end
settings(*keys) click to toggle source

Private access to the extra args received in the instantiation. Returns a hash (copy of the instantiation extra args) with only the args included in 'keys' or all of them when called with no args.

# File lib/drsi/dci/context.rb, line 130
def settings(*keys)
  return @settings.dup if keys.empty?
  entries = @settings.reject {|k, v| !keys.include?(k)}
  keys.size == 1 ? entries.values.first : entries
end