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 +22 -0
- data/lib/fuzzy_logic.rb +188 -0
- data/lib/linear_interpolator.rb +29 -0
- data/lib/shattered_model.rb +11 -0
- data/lib/shattered_model_tester.rb +2 -0
- metadata +40 -0
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
|
data/lib/fuzzy_logic.rb
ADDED
@@ -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'
|
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: []
|