shattered_pack 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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