game-resolver-cpp 0.1.0__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.
Files changed (100) hide show
  1. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/.gitignore +9 -0
  2. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/CMakeLists.txt +86 -1
  3. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/Makefile +5 -1
  4. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/PKG-INFO +63 -5
  5. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/README.md +207 -1
  6. game_resolver_cpp-0.1.2/assets/editor.css +644 -0
  7. game_resolver_cpp-0.1.2/assets/editor.html.in +110 -0
  8. game_resolver_cpp-0.1.2/assets/editor.js +2394 -0
  9. game_resolver_cpp-0.1.2/assets/tree_view.css +641 -0
  10. game_resolver_cpp-0.1.2/assets/tree_view.html.in +68 -0
  11. game_resolver_cpp-0.1.2/assets/tree_view.js +773 -0
  12. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/docs/README_for_pypi.md +62 -4
  13. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/examples/CMakeLists.txt +3 -0
  14. game_resolver_cpp-0.1.2/examples/centipede.cpp +78 -0
  15. game_resolver_cpp-0.1.2/examples/entry_deterrence.cpp +46 -0
  16. game_resolver_cpp-0.1.2/examples/extensive_battle_of_sex.cpp +72 -0
  17. game_resolver_cpp-0.1.2/include/game_resolver/extensive/editor.hpp +35 -0
  18. game_resolver_cpp-0.1.2/include/game_resolver/extensive/extensive_form_game.hpp +157 -0
  19. game_resolver_cpp-0.1.2/include/game_resolver/extensive/subgame_perfect_equilibrium.hpp +102 -0
  20. game_resolver_cpp-0.1.2/include/game_resolver/extensive/tree_view.hpp +71 -0
  21. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/pyproject.toml +4 -1
  22. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/__init__.py +2 -0
  23. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/bayesian_nash_equilibrium.py +16 -1
  24. game_resolver_cpp-0.1.2/python/game_resolver_cpp/extensive_form_game.py +268 -0
  25. game_resolver_cpp-0.1.2/python/game_resolver_cpp/web.py +257 -0
  26. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/src/binding.cpp +172 -3
  27. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/tests/test_compat.py +32 -0
  28. game_resolver_cpp-0.1.2/python/tests/test_extensive.py +181 -0
  29. game_resolver_cpp-0.1.2/python/tests/test_web.py +168 -0
  30. game_resolver_cpp-0.1.2/src/extensive/editor.cpp +54 -0
  31. game_resolver_cpp-0.1.2/src/extensive/extensive_form_game.cpp +332 -0
  32. game_resolver_cpp-0.1.2/src/extensive/html_util.cpp +114 -0
  33. game_resolver_cpp-0.1.2/src/extensive/html_util.hpp +36 -0
  34. game_resolver_cpp-0.1.2/src/extensive/subgame_perfect_equilibrium.cpp +390 -0
  35. game_resolver_cpp-0.1.2/src/extensive/tree_view.cpp +252 -0
  36. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/src/version.cpp +1 -1
  37. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/CMakeLists.txt +4 -0
  38. game_resolver_cpp-0.1.2/tests/editor_test.cpp +93 -0
  39. game_resolver_cpp-0.1.2/tests/extensive_form_test.cpp +172 -0
  40. game_resolver_cpp-0.1.2/tests/extensive_games.hpp +185 -0
  41. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/smoke_test.cpp +1 -1
  42. game_resolver_cpp-0.1.2/tests/spne_test.cpp +215 -0
  43. game_resolver_cpp-0.1.2/tests/tree_view_test.cpp +160 -0
  44. game_resolver_cpp-0.1.2/third_party/d3-mini/LICENSE +13 -0
  45. game_resolver_cpp-0.1.2/third_party/d3-mini/LICENSE.d3-ease +28 -0
  46. game_resolver_cpp-0.1.2/third_party/d3-mini/README.md +36 -0
  47. game_resolver_cpp-0.1.2/third_party/d3-mini/d3-mini.min.js +1 -0
  48. game_resolver_cpp-0.1.2/third_party/d3-mini/entry.js +7 -0
  49. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/.clang-format +0 -0
  50. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/.clang-tidy +0 -0
  51. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/LICENSE +0 -0
  52. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/benchmarks/CMakeLists.txt +0 -0
  53. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/benchmarks/benchmark.cpp +0 -0
  54. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/cmake/game_resolverConfig.cmake.in +0 -0
  55. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/examples/battle_of_sex.cpp +0 -0
  56. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/examples/bayesian_battle_of_sex.cpp +0 -0
  57. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/examples/cournot.cpp +0 -0
  58. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/examples/hawk_dove.cpp +0 -0
  59. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/examples/prisoners_dilemma.cpp +0 -0
  60. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/action.hpp +0 -0
  61. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/bayesian/bayesian_game.hpp +0 -0
  62. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/bayesian/bayesian_nash_equilibrium.hpp +0 -0
  63. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/bayesian/bayesian_player.hpp +0 -0
  64. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/error.hpp +0 -0
  65. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/game.hpp +0 -0
  66. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/game_resolver.hpp +0 -0
  67. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/nash_equilibrium.hpp +0 -0
  68. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/ndarray.hpp +0 -0
  69. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/payoff.hpp +0 -0
  70. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/player.hpp +0 -0
  71. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/solver/nash_solver.hpp +0 -0
  72. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/include/game_resolver/version.hpp +0 -0
  73. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/CMakeLists.txt +0 -0
  74. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/benchmarks/compare_with_python.py +0 -0
  75. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/bayesian_game.py +0 -0
  76. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/bayesian_player.py +0 -0
  77. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/game.py +0 -0
  78. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/nash_equilibrium.py +0 -0
  79. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/payoff.py +0 -0
  80. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/python/game_resolver_cpp/player.py +0 -0
  81. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/src/action.cpp +0 -0
  82. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/src/bayesian/bayesian_nash_equilibrium.cpp +0 -0
  83. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/src/bayesian/bayesian_player.cpp +0 -0
  84. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/src/nash_equilibrium.cpp +0 -0
  85. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/src/payoff.cpp +0 -0
  86. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/src/solver/nash_solver.cpp +0 -0
  87. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/action_test.cpp +0 -0
  88. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/bayesian_test.cpp +0 -0
  89. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/example_games.hpp +0 -0
  90. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/golden/battle_of_sex.txt +0 -0
  91. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/golden/bayesian_battle_of_sex.txt +0 -0
  92. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/golden/cournot.txt +0 -0
  93. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/golden/generate.py +0 -0
  94. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/golden/hawk_dove.txt +0 -0
  95. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/golden/prisoners_dilemma.txt +0 -0
  96. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/golden/verify_exact.py +0 -0
  97. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/golden_test.cpp +0 -0
  98. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/nash_equilibrium_test.cpp +0 -0
  99. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/nash_solver_test.cpp +0 -0
  100. {game_resolver_cpp-0.1.0 → game_resolver_cpp-0.1.2}/tests/ndarray_test.cpp +0 -0
