shattered_model 0.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.
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: []