shattered_model 0.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/base.rb ADDED
@@ -0,0 +1,22 @@
1
+ include ShatteredSupport
2
+
3
+ module ShatteredModel
4
+ def self.append_features(base)
5
+ super
6
+ base.extend(ClassMethods)
7
+ end
8
+ module ClassMethods
9
+ def fuzzy_logic( file )
10
+ before_init_set("self", {:fuzzy_logic => [file]})
11
+ end
12
+ end
13
+ class Base < ShatteredSupport::Base
14
+ def fuzzy_logic=(file)
15
+ @fuzzy_logic = FuzzyLogic.new
16
+ @fuzzy_logic.parse_fuzzy_file(File.dirname(__FILE__)+"/#{file}")
17
+ end
18
+ def update_fuzzy_logic
19
+ @fuzzy_logic.update(self)
20
+ end
21
+ end
22
+ 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
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
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
+ begin
2
+ # require File.dirname(__FILE__) + '/../../shattered_support/lib/shattered_support'
3
+ require 'shattered_support'
4
+ rescue MissingSourceFile
5
+ require 'rubygems'
6
+ require 'shattered_support'
7
+ end
8
+
9
+ require File.dirname(__FILE__) + '/base'
10
+ require File.dirname(__FILE__) + '/fuzzy_logic'
11
+ require File.dirname(__FILE__) + '/linear_interpolator'
@@ -0,0 +1,2 @@
1
+ $: << File.dirname(__FILE__) + "/../../shattered_support/lib"
2
+ require 'shattered_model'
metadata ADDED
@@ -0,0 +1,40 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.10
3
+ specification_version: 1
4
+ name: shattered_model
5
+ version: !ruby/object:Gem::Version
6
+ version: "0.3"
7
+ date: 2006-04-23
8
+ summary: "Shattered Model: Provides basic commands any game object needs."
9
+ require_paths:
10
+ - lib
11
+ email:
12
+ homepage: http://www.hastilymade.com
13
+ rubyforge_project:
14
+ description: Shattered Model is the basis for the shattered powered game. It facilitatess the most testable and gameplay related code.
15
+ autorequire: shattered_model
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: "true"
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ authors: []
28
+ files:
29
+ - lib/base.rb
30
+ - lib/fuzzy_logic.rb
31
+ - lib/linear_interpolator.rb
32
+ - lib/shattered_model.rb
33
+ - lib/shattered_model_tester.rb
34
+ test_files: []
35
+ rdoc_options: []
36
+ extra_rdoc_files: []
37
+ executables: []
38
+ extensions: []
39
+ requirements: []
40
+ dependencies: []