minigame 0.0.1
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.
- checksums.yaml +15 -0
- data/.gitignore +99 -0
- data/.rvmrc +1 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +27 -0
- data/LICENSE.txt +22 -0
- data/README.md +85 -0
- data/Rakefile +8 -0
- data/lib/minigame/array_of_hashes.rb +68 -0
- data/lib/minigame/game.rb +3 -0
- data/lib/minigame/gameable.rb +273 -0
- data/lib/minigame/payoff.rb +12 -0
- data/lib/minigame/player.rb +5 -0
- data/lib/minigame/strategy.rb +4 -0
- data/lib/minigame/strategy_profile.rb +4 -0
- data/lib/minigame/version.rb +3 -0
- data/lib/minigame.rb +9 -0
- data/minigame.gemspec +25 -0
- data/test/test_minigame.rb +107 -0
- metadata +105 -0
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
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,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,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
|
data/lib/minigame.rb
ADDED
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
|