""" Builds an EpisodicMemory offline, at packaging time, by playing self-play games against a MIXED opponent (weak heuristic + random + the stronger 1-ply-deeper heuristic) using the model's own real adversarial search. WON games are stored as positive (attraction) examples; LOST/drawn games are ALSO stored, as negative (repulsion) examples -- deliberately mixed opposition, not just the single fixed weak training opponent, so a loss has to have actually lost to a real mix of opposition before it gets stored as "this is bad," rather than encoding one narrow opponent's particular blind spots as universal truth. """ import torch from .env import ConnectXEnv from .adversarial_search import real_adversarial_plan_action from .episodic_memory import EpisodicMemory, add_trajectory_from_real_path, add_negative_trajectory_from_real_path @torch.no_grad() def build_episodic_memory(env, model, normalizer, rng, n_games=500, opponent_epsilon=0.2, opponent_strong_epsilon=0.3, adversarial_rounds=2, penalty=None): """`penalty` (default 2x max_steps, deliberately bigger than the value head's own unsolved_penalty): these are discrete stored memory points, not a training-loss target, so being a bit more emphatic buys sharper repulsion without the overfitting risk more gradient steps would carry.""" memory = EpisodicMemory() diverse_env = ConnectXEnv(width=env.width, height=env.height, win_len=env.win_len, opponent_epsilon=opponent_epsilon, opponent_strong_epsilon=opponent_strong_epsilon) max_steps = (env.width * env.height) // 2 + 2 if penalty is None: penalty = 2 * max_steps wins, losses = 0, 0 for _ in range(n_games): state, _ = diverse_env.random_problem(rng) path_states = [state] for _ in range(max_steps): if diverse_env.is_solved(state): break a = real_adversarial_plan_action(diverse_env, model, normalizer, state, rounds=adversarial_rounds) if a is None: break state, _r, done = diverse_env.step(state, a) path_states.append(state) if done: break if diverse_env.is_solved(state): wins += 1 add_trajectory_from_real_path(model, normalizer, memory, path_states, env=diverse_env) else: losses += 1 add_negative_trajectory_from_real_path(model, normalizer, memory, path_states, penalty, env=diverse_env) print(f" built memory from {wins} won + {losses} lost self-play games " f"(mixed opponent) -> {len(memory)} stored states") return memory