minigame 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YWY0MTkxZDc2ZTg1NWExMTgwODMyMDAwNjEwNmQ3YzZiODJmYTM1Zg==
5
+ data.tar.gz: !binary |-
6
+ NGYzYTBiYTQ0ODFkNDg5OWVlYWRiZDJmYTJjNDIyNzZlY2VmNzk3Yw==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ OTM4MDgzZGY3MWNlNWUwOWY0ZGE0ZmM2ZDg4NWQ0NmQxNzU0YTc2Y2JiNWQy
10
+ MWMzMTA3MmY5MDk1Mjk0ZjVjMGNkODNlYTU0OGU2ZjJlMGYzYTdjYTBmOWE3
11
+ NTA2OWMzMDhhNGU2OGFmYjFhNzA5ZGJiNjVjZGMyODg2MThiYTg=
12
+ data.tar.gz: !binary |-
13
+ YjhlN2JiMzQzMjRmZDBmOGFjODQyMjA5Nzk5ZjRkZDAwZGYyN2Y4NGM3YjQ4
14
+ ZjZhM2NjMWI4YmNlNWQwZjc3OTVkMDk3ZDQwODhiNGJjNmVhMzc4YWUxNjI1
15
+ ZWJhZjk5YTNmMzUyMWMyMzUwYWNkY2ZiYTNjMzgzNzk1Y2RjNzA=
data/.gitignore ADDED
@@ -0,0 +1,99 @@
1
+ #----------------------------------------------------------------------------
2
+ # Ignore these files when commiting to a git repository.
3
+ #
4
+ # See http://help.github.com/ignore-files/ for more about ignoring files.
5
+ #
6
+ # The original version of this file is found here:
7
+ # https://github.com/RailsApps/rails3-application-templates/raw/master/files/gitignore.txt
8
+ #
9
+ # Corrections? Improvements? Create a GitHub issue:
10
+ # http://github.com/RailsApps/rails3-application-templates/issues
11
+ #----------------------------------------------------------------------------
12
+
13
+ # bundler state
14
+ /.bundle
15
+ /vendor/bundle/
16
+
17
+ # minimal Rails specific artifacts
18
+ db/*.sqlite3
19
+ /log/*.log
20
+ /tmp
21
+
22
+ # various artifacts
23
+ **.war
24
+ *.rbc
25
+ *.sassc
26
+ .rspec
27
+ .redcar/
28
+ .sass-cache
29
+ /config/config.yml
30
+ /config/database.yml
31
+ /coverage.data
32
+ /coverage/
33
+ /db/*.javadb/
34
+ /db/*.sqlite3-journal
35
+ /doc/api/
36
+ /doc/app/
37
+ /doc/features.html
38
+ /doc/specs.html
39
+ /public/cache
40
+ /public/stylesheets/compiled
41
+ /public/system
42
+ /spec/tmp/*
43
+ /cache
44
+ /capybara*
45
+ /capybara-*.html
46
+ /gems
47
+ /rerun.txt
48
+ /spec/requests
49
+ /spec/routing
50
+ /spec/views
51
+ /specifications
52
+ # If you find yourself ignoring temporary files generated by your text editor
53
+ # or operating system, you probably want to add a global ignore instead:
54
+ # git config --global core.excludesfile ~/.gitignore_global
55
+ #
56
+ # Here are some files you may want to ignore globally:
57
+
58
+ # scm revert files
59
+ **.orig
60
+
61
+ # Mac finder artifacts
62
+ .DS_Store
63
+ *~
64
+
65
+ # Netbeans project directory
66
+ /nbproject/
67
+
68
+ # RubyMine project files
69
+ .idea
70
+
71
+ # Textmate project files
72
+ /*.tmproj
73
+
74
+ # vim artifacts
75
+ **.swp
76
+ **.swo
77
+
78
+ # Windows artifacts
79
+ Thumbs.db
80
+
81
+ # ctags artifact
82
+ /tags
83
+
84
+ *.rbc
85
+ *.sassc
86
+ .sass-cache
87
+ capybara-*.html
88
+ .rspec
89
+ /.bundle
90
+ /vendor/bundle
91
+ /log/*
92
+ /tmp/*
93
+ /db/*.sqlite3
94
+ /public/system/*
95
+ /coverage/
96
+ /spec/tmp/*
97
+ **.orig
98
+ rerun.txt
99
+ pickle-email-*.html
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3-p327@minitest-dm --create
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in minigame.gemspec
4
+ gemspec
5
+ gem 'debugger'
data/Gemfile.lock ADDED
@@ -0,0 +1,27 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ minigame (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ columnize (0.3.6)
10
+ debugger (1.6.1)
11
+ columnize (>= 0.3.1)
12
+ debugger-linecache (~> 1.2.0)
13
+ debugger-ruby_core_source (~> 1.2.3)
14
+ debugger-linecache (1.2.0)
15
+ debugger-ruby_core_source (1.2.3)
16
+ minitest (5.0.6)
17
+ rake (10.1.0)
18
+
19
+ PLATFORMS
20
+ ruby
21
+
22
+ DEPENDENCIES
23
+ bundler (~> 1.3)
24
+ debugger
25
+ minigame!
26
+ minitest
27
+ rake
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Wavell Watson
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # MiniGame
2
+ ## Simplistic Game Theory Library in Ruby
3
+
4
+ Usage:
5
+
6
+ To create new players:
7
+ ```
8
+ employer = Player.new name: "IBM"
9
+ employee = Player.new name: "John Smith
10
+ ```
11
+
12
+ To create a new list of strategies for all players in a game:
13
+ ```
14
+ strategies = Strategy.new [{name: "Accept Training"},
15
+ {name: "Generic Training"},
16
+ {name: "Task Specific Training"},
17
+ {name: "Deny Training"}]
18
+ ```
19
+
20
+ To create new StrategyProfiles:
21
+ ```
22
+
23
+ # Regular training game -- generic strictly dominated
24
+ #
25
+ # Employer: | Task Specific | Generic
26
+ # Employee: ---------------|---------------------|------------------
27
+ # | |
28
+ # Deny Training | (-2, 0) | (-2, -1)
29
+ # ------------------------|---------------------|------------------
30
+ # Accept Training | (1, 4) | (5, -2)
31
+ # ------------------------|---------------------|------------------
32
+ strategy_profile = StrategyProfile.new
33
+ strategy_profile << {id: 1, strategy: generic, payoff: -2, player: employer}
34
+ strategy_profile << {id: 1, strategy: accept, payoff: 5, player: employee}
35
+
36
+ strategy_profile << {id: 2, strategy: generic, payoff: -1, player: employer}
37
+ strategy_profile << {id: 2, strategy: deny, payoff: -2, player: employee}
38
+
39
+ strategy_profile << {id: 3, strategy: task, payoff: 0, player: employer}
40
+ strategy_profile << {id: 3, strategy: deny, payoff: -2, player: employee}
41
+
42
+ strategy_profile << {id: 4, strategy: task, payoff: 4, player: employer}
43
+ strategy_profile << {id: 4, strategy: accept, payoff: 1, player: employee}
44
+ ```
45
+
46
+ To create a new game:
47
+ ```
48
+ gs = Game.new
49
+ gs.strategy_profiles = strategy_profile
50
+ ```
51
+
52
+ To get the list of strictly dominated strategies:
53
+ ```
54
+ gs.strictly_dominated_list
55
+
56
+ [{:name=>"Generic Training"}, {:name=>"Deny Training"}]
57
+
58
+ ```
59
+
60
+ To get the list a weakly dominated strategies:
61
+ ```
62
+ # training game -- generic weakly dominated (not realistic)
63
+ #
64
+ # Employer: | Task Specific | Generic
65
+ # Employer: ----------------|---------------------|------------------
66
+ # | |
67
+ # Deny Training | (-2, -1) | (-2, -1)
68
+ # ------------------------|---------------------|------------------
69
+ # Accept Training | (1, 4) | (5, -2)
70
+ # ------------------------|---------------------|------------------
71
+ gs.weakly_dominated_list
72
+
73
+ [{:name=>"Generic Training"}, {:name=>"Deny Training"}]
74
+
75
+ ```
76
+
77
+ To get the list strategy profiles that are nash equilibria:
78
+ ```
79
+ gs.nash
80
+
81
+ [["Accept Training", "Task Specific Training"]]
82
+
83
+ ```
84
+
85
+
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ end
6
+
7
+ desc "Run tests"
8
+ task :default => :test
@@ -0,0 +1,68 @@
1
+ module ArrayOfHashes
2
+ include Enumerable
3
+
4
+ def initialize(*array_of_hashes)
5
+ array_of_hashes = [array_of_hashes].flatten
6
+ @keys ||=[]
7
+ # checks to see if required_keys was called
8
+ # as a singleton method on the included class
9
+ @keys = self.class.keys if self.class.keys
10
+ check_array(array_of_hashes)
11
+ @array_of_hashes=array_of_hashes
12
+ end
13
+
14
+ def each(&block)
15
+ @array_of_hashes.each do |matchup|
16
+ block.call(matchup)
17
+ end
18
+ end
19
+
20
+ # creates a singleton method on the included class
21
+ # which avoids having to put 'required_keys' into the
22
+ # included classes initialize method
23
+ def self.included(klass)
24
+ class << klass
25
+ attr_accessor :keys
26
+ def required_keys(*keys)
27
+ # force single keys to be an array
28
+ keys = [keys].flatten
29
+ keys.each do |x|
30
+ raise 'Not a symbol' if !keys[0].is_a? Symbol
31
+ end
32
+ @keys=keys
33
+ end
34
+ end
35
+ end
36
+
37
+ # required keys available on the instance
38
+ def required_keys(*keys)
39
+ # force single keys to be an array
40
+ keys = [keys].flatten
41
+ keys.each do |x|
42
+ raise 'Not a symbol' if !keys[0].is_a? Symbol
43
+ end
44
+ @keys = keys
45
+ end
46
+
47
+ def required_keys=(keys)
48
+ required_keys(keys)
49
+ end
50
+
51
+ def check_array(array_of_hashes)
52
+ raise 'Not an array' if !array_of_hashes.is_a? Array
53
+ array_of_hashes.each{|x|check_keys(x)}
54
+ end
55
+
56
+ def check_keys(val)
57
+ raise "Not a Hash" if val.class != Hash
58
+ @keys.each do |x|
59
+ raise "#{x.to_s.capitalize} required" if !val.keys.include?(x)
60
+ end
61
+ end
62
+
63
+ def <<(val)
64
+ check_keys(val)
65
+ @array_of_hashes << val
66
+ end
67
+
68
+ end
@@ -0,0 +1,3 @@
1
+ class Game
2
+ include Gameable
3
+ end
@@ -0,0 +1,273 @@
1
+ module Gameable
2
+ attr_accessor :strategy_profiles
3
+
4
+ # any set of strategies that are best responses for each other are nash equilibriums
5
+ def nash
6
+ # assume the same number of strategies for each side for now
7
+ # get one player
8
+ firstp = player_enum.first
9
+ # get opposing player
10
+ opp_p = player_enum[1]
11
+ # get strategies for the player
12
+ pstrats = player_strategy_profiles(firstp)
13
+ oppstrats = player_strategy_profiles(opp_p)
14
+ # loop through first strategies and get best responses
15
+ nash ||=[]
16
+ nash = pstrats.inject([]) do |acc, strat|
17
+ #loop through opposing strategies
18
+ oppstrats.map do |opp_strat|
19
+ br = best_response_against(strat)[:strategy][:name]
20
+ br_opposing = best_response_against(opp_strat)[:strategy][:name]
21
+ # best response for both sides that match each other are nash equilibriums
22
+ if br == opp_strat[:strategy][:name] && br_opposing == strat[:strategy][:name]
23
+ acc << [br,br_opposing]
24
+ end
25
+ end
26
+ acc
27
+ end
28
+ nash.uniq
29
+ end
30
+
31
+ def best_response_against(opponent_strategy)
32
+ best_resp=[]
33
+ # get opponent player from strategy
34
+ opp_player = player_from_strategy(opponent_strategy[:strategy]).first
35
+ # get player from strategy
36
+ player = players.detect{|x| x[:name] != opp_player[:name]}
37
+ strat_payoff_list ||=[]
38
+ # only collect possible payoffs that are opposed to the passed in opponent's strategy
39
+ strat_payoff_list = player_strategy_profiles(player).inject([]) do |acc, str|
40
+ if opposing_player_strategy_profile(str)[:strategy][:name]==opponent_strategy[:strategy][:name]
41
+ acc << str
42
+ end
43
+ acc
44
+ end
45
+ best_resp = strat_payoff_list.sort{|x| x[:payoff]}.reverse
46
+ # return top performer, later need to return all strategies that are equal in payoff
47
+ best_resp.first
48
+ end
49
+
50
+ def strictly_dominated_list
51
+ dominated_list ||=[]
52
+ players.each do |player|
53
+ dominated_list << player_strategy_profiles(player).inject([]) do |acc, strat|
54
+ # if count of total strategies for opposing player = count of better payoffs, then current strategy is strictly dominated
55
+ dev = deviating_strategies(strat)
56
+ new_strat_list =[]
57
+ dev.each do |dev_strat|
58
+ if compare_deviating_strategy(strat[:strategy], dev_strat) == :strictly_dominated
59
+ new_strat_list << strat[:strategy]
60
+ end
61
+ end
62
+ acc << new_strat_list
63
+ end
64
+ end
65
+ dominated_list.flatten.uniq
66
+ end
67
+
68
+ def weakly_dominated_list
69
+ dominated_list ||=[]
70
+ players.each do |player|
71
+ dominated_list << player_strategy_profiles(player).inject([]) do |acc, strat|
72
+ dev = deviating_strategies(strat)
73
+ dev.each do |dev_strat|
74
+ if compare_deviating_strategy(strat[:strategy], dev_strat) == :weakly_dominated
75
+ acc << strat[:strategy]
76
+ else
77
+ acc
78
+ end
79
+ end
80
+ end
81
+ end
82
+ dominated_list.flatten.uniq
83
+ end
84
+
85
+ def strategies_by_player(player)
86
+ strats = strategy_profiles.inject([]) do |acc, strat|
87
+ if strat[:player] == player
88
+ acc << strat[:strategy]
89
+ else
90
+ acc
91
+ end
92
+ end
93
+ strats.uniq
94
+ end
95
+
96
+ # retrieve a player assigned to the strategy
97
+ def player_from_strategy(strategy)
98
+ stratp = strategy_profiles.detect{|x| x[:strategy][:name] == strategy[:name]}
99
+ stratp[:player]
100
+ end
101
+
102
+ # valid deviations must belong to the same player
103
+ def deviating_strategies(strategy_payoff)
104
+ strategies(strategy_payoff[:player].first).select{|x| x[:name] != strategy_payoff[:strategy][:name]}
105
+ end
106
+
107
+ # valid deviations must belong to the same player
108
+ def valid_deviation?(strategy, deviation)
109
+ selected_profile_list = self.strategy_profiles.detect{|x| x[:strategy][:name] == strategy[:name]}
110
+ matching_strategy_deviation = self.strategy_profiles.detect{|x| x[:strategy][:name] == deviation[:name] && x[:player] == selected_profile_list[:player] }
111
+ end
112
+
113
+ # for a strategy, take each strategy profile and match it against
114
+ # it's corresonding but deviating strategy profile
115
+ def compare_deviating_strategy(strategy, deviation)
116
+ raise "invalid deviation for submitted strategy" if !valid_deviation?(strategy, deviation)
117
+
118
+ one_equal = false
119
+ one_better = false
120
+ selected_profile_list = self.strategy_profiles.select{|x| x[:strategy][:name] == strategy[:name]}
121
+ # loop through strategy profiles from the strategy
122
+ selected_profile_list.each do |strat|
123
+ # store strat payoff e.g. -2
124
+ strat_payoff = strat[:payoff]
125
+ opposing_strategy = opposing_player_strategy_profile(strat)
126
+ # get deviating profile's payoff that corresponds to opposing strategy
127
+ deviating_profile = self.strategy_profiles.select do |x|
128
+ x[:strategy][:name] == deviation[:name] && opposing_player_strategy_profile(x)[:strategy][:name] == opposing_strategy[:strategy][:name]
129
+ end
130
+ # compare payoffs
131
+ # if strategy payoff is better than even just one deviating
132
+ # payoff, the strategy is not dominated
133
+ if strat_payoff > deviating_profile[0][:payoff]
134
+ return :not_dominated
135
+ # if payoff for deviating strategy is better, remember that fact
136
+ elsif strat_payoff < deviating_profile[0][:payoff]
137
+ one_better = true
138
+ # if payoff for deviating strategy is equal, remember that fact
139
+ elsif strat_payoff == deviating_profile[0][:payoff]
140
+ one_equal = true
141
+ end
142
+ end
143
+ # if made it here, the deviating profile has either all better, or some better and some equal payoffs
144
+ # if all deviation profiles are equal or better, strategy is weakly dominated by the deviation
145
+ return :weakly_dominated if one_equal
146
+ # if all deviating profiles are better, strategy is dominated by the deviation
147
+ return :strictly_dominated if one_better
148
+
149
+ raise "nothing better, equal, or worse"
150
+ end
151
+
152
+
153
+ def equal_moves(strat)
154
+ complementary_moves(strat).select{|x|x["payoff"]==strat["payoff"]}
155
+ end
156
+
157
+ def payoff(strategy, player)
158
+ strategy_profile = [@strategy_profiles.select{|x| x[:strategy]==strategy && x[:player]==player}].flatten.first
159
+ strategy_profile[:payoff] if strategy_profile
160
+ end
161
+
162
+ def player_names
163
+ players.inject([]) do |acc, x|
164
+ acc << x[:name]
165
+ end
166
+ end
167
+
168
+ def players
169
+ # need inject to remove duplicates
170
+ @strategy_profiles.inject([]) do |acc, x|
171
+ if acc.nil? || acc.empty?
172
+ # should be only one player per strategy_profile
173
+ acc << x[:player].first
174
+ elsif !acc.include?(x[:player].first)
175
+ acc << x[:player].first
176
+ else
177
+ acc
178
+ end
179
+ end
180
+ end
181
+
182
+ def player_enum
183
+ # need inject to remove duplicates
184
+ player_enums = @strategy_profiles.inject([]) do |acc, x|
185
+ acc << x[:player]
186
+ end
187
+ player_enums.uniq
188
+ end
189
+
190
+ def opposing_players(strategy_profile)
191
+ players - [strategy_profile[:player].first]
192
+ end
193
+
194
+ def player_strategy_profiles(player)
195
+ # find some way to standardize what a player is ...
196
+ # possibly plural is enumerable, singular is a hash
197
+ # same problem with other classes
198
+ if player.class == Player
199
+ @strategy_profiles.select{|x| x[:player] == player}
200
+ else
201
+ @strategy_profiles.select{|x| x[:player].first == player}
202
+ end
203
+ end
204
+
205
+ def strategies(player)
206
+ player_strategy_profiles(player).reduce([]){|acc,strat| acc << strat[:strategy]}.uniq
207
+ end
208
+
209
+ # returns the strategy associated with the passed in profile
210
+ # belonging to the opposing player
211
+ def opposing_player_strategy_profile(strategy_profile)
212
+ # list of players
213
+ # get all strategies profiles for the first player that does not equal
214
+ # the passed in player
215
+ opposing_player = opposing_players(strategy_profile).first
216
+ # return the first strategy with the strategy profile id equal
217
+ # to the passed in strategy profile id
218
+ opposing_strategies = player_strategy_profiles(opposing_player)
219
+ opposing_strategies.select{|x| x[:id]==strategy_profile[:id]}.first
220
+ end
221
+
222
+ # all other moves other than the passed in strategy (for the player)
223
+ def complementary_moves(strategy_profile)
224
+ psp = player_strategy_profiles(strategy_profile[:player])
225
+ psp.select do |x|
226
+ # not the current strategy
227
+ if x[:strategy] != strategy_profile[:strategy] &&
228
+ # only return profiles matched vs original opposing strategy
229
+ opposing_player_strategy_profile(strategy_profile)[:strategy] ==
230
+ opposing_player_strategy_profile(x)[:strategy]
231
+ true
232
+ else
233
+ false
234
+ end
235
+ end
236
+ end
237
+
238
+ # all other payoffs other than the passed in strategy (for the player)
239
+ def deviating_payoffs(strategy_profile)
240
+ psp = player_strategy_profiles(strategy_profile[:player])
241
+ dev_pay = psp.select do |x|
242
+ # not the current strategy
243
+ if x[:strategy] != strategy_profile[:strategy] &&
244
+ # only payoffs matched with original opposing strategy are
245
+ # considered deviating payoffs
246
+ opposing_player_strategy_profile(strategy_profile)[:strategy] ==
247
+ opposing_player_strategy_profile(x)[:strategy]
248
+ true
249
+ else
250
+ false
251
+ end
252
+ end
253
+ dev_pay.reduce([]){|acc,p| acc << p}
254
+ end
255
+
256
+ alias_method :other_moves, :complementary_moves
257
+
258
+ # this should list all of the deviating payoffs for the opponent's strategy
259
+ # within the passed in strategy profile. Only the payoffs corresponding
260
+ # to the opponent's strategy are considered deviating payoffs
261
+ def better_payoffs?(strategy_profile)
262
+ deviating_payoffs(strategy_profile).select{|x| x[:payoff] > strategy_profile[:payoff]}
263
+ end
264
+
265
+ def better_or_equal_payoffs?(strategy_profile)
266
+ deviating_payoffs(strategy_profile).select{|x| x[:payoff] >= strategy_profile[:payoff]}
267
+ end
268
+
269
+ def worse_payoffs?(strategy_profile)
270
+ deviating_payoffs(strategy_profile).select{|x| x[:payoff] < strategy_profile[:payoff]}
271
+ end
272
+
273
+ end
@@ -0,0 +1,12 @@
1
+ require 'data_mapper'
2
+ #class Payoff < ActiveRecord::Base
3
+ # belongs_to :player
4
+ # belongs_to :matchup
5
+ # attr_accessible :payoff
6
+ #end
7
+ class Payoff
8
+ include DataMapper::Resource
9
+ belongs_to :player
10
+ belongs_to :matchup
11
+ #attr_accessible :payoff
12
+ end
@@ -0,0 +1,5 @@
1
+ require 'minigame/array_of_hashes'
2
+ class Player
3
+ include ArrayOfHashes
4
+ required_keys :name
5
+ end
@@ -0,0 +1,4 @@
1
+ class Strategy
2
+ include ArrayOfHashes
3
+ required_keys :name
4
+ end
@@ -0,0 +1,4 @@
1
+ class StrategyProfile
2
+ include ArrayOfHashes
3
+ required_keys :id, :strategy, :payoff, :player
4
+ end
@@ -0,0 +1,3 @@
1
+ module Minigame
2
+ VERSION = "0.0.1"
3
+ end
data/lib/minigame.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "minigame/version"
2
+ require 'minigame/array_of_hashes'
3
+ require 'minigame/gameable'
4
+ require 'minigame/strategy'
5
+ require 'minigame/strategy_profile'
6
+
7
+ module MiniGame
8
+ include Gameable
9
+ end
data/minigame.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'minigame/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "minigame"
8
+ spec.version = Minigame::VERSION
9
+ spec.authors = ["Wavell Watson"]
10
+ spec.email = ["wavell.watson@gmail.com"]
11
+ spec.description = %q{A game theory library}
12
+ spec.summary = %q{A minimalistic game theory library that computes nash equilibrium}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "minitest"
24
+
25
+ end
@@ -0,0 +1,107 @@
1
+ require 'minigame'
2
+ require 'minigame/player'
3
+ require 'debugger'
4
+ require 'minitest/autorun'
5
+ require 'minitest/unit'
6
+
7
+ class MiniGameTest < Minitest::Test
8
+ class Game
9
+ include MiniGame
10
+ end
11
+
12
+ def new_players
13
+ employer = Player.new name: "IBM"
14
+ employee = Player.new name: "John Smith"
15
+ [employer, employee]
16
+ end
17
+ def new_strategies
18
+ strategies = Strategy.new [{name: "Accept Training"},
19
+ {name: "Generic Training"},
20
+ {name: "Task Specific Training"},
21
+ {name: "Deny Training"}]
22
+ # send back an array of strategies
23
+ strategies.inject([]){|x,y| x << y}
24
+ end
25
+
26
+ def new_profiles
27
+ employer, employee = new_players
28
+ accept, generic, task, deny = new_strategies
29
+ strategy_profile = StrategyProfile.new
30
+ strategy_profile << {id: 1, strategy: generic, payoff: -2, player: employer}
31
+ strategy_profile << {id: 1, strategy: accept, payoff: 5, player: employee}
32
+
33
+ strategy_profile << {id: 2, strategy: generic, payoff: -1, player: employer}
34
+ strategy_profile << {id: 2, strategy: deny, payoff: -2, player: employee}
35
+
36
+ strategy_profile << {id: 3, strategy: task, payoff: 0, player: employer}
37
+ strategy_profile << {id: 3, strategy: deny, payoff: -2, player: employee}
38
+
39
+ strategy_profile << {id: 4, strategy: task, payoff: 4, player: employer}
40
+ strategy_profile << {id: 4, strategy: accept, payoff: 1, player: employee}
41
+ strategy_profile
42
+ end
43
+
44
+ def new_weak_profiles
45
+ employer, employee = new_players
46
+ accept, generic, task, deny = new_strategies
47
+ strategy_profile = StrategyProfile.new
48
+ strategy_profile << {id: 1, strategy: generic, payoff: -2, player: employer}
49
+ strategy_profile << {id: 1, strategy: accept, payoff: 5, player: employee}
50
+
51
+ strategy_profile << {id: 2, strategy: generic, payoff: -1, player: employer}
52
+ strategy_profile << {id: 2, strategy: deny, payoff: -2, player: employee}
53
+
54
+ # not realistic
55
+ strategy_profile << {id: 3, strategy: task, payoff: -1, player: employer}
56
+ strategy_profile << {id: 3, strategy: deny, payoff: -2, player: employee}
57
+
58
+ strategy_profile << {id: 4, strategy: task, payoff: 4, player: employer}
59
+ strategy_profile << {id: 4, strategy: accept, payoff: 1, player: employee}
60
+ end
61
+
62
+ def test_mixin_game
63
+ game = Game.new
64
+ assert_equal Game, game.class
65
+ end
66
+
67
+ def test_new_player
68
+ employer, employee = new_players
69
+ assert_equal "IBM", employer.first[:name]
70
+ end
71
+
72
+ def test_new_strategy
73
+ strategies = new_strategies
74
+ assert_equal 4, strategies.count
75
+
76
+ end
77
+
78
+ def test_new_profiles
79
+ profiles = new_profiles
80
+ assert_equal 8, profiles.count
81
+ end
82
+
83
+ def test_game_w_profile
84
+ game = Game.new
85
+ game.strategy_profiles = new_profiles
86
+ assert_equal 8, game.strategy_profiles.count
87
+ end
88
+
89
+ def test_strictly_dominated_list
90
+ game = Game.new
91
+ game.strategy_profiles = new_profiles
92
+ assert_equal [{:name=>"Generic Training"}, {:name=>"Deny Training"}], game.strictly_dominated_list
93
+ end
94
+
95
+ def test_weakly_dominated_list
96
+ game = Game.new
97
+ game.strategy_profiles = new_profiles
98
+ assert_equal [{:name=>"Generic Training"}, {:name=>"Deny Training"}], game.weakly_dominated_list
99
+ end
100
+
101
+ def test_nash
102
+ game = Game.new
103
+ game.strategy_profiles = new_profiles
104
+ assert_equal [["Accept Training", "Task Specific Training"]], game.nash
105
+ end
106
+
107
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: minigame
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Wavell Watson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: A game theory library
56
+ email:
57
+ - wavell.watson@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - .gitignore
63
+ - .rvmrc
64
+ - Gemfile
65
+ - Gemfile.lock
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - lib/minigame.rb
70
+ - lib/minigame/array_of_hashes.rb
71
+ - lib/minigame/game.rb
72
+ - lib/minigame/gameable.rb
73
+ - lib/minigame/payoff.rb
74
+ - lib/minigame/player.rb
75
+ - lib/minigame/strategy.rb
76
+ - lib/minigame/strategy_profile.rb
77
+ - lib/minigame/version.rb
78
+ - minigame.gemspec
79
+ - test/test_minigame.rb
80
+ homepage: ''
81
+ licenses:
82
+ - MIT
83
+ metadata: {}
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 2.0.3
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: A minimalistic game theory library that computes nash equilibrium
104
+ test_files:
105
+ - test/test_minigame.rb