game-resolver-cpp 0.1.1__tar.gz → 0.1.2__tar.gz
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.
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/.gitignore +9 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/CMakeLists.txt +86 -1
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/Makefile +5 -1
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/PKG-INFO +38 -2
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/README.md +205 -1
- game_resolver_cpp-0.1.2/assets/editor.css +644 -0
- game_resolver_cpp-0.1.2/assets/editor.html.in +110 -0
- game_resolver_cpp-0.1.2/assets/editor.js +2394 -0
- game_resolver_cpp-0.1.2/assets/tree_view.css +641 -0
- game_resolver_cpp-0.1.2/assets/tree_view.html.in +68 -0
- game_resolver_cpp-0.1.2/assets/tree_view.js +773 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/docs/README_for_pypi.md +37 -1
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/examples/CMakeLists.txt +3 -0
- game_resolver_cpp-0.1.2/examples/centipede.cpp +78 -0
- game_resolver_cpp-0.1.2/examples/entry_deterrence.cpp +46 -0
- game_resolver_cpp-0.1.2/examples/extensive_battle_of_sex.cpp +72 -0
- game_resolver_cpp-0.1.2/include/game_resolver/extensive/editor.hpp +35 -0
- game_resolver_cpp-0.1.2/include/game_resolver/extensive/extensive_form_game.hpp +157 -0
- game_resolver_cpp-0.1.2/include/game_resolver/extensive/subgame_perfect_equilibrium.hpp +102 -0
- game_resolver_cpp-0.1.2/include/game_resolver/extensive/tree_view.hpp +71 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/pyproject.toml +4 -1
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/__init__.py +2 -0
- game_resolver_cpp-0.1.2/python/game_resolver_cpp/extensive_form_game.py +268 -0
- game_resolver_cpp-0.1.2/python/game_resolver_cpp/web.py +257 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/python/src/binding.cpp +121 -0
- game_resolver_cpp-0.1.2/python/tests/test_extensive.py +181 -0
- game_resolver_cpp-0.1.2/python/tests/test_web.py +168 -0
- game_resolver_cpp-0.1.2/src/extensive/editor.cpp +54 -0
- game_resolver_cpp-0.1.2/src/extensive/extensive_form_game.cpp +332 -0
- game_resolver_cpp-0.1.2/src/extensive/html_util.cpp +114 -0
- game_resolver_cpp-0.1.2/src/extensive/html_util.hpp +36 -0
- game_resolver_cpp-0.1.2/src/extensive/subgame_perfect_equilibrium.cpp +390 -0
- game_resolver_cpp-0.1.2/src/extensive/tree_view.cpp +252 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/src/version.cpp +1 -1
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/tests/CMakeLists.txt +4 -0
- game_resolver_cpp-0.1.2/tests/editor_test.cpp +93 -0
- game_resolver_cpp-0.1.2/tests/extensive_form_test.cpp +172 -0
- game_resolver_cpp-0.1.2/tests/extensive_games.hpp +185 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/tests/smoke_test.cpp +1 -1
- game_resolver_cpp-0.1.2/tests/spne_test.cpp +215 -0
- game_resolver_cpp-0.1.2/tests/tree_view_test.cpp +160 -0
- game_resolver_cpp-0.1.2/third_party/d3-mini/LICENSE +13 -0
- game_resolver_cpp-0.1.2/third_party/d3-mini/LICENSE.d3-ease +28 -0
- game_resolver_cpp-0.1.2/third_party/d3-mini/README.md +36 -0
- game_resolver_cpp-0.1.2/third_party/d3-mini/d3-mini.min.js +1 -0
- game_resolver_cpp-0.1.2/third_party/d3-mini/entry.js +7 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/.clang-format +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/.clang-tidy +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/LICENSE +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/benchmarks/CMakeLists.txt +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/benchmarks/benchmark.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/cmake/game_resolverConfig.cmake.in +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/examples/battle_of_sex.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/examples/bayesian_battle_of_sex.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/examples/cournot.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/examples/hawk_dove.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/examples/prisoners_dilemma.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/include/game_resolver/action.hpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/include/game_resolver/bayesian/bayesian_game.hpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/include/game_resolver/bayesian/bayesian_nash_equilibrium.hpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/include/game_resolver/bayesian/bayesian_player.hpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/include/game_resolver/error.hpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/include/game_resolver/game.hpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/include/game_resolver/game_resolver.hpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/include/game_resolver/nash_equilibrium.hpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/include/game_resolver/ndarray.hpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/include/game_resolver/payoff.hpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/include/game_resolver/player.hpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/include/game_resolver/solver/nash_solver.hpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/include/game_resolver/version.hpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/python/CMakeLists.txt +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/python/benchmarks/compare_with_python.py +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/bayesian_game.py +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/bayesian_nash_equilibrium.py +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/bayesian_player.py +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/game.py +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/nash_equilibrium.py +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/payoff.py +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/player.py +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/python/tests/test_compat.py +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/src/action.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/src/bayesian/bayesian_nash_equilibrium.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/src/bayesian/bayesian_player.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/src/nash_equilibrium.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/src/payoff.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/src/solver/nash_solver.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/tests/action_test.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/tests/bayesian_test.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/tests/example_games.hpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/tests/golden/battle_of_sex.txt +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/tests/golden/bayesian_battle_of_sex.txt +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/tests/golden/cournot.txt +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/tests/golden/generate.py +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/tests/golden/hawk_dove.txt +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/tests/golden/prisoners_dilemma.txt +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/tests/golden/verify_exact.py +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/tests/golden_test.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/tests/nash_equilibrium_test.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/tests/nash_solver_test.cpp +0 -0
- {game_resolver_cpp-0.1.1 → game_resolver_cpp-0.1.2}/tests/ndarray_test.cpp +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
cmake_minimum_required(VERSION 3.21)
|
|
2
|
-
project(game_resolver_cpp VERSION 0.1.
|
|
2
|
+
project(game_resolver_cpp VERSION 0.1.2 LANGUAGES CXX)
|
|
3
3
|
|
|
4
4
|
option(GAME_RESOLVER_BUILD_TESTS "Build the test suite" ${PROJECT_IS_TOP_LEVEL})
|
|
5
5
|
option(GAME_RESOLVER_BUILD_EXAMPLES "Build the example programs" ${PROJECT_IS_TOP_LEVEL})
|
|
@@ -27,6 +27,84 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
|
|
27
27
|
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
|
|
28
28
|
endif()
|
|
29
29
|
|
|
30
|
+
# ------------------------------------------------------- 可視化アセットの埋め込み
|
|
31
|
+
# assets/ と third_party/d3-mini/ を C++ の生文字列リテラルにしたヘッダを configure 時に作る。
|
|
32
|
+
# こうしておくと tree_view.cpp が吐く HTML が 1 ファイルで完結し、実行時に何も探しに行かない。
|
|
33
|
+
#
|
|
34
|
+
# MSVC は 1 つの文字列リテラルを 65535 バイトまでしか許さないので、16 KB ずつに割って
|
|
35
|
+
# 配列にし、実行時に連結する(d3-mini.min.js が 57 KB あり、素直に書くと将来引っかかる)。
|
|
36
|
+
set(GR_ASSET_FILES
|
|
37
|
+
${CMAKE_CURRENT_SOURCE_DIR}/assets/tree_view.html.in
|
|
38
|
+
${CMAKE_CURRENT_SOURCE_DIR}/assets/tree_view.css
|
|
39
|
+
${CMAKE_CURRENT_SOURCE_DIR}/assets/tree_view.js
|
|
40
|
+
${CMAKE_CURRENT_SOURCE_DIR}/assets/editor.html.in
|
|
41
|
+
${CMAKE_CURRENT_SOURCE_DIR}/assets/editor.css
|
|
42
|
+
${CMAKE_CURRENT_SOURCE_DIR}/assets/editor.js
|
|
43
|
+
${CMAKE_CURRENT_SOURCE_DIR}/third_party/d3-mini/d3-mini.min.js
|
|
44
|
+
)
|
|
45
|
+
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${GR_ASSET_FILES})
|
|
46
|
+
|
|
47
|
+
function(gr_emit_asset out_var function_name path)
|
|
48
|
+
file(READ "${path}" _content)
|
|
49
|
+
string(FIND "${_content}" ")GRASSET\"" _clash)
|
|
50
|
+
if(NOT _clash EQUAL -1)
|
|
51
|
+
message(FATAL_ERROR "${path}: 生文字列リテラルの終端子 )GRASSET\" が中身に含まれています")
|
|
52
|
+
endif()
|
|
53
|
+
|
|
54
|
+
string(LENGTH "${_content}" _len)
|
|
55
|
+
set(_chunks "")
|
|
56
|
+
set(_at 0)
|
|
57
|
+
while(_at LESS _len)
|
|
58
|
+
math(EXPR _take "${_len} - ${_at}")
|
|
59
|
+
if(_take GREATER 16000)
|
|
60
|
+
set(_take 16000)
|
|
61
|
+
endif()
|
|
62
|
+
string(SUBSTRING "${_content}" ${_at} ${_take} _piece)
|
|
63
|
+
string(APPEND _chunks " R\"GRASSET(${_piece})GRASSET\",\n")
|
|
64
|
+
math(EXPR _at "${_at} + ${_take}")
|
|
65
|
+
endwhile()
|
|
66
|
+
|
|
67
|
+
string(APPEND ${out_var}
|
|
68
|
+
"inline std::string ${function_name}() {\n"
|
|
69
|
+
" static const char* const kParts[] = {\n${_chunks} };\n"
|
|
70
|
+
" return detail::join(kParts, sizeof(kParts) / sizeof(kParts[0]));\n"
|
|
71
|
+
"}\n\n")
|
|
72
|
+
set(${out_var} "${${out_var}}" PARENT_SCOPE)
|
|
73
|
+
endfunction()
|
|
74
|
+
|
|
75
|
+
set(GR_ASSET_BODY "")
|
|
76
|
+
gr_emit_asset(GR_ASSET_BODY tree_view_template ${CMAKE_CURRENT_SOURCE_DIR}/assets/tree_view.html.in)
|
|
77
|
+
gr_emit_asset(GR_ASSET_BODY tree_view_css ${CMAKE_CURRENT_SOURCE_DIR}/assets/tree_view.css)
|
|
78
|
+
gr_emit_asset(GR_ASSET_BODY tree_view_js ${CMAKE_CURRENT_SOURCE_DIR}/assets/tree_view.js)
|
|
79
|
+
gr_emit_asset(GR_ASSET_BODY editor_template ${CMAKE_CURRENT_SOURCE_DIR}/assets/editor.html.in)
|
|
80
|
+
gr_emit_asset(GR_ASSET_BODY editor_css ${CMAKE_CURRENT_SOURCE_DIR}/assets/editor.css)
|
|
81
|
+
gr_emit_asset(GR_ASSET_BODY editor_js ${CMAKE_CURRENT_SOURCE_DIR}/assets/editor.js)
|
|
82
|
+
gr_emit_asset(GR_ASSET_BODY d3_mini ${CMAKE_CURRENT_SOURCE_DIR}/third_party/d3-mini/d3-mini.min.js)
|
|
83
|
+
|
|
84
|
+
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/generated/game_resolver_assets.hpp
|
|
85
|
+
"// CMake が assets/ と third_party/d3-mini/ から生成する。直接編集しない。
|
|
86
|
+
// 元ファイルを直せば configure が走り直して作り直される。
|
|
87
|
+
#pragma once
|
|
88
|
+
|
|
89
|
+
#include <cstddef>
|
|
90
|
+
#include <string>
|
|
91
|
+
|
|
92
|
+
namespace game_resolver::assets {
|
|
93
|
+
namespace detail {
|
|
94
|
+
|
|
95
|
+
inline std::string join(const char* const* parts, std::size_t count) {
|
|
96
|
+
std::string out;
|
|
97
|
+
for (std::size_t i = 0; i < count; ++i) {
|
|
98
|
+
out += parts[i];
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
} // namespace detail
|
|
104
|
+
|
|
105
|
+
${GR_ASSET_BODY}} // namespace game_resolver::assets
|
|
106
|
+
")
|
|
107
|
+
|
|
30
108
|
# ---------------------------------------------------------------- library
|
|
31
109
|
add_library(game_resolver
|
|
32
110
|
src/version.cpp
|
|
@@ -36,6 +114,11 @@ add_library(game_resolver
|
|
|
36
114
|
src/solver/nash_solver.cpp
|
|
37
115
|
src/bayesian/bayesian_player.cpp
|
|
38
116
|
src/bayesian/bayesian_nash_equilibrium.cpp
|
|
117
|
+
src/extensive/extensive_form_game.cpp
|
|
118
|
+
src/extensive/subgame_perfect_equilibrium.cpp
|
|
119
|
+
src/extensive/tree_view.cpp
|
|
120
|
+
src/extensive/html_util.cpp
|
|
121
|
+
src/extensive/editor.cpp
|
|
39
122
|
)
|
|
40
123
|
add_library(game_resolver::game_resolver ALIAS game_resolver)
|
|
41
124
|
|
|
@@ -43,6 +126,8 @@ target_include_directories(game_resolver PUBLIC
|
|
|
43
126
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
44
127
|
$<INSTALL_INTERFACE:include>
|
|
45
128
|
)
|
|
129
|
+
# 生成した game_resolver_assets.hpp は tree_view.cpp だけが使う。公開ヘッダではない。
|
|
130
|
+
target_include_directories(game_resolver PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated)
|
|
46
131
|
target_compile_features(game_resolver PUBLIC cxx_std_20)
|
|
47
132
|
|
|
48
133
|
# 静的ライブラリを共有オブジェクトにリンクできるようにする。
|
|
@@ -4,7 +4,7 @@ BUILD_DIR ?= build
|
|
|
4
4
|
BUILD_TYPE ?= Release
|
|
5
5
|
GENERATOR ?= Ninja
|
|
6
6
|
|
|
7
|
-
.PHONY: all configure build test bench format format-check tidy clean python-install python-test python-bench
|
|
7
|
+
.PHONY: all configure build test bench format format-check tidy clean python-install python-test python-bench web
|
|
8
8
|
|
|
9
9
|
all: build
|
|
10
10
|
|
|
@@ -30,6 +30,10 @@ python-test:
|
|
|
30
30
|
python-bench:
|
|
31
31
|
python3 python/benchmarks/compare_with_python.py
|
|
32
32
|
|
|
33
|
+
# ブラウザでゲームツリーを描いて解く(python-install 済みであること)
|
|
34
|
+
web:
|
|
35
|
+
python3 -m game_resolver_cpp.web
|
|
36
|
+
|
|
33
37
|
# サニタイザ付きのデバッグビルドでテストする
|
|
34
38
|
test-asan:
|
|
35
39
|
$(CMAKE) -S . -B $(BUILD_DIR)-asan -G $(GENERATOR) -DCMAKE_BUILD_TYPE=Debug -DGAME_RESOLVER_SANITIZE=ON
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: game_resolver_cpp
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: C++ backed game equilibrium solver with the game_resolver interface
|
|
5
5
|
Keywords: game-theory,nash-equilibrium,bayesian-game
|
|
6
6
|
Author: nishino-lab
|
|
@@ -158,10 +158,46 @@ an exact `==`, because an exact comparison makes genuine ties depend on rounding
|
|
|
158
158
|
Cournot example has three equilibria on its grid; the Python version reports one of them.
|
|
159
159
|
Pass `tolerance=0` to `NashEquilibrium` for the Python version's strict behaviour.
|
|
160
160
|
|
|
161
|
+
## Run an extensive form game
|
|
162
|
+
|
|
163
|
+
Build a game tree and get its pure strategy subgame perfect equilibria. Information sets
|
|
164
|
+
turn the same tree into a simultaneous move game.
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from game_resolver_cpp.extensive_form_game import ExtensiveFormGame
|
|
168
|
+
|
|
169
|
+
game = ExtensiveFormGame()
|
|
170
|
+
entrant = game.add_player("Entrant")
|
|
171
|
+
incumbent = game.add_player("Incumbent")
|
|
172
|
+
|
|
173
|
+
enter = game.node(entrant, "E")
|
|
174
|
+
react = game.node(incumbent, "I")
|
|
175
|
+
game.edge(enter, game.terminal([0, 2], "out"), "Out")
|
|
176
|
+
game.edge(enter, react, "In")
|
|
177
|
+
game.edge(react, game.terminal([-1, -1], "fight"), "Fight")
|
|
178
|
+
game.edge(react, game.terminal([1, 1], "share"), "Accommodate")
|
|
179
|
+
game.auto_information_sets()
|
|
180
|
+
|
|
181
|
+
print(game.get_subgame_perfect_equilibrium())
|
|
182
|
+
# [{'payoff': [1.0, 1.0], 'strategy': {'E': 'In', 'I': 'Accommodate'},
|
|
183
|
+
# 'path': ['E', 'I', 'share'], 'outcome': 'share'}]
|
|
184
|
+
|
|
185
|
+
game.draw_gametree("entry.html") # self-contained page with the tree and the equilibria
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Or draw the tree in the browser and press ▶ 解く:
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
python -m game_resolver_cpp.web
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The editor saves games as JSON, `ExtensiveFormGame.load()` reads them, and `game.edit()`
|
|
195
|
+
opens a game built in code in the editor.
|
|
196
|
+
|
|
161
197
|
## Not supported
|
|
162
198
|
|
|
163
199
|
- Mixed strategy Nash equilibria (the Python version does not have them either)
|
|
164
|
-
-
|
|
200
|
+
- Chance (Nature) nodes in extensive form games
|
|
165
201
|
|
|
166
202
|
## Links
|
|
167
203
|
|
|
@@ -250,6 +250,140 @@ std::cout << nash << '\n';
|
|
|
250
250
|
|
|
251
251
|
`payoff` is the expected payoff indexed by `[equilibrium][player][type]`.
|
|
252
252
|
|
|
253
|
+
## Run an extensive form game
|
|
254
|
+
|
|
255
|
+
Build a game tree, get its pure strategy subgame perfect equilibria, and draw it.
|
|
256
|
+
|
|
257
|
+
```cpp
|
|
258
|
+
#include <iostream>
|
|
259
|
+
|
|
260
|
+
#include "game_resolver/extensive/subgame_perfect_equilibrium.hpp"
|
|
261
|
+
#include "game_resolver/extensive/tree_view.hpp"
|
|
262
|
+
|
|
263
|
+
using namespace game_resolver;
|
|
264
|
+
|
|
265
|
+
int main() {
|
|
266
|
+
ExtensiveFormGame game;
|
|
267
|
+
const int entrant = game.add_player("Entrant");
|
|
268
|
+
const int incumbent = game.add_player("Incumbent");
|
|
269
|
+
|
|
270
|
+
const NodeId enter = game.decision_node(entrant, "E");
|
|
271
|
+
const NodeId react = game.decision_node(incumbent, "I");
|
|
272
|
+
|
|
273
|
+
game.edge(enter, game.terminal_node({0.0, 2.0}, "out"), Action(std::string("Out")));
|
|
274
|
+
game.edge(enter, react, Action(std::string("In")));
|
|
275
|
+
game.edge(react, game.terminal_node({-1.0, -1.0}, "fight"), Action(std::string("Fight")));
|
|
276
|
+
game.edge(react, game.terminal_node({1.0, 1.0}, "share"), Action(std::string("Accommodate")));
|
|
277
|
+
|
|
278
|
+
game.auto_information_sets(); // perfect information: one node per information set
|
|
279
|
+
|
|
280
|
+
const SubgamePerfectEquilibrium equilibrium(game);
|
|
281
|
+
std::cout << describe(game, equilibrium.solve()) << '\n';
|
|
282
|
+
|
|
283
|
+
write_solved_tree_view(game, "entry.html", "Entry deterrence");
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
subgame perfect equilibria: 1
|
|
289
|
+
[0] payoff=(1, 1)
|
|
290
|
+
strategy: E=In, I=Accommodate
|
|
291
|
+
path: E -In-> I -Accommodate-> share
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`(Out, Fight)` is a Nash equilibrium of the normal form but not a subgame perfect one: once
|
|
295
|
+
the entrant is in, fighting pays -1 and accommodating pays 1, so the threat is never carried
|
|
296
|
+
out. Backward induction drops it.
|
|
297
|
+
|
|
298
|
+
### Imperfect information
|
|
299
|
+
|
|
300
|
+
Put several nodes in one information set and the player cannot tell them apart. The same
|
|
301
|
+
tree then describes a simultaneous move game:
|
|
302
|
+
|
|
303
|
+
```cpp
|
|
304
|
+
// instead of auto_information_sets()
|
|
305
|
+
game.information_set(her, {boxing, ballet}, {first}, "her");
|
|
306
|
+
game.information_set(him, {boxing, ballet}, {after_boxing, after_ballet}, "him (blind)");
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Every node in one information set must offer the same actions in the same order — otherwise
|
|
310
|
+
the player could tell the nodes apart by what they can do. `validate()` checks this.
|
|
311
|
+
|
|
312
|
+
There is one algorithm for both cases. Subgames are found and collapsed deepest first; each
|
|
313
|
+
is converted to its induced normal form and handed to the same `solve_pure_nash` the rest of
|
|
314
|
+
the library uses. Under perfect information every decision node is its own subgame and the
|
|
315
|
+
induced normal form degenerates to picking a maximum, which is exactly backward induction.
|
|
316
|
+
|
|
317
|
+
### The HTML view
|
|
318
|
+
|
|
319
|
+
`write_solved_tree_view` writes one self-contained file — no network access, nothing to
|
|
320
|
+
serve, just open it.
|
|
321
|
+
|
|
322
|
+
| | |
|
|
323
|
+
| --- | --- |
|
|
324
|
+
| Layout | tidy tree (Reingold-Tilford) via `d3-hierarchy`. Deterministic: the same game always draws the same picture |
|
|
325
|
+
| Payoffs | shown at the leaves, one colored dot per player next to the number |
|
|
326
|
+
| Information sets | non-singleton sets are joined by a dashed connector |
|
|
327
|
+
| Equilibria | listed in the sidebar; selecting one highlights its path |
|
|
328
|
+
| Off-path moves | drawn as dashed colored edges — a subgame perfect equilibrium fixes behaviour at information sets the path never reaches, and that is what separates it from a plain Nash equilibrium |
|
|
329
|
+
| Interaction | pan / zoom, click a node to collapse its subtree, hover for details, `f` to fit, `1`–`9` to pick an equilibrium |
|
|
330
|
+
| Size | about 97 KB, of which 57 KB is the embedded subset of D3 (see [third_party/d3-mini/](third_party/d3-mini/)) |
|
|
331
|
+
|
|
332
|
+
Player colors come from a palette validated for colorblind separation, but identity never
|
|
333
|
+
rests on color alone: each node shows the player number, and the legend is always visible.
|
|
334
|
+
Beyond three players the categorical hues cannot be kept pairwise distinguishable, so the
|
|
335
|
+
number and the legend are what carry identity.
|
|
336
|
+
|
|
337
|
+
Runnable versions of all of the above are in
|
|
338
|
+
[examples/entry_deterrence.cpp](examples/entry_deterrence.cpp),
|
|
339
|
+
[examples/extensive_battle_of_sex.cpp](examples/extensive_battle_of_sex.cpp) and
|
|
340
|
+
[examples/centipede.cpp](examples/centipede.cpp).
|
|
341
|
+
|
|
342
|
+
### Draw the tree in the browser instead
|
|
343
|
+
|
|
344
|
+
You do not have to write the tree as code. The package ships an editor that runs in the
|
|
345
|
+
browser: draw the tree, press **▶ 解く**, and the equilibria are computed by the same C++
|
|
346
|
+
solver and drawn on the tree.
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
pip install .
|
|
350
|
+
python -m game_resolver_cpp.web # or: game-resolver-web
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
This starts a local server on `http://127.0.0.1:8765/` and opens it. Nothing is installed
|
|
354
|
+
besides the package itself; the server is the standard library's `http.server`, and the
|
|
355
|
+
page is the same self-contained HTML the tree view uses.
|
|
356
|
+
|
|
357
|
+
| | |
|
|
358
|
+
| --- | --- |
|
|
359
|
+
| Building | click a node to edit it in the inspector; hover a node and press **+** (or `a`) to add a branch; each branch has a menu to make its child a terminal node or a decision node of some player |
|
|
360
|
+
| Information sets | select a decision node and tick the other nodes of the same player under 「同じ情報集合に入れる」. Only nodes with the same actions in the same order are offered, and a node cannot be joined with its own ancestor (that would break perfect recall). Adding, removing or renaming a branch on one member is applied to every member, so a set never goes out of sync |
|
|
361
|
+
| Solving | **▶ 解く** (`Ctrl+Enter`) lists the pure strategy subgame perfect equilibria; picking one highlights its path and shows off-path choices as dashed edges, as in the HTML view |
|
|
362
|
+
| Saving | **保存** downloads the game as JSON, **開く** reads it back, and edits are also kept in the browser's local storage so a reload does not lose them. `Ctrl+Z` undoes |
|
|
363
|
+
| Getting it into code | **Python** shows the `ExtensiveFormGame` code that builds the same tree; **HTML** downloads the solved tree view. `ExtensiveFormGame.load("game.json")` reads a saved file directly |
|
|
364
|
+
| Samples | 参入阻止 / 男女の争い(逐次・同時)/ ムカデゲーム, from the menu at the top |
|
|
365
|
+
|
|
366
|
+
The other direction works too: `game.edit()` on a game built in Python opens it in the
|
|
367
|
+
editor. See [Extensive form games from Python](#extensive-form-games-from-python).
|
|
368
|
+
|
|
369
|
+
From C++, `game_json()` / `tree_view_json()` in
|
|
370
|
+
[tree_view.hpp](include/game_resolver/extensive/tree_view.hpp) return the JSON the editor
|
|
371
|
+
and the view read, and `write_editor()` in [editor.hpp](include/game_resolver/extensive/editor.hpp)
|
|
372
|
+
writes the editor page itself. Without a server that page can edit and export but not solve;
|
|
373
|
+
the solving is done by the C++ library on the server side, not re-implemented in JavaScript.
|
|
374
|
+
|
|
375
|
+
### Limits
|
|
376
|
+
|
|
377
|
+
- **Pure strategies only**, same as the rest of the library. A game with no pure strategy
|
|
378
|
+
equilibrium returns an empty result rather than a mixed one.
|
|
379
|
+
- **Perfect recall is only partly checked.** `validate()` rejects an information set that
|
|
380
|
+
contains a node and one of its own descendants, which is the case that silently breaks the
|
|
381
|
+
conversion to normal form. It does not verify the full perfect recall condition.
|
|
382
|
+
- **No chance (Nature) nodes yet.**
|
|
383
|
+
- The induced normal form of a subgame has `Π_players Π_{their information sets} |actions|`
|
|
384
|
+
cells, which grows doubly exponentially. `SpneOptions::max_cells` (10,000,000 by default)
|
|
385
|
+
stops a mis-built game from quietly allocating gigabytes.
|
|
386
|
+
|
|
253
387
|
## Use it from Python
|
|
254
388
|
|
|
255
389
|
The package exposes the same interface as the Python `game_resolver`, so existing lab code
|
|
@@ -299,6 +433,55 @@ payoff.update(more_entries) # also works
|
|
|
299
433
|
Player(0, actions=actions, payoff=mapping) # a plain dict is accepted too
|
|
300
434
|
```
|
|
301
435
|
|
|
436
|
+
### Extensive form games from Python
|
|
437
|
+
|
|
438
|
+
`node` / `edge` / `information_set` / `draw_gametree` keep the names used by the prototype on
|
|
439
|
+
the Python side's `feature/extensive_form_game` branch. Terminal nodes carry payoffs, which
|
|
440
|
+
that branch did not have, and nodes are integer ids rather than objects.
|
|
441
|
+
|
|
442
|
+
```python
|
|
443
|
+
from game_resolver_cpp.extensive_form_game import ExtensiveFormGame
|
|
444
|
+
|
|
445
|
+
game = ExtensiveFormGame()
|
|
446
|
+
entrant = game.add_player("Entrant")
|
|
447
|
+
incumbent = game.add_player("Incumbent")
|
|
448
|
+
|
|
449
|
+
enter = game.node(entrant, "E")
|
|
450
|
+
react = game.node(incumbent, "I")
|
|
451
|
+
game.edge(enter, game.terminal([0, 2], "out"), "Out")
|
|
452
|
+
game.edge(enter, react, "In")
|
|
453
|
+
game.edge(react, game.terminal([-1, -1], "fight"), "Fight")
|
|
454
|
+
game.edge(react, game.terminal([1, 1], "share"), "Accommodate")
|
|
455
|
+
game.auto_information_sets()
|
|
456
|
+
|
|
457
|
+
for eq in game.get_subgame_perfect_equilibrium():
|
|
458
|
+
print(eq)
|
|
459
|
+
# {'payoff': [1.0, 1.0], 'strategy': {'E': 'In', 'I': 'Accommodate'},
|
|
460
|
+
# 'path': ['E', 'I', 'share'], 'outcome': 'share'}
|
|
461
|
+
|
|
462
|
+
game.draw_gametree("entry.html", "Entry deterrence")
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
`strategy` covers every information set, including ones the equilibrium path never reaches.
|
|
466
|
+
For imperfect information, pass several nodes to `information_set`:
|
|
467
|
+
|
|
468
|
+
```python
|
|
469
|
+
game.information_set(him, ["Boxing", "Ballet"], after_boxing, after_ballet, label="him")
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
A game can be moved between code and the browser editor in both directions:
|
|
473
|
+
|
|
474
|
+
```python
|
|
475
|
+
game.edit() # open this game in the editor (Ctrl+C to stop)
|
|
476
|
+
game = ExtensiveFormGame.load("entry.json") # read a file saved from the editor
|
|
477
|
+
game.save("entry.json") # the same format, from code
|
|
478
|
+
data = game.to_dict(); ExtensiveFormGame.from_dict(data)
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
The JSON is `{"players", "root", "nodes", "informationSets"}`; `from_dict` also accepts a
|
|
482
|
+
shorter form (player names as strings, no ids, only the non-singleton information sets),
|
|
483
|
+
which is what the editor writes.
|
|
484
|
+
|
|
302
485
|
### What actually gets faster
|
|
303
486
|
|
|
304
487
|
One run of `python3 python/benchmarks/compare_with_python.py` on an 11-core Apple M series
|
|
@@ -363,6 +546,26 @@ The reasoning behind each of these, and the alternatives that were measured and
|
|
|
363
546
|
| `BayesianNashEquilibrium(g)` + `initialize_payoff_matrices()` | `BayesianNashEquilibrium(g)` (built in the constructor) |
|
|
364
547
|
| strategy = `dict[type, action]` | strategy = `Strategy` (a vector of action indices, one per type) |
|
|
365
548
|
|
|
549
|
+
From the `feature/extensive_form_game` branch:
|
|
550
|
+
|
|
551
|
+
| Python prototype | C++ |
|
|
552
|
+
| --- | --- |
|
|
553
|
+
| `ExtensiveFormPlayer(id=0)` + `game.add(p)` | `game.add_player("name")` -> `int` |
|
|
554
|
+
| `game.node(player)` -> `Node` | `game.decision_node(player)` -> `NodeId` |
|
|
555
|
+
| — (terminals were invented at draw time) | `game.terminal_node({2.0, 1.0})` -> `NodeId` |
|
|
556
|
+
| `game.edge(parent, child, action)` | same, with `NodeId` |
|
|
557
|
+
| `game.information_set(player, actions, *nodes)` | `game.information_set(player, actions, {nodes})` |
|
|
558
|
+
| `player.setup_actions()` | not needed — the solver derives the induced normal form |
|
|
559
|
+
| `SubgamePerfectNashEquilibrium(g)` (an empty stub) | `SubgamePerfectEquilibrium(g).solve()` |
|
|
560
|
+
| `game.draw_gametree()` -> pyvis, physics layout | `write_solved_tree_view(g, path, title)` -> deterministic tidy tree |
|
|
561
|
+
|
|
562
|
+
What that branch had was the tree structure and the idea of taking the product of local
|
|
563
|
+
strategies to get the induced normal form. It had no payoffs on the tree
|
|
564
|
+
(`draw_gametree` carried a `# ToDo 利得ベクトルを書く!` where the payoff vector belongs and
|
|
565
|
+
manufactured terminal nodes from `local_actions` at draw time), and
|
|
566
|
+
`SubgamePerfectNashEquilibrium.initialize_payoff_matrices` was `pass`. The solver and the
|
|
567
|
+
HTML view here are new.
|
|
568
|
+
|
|
366
569
|
Deliberate differences:
|
|
367
570
|
|
|
368
571
|
- **The Type1 / Type2 distinction is gone from the C++ API.** In the Python version, whether
|
|
@@ -401,6 +604,7 @@ make test
|
|
|
401
604
|
| `make python-install` | `pip install ".[test]"` |
|
|
402
605
|
| `make python-test` | run the Python tests |
|
|
403
606
|
| `make python-bench` | compare against the Python version |
|
|
607
|
+
| `make web` | open the browser editor (needs `make python-install` first) |
|
|
404
608
|
|
|
405
609
|
Or drive CMake directly:
|
|
406
610
|
|
|
@@ -516,7 +720,7 @@ Two things are deliberately excluded:
|
|
|
516
720
|
## Not supported
|
|
517
721
|
|
|
518
722
|
- Mixed strategy Nash equilibria (the Python version does not have them either)
|
|
519
|
-
-
|
|
723
|
+
- Chance (Nature) nodes in extensive form games
|
|
520
724
|
- A Bayesian game's strategy space grows as `|actions|^|types|`. That blow-up is inherent,
|
|
521
725
|
so oversized settings are rejected up front.
|
|
522
726
|
|