shattered_pack 0.3.3

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.
@@ -0,0 +1,3 @@
1
+ %w(base fuzzy_logic linear_interpolator).each do |dependency|
2
+ require "shattered_model/#{dependency}"
3
+ end
@@ -0,0 +1,24 @@
1
+ module ShatteredModel #:nodoc:
2
+ def self.append_features(base)
3
+ super
4
+ base.extend(ClassMethods)
5
+ end
6
+ module ClassMethods #:nodoc:
7
+ def fuzzy_logic( file )
8
+ before_init_set("self", {:fuzzy_logic => [file]})
9
+ end
10
+ end
11
+ # Models encompass all of the game play logic and the game specific data.
12
+ #
13
+ # Models are useful for unit tests and for making game rules.
14
+ # They are where all logic (AI/collisions/etc) go.
15
+ class Base < ShatteredPack::Base
16
+ def fuzzy_logic=(file) #:nodoc:
17
+ @fuzzy_logic = FuzzyLogic.new
18
+ @fuzzy_logic.parse_fuzzy_file(File.dirname(__FILE__)+"/#{file}")
19
+ end
20
+ def update_fuzzy_logic #:nodoc:
21
+ @fuzzy_logic.update(self)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,188 @@
1
+ require 'yaml'
2
+ # This provides basic fuzzy logic. That is, it will tell you what properties any given
3
+ # attribute contains based upon previous definition.
4
+ #
5
+ # Example:
6
+ # player_logic.add_classification( :hp, :dieing, :hp, :max => 5, :min => 45 )
7
+ # player_logic.add_classification( :hp, :healthy, :max => 80, :min => 65 )
8
+ # player_logic.hp(15) => [[:dieing, 0.75], [:healthy, 0.0]]
9
+ class FuzzyLogic #:nodoc:
10
+ attr_reader :consequences
11
+ def initialize
12
+ @attributes = {}
13
+ @consequences = {}
14
+ end
15
+
16
+ def add_consequence( consequence, classifications={} )
17
+ @consequences[classifications] ||= []
18
+ @consequences[classifications] += [consequence]
19
+ end
20
+
21
+ def add_classification( attribute, classification, options = {} )
22
+ define_attribute(attribute) if(@attributes[attribute].nil?)
23
+ define_interpolator(attribute,classification,options)
24
+ end
25
+
26
+ def consequences_of(options={})
27
+ # transform the passed in attributes to probability sets
28
+ valid_sequences = []
29
+ options.each_pair do |attribute, value|
30
+ valid_sequences << {attribute => classifications_of(attribute,value)}
31
+ end
32
+ # combine the probability sets
33
+ valid_combinations = compile_consequence_combinations(valid_sequences)
34
+ # realize the consequences of the combined probability sets
35
+ return consequences_of_combinations(valid_combinations)
36
+ end
37
+
38
+ def parse_fuzzy_file( file_name )
39
+ file_name += ".yaml" if not(file_name =~ /\.yaml$/)
40
+ raise ArgumentError, "Cannot find #{file_name}" if not(File.file? file_name)
41
+ begin
42
+ fuzzy_file = YAML::load(File.open(file_name))
43
+ rescue ArgumentError => bang
44
+ raise ArgumentError, "Error when loading #{file_name} : #{bang.message}"
45
+ end
46
+ parse_attributes(fuzzy_file)
47
+ add_consequences_from_parsed(parse_consequences(fuzzy_file['consequences']))
48
+ end
49
+
50
+ # The algorithm always performs an action on update.
51
+ def update(object)
52
+ status = {}
53
+ @attributes.each_pair do |attribute, classifications|
54
+ status.merge!({attribute => object.send(attribute)})
55
+ end
56
+ consequences = consequences_of(status)
57
+ total_probability = 0
58
+ consequences.each_pair do |actions, probability|
59
+ total_probability+=probability
60
+ end
61
+ action_at_probability = rand*total_probability
62
+ total_probability = 0
63
+ consequences.each_pair do |actions, probability|
64
+ total_probability += probability
65
+ if( action_at_probability < total_probability )
66
+ actions.each do |action|
67
+ object.send(action)
68
+ end
69
+ break
70
+ end
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ # This function recursively moves through each valid combination of probabilities.
77
+ # It sums the probabilities by multiplication.
78
+ def compile_consequence_combinations(valid_sequences, compiled={}, probability=1.0)
79
+ retv = []
80
+ attribute = valid_sequences[0].keys[0]
81
+ classifications = valid_sequences[0][attribute]
82
+ classifications.each do |classification, chance|
83
+ next if chance == 0
84
+ nprobability=probability*chance
85
+ compiled[attribute]=classification
86
+ if(valid_sequences.length == 1)
87
+ retv << [compiled.dup, nprobability]
88
+ else
89
+ retv += compile_consequence_combinations(valid_sequences[1..-1],compiled,nprobability)
90
+ end
91
+ end
92
+ return retv
93
+ end
94
+
95
+ def consequences_of_combinations(combinations)
96
+ consequences = {}
97
+ combinations.each do |combination, probability|
98
+ # where a = { {1 => 2}, 3 } and b = {1,2}, a[b] != 3
99
+ # in order to circumvent this (error?) we use the fact that
100
+ # a.keys[0] == b
101
+ @consequences.keys.each do |index|
102
+ next if index != combination
103
+ consequences[@consequences[index]] ||= 0
104
+ consequences[@consequences[index]]+=probability
105
+ end
106
+ end
107
+ return consequences
108
+ end
109
+
110
+ def parse_attributes(yaml)
111
+ yaml['attributes'].each_pair do |attribute, classifications|
112
+ classifications.each_pair do |classification, options|
113
+ options.keys.each do |key|
114
+ options[key.to_sym]=options.delete key
115
+ end
116
+ add_classification( attribute.to_sym, classification.to_sym, options )
117
+ end
118
+ end
119
+ end
120
+
121
+ def parse_consequences(consequences,result={})
122
+ compilation = []
123
+ consequences.each_pair do |consequence, action|
124
+ nresult = result.dup
125
+ nresult.merge!(attribute_for(consequence.to_sym) => consequence.to_sym)
126
+ if action.is_a? String
127
+ compilation += [nresult => action.to_sym]
128
+ else
129
+ compilation+=parse_consequences(action,nresult)
130
+ end
131
+ end
132
+ return compilation
133
+ end
134
+
135
+ def add_consequences_from_parsed(compilation)
136
+ compilation.each do |item|
137
+ item.each_pair do |index, action|
138
+ add_consequence(action, index)
139
+ end
140
+ end
141
+ end
142
+
143
+ def attribute_for(unknown_classification)
144
+ @attributes.each_pair do |attribute, classifications|
145
+ classifications.keys.each do |classification|
146
+ return attribute if classification == unknown_classification
147
+ end
148
+ end
149
+ return nil
150
+ end
151
+
152
+ def define_attribute(attribute)
153
+ @attributes[attribute] = {}
154
+ eval <<-EOF
155
+ class << self
156
+ define_method(:#{attribute}) do |at|
157
+ classifications_of(:#{attribute},at)
158
+ end
159
+ end
160
+ EOF
161
+ end
162
+
163
+ def define_interpolator( attribute, classification,options )
164
+ interpolator = LinearInterpolator.new
165
+ @attributes[attribute][classification] = interpolator
166
+
167
+ add_point(interpolator, options[:max],1)
168
+ add_point(interpolator, options[:min],0)
169
+ end
170
+
171
+ def classifications_of( attribute, at )
172
+ retv = []
173
+ @attributes[attribute].each_pair do |classification,interpolator|
174
+ retv << [ classification, interpolator.value_at(at) ]
175
+ end
176
+ return retv.sort_by { |classification| (1.0-classification[1]) }
177
+ end
178
+
179
+ def add_point( interpolator, points, value )
180
+ if(points.is_a? Array)
181
+ points.each do |x|
182
+ interpolator.add_point(x,value)
183
+ end
184
+ else
185
+ interpolator.add_point(points,value)
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,29 @@
1
+ # This provides basic linear interpolation between any number of arbitrarily defined points.
2
+ # It is used for fuzzy logic. Although, fuzzy logic based on a statistically normal curve
3
+ # provides best results, linear interpolation provides a "gud 'nuf" approach.
4
+ class LinearInterpolator #:nodoc:
5
+ def add_point(x,y)
6
+ @points ||= []
7
+ @points << [x,y]
8
+ @points = @points.sort_by { |x| x[0] }
9
+ end
10
+ def value_at(x)
11
+ return 0 if @points.nil?
12
+ return @points[0][1] if x < @points[0][0]
13
+ return @points[-1][1] if x > @points[-1][0]
14
+
15
+ after,before=nil,nil
16
+ @points.each_with_index do |point,i|
17
+ if(point[0] >= x)
18
+ after = point
19
+ before = @points[i-1]
20
+ end
21
+ end
22
+
23
+ m=(before[1]-after[1])/(before[0]-after[0]).to_f
24
+ x-=before[0]
25
+ b=before[1]
26
+
27
+ return m*x+b
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ $: << File.expand_path(File.dirname(__FILE__))
2
+
3
+ %w( base runner ).each do |component|
4
+ require "shattered_pack/#{component}"
5
+ end
6
+
7
+ require 'shattered_support'
8
+ require 'shattered_ogre'
9
+ require 'shattered_model'
10
+ require 'shattered_view'
11
+ require 'shattered_controller'
@@ -0,0 +1,127 @@
1
+
2
+ $:.unshift(File.dirname(__FILE__)) unless
3
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
4
+
5
+ require 'timer/timer'
6
+ require 'pre_initialize/pre_initialize'
7
+
8
+ module ShatteredPack #:nodoc:
9
+
10
+ class Base
11
+ attr_accessor :time_elapsed
12
+ alias_method :per_second, :time_elapsed
13
+
14
+ # This is overwritten to allow for a pre_initialize before initialize
15
+ def self.new(*options) #:nodoc
16
+ new_base = allocate
17
+ new_base.pre_initialize
18
+ new_base.send(:initialize, *options)
19
+ return new_base
20
+ end
21
+
22
+ # Retrieve the current state
23
+ def state
24
+ Configuration.environment[:state]
25
+ end
26
+
27
+ # TODO - is this called anymore?
28
+ def update_event(time_elapsed) #:nodoc:
29
+ actors.each do |actor|
30
+ actor.update_actors(time_elapsed)
31
+ actor.update(time_elapsed)
32
+ end
33
+ end
34
+
35
+ # remove_from_scene? should just be defined in model.
36
+ # Refactor? TODO
37
+ def remove_from_scene? #:nodoc:
38
+ return false
39
+ end
40
+
41
+ # TODO outdated?
42
+ def update_actors(time_elapsed) #:nodoc:
43
+ update_event time_elapsed
44
+ remove_dead_actors
45
+ end
46
+
47
+ # TODO outdated?
48
+ def remove_dead_actors #:nodoc:
49
+ actors.each do |actor|
50
+ remove_from_scene actor if actor.remove_from_scene?
51
+ end
52
+ end
53
+
54
+ # TODO outdated?
55
+ def remove_from_scene(actor)
56
+ actors.delete actor
57
+ actor.unload!
58
+ end
59
+
60
+
61
+ public
62
+
63
+ # attr helpers. These are instance level attr_* functions.
64
+ def attr_reader(*args)
65
+ name, value = args
66
+ attr_define(:reader, name, value)
67
+ end
68
+
69
+ # attr helpers. These are instance level attr_* functions.
70
+ # attr_writer accepts a value as the second argument
71
+ def attr_writer(*args)
72
+ name, value = args
73
+ attr_define(:writer, name, value)
74
+ end
75
+
76
+ # attr helpers. These are instance level attr_* functions.
77
+ # attr_accessor accepts a value as the second argument
78
+ def attr_accessor(*args)
79
+ name, value = args
80
+ attr_define(:accessor, name, value)
81
+ end
82
+
83
+ # Define a block to execute when an object is unloaded.
84
+ def when_unloaded(&block)
85
+ @unloaded_events ||= []
86
+ @unloaded_events << block
87
+ end
88
+
89
+ protected
90
+
91
+ def attr_define(accessor_level, name, value)
92
+ self.class.send("attr_#{accessor_level}".to_sym, "#{name}".to_sym)
93
+ instance_variable_set("@#{name}".to_sym, value) if !value.nil?
94
+ end
95
+
96
+ private
97
+
98
+ def disabled?
99
+ @unloaded_events == nil
100
+ end
101
+
102
+ def unload!
103
+ return if disabled?
104
+ @unloaded_events.each do |event|
105
+ event.call
106
+ end
107
+ @unloaded_events = nil
108
+ end
109
+
110
+ end
111
+
112
+ class Error < StandardError # :nodoc:
113
+ end
114
+
115
+ class RetossError < StandardError # :nodoc:
116
+ attr_accessor :message
117
+ def initialize(error, message)
118
+ self.message = message
119
+ set_backtrace error.backtrace
120
+ end
121
+ end
122
+ end
123
+
124
+ ShatteredPack::Base.class_eval do
125
+ include ShatteredPack::Timer
126
+ include ShatteredPack::PreInitialize
127
+ end
@@ -0,0 +1,105 @@
1
+ module ShatteredPack
2
+ module PreInitialize #:nodoc:all
3
+ def self.append_features(base)
4
+ super
5
+ base.extend(ClassMethods)
6
+ base.send(:include, InstanceMethods)
7
+ end
8
+
9
+ BEFORE_INIT_CALL_VALUES = :before_init_call_values
10
+ BEFORE_INIT_SET_VALUES = :before_init_set_values
11
+
12
+ module ClassMethods
13
+
14
+ # In the pre_initialization phase (see #before_init_call), send an object a bunch of sets.
15
+ #
16
+ # This is used in actor, camera, mesh, and virtually everywhere to support the format of:
17
+ # mesh "ruby", :position => v(1,0,0)
18
+ #
19
+ # becomes
20
+ # before_init_set( :ruby, {:position => v(1,0,0) } )
21
+ # becomes
22
+ # ruby.position=v(1,0,0)
23
+ # when the obect is initialized.
24
+ def before_init_set(variable, options={})
25
+ self.write_inheritable_array(BEFORE_INIT_SET_VALUES, [[ variable, options ]] )
26
+ end
27
+
28
+ # All shattered objects have a pre_initialization phase. Use this to specify that
29
+ # a function should be called when the object is created.
30
+ #
31
+ # _Example_:
32
+ # class BulletModel < ShatteredModel::Base
33
+ # before_init_call(:calculate_trajectory, v(0,0,0), v(0,0,1))
34
+ # def calculate_trajectory
35
+ # #called before initialize is
36
+ # end
37
+ # end
38
+ #
39
+ def before_init_call(function, *arguments)
40
+ self.write_inheritable_array(BEFORE_INIT_CALL_VALUES, [[function, [*arguments]]])
41
+ end
42
+ end
43
+ module InstanceMethods
44
+ # This function is called after an object is allocated, but before it's initialized.
45
+ #
46
+ # See ShatteredPack::ClassMethods#before_init_call for usage.
47
+ def pre_initialize #:nodoc:
48
+ pre_initialize_set
49
+ pre_initialize_call
50
+ end
51
+
52
+ # Used in pre_initialize
53
+ def each_init_value(name) #:nodoc:
54
+ startup_attributes = self.class.read_inheritable_attribute( name ) || []
55
+ startup_attributes.each do |actor, options|
56
+ next if options.nil?
57
+ yield actor, options
58
+ end
59
+ end
60
+
61
+ # Used in pre_initialize for before_init_set
62
+ def pre_initialize_set #:nodoc:
63
+ each_init_value(BEFORE_INIT_SET_VALUES) do |variable, options|
64
+ call_object_function_for_each_key( variable, options )
65
+ end
66
+ end
67
+
68
+ # Used in pre_initialize for before_init_call
69
+ def pre_initialize_call #:nodoc:
70
+ each_init_value(BEFORE_INIT_CALL_VALUES) do |function, args|
71
+ call_object_function( :self, function, args )
72
+ end
73
+ end
74
+
75
+ def call_object_function_for_each_key( actor, options )
76
+ options.each_pair do |action, params|
77
+ call_object_function actor, "#{action}=", params
78
+ end
79
+ end
80
+
81
+ def call_object_function(actor, action, params)
82
+ begin
83
+ if params.is_a? Symbol #|| (params.length == 1 && params[0].is_a?(Symbol)) this will allow substitution in before_init_call. This may not be intended behavior.
84
+ params = eval(params.to_s)
85
+ end
86
+ rescue NameError
87
+ puts "It is not advisable to pass #{params.inspect} to #{action.inspect} for #{actor.inspect}."
88
+ puts " It will try to be evaluated. Use a string instead."
89
+ end
90
+ begin
91
+ sendee = eval(actor.to_s)
92
+ if(params.is_a? Array)
93
+ sendee.send( action.to_sym, *params )
94
+ else
95
+ sendee.send( action.to_sym, params )
96
+ end
97
+ rescue NoMethodError, ArgumentError => bang
98
+ message="Error upon #{actor.to_s}.send(#{action.to_sym.inspect}, #{params.inspect}):\n\r #{bang.class}: \n\r\t #{bang.message}\n\r #{bang.backtrace[0]}\n\r"
99
+ bang = ShatteredPack::RetossError.new(bang,message)
100
+ raise bang
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end