@@ -17,3 +17,12 @@ compile_commands.json
17
17
  dist/
18
18
  wheelhouse/
19
19
  *.egg-info/
20
+ __pycache__/
21
+ *.pyc
22
+
23
+ # examples が吐くゲームツリー
24
+ entry_deterrence.html
25
+ battle_sequential.html
26
+ battle_simultaneous.html
27
+ centipede.html
28
+ game_tree.html
@@ -1,5 +1,5 @@
1
1
  cmake_minimum_required(VERSION 3.21)
2
- project(game_resolver_cpp VERSION 0.1.0 LANGUAGES CXX)
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.0
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
@@ -78,7 +78,11 @@ All four ways of defining payoffs work, exactly as in `game_resolver`:
78
78
 
79
79
  Subclass `BayesianGame` and implement `posterior()`.
80
80
 
81
+ A Bayesian battle of the sexes. Each player has a type `A` / `B`, observes their own type,
82
+ and computes the expected payoff with the posterior over the other player's type.
83
+
81
84
  ```python
85
+ import numpy as np
82
86
  from game_resolver_cpp.bayesian_game import BayesianGame
83
87
  from game_resolver_cpp.bayesian_nash_equilibrium import BayesianNashEquilibrium
84
88
  from game_resolver_cpp.bayesian_player import BayesianPlayer
@@ -97,14 +101,32 @@ class SampleGame(BayesianGame):
97
101
  other = type_tuple[(player_id + 1) % 2]
98
102
  return 1 / 4 if own == other else 3 / 4
99
103
 
100
- # payoff functions take (own type, each player's action)
101
- def male(self, type, male_action, female_action): ...
102
- def female(self, type, male_action, female_action): ...
104
+ # payoff functions take (own type, each player's action), and must return a number
105
+ def male(self, type, male_action, female_action):
106
+ if male_action != female_action:
107
+ return 0.0
108
+ if type == "A":
109
+ return 2.0 if male_action == "Boxing" else 1.0
110
+ return 1.0 if male_action == "Boxing" else 2.0
111
+
112
+ def female(self, type, male_action, female_action):
113
+ if male_action != female_action:
114
+ return 0.0
115
+ if type == "A":
116
+ return 2.0 if female_action == "Boxing" else 1.0
117
+ return 1.0 if female_action == "Boxing" else 2.0
103
118
 
104
119
 
105
120
  nash = BayesianNashEquilibrium(SampleGame()).get_nash_equilibrium()
121
+ print(nash["index"]) # [(0, 0), (1, 2), (2, 1), (3, 3)]
122
+ print(nash["profile"][0]) # [{'A': 'Boxing', 'B': 'Boxing'}, {'A': 'Boxing', 'B': 'Boxing'}]
123
+ print(nash["payoff"][0]) # [{'A': 2.0, 'B': 1.0}, {'A': 2.0, 'B': 1.0}]
106
124
  ```
