kaggle-environments 1.23.3__py3-none-any.whl → 1.23.5__py3-none-any.whl
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.
Potentially problematic release.
This version of kaggle-environments might be problematic. Click here for more details.
- kaggle_environments/envs/open_spiel_env/games/repeated_poker/repeated_poker.js +2 -2
- kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/components/getRepeatedPokerStateForStep.js +41 -67
- kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/images/poker_chip_1.svg +22 -0
- kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/images/poker_chip_10.svg +22 -0
- kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/images/poker_chip_100.svg +48 -0
- kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/images/poker_chip_25.svg +22 -0
- kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/images/poker_chip_5.svg +22 -0
- kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/repeated_poker_renderer.js +557 -332
- kaggle_environments/envs/werewolf/README.md +190 -0
- kaggle_environments/envs/werewolf/harness/__init__.py +0 -0
- kaggle_environments/envs/werewolf/harness/base.py +767 -0
- kaggle_environments/envs/werewolf/harness/litellm_models.yaml +51 -0
- kaggle_environments/envs/werewolf/harness/test_base.py +35 -0
- kaggle_environments/envs/werewolf/runner.py +146 -0
- kaggle_environments/envs/werewolf/scripts/__init__.py +0 -0
- kaggle_environments/envs/werewolf/scripts/add_audio.py +425 -0
- kaggle_environments/envs/werewolf/scripts/configs/audio/standard.yaml +24 -0
- kaggle_environments/envs/werewolf/scripts/configs/run/block_basic.yaml +102 -0
- kaggle_environments/envs/werewolf/scripts/configs/run/comprehensive.yaml +100 -0
- kaggle_environments/envs/werewolf/scripts/configs/run/roundrobin_discussion_DisableDoctorSelfSave_DisableDoctorConsecutiveSave_large.yaml +104 -0
- kaggle_environments/envs/werewolf/scripts/configs/run/roundrobin_discussion_large.yaml +103 -0
- kaggle_environments/envs/werewolf/scripts/configs/run/roundrobin_discussion_small.yaml +103 -0
- kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard.yaml +103 -0
- kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard_DisableDoctorSelfSave_DisableDoctorConsecutiveSave.yaml +104 -0
- kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard_DisableDoctorSelfSave_SeerRevealTeam.yaml +105 -0
- kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard_DisableDoctorSelfSave_SeerRevealTeam_NightEliminationNoReveal_DayExileNoReveal.yaml +105 -0
- kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard_DisableDoctorSelfSave_SeerRevealTeam_NightEliminationRevealTeam_DayExileRevealTeam.yaml +105 -0
- kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard_disable_doctor_self_save.yaml +103 -0
- kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard_parallel_voting.yaml +103 -0
- kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard_parallel_voting_no_tie_exile.yaml +103 -0
- kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard_parallel_voting_roundbiddiscussion.yaml +105 -0
- kaggle_environments/envs/werewolf/scripts/configs/run/run_config.yaml +58 -0
- kaggle_environments/envs/werewolf/scripts/configs/run/vertex_api_example_config.yaml +115 -0
- kaggle_environments/envs/werewolf/scripts/measure_cost.py +251 -0
- kaggle_environments/envs/werewolf/scripts/plot_existing_trajectories.py +135 -0
- kaggle_environments/envs/werewolf/scripts/rerender_html.py +87 -0
- kaggle_environments/envs/werewolf/scripts/run.py +93 -0
- kaggle_environments/envs/werewolf/scripts/run_block.py +237 -0
- kaggle_environments/envs/werewolf/scripts/run_pairwise_matrix.py +222 -0
- kaggle_environments/envs/werewolf/scripts/self_play.py +196 -0
- kaggle_environments/envs/werewolf/scripts/utils.py +47 -0
- {kaggle_environments-1.23.3.dist-info → kaggle_environments-1.23.5.dist-info}/METADATA +1 -1
- {kaggle_environments-1.23.3.dist-info → kaggle_environments-1.23.5.dist-info}/RECORD +46 -8
- {kaggle_environments-1.23.3.dist-info → kaggle_environments-1.23.5.dist-info}/WHEEL +0 -0
- {kaggle_environments-1.23.3.dist-info → kaggle_environments-1.23.5.dist-info}/entry_points.txt +0 -0
- {kaggle_environments-1.23.3.dist-info → kaggle_environments-1.23.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Run pairwise zero-sum setting where one player play the entire team of Werewolf and another player play
|
|
2
|
+
the team of Villager. Given a config, we play all possible pairwise combinations N times.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import logging
|
|
7
|
+
import math
|
|
8
|
+
import multiprocessing
|
|
9
|
+
import os
|
|
10
|
+
import random
|
|
11
|
+
from copy import deepcopy
|
|
12
|
+
from typing import List
|
|
13
|
+
|
|
14
|
+
import tenacity
|
|
15
|
+
import yaml
|
|
16
|
+
from tqdm import tqdm
|
|
17
|
+
|
|
18
|
+
from kaggle_environments.envs.werewolf.game.consts import RoleConst
|
|
19
|
+
from kaggle_environments.envs.werewolf.runner import LogExecutionTime, append_timestamp_to_dir, setup_logger
|
|
20
|
+
from kaggle_environments.envs.werewolf.scripts.utils import run_single_game_cli
|
|
21
|
+
|
|
22
|
+
# Initialize a placeholder logger
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_config(config_path):
|
|
27
|
+
"""Loads the configuration from a YAML file."""
|
|
28
|
+
with open(config_path, "r") as f:
|
|
29
|
+
return yaml.safe_load(f)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_team_roles(base_roles: List[str]) -> (List[str], List[str]):
|
|
33
|
+
"""Partitions roles into villager and werewolf teams."""
|
|
34
|
+
villager_roles = []
|
|
35
|
+
werewolf_roles = []
|
|
36
|
+
for role_name in base_roles:
|
|
37
|
+
role = RoleConst(role_name)
|
|
38
|
+
if role == RoleConst.WEREWOLF:
|
|
39
|
+
werewolf_roles.append(role_name)
|
|
40
|
+
else:
|
|
41
|
+
villager_roles.append(role_name)
|
|
42
|
+
return villager_roles, werewolf_roles
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
run_single_game_with_retry = tenacity.retry(
|
|
46
|
+
wait=tenacity.wait_exponential(multiplier=1, min=2, max=10),
|
|
47
|
+
stop=tenacity.stop_after_attempt(3),
|
|
48
|
+
before_sleep=tenacity.before_sleep_log(logger, logging.INFO),
|
|
49
|
+
)(run_single_game_cli)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def game_runner_wrapper(args):
|
|
53
|
+
"""Wrapper to unpack arguments for the multiprocessing pool."""
|
|
54
|
+
game_dir, game_config, use_random_agents, debug, _, _ = args
|
|
55
|
+
run_single_game_with_retry(game_dir, game_config, use_random_agents, debug)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def assign_roles_dup_agents(roles, agent_config, player_ids):
|
|
59
|
+
agents = [deepcopy(agent_config) for _ in range(len(roles))]
|
|
60
|
+
for role, agent, player_id in zip(roles, agents, player_ids):
|
|
61
|
+
agent["role"] = role
|
|
62
|
+
agent["id"] = player_id
|
|
63
|
+
return agents
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def prepare_pairwise_agents(villager_roles, werewolf_roles, player_a_config, player_b_config, player_ids):
|
|
67
|
+
pid_v, pid_w = player_ids[: len(villager_roles)], player_ids[len(villager_roles) :]
|
|
68
|
+
agents_v = assign_roles_dup_agents(villager_roles, player_a_config, pid_v)
|
|
69
|
+
agents_w = assign_roles_dup_agents(werewolf_roles, player_b_config, pid_w)
|
|
70
|
+
agents = agents_v + agents_w
|
|
71
|
+
return agents
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def generate_game_tasks(output_dir, num_tournaments, config, use_random_agents, debug):
|
|
75
|
+
"""
|
|
76
|
+
Generates game configurations for a pairwise matrix tournament.
|
|
77
|
+
"""
|
|
78
|
+
base_game_config = config["game_config"]
|
|
79
|
+
all_players = base_game_config["agents"]
|
|
80
|
+
num_players = len(all_players)
|
|
81
|
+
base_roles = [agent["role"] for agent in all_players]
|
|
82
|
+
player_ids = [agent["id"] for agent in all_players]
|
|
83
|
+
|
|
84
|
+
villager_roles, werewolf_roles = get_team_roles(base_roles)
|
|
85
|
+
|
|
86
|
+
if not werewolf_roles:
|
|
87
|
+
raise ValueError("Configuration must include at least one werewolf role.")
|
|
88
|
+
if not villager_roles:
|
|
89
|
+
raise ValueError("Configuration must include at least one villager role.")
|
|
90
|
+
|
|
91
|
+
for tourney_idx in range(num_tournaments):
|
|
92
|
+
for i in range(num_players):
|
|
93
|
+
for j in range(num_players):
|
|
94
|
+
game_dir = os.path.join(output_dir, f"tourney_{tourney_idx}", f"game_{i}_vs_{j}")
|
|
95
|
+
os.makedirs(game_dir, exist_ok=True)
|
|
96
|
+
|
|
97
|
+
player_a_config = all_players[i]
|
|
98
|
+
player_b_config = all_players[j]
|
|
99
|
+
|
|
100
|
+
game_agents_config = prepare_pairwise_agents(
|
|
101
|
+
villager_roles, werewolf_roles, player_a_config, player_b_config, player_ids
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# since name has to be unique and all names come from config, we by default shuffle all names
|
|
105
|
+
# since name might change
|
|
106
|
+
random.shuffle(player_ids)
|
|
107
|
+
for agent_ind, agent in enumerate(game_agents_config):
|
|
108
|
+
agent["id"] = player_ids[agent_ind]
|
|
109
|
+
|
|
110
|
+
random.shuffle(game_agents_config)
|
|
111
|
+
|
|
112
|
+
game_config = {**base_game_config, "agents": game_agents_config}
|
|
113
|
+
yield game_dir, game_config, use_random_agents, debug, tourney_idx, f"{i}_vs_{j}"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def run_tournament(output_dir, num_tournaments, config, use_random_agents, debug, parallel, num_processes):
|
|
117
|
+
"""
|
|
118
|
+
Runs a tournament by generating all game tasks and processing them,
|
|
119
|
+
potentially in parallel.
|
|
120
|
+
"""
|
|
121
|
+
total_games = num_tournaments * len(config["game_config"]["agents"]) ** 2
|
|
122
|
+
|
|
123
|
+
if parallel:
|
|
124
|
+
logger.info(f"Running games in parallel with up to {num_processes} processes.")
|
|
125
|
+
|
|
126
|
+
game_tasks = generate_game_tasks(output_dir, num_tournaments, config, use_random_agents, debug)
|
|
127
|
+
|
|
128
|
+
# the following shuffle is to reduce the load of a particular LLM api
|
|
129
|
+
game_tasks = [*game_tasks]
|
|
130
|
+
random.shuffle(game_tasks)
|
|
131
|
+
|
|
132
|
+
with tqdm(total=total_games, desc="Processing Games") as pbar:
|
|
133
|
+
if parallel:
|
|
134
|
+
with multiprocessing.Pool(processes=num_processes) as pool:
|
|
135
|
+
for _ in pool.imap_unordered(game_runner_wrapper, game_tasks):
|
|
136
|
+
pbar.update(1)
|
|
137
|
+
else:
|
|
138
|
+
for task_args in game_tasks:
|
|
139
|
+
game_runner_wrapper(task_args)
|
|
140
|
+
pbar.update(1)
|
|
141
|
+
|
|
142
|
+
logger.info("All game tasks have been processed.")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def main():
|
|
146
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
147
|
+
default_config_path = os.path.join(script_dir, "configs", "run", "run_config.yaml")
|
|
148
|
+
|
|
149
|
+
parser = argparse.ArgumentParser(description="Run a pairwise matrix tournament for the Werewolf game.")
|
|
150
|
+
parser.add_argument(
|
|
151
|
+
"-o",
|
|
152
|
+
"--output_dir",
|
|
153
|
+
type=str,
|
|
154
|
+
help="Output directory for game replays and logs.",
|
|
155
|
+
default="werewolf_pairwise_matrix",
|
|
156
|
+
)
|
|
157
|
+
parser.add_argument(
|
|
158
|
+
"-c", "--config", type=str, default=default_config_path, help="Path to the base configuration YAML file."
|
|
159
|
+
)
|
|
160
|
+
parser.add_argument(
|
|
161
|
+
"-t",
|
|
162
|
+
"--num_tournaments",
|
|
163
|
+
type=int,
|
|
164
|
+
default=1,
|
|
165
|
+
help="Number of tournaments to run. Each tournament is a full N*N matrix of games.",
|
|
166
|
+
)
|
|
167
|
+
parser.add_argument(
|
|
168
|
+
"-r", "--use_random_agents", action="store_true", help="Use random agents for all players for fast testing."
|
|
169
|
+
)
|
|
170
|
+
parser.add_argument(
|
|
171
|
+
"-d",
|
|
172
|
+
"--debug",
|
|
173
|
+
action="store_true",
|
|
174
|
+
help="Enable debug mode for the game environment. Forces sequential execution.",
|
|
175
|
+
)
|
|
176
|
+
parser.add_argument("-p", "--parallel", action="store_true", help="Run games in parallel using multiple processes.")
|
|
177
|
+
parser.add_argument(
|
|
178
|
+
"-n", "--num_processes", type=int, default=None, help="Number of processes for parallel execution."
|
|
179
|
+
)
|
|
180
|
+
parser.add_argument(
|
|
181
|
+
"-a", "--append_timestamp_to_dir", action="store_true", help="Append a timestamp to the output directory."
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
args = parser.parse_args()
|
|
185
|
+
|
|
186
|
+
output_dir = append_timestamp_to_dir(args.output_dir, append=args.append_timestamp_to_dir)
|
|
187
|
+
|
|
188
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
189
|
+
|
|
190
|
+
setup_logger(output_dir, "run_pairwise_matrix")
|
|
191
|
+
|
|
192
|
+
config = load_config(args.config)
|
|
193
|
+
|
|
194
|
+
if args.num_processes is None:
|
|
195
|
+
num_processes = max(1, math.floor(multiprocessing.cpu_count() * 0.8))
|
|
196
|
+
else:
|
|
197
|
+
num_processes = args.num_processes
|
|
198
|
+
|
|
199
|
+
logger.info("Starting tournament with the following settings:")
|
|
200
|
+
logger.info(f"Output Directory: {output_dir}")
|
|
201
|
+
logger.info(f"Number of Tournaments: {args.num_tournaments}")
|
|
202
|
+
logger.info(f"Parallel Execution: {args.parallel}")
|
|
203
|
+
if args.parallel:
|
|
204
|
+
logger.info(f"Number of Processes: {num_processes}")
|
|
205
|
+
logger.info(f"Debug Mode: {args.debug}")
|
|
206
|
+
logger.info(f"Use Random Agents: {args.use_random_agents}")
|
|
207
|
+
|
|
208
|
+
with LogExecutionTime(logger_obj=logger, task_str="pairwise matrix tournament"):
|
|
209
|
+
run_tournament(
|
|
210
|
+
output_dir=output_dir,
|
|
211
|
+
num_tournaments=args.num_tournaments,
|
|
212
|
+
config=config,
|
|
213
|
+
use_random_agents=args.use_random_agents,
|
|
214
|
+
debug=args.debug,
|
|
215
|
+
parallel=args.parallel,
|
|
216
|
+
num_processes=num_processes,
|
|
217
|
+
)
|
|
218
|
+
logger.info("Tournament finished successfully.")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
if __name__ == "__main__":
|
|
222
|
+
main()
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Run the settings in a given config with all agents llm agents by substituting all with a single model.
|
|
2
|
+
This is useful for example to evaluate the game rule balance.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import copy
|
|
7
|
+
import logging
|
|
8
|
+
import multiprocessing
|
|
9
|
+
import os
|
|
10
|
+
import random
|
|
11
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
12
|
+
|
|
13
|
+
import tenacity
|
|
14
|
+
import yaml
|
|
15
|
+
from tqdm import tqdm
|
|
16
|
+
|
|
17
|
+
from kaggle_environments.envs.werewolf.runner import LogExecutionTime, append_timestamp_to_dir, setup_logger
|
|
18
|
+
from kaggle_environments.envs.werewolf.scripts.utils import run_single_game_cli
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
run_single_game_with_retry = tenacity.retry(
|
|
24
|
+
wait=tenacity.wait_random_exponential(multiplier=1, min=2, max=10),
|
|
25
|
+
stop=tenacity.stop_after_attempt(3),
|
|
26
|
+
before_sleep=tenacity.before_sleep_log(logger, logging.INFO),
|
|
27
|
+
)(run_single_game_cli)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def game_runner_wrapper(args):
|
|
31
|
+
"""Wrapper to unpack arguments for the multiprocessing pool."""
|
|
32
|
+
game_dir, game_config, use_random_agents, debug = args
|
|
33
|
+
run_single_game_with_retry(game_dir, game_config, use_random_agents, debug)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def shuffle_field(agents, field_name):
|
|
37
|
+
values = [agent[field_name] for agent in agents]
|
|
38
|
+
random.shuffle(values)
|
|
39
|
+
for agent, value in zip(agents, values):
|
|
40
|
+
agent[field_name] = value
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def run_self_play_games(
|
|
44
|
+
model_name,
|
|
45
|
+
thumbnail,
|
|
46
|
+
output_dir,
|
|
47
|
+
num_games,
|
|
48
|
+
config,
|
|
49
|
+
use_random_agents,
|
|
50
|
+
debug,
|
|
51
|
+
parallel,
|
|
52
|
+
num_processes,
|
|
53
|
+
shuffle_roles,
|
|
54
|
+
):
|
|
55
|
+
"""
|
|
56
|
+
Generates and runs game tasks for the self-play experiment.
|
|
57
|
+
"""
|
|
58
|
+
if debug:
|
|
59
|
+
logger.warning("Debug mode is enabled. Forcing sequential execution.")
|
|
60
|
+
|
|
61
|
+
game_tasks = []
|
|
62
|
+
base_game_config = config["game_config"]
|
|
63
|
+
|
|
64
|
+
# modify the config to use a single model
|
|
65
|
+
agents = base_game_config["agents"]
|
|
66
|
+
for agent in agents:
|
|
67
|
+
agent["thumbnail"] = thumbnail
|
|
68
|
+
agent["agent_id"] = f"llm/{model_name}"
|
|
69
|
+
agent["display_name"] = os.path.basename(model_name)
|
|
70
|
+
agent["llms"][0]["model_name"] = model_name
|
|
71
|
+
|
|
72
|
+
for i in range(num_games):
|
|
73
|
+
game_output_dir = os.path.join(output_dir, f"game_{i}")
|
|
74
|
+
os.makedirs(game_output_dir, exist_ok=True)
|
|
75
|
+
|
|
76
|
+
game_config = copy.deepcopy(base_game_config)
|
|
77
|
+
|
|
78
|
+
if shuffle_roles:
|
|
79
|
+
logger.info(f"Shuffling roles for game {i}")
|
|
80
|
+
role_configs = [
|
|
81
|
+
{"role": agent["role"], "role_params": agent.get("role_params", {})} for agent in game_config["agents"]
|
|
82
|
+
]
|
|
83
|
+
random.shuffle(role_configs)
|
|
84
|
+
for agent, role_config in zip(game_config["agents"], role_configs):
|
|
85
|
+
agent["role"] = role_config["role"]
|
|
86
|
+
agent["role_params"] = role_config["role_params"]
|
|
87
|
+
|
|
88
|
+
# shuffle player ids
|
|
89
|
+
logger.info(f"Shuffling player ids for game {i}")
|
|
90
|
+
shuffle_field(game_config["agents"], "id")
|
|
91
|
+
|
|
92
|
+
task = (game_output_dir, game_config, use_random_agents, debug)
|
|
93
|
+
game_tasks.append(task)
|
|
94
|
+
|
|
95
|
+
with tqdm(total=num_games, desc="Running Self-Play Games") as pbar:
|
|
96
|
+
if parallel:
|
|
97
|
+
with ThreadPoolExecutor(max_workers=num_processes) as executor:
|
|
98
|
+
futures = [executor.submit(game_runner_wrapper, task) for task in game_tasks]
|
|
99
|
+
for future in as_completed(futures):
|
|
100
|
+
# You could also add error handling here by checking future.exception()
|
|
101
|
+
pbar.update(1)
|
|
102
|
+
else:
|
|
103
|
+
for task in game_tasks:
|
|
104
|
+
game_runner_wrapper(task)
|
|
105
|
+
pbar.update(1)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def main():
|
|
109
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
110
|
+
default_config_path = os.path.join(script_dir, "configs", "run", "roundrobin_discussion_small.yaml")
|
|
111
|
+
|
|
112
|
+
parser = argparse.ArgumentParser(description="Run N self-play Werewolf games based on a configuration file.")
|
|
113
|
+
parser.add_argument(
|
|
114
|
+
"-c", "--config_path", type=str, default=default_config_path, help="Path to the YAML configuration file."
|
|
115
|
+
)
|
|
116
|
+
parser.add_argument(
|
|
117
|
+
"-o",
|
|
118
|
+
"--output_dir",
|
|
119
|
+
type=str,
|
|
120
|
+
default="werewolf_self_play",
|
|
121
|
+
help="Output directory for the log and replay files.",
|
|
122
|
+
)
|
|
123
|
+
parser.add_argument(
|
|
124
|
+
"-m",
|
|
125
|
+
"--model_name",
|
|
126
|
+
type=str,
|
|
127
|
+
default="gemini/gemini-2.5-flash",
|
|
128
|
+
help="The model name by litellm for self play.",
|
|
129
|
+
)
|
|
130
|
+
parser.add_argument(
|
|
131
|
+
"-t",
|
|
132
|
+
"--thumbnail",
|
|
133
|
+
type=str,
|
|
134
|
+
default="https://logos-world.net/wp-content/uploads/2025/01/Google-Gemini-Symbol.png",
|
|
135
|
+
help="The thumbnail image url.",
|
|
136
|
+
)
|
|
137
|
+
parser.add_argument("-n", "--num_games", type=int, default=1, help="Number of self-play games to run.")
|
|
138
|
+
parser.add_argument("-d", "--debug", action="store_true", help="Enable debug mode.")
|
|
139
|
+
parser.add_argument(
|
|
140
|
+
"-r", "--random_agents", action="store_true", help="Use random agents for all players for fast testing."
|
|
141
|
+
)
|
|
142
|
+
parser.add_argument(
|
|
143
|
+
"-a", "--append_timestamp_to_dir", action="store_true", help="Append a timestamp to the output directory."
|
|
144
|
+
)
|
|
145
|
+
parser.add_argument(
|
|
146
|
+
"-s", "--shuffle_roles", action="store_true", help="If provided, shuffle the roles for each game."
|
|
147
|
+
)
|
|
148
|
+
parser.add_argument("-p", "--parallel", action="store_true", help="Run games in parallel using multiple processes.")
|
|
149
|
+
parser.add_argument("--num_processes", type=int, default=None, help="Number of processes for parallel execution.")
|
|
150
|
+
|
|
151
|
+
args = parser.parse_args()
|
|
152
|
+
|
|
153
|
+
run_output_dir = append_timestamp_to_dir(args.output_dir, append=args.append_timestamp_to_dir)
|
|
154
|
+
os.makedirs(run_output_dir, exist_ok=True)
|
|
155
|
+
setup_logger(output_dir=run_output_dir, base_name="self_play")
|
|
156
|
+
|
|
157
|
+
with open(args.config_path, "r") as f:
|
|
158
|
+
config = yaml.safe_load(f)
|
|
159
|
+
|
|
160
|
+
num_processes = args.num_processes
|
|
161
|
+
if args.parallel and num_processes is None:
|
|
162
|
+
# Default to 4x the number of CPUs for I/O bound tasks
|
|
163
|
+
num_processes = multiprocessing.cpu_count() * 4
|
|
164
|
+
|
|
165
|
+
logger.info("Starting self-play with the following settings:")
|
|
166
|
+
logger.info(f"Model Name: {args.model_name}")
|
|
167
|
+
logger.info(f"Thumbnail: {args.thumbnail}")
|
|
168
|
+
logger.info(f"Output Directory: {run_output_dir}")
|
|
169
|
+
logger.info(f"Number of Games: {args.num_games}")
|
|
170
|
+
logger.info(f"Config Path: {args.config_path}")
|
|
171
|
+
logger.info(f"Parallel Execution: {args.parallel}")
|
|
172
|
+
if args.parallel:
|
|
173
|
+
logger.info(f"Number of Processes: {num_processes}")
|
|
174
|
+
logger.info(f"Debug Mode: {args.debug}")
|
|
175
|
+
logger.info(f"Use Random Agents: {args.random_agents}")
|
|
176
|
+
logger.info(f"Shuffle Roles: {args.shuffle_roles}")
|
|
177
|
+
|
|
178
|
+
with LogExecutionTime(logger_obj=logger, task_str=f"{args.num_games} self-play games"):
|
|
179
|
+
run_self_play_games(
|
|
180
|
+
model_name=args.model_name,
|
|
181
|
+
thumbnail=args.thumbnail,
|
|
182
|
+
output_dir=run_output_dir,
|
|
183
|
+
num_games=args.num_games,
|
|
184
|
+
config=config,
|
|
185
|
+
use_random_agents=args.random_agents,
|
|
186
|
+
debug=args.debug,
|
|
187
|
+
parallel=args.parallel,
|
|
188
|
+
num_processes=num_processes,
|
|
189
|
+
shuffle_roles=args.shuffle_roles,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
logger.info("Self-play run finished successfully.")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
if __name__ == "__main__":
|
|
196
|
+
main()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_single_game_cli(game_dir, game_config, use_random_agents, debug):
|
|
12
|
+
"""
|
|
13
|
+
Sets up and runs a single game instance by calling run.py. Running a separate process has the distinct advantage
|
|
14
|
+
of an atomic game execution unit, so the logging and dumps including html render and json are cleaner.
|
|
15
|
+
"""
|
|
16
|
+
out_config = {"game_config": game_config}
|
|
17
|
+
config_path = os.path.join(game_dir, "config.yaml")
|
|
18
|
+
with open(config_path, "w") as f:
|
|
19
|
+
yaml.dump(out_config, f, default_flow_style=False)
|
|
20
|
+
|
|
21
|
+
run_py_path = os.path.join(os.path.dirname(__file__), "run.py")
|
|
22
|
+
cmd = [
|
|
23
|
+
sys.executable,
|
|
24
|
+
run_py_path,
|
|
25
|
+
"--config_path",
|
|
26
|
+
config_path,
|
|
27
|
+
"--output_dir",
|
|
28
|
+
game_dir,
|
|
29
|
+
]
|
|
30
|
+
if use_random_agents:
|
|
31
|
+
cmd.append("--random_agents")
|
|
32
|
+
if debug:
|
|
33
|
+
cmd.append("--debug")
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
37
|
+
logger.info(f"Game in {game_dir} completed successfully.")
|
|
38
|
+
if result.stdout:
|
|
39
|
+
logger.info(result.stdout)
|
|
40
|
+
if result.stderr:
|
|
41
|
+
logger.warning(f"Stderr (non-fatal) from game in {game_dir}: {result.stderr}")
|
|
42
|
+
except subprocess.CalledProcessError as e:
|
|
43
|
+
error_message = (
|
|
44
|
+
f"Error running game in {game_dir}.\nReturn Code: {e.returncode}\nStdout: {e.stdout}\nStderr: {e.stderr}"
|
|
45
|
+
)
|
|
46
|
+
logger.error(error_message)
|
|
47
|
+
raise RuntimeError(error_message) from e
|
|
@@ -188,16 +188,21 @@ kaggle_environments/envs/open_spiel_env/games/connect_four/connect_four_proxy.py
|
|
|
188
188
|
kaggle_environments/envs/open_spiel_env/games/go/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
189
189
|
kaggle_environments/envs/open_spiel_env/games/go/go.js,sha256=SWUPrnQCMBlDjUiKTgVn9LeTUM85YbR10JRrq3ddw14,23751
|
|
190
190
|
kaggle_environments/envs/open_spiel_env/games/go/go_proxy.py,sha256=_b-PTFlN7FUtjSx26ciR6Os4rTop8A6vemF86HMYMO8,3407
|
|
191
|
-
kaggle_environments/envs/open_spiel_env/games/repeated_poker/repeated_poker.js,sha256=
|
|
191
|
+
kaggle_environments/envs/open_spiel_env/games/repeated_poker/repeated_poker.js,sha256=a2DJk6SrKCU_j3cvTk_eaIIWhyCIdg4r5fqYLzFMP4o,35306
|
|
192
192
|
kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/index.html,sha256=9BsANJfu4GHJhJbtwPGGyF8vjLpYryYvj5NtghwSRzU,372
|
|
193
193
|
kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/package.json,sha256=wB7388yBrChi5rrP2bIUguAgYL_YJ-a9LqZD5lu4Pcs,536
|
|
194
194
|
kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/tsconfig.json,sha256=3X9dsQOgFw_cZ_uXByZIsGrQ5jhFfAZZL7QC6ApJWak,133
|
|
195
195
|
kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/vite.config.ts,sha256=KhIjUn0WWhaoQzQ5YKuWjNndimRF0kFlYDgEnZ0cg7U,208
|
|
196
196
|
kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/replays/test-replay.json,sha256=jf4ilR6SmOYPNohkIGJvmKP5Gju5FY6sfSStX1qtFZg,28919900
|
|
197
197
|
kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/main.ts,sha256=5NZlic4P9aSdMJpWxckaDrObEx2OJj8FNG49g-Z9_ow,1095
|
|
198
|
-
kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/repeated_poker_renderer.js,sha256=
|
|
199
|
-
kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/components/getRepeatedPokerStateForStep.js,sha256=
|
|
198
|
+
kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/repeated_poker_renderer.js,sha256=RmbMsVoJobYNypSiyv1kdG-hleRR5-jquIZJZZdVcyM,26598
|
|
199
|
+
kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/components/getRepeatedPokerStateForStep.js,sha256=T-2pegCVHT1OP6PhllPS-3RRQYWe912v2dw5eR5tb0k,8051
|
|
200
200
|
kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/components/utils.js,sha256=pXDAu4V2OppRCvMdJKQ56q1uFTJReMPIvBL6gwxIJoI,5734
|
|
201
|
+
kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/images/poker_chip_1.svg,sha256=v9yCvpnaQAg8OSUJdJ5PhuTHm9_zXnww-9_7oR_DJpc,22160
|
|
202
|
+
kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/images/poker_chip_10.svg,sha256=z3CP2h5eUGlgBdqNoWGcioekAyPgiuzhyRNr-nbutOE,22160
|
|
203
|
+
kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/images/poker_chip_100.svg,sha256=y4T3Dolr1ZH7HPlTK8B_0SOJO_cDr9HzyqWMuyS7Q-s,63077
|
|
204
|
+
kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/images/poker_chip_25.svg,sha256=oaxCKGaeSZ-LklQ6PRv5OS9wz-tfuvJ2BX4uT15KNEo,22160
|
|
205
|
+
kaggle_environments/envs/open_spiel_env/games/repeated_poker/visualizer/default/src/images/poker_chip_5.svg,sha256=KRgV4RAzJD9DJqzNc0wkKqlN6DhJAuEBl919nQMCJhY,22160
|
|
201
206
|
kaggle_environments/envs/open_spiel_env/games/tic_tac_toe/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
202
207
|
kaggle_environments/envs/open_spiel_env/games/tic_tac_toe/tic_tac_toe.js,sha256=OD7FbhhOqmL8OC7UqPt0S9znwcUY4YTuhRCdsBd3ALE,17339
|
|
203
208
|
kaggle_environments/envs/open_spiel_env/games/tic_tac_toe/tic_tac_toe_proxy.py,sha256=r1A2Yl0vapJRrTEnvwfP9-P-uz_xtyWdWKXGsRXdR4o,2937
|
|
@@ -218,7 +223,9 @@ kaggle_environments/envs/tictactoe/tictactoe.js,sha256=NZDT-oSG0a6a-rso9Ldh9qkJw
|
|
|
218
223
|
kaggle_environments/envs/tictactoe/tictactoe.json,sha256=zMXZ8-fpT7FBhzz2FFBvRLn4XwtngjEqOieMvI6cCj8,1121
|
|
219
224
|
kaggle_environments/envs/tictactoe/tictactoe.py,sha256=uq3sTHWNMg0dxX2v9pTbJAKM7fwerxQt7OQjCX96m-Y,3657
|
|
220
225
|
kaggle_environments/envs/werewolf/GAME_RULE.md,sha256=KagfA21SyAaT4qgcfAcBwsaxU71RUo-6vajCqFe7PbE,3730
|
|
226
|
+
kaggle_environments/envs/werewolf/README.md,sha256=2pQUjBZjTRNJrIAyKT4qvrx3FjzIM4aJtBFDjBrFxQY,9159
|
|
221
227
|
kaggle_environments/envs/werewolf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
228
|
+
kaggle_environments/envs/werewolf/runner.py,sha256=grz8y4Sa6Qr4J5uCyjDuErcu6LhscBYMgHz9lMBa0HQ,5005
|
|
222
229
|
kaggle_environments/envs/werewolf/test_werewolf.py,sha256=_tXQ3hxGODIGuNYkl2r06mNGIoAQBLRvf1MYFWabmmE,4457
|
|
223
230
|
kaggle_environments/envs/werewolf/test_werewolf_deterministic.py,sha256=wj3TzO60msKwuwb8oA0gxENqCA9mdN5rvH8L4xn9sb8,9068
|
|
224
231
|
kaggle_environments/envs/werewolf/werewolf.js,sha256=41MKCrV8tka4z_YwrEdtA7jI-UuISJK8bGaTv2b4bw4,192343
|
|
@@ -240,9 +247,40 @@ kaggle_environments/envs/werewolf/game/protocols/bid.py,sha256=ieaL2zxwQiUvRIc78
|
|
|
240
247
|
kaggle_environments/envs/werewolf/game/protocols/chat.py,sha256=wFewQogic1CHr4AW_tZqvkdF3IiX5ZzCzhyp0tMkcJg,18892
|
|
241
248
|
kaggle_environments/envs/werewolf/game/protocols/factory.py,sha256=hg9Xj5Z-8USmqhouVzo8b7Ktl91xzFsE5_8naD6yErY,2316
|
|
242
249
|
kaggle_environments/envs/werewolf/game/protocols/vote.py,sha256=hKw64ubLMCIAIG5IcmNeV9MPTWWmWpkkCAyia_5kXn0,20716
|
|
250
|
+
kaggle_environments/envs/werewolf/harness/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
251
|
+
kaggle_environments/envs/werewolf/harness/base.py,sha256=c3bAJjwJKZmpqE6uZrBy1OkN2REaDDDywW3K9SPBpkw,32194
|
|
252
|
+
kaggle_environments/envs/werewolf/harness/litellm_models.yaml,sha256=58U7eQ5O72cCLTI9Dea-v1tIHSArgOl2IJHQNmrFWtQ,1573
|
|
253
|
+
kaggle_environments/envs/werewolf/harness/test_base.py,sha256=lYxOJrtLX-ttHirtD18sM2XkztH0cRgy69RTFVNZE44,1005
|
|
254
|
+
kaggle_environments/envs/werewolf/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
255
|
+
kaggle_environments/envs/werewolf/scripts/add_audio.py,sha256=jFqne5pMPuyrc936AqBn3rWjgFulBnj-V7tc3RL6SDI,18316
|
|
256
|
+
kaggle_environments/envs/werewolf/scripts/measure_cost.py,sha256=_AF0J9BcTtUc_8SgzHk6sdPIHM0v6VFvxKmE677Z6NY,10327
|
|
257
|
+
kaggle_environments/envs/werewolf/scripts/plot_existing_trajectories.py,sha256=VWjN5YhbbbzvmeLZWTt_ZH9InW4I1e8SsfaIPX22X90,5465
|
|
258
|
+
kaggle_environments/envs/werewolf/scripts/rerender_html.py,sha256=QvsOa_FIDK8RgGwVP5AHa8I0DbvhgzhxoQ4z-Q5lbnE,3097
|
|
259
|
+
kaggle_environments/envs/werewolf/scripts/run.py,sha256=WNqdp0Q6nCZPpq6y7UZdHqI28LTPV6BfJ-9v6HWEEKc,3394
|
|
260
|
+
kaggle_environments/envs/werewolf/scripts/run_block.py,sha256=oyaIEMFtQiLzBf4pUUxjmV8f2aLwPXAsz6TIbH9AjKg,8760
|
|
261
|
+
kaggle_environments/envs/werewolf/scripts/run_pairwise_matrix.py,sha256=vUef22sgClpQY_UHuPPXXYMrVO5fUxB8RH5G3-kT3U0,8299
|
|
262
|
+
kaggle_environments/envs/werewolf/scripts/self_play.py,sha256=tVTc_B5VgK4MgQFsyNwzbVKUtluXC15iGdULb4iWSy4,7210
|
|
263
|
+
kaggle_environments/envs/werewolf/scripts/utils.py,sha256=JPLqWaEJRWLhh3x4TyEOsKMuGq1Nvq4vsctG2uDLA74,1574
|
|
264
|
+
kaggle_environments/envs/werewolf/scripts/configs/audio/standard.yaml,sha256=3KbvRfS054CCz8qJqbmi6jH3otq97Wi0BmxJEKesIa8,1211
|
|
265
|
+
kaggle_environments/envs/werewolf/scripts/configs/run/block_basic.yaml,sha256=S0kpIwyVTSVwF9rZMnjbVNXaauZs1rBl0Bq3jMMlBQg,4383
|
|
266
|
+
kaggle_environments/envs/werewolf/scripts/configs/run/comprehensive.yaml,sha256=PrtcvzrIipeHYywFe-ZN0dPr0jZPLMrSvA4ibsTeYpk,3771
|
|
267
|
+
kaggle_environments/envs/werewolf/scripts/configs/run/roundrobin_discussion_DisableDoctorSelfSave_DisableDoctorConsecutiveSave_large.yaml,sha256=7Wm9MUvmZsye2yGLkQarvTEFOXbo3vMtaRncmx0kJkQ,3668
|
|
268
|
+
kaggle_environments/envs/werewolf/scripts/configs/run/roundrobin_discussion_large.yaml,sha256=kv1oM2WCT0aaGmqNSI0p1QUCAIJYZk4GHH1v-0B02V0,3628
|
|
269
|
+
kaggle_environments/envs/werewolf/scripts/configs/run/roundrobin_discussion_small.yaml,sha256=MO3VRvkemPxkifFYuKiq3QkGQ8ePK_1ovsaSrkPH9fU,3666
|
|
270
|
+
kaggle_environments/envs/werewolf/scripts/configs/run/run_config.yaml,sha256=KDlo8slKjUg11a73PsdBliBU8NGh5GzOl5FSTfOVJmk,1590
|
|
271
|
+
kaggle_environments/envs/werewolf/scripts/configs/run/vertex_api_example_config.yaml,sha256=EBYgyXEh8VyxqaBeuKA6SwwanCKPatZPsLaFXbG6VM4,4832
|
|
272
|
+
kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard.yaml,sha256=MO3VRvkemPxkifFYuKiq3QkGQ8ePK_1ovsaSrkPH9fU,3666
|
|
273
|
+
kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard_DisableDoctorSelfSave_DisableDoctorConsecutiveSave.yaml,sha256=UKiyNrbXR8QQ0Mf_swe1XDgpCT0vt70Drc9j2Aki1N8,3706
|
|
274
|
+
kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard_DisableDoctorSelfSave_SeerRevealTeam.yaml,sha256=akqN1D4ta87bovR2f346T8OOcUGZXAk8Jw82Lk7Yp5Y,3715
|
|
275
|
+
kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard_DisableDoctorSelfSave_SeerRevealTeam_NightEliminationNoReveal_DayExileNoReveal.yaml,sha256=GyqWm9fJ0kQNmKqMN-aYYxOb-g3m0gdgW02ceAr2-GU,3725
|
|
276
|
+
kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard_DisableDoctorSelfSave_SeerRevealTeam_NightEliminationRevealTeam_DayExileRevealTeam.yaml,sha256=1RmxdyHBliWWeoif0j3h6gROEeGMYYp_7wNGmlQoWCg,3715
|
|
277
|
+
kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard_disable_doctor_self_save.yaml,sha256=U8IvlC0CiwnT1nhqPQZ_SmRjmVehVuzPUclbe6FBLrw,3667
|
|
278
|
+
kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard_parallel_voting.yaml,sha256=jcfXR3L0cRRSgkbLMZk3KNCPIcvc3jut_nEkc-rlSyA,3658
|
|
279
|
+
kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard_parallel_voting_no_tie_exile.yaml,sha256=sfSFlFU4F7doZ-wXUWBl-JgJtmpjrLR-SpCAqKnUYeQ,3662
|
|
280
|
+
kaggle_environments/envs/werewolf/scripts/configs/run/rule_experiment/standard_parallel_voting_roundbiddiscussion.yaml,sha256=UGSLfOhmC-4pRqWsJvOtZRU0YLUuOMAGeEHtxTf3wf8,3710
|
|
243
281
|
kaggle_environments/static/player.html,sha256=Icl5yYscPe4BRoWt0HLOSRJWnznQq2MdTHHCaC2OrQQ,27753
|
|
244
|
-
kaggle_environments-1.23.
|
|
245
|
-
kaggle_environments-1.23.
|
|
246
|
-
kaggle_environments-1.23.
|
|
247
|
-
kaggle_environments-1.23.
|
|
248
|
-
kaggle_environments-1.23.
|
|
282
|
+
kaggle_environments-1.23.5.dist-info/entry_points.txt,sha256=h03sq76TdcHvXKcsre1Qm3lIni9dkWehu61xJqI-p8k,69
|
|
283
|
+
kaggle_environments-1.23.5.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
284
|
+
kaggle_environments-1.23.5.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
285
|
+
kaggle_environments-1.23.5.dist-info/METADATA,sha256=fO4oOApn4Mfm40bqXjJHUdnUYh9nqeKvB6ju2FHGLIc,916
|
|
286
|
+
kaggle_environments-1.23.5.dist-info/RECORD,,
|
|
File without changes
|
{kaggle_environments-1.23.3.dist-info → kaggle_environments-1.23.5.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{kaggle_environments-1.23.3.dist-info → kaggle_environments-1.23.5.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|