107
125
 
126
+ `profile` and `payoff` are indexed by `[equilibrium][player][type]`. The types and actions
127
+ are handed back exactly as you passed them in, so with `np.array` they print as
128
+ `np.str_('A')`; pass plain lists if you want plain strings.
129
+
108
130
  ## What gets faster
109
131
 
110
132
  Against the pure Python `game_resolver`, on an 11-core Apple M series machine.
@@ -136,10 +158,46 @@ an exact `==`, because an exact comparison makes genuine ties depend on rounding
136
158
  Cournot example has three equilibria on its grid; the Python version reports one of them.
137
159
  Pass `tolerance=0` to `NashEquilibrium` for the Python version's strict behaviour.
138
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
+
139
197
  ## Not supported
140
198
 
141
199
  - Mixed strategy Nash equilibria (the Python version does not have them either)
142
- - Extensive form games, subgame perfect equilibria
200
+ - Chance (Nature) nodes in extensive form games
143
201
 
144
202
  ## Links
145
203
 
@@ -235,6 +235,8 @@ class SampleGame : public BayesianGame {
235
235
  };
236
236
  ```
237
237
 
238
+ The bodies left out here are in [examples/bayesian_battle_of_sex.cpp](examples/bayesian_battle_of_sex.cpp).
239
+
238
240
  ```cpp
239
241
  SampleGame game;
240
242
  BayesianNashEquilibrium equilibrium(game);
@@ -248,6 +250,140 @@ std::cout << nash << '\n';
248
250
 
249
251
  `payoff` is the expected payoff indexed by `[equilibrium][player][type]`.
250
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
+
251
387
  ## Use it from Python
252
388
 
253
389
  The package exposes the same interface as the Python `game_resolver`, so existing lab code
@@ -297,6 +433,55 @@ payoff.update(more_entries) # also works
297
433
  Player(0, actions=actions, payoff=mapping) # a plain dict is accepted too
298
434
  ```
299
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
+
300
485
  ### What actually gets faster
301
486
 
302
487
  One run of `python3 python/benchmarks/compare_with_python.py` on an 11-core Apple M series
@@ -361,6 +546,26 @@ The reasoning behind each of these, and the alternatives that were measured and
361
546
  | `BayesianNashEquilibrium(g)` + `initialize_payoff_matrices()` | `BayesianNashEquilibrium(g)` (built in the constructor) |
362
547
  | strategy = `dict[type, action]` | strategy = `Strategy` (a vector of action indices, one per type) |
363
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
+
364
569
  Deliberate differences:
365
570
 
366
571
  - **The Type1 / Type2 distinction is gone from the C++ API.** In the Python version, whether
@@ -399,6 +604,7 @@ make test
399
604
  | `make python-install` | `pip install ".[test]"` |
400
605
  | `make python-test` | run the Python tests |
401
606
  | `make python-bench` | compare against the Python version |
607
+ | `make web` | open the browser editor (needs `make python-install` first) |
402
608
 
403
609
  Or drive CMake directly:
404
610
 
@@ -514,7 +720,7 @@ Two things are deliberately excluded:
514
720
  ## Not supported
515
721
 
516
722
  - Mixed strategy Nash equilibria (the Python version does not have them either)
517
- - Extensive form games, subgame perfect equilibria
723
+ - Chance (Nature) nodes in extensive form games
518
724
  - A Bayesian game's strategy space grows as `|actions|^|types|`. That blow-up is inherent,
519
725
  so oversized settings are rejected up front.
520
726