Completes the sim → weights → deck builder feedback loop. - Add run_update_weights() to overnight-run.py - Add auto_update_weights config key (default True) - Add --no-update-weights, --weights-min-games, --weights-lr CLI flags - Runs before neural net training so both pipelines get fresh data
629 lines
24 KiB
Python
629 lines
24 KiB
Python
"""
|
||
Commander AI Lab — Overnight AFK Runner
|
||
════════════════════════════════════════
|
||
Runs repeated batch simulations and then triggers ML training,
|
||
designed to run unattended for hours (e.g., overnight while AFK).
|
||
|
||
Usage (from commander-ai-lab folder):
|
||
python overnight-run.py
|
||
|
||
Or via the batch file:
|
||
overnight-run.bat
|
||
|
||
Prerequisites:
|
||
- Lab server must already be running (start-lab.bat)
|
||
- At least 1 deck available
|
||
- LM Studio / DeepSeek must be running for DeepSeek engine
|
||
- Forge must be built for Java engine
|
||
|
||
Configuration:
|
||
Edit the settings below, or pass command-line arguments.
|
||
"""
|
||
|
||
import argparse
|
||
import json
|
||
import logging
|
||
import logging.handlers
|
||
import os
|
||
import subprocess
|
||
import sys
|
||
import time
|
||
import urllib.request
|
||
import urllib.error
|
||
from datetime import datetime, timedelta
|
||
from pathlib import Path
|
||
|
||
|
||
# ── Default Config ──────────────────────────────────────────────
|
||
DEFAULT_CONFIG = {
|
||
"lab_url": "http://localhost:8080",
|
||
"engine": "deepseek", # "deepseek" or "java"
|
||
"games_per_batch": 30, # games per batch run
|
||
"num_batches": 0, # 0 = run until time_limit_hours
|
||
"time_limit_hours": 11.5, # stop after this many hours (leave buffer for training)
|
||
"pause_between_batches": 10, # seconds to pause between batches
|
||
"auto_train": True, # trigger ML training after all batches
|
||
"auto_update_weights": True, # run update_weights.py after sims
|
||
"weights_min_games": 10, # minimum games required for weight update
|
||
"weights_lr": 0.05, # learning rate for weight update
|
||
"train_epochs": 100, # training epochs
|
||
"train_lr": 0.001, # learning rate
|
||
"train_batch_size": 256, # training batch size
|
||
"train_patience": 15, # early stopping patience
|
||
# Java-only settings (ignored for DeepSeek):
|
||
"java_threads": 4,
|
||
"java_clock": 300,
|
||
"java_decks": [], # exactly 3 deck names required for Java
|
||
# DeepSeek settings:
|
||
"ds_decks": [], # deck names (auto-detected if empty)
|
||
}
|
||
|
||
|
||
# ── Logging ──────────────────────────────────────────────────
|
||
_LOG_DIR = Path(os.environ.get("CAL_LOG_DIR", "logs"))
|
||
|
||
|
||
def setup_overnight_logging() -> logging.Logger:
|
||
"""Configure logging for the overnight runner.
|
||
|
||
Outputs to both console and a rotating log file so that
|
||
unattended runs always have a persistent record.
|
||
"""
|
||
_LOG_DIR.mkdir(parents=True, exist_ok=True)
|
||
|
||
logger = logging.getLogger("commander_ai_lab.overnight")
|
||
logger.setLevel(logging.INFO)
|
||
|
||
if logger.handlers:
|
||
return logger
|
||
|
||
fmt = logging.Formatter(
|
||
"%(asctime)s %(levelname)-5s [%(name)s] %(message)s",
|
||
datefmt="%Y-%m-%d %H:%M:%S",
|
||
)
|
||
|
||
console = logging.StreamHandler(sys.stdout)
|
||
console.setLevel(logging.INFO)
|
||
console.setFormatter(fmt)
|
||
logger.addHandler(console)
|
||
|
||
file_handler = logging.handlers.RotatingFileHandler(
|
||
_LOG_DIR / "overnight-run.log",
|
||
maxBytes=10 * 1024 * 1024, # 10 MB
|
||
backupCount=5,
|
||
encoding="utf-8",
|
||
)
|
||
file_handler.setLevel(logging.INFO)
|
||
file_handler.setFormatter(fmt)
|
||
logger.addHandler(file_handler)
|
||
|
||
return logger
|
||
|
||
|
||
log = setup_overnight_logging()
|
||
|
||
|
||
def api_get(base_url, path):
|
||
"""GET request to lab API."""
|
||
url = base_url.rstrip("/") + path
|
||
req = urllib.request.Request(url)
|
||
try:
|
||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||
return json.loads(resp.read().decode())
|
||
except urllib.error.HTTPError as e:
|
||
body = e.read().decode() if e.fp else ""
|
||
raise RuntimeError(f"HTTP {e.code}: {body}")
|
||
|
||
|
||
def api_post(base_url, path, data=None):
|
||
"""POST request to lab API."""
|
||
url = base_url.rstrip("/") + path
|
||
body = json.dumps(data or {}).encode()
|
||
req = urllib.request.Request(url, data=body, method="POST")
|
||
req.add_header("Content-Type", "application/json")
|
||
try:
|
||
with urllib.request.urlopen(req, timeout=300) as resp:
|
||
return json.loads(resp.read().decode())
|
||
except urllib.error.HTTPError as e:
|
||
err_body = e.read().decode() if e.fp else ""
|
||
raise RuntimeError(f"HTTP {e.code}: {err_body}")
|
||
|
||
|
||
def check_server(base_url):
|
||
"""Verify the lab server is running."""
|
||
try:
|
||
api_get(base_url, "/api/lab/decks")
|
||
return True
|
||
except Exception:
|
||
return False
|
||
|
||
|
||
def get_available_decks(base_url):
|
||
"""Fetch all deck names."""
|
||
resp = api_get(base_url, "/api/lab/decks")
|
||
return [d["name"] for d in resp.get("decks", [])]
|
||
|
||
|
||
def start_deepseek_batch(base_url, decks, num_games):
|
||
"""Start a DeepSeek batch and return the batch ID."""
|
||
resp = api_post(base_url, "/api/lab/start-deepseek", {
|
||
"decks": decks,
|
||
"numGames": num_games,
|
||
})
|
||
return resp.get("batchId")
|
||
|
||
|
||
def start_java_batch(base_url, decks, num_games, threads, clock):
|
||
"""Start a Java/Forge batch and return the batch ID."""
|
||
resp = api_post(base_url, "/api/lab/start", {
|
||
"decks": decks,
|
||
"numGames": num_games,
|
||
"threads": threads,
|
||
"clock": clock,
|
||
})
|
||
return resp.get("batchId")
|
||
|
||
|
||
def wait_for_batch(base_url, batch_id, engine="deepseek", deadline=None):
|
||
"""Poll until batch completes or deadline is reached.
|
||
Returns result dict, or None on error/timeout."""
|
||
poll_interval = 15 # seconds
|
||
consecutive_errors = 0
|
||
last_completed = -1
|
||
stall_checks = 0
|
||
|
||
while True:
|
||
# Hard deadline check — stop waiting if we've run out of time
|
||
if deadline and time.time() >= deadline:
|
||
log.info(f" Time limit reached while batch {batch_id} was running.")
|
||
log.info(f" The batch will continue on the server but we're moving on.")
|
||
return {"completed": 0, "timed_out": True}
|
||
|
||
try:
|
||
status = api_get(base_url, f"/api/lab/status?batchId={batch_id}")
|
||
consecutive_errors = 0
|
||
|
||
completed = status.get("completedGames", 0)
|
||
total = status.get("totalGames", "?")
|
||
running = status.get("running", False)
|
||
error = status.get("error")
|
||
elapsed = status.get("elapsedMs", 0)
|
||
|
||
if error:
|
||
log.error(f" Batch {batch_id} ERROR: {error}")
|
||
return None
|
||
|
||
if not running and completed > 0:
|
||
elapsed_s = elapsed / 1000 if elapsed else 0
|
||
log.info(f" Batch {batch_id} complete: {completed} games in {elapsed_s:.0f}s")
|
||
try:
|
||
result = api_get(base_url, f"/api/lab/result?batchId={batch_id}")
|
||
return result
|
||
except Exception:
|
||
return {"completed": completed}
|
||
|
||
# Not started yet check (running=False, completed=0)
|
||
if not running and completed == 0:
|
||
stall_checks += 1
|
||
if stall_checks > 20: # 5 min with no progress
|
||
log.info(f" Batch {batch_id} never started. Aborting.")
|
||
return None
|
||
|
||
# Progress log with ETA
|
||
if deadline:
|
||
mins_left = (deadline - time.time()) / 60
|
||
log.info(f" Progress: {completed}/{total} games ({elapsed / 1000:.0f}s) — {mins_left:.0f}min left in run")
|
||
else:
|
||
log.info(f" Progress: {completed}/{total} games ({elapsed / 1000:.0f}s)")
|
||
|
||
# Track stall
|
||
if completed == last_completed:
|
||
stall_checks += 1
|
||
else:
|
||
stall_checks = 0
|
||
last_completed = completed
|
||
|
||
except Exception as e:
|
||
consecutive_errors += 1
|
||
if consecutive_errors > 10:
|
||
log.info(f" Lost connection to server after 10 retries. Aborting batch.")
|
||
return None
|
||
log.info(f" Connection error (retry {consecutive_errors}/10): {e}")
|
||
|
||
time.sleep(poll_interval)
|
||
|
||
|
||
def run_update_weights(cfg):
|
||
"""
|
||
Run ml.scripts.update_weights to nudge learned_weights.json from sim
|
||
decision logs. This feeds the deck builder's sim_insights injection.
|
||
|
||
Returns True on success, False on failure (non-fatal — overnight run
|
||
continues regardless).
|
||
"""
|
||
log.info("")
|
||
log.info("── Auto-update sim weights ─────────────────────────────────")
|
||
log.info("Running update_weights.py to refresh learned_weights.json...")
|
||
|
||
cmd = [
|
||
sys.executable, "-m", "ml.scripts.update_weights",
|
||
"--min-games", str(cfg["weights_min_games"]),
|
||
"--lr", str(cfg["weights_lr"]),
|
||
]
|
||
|
||
try:
|
||
result = subprocess.run(
|
||
cmd,
|
||
capture_output=True,
|
||
text=True,
|
||
timeout=120, # 2 min ceiling — this should complete in seconds
|
||
)
|
||
if result.returncode == 0:
|
||
# Print the last ~800 chars of the weight report to the log
|
||
output = (result.stdout or "").strip()
|
||
if output:
|
||
for line in output.splitlines()[-20:]:
|
||
log.info(f" {line}")
|
||
log.info("Sim weights updated — deck builder will use new insights on next build.")
|
||
return True
|
||
else:
|
||
log.warning(f"update_weights.py exited with code {result.returncode}")
|
||
if result.stderr:
|
||
log.warning(result.stderr.strip()[-300:])
|
||
return False
|
||
except subprocess.TimeoutExpired:
|
||
log.warning("update_weights.py timed out after 120s — skipping")
|
||
return False
|
||
except Exception as e:
|
||
log.warning(f"update_weights.py failed: {e}")
|
||
return False
|
||
|
||
|
||
def start_training(base_url, epochs, lr, batch_size, patience):
|
||
"""Trigger ML training and return immediately."""
|
||
resp = api_post(base_url, "/api/ml/train", {
|
||
"epochs": epochs,
|
||
"lr": lr,
|
||
"batchSize": batch_size,
|
||
"patience": patience,
|
||
"rebuildDataset": True,
|
||
})
|
||
return resp
|
||
|
||
|
||
def wait_for_training(base_url):
|
||
"""Poll until training completes."""
|
||
poll_interval = 30 # seconds
|
||
|
||
while True:
|
||
try:
|
||
status = api_get(base_url, "/api/ml/train/status")
|
||
running = status.get("running", False)
|
||
phase = status.get("phase", "unknown")
|
||
message = status.get("message", "")
|
||
epoch = status.get("current_epoch", 0)
|
||
total = status.get("total_epochs", 0)
|
||
error = status.get("error")
|
||
|
||
if error:
|
||
log.error(f" Training ERROR: {error}")
|
||
return False
|
||
|
||
if not running and phase in ("done", "idle") and epoch > 0:
|
||
result = status.get("result", {})
|
||
log.info(f" Training complete!")
|
||
if result:
|
||
log.info(f" Final accuracy: {result.get('best_val_accuracy', '?')}")
|
||
log.info(f" Model saved: {result.get('model_path', '?')}")
|
||
return True
|
||
|
||
if phase == "training" and total > 0:
|
||
metrics = status.get("metrics", {})
|
||
val_acc = metrics.get("val_accuracy", "?") if metrics else "?"
|
||
log.info(f" Training: epoch {epoch}/{total} — {message} (val_acc: {val_acc})")
|
||
else:
|
||
log.info(f" [{phase}] {message}")
|
||
|
||
except Exception as e:
|
||
log.info(f" Connection error: {e}")
|
||
|
||
time.sleep(poll_interval)
|
||
|
||
|
||
def get_ml_data_summary(base_url):
|
||
"""Get current ML training data stats."""
|
||
try:
|
||
status = api_get(base_url, "/api/ml/train/status")
|
||
data_files = status.get("data_files", [])
|
||
total = sum(f.get("decisions", 0) for f in data_files)
|
||
return total, len(data_files)
|
||
except Exception:
|
||
return 0, 0
|
||
|
||
|
||
def run_overnight(cfg):
|
||
"""Main overnight pipeline."""
|
||
base_url = cfg["lab_url"]
|
||
|
||
log.info("=" * 60)
|
||
log.info(" Commander AI Lab — Overnight AFK Runner")
|
||
log.info("=" * 60)
|
||
|
||
# ── Verify server ────────────────────────────────────────
|
||
log.info("Checking lab server...")
|
||
if not check_server(base_url):
|
||
log.error(f"ERROR: Cannot reach lab server at {base_url}")
|
||
log.info("Start the lab first: start-lab.bat")
|
||
return False
|
||
|
||
log.info(f"Server OK at {base_url}")
|
||
|
||
# ── Get decks ─────────────────────────────────────────────
|
||
available_decks = get_available_decks(base_url)
|
||
log.info(f"Found {len(available_decks)} decks: {', '.join(available_decks[:5])}{'...' if len(available_decks) > 5 else ''}")
|
||
|
||
engine = cfg["engine"]
|
||
if engine == "deepseek":
|
||
decks = cfg["ds_decks"] if cfg["ds_decks"] else available_decks
|
||
if not decks:
|
||
log.error("ERROR: No decks available. Create decks in the Deck Builder first.")
|
||
return False
|
||
log.info(f"Using DeepSeek engine with {len(decks)} deck(s)")
|
||
else:
|
||
decks = cfg["java_decks"]
|
||
if len(decks) != 3:
|
||
log.error("ERROR: Java engine requires exactly 3 deck names.")
|
||
log.info("Set java_decks in the config or use --decks deck1 deck2 deck3")
|
||
return False
|
||
log.info(f"Using Java/Forge engine with decks: {decks}")
|
||
|
||
# ── ML logging ────────────────────────────────────────────
|
||
log.info("Enabling ML logging...")
|
||
try:
|
||
api_post(base_url, "/api/ml/toggle?enable=true", {})
|
||
log.info("ML logging enabled")
|
||
except Exception as e:
|
||
log.warning(f"WARNING: Could not enable ML logging: {e}")
|
||
log.info("(DeepSeek batches always log ML data regardless)")
|
||
# Try alternate form
|
||
try:
|
||
api_get(base_url, "/api/ml/toggle?enable=true")
|
||
except Exception:
|
||
pass
|
||
|
||
# ── Initial data stats ────────────────────────────────────
|
||
decisions_before, files_before = get_ml_data_summary(base_url)
|
||
log.info(f"Current ML data: {decisions_before} decisions in {files_before} files")
|
||
|
||
# ── Run batches ───────────────────────────────────────────
|
||
games_per_batch = cfg["games_per_batch"]
|
||
num_batches = cfg["num_batches"]
|
||
time_limit = cfg["time_limit_hours"] * 3600 # convert to seconds
|
||
pause_sec = cfg["pause_between_batches"]
|
||
|
||
start_time = time.time()
|
||
deadline = start_time + time_limit
|
||
batch_num = 0
|
||
total_games = 0
|
||
total_decisions = 0
|
||
|
||
log.info("")
|
||
log.info(f"Starting batch loop:")
|
||
if num_batches > 0:
|
||
log.info(f" Mode: {num_batches} batches × {games_per_batch} games")
|
||
else:
|
||
log.info(f" Mode: Run for {cfg['time_limit_hours']}h, {games_per_batch} games/batch")
|
||
log.info(f" Engine: {engine}")
|
||
log.info(f" Auto-update weights: {'Yes' if cfg['auto_update_weights'] else 'No'}")
|
||
log.info(f" Auto-train after: {'Yes' if cfg['auto_train'] else 'No'}")
|
||
log.info("")
|
||
|
||
while True:
|
||
# Check stopping conditions
|
||
if num_batches > 0 and batch_num >= num_batches:
|
||
log.info(f"Reached target of {num_batches} batches. Stopping.")
|
||
break
|
||
|
||
elapsed = time.time() - start_time
|
||
remaining = deadline - time.time()
|
||
if remaining < 120: # need at least 2 min for a batch
|
||
log.info(f"Time limit approaching ({remaining:.0f}s left). Stopping sim loop.")
|
||
break
|
||
|
||
batch_num += 1
|
||
elapsed_str = str(timedelta(seconds=int(elapsed)))
|
||
remaining_str = str(timedelta(seconds=int(remaining)))
|
||
log.info(f"── Batch {batch_num} ── (elapsed: {elapsed_str}, remaining: {remaining_str})")
|
||
|
||
try:
|
||
if engine == "deepseek":
|
||
batch_id = start_deepseek_batch(base_url, decks, games_per_batch)
|
||
else:
|
||
batch_id = start_java_batch(
|
||
base_url, decks, games_per_batch,
|
||
cfg["java_threads"], cfg["java_clock"],
|
||
)
|
||
|
||
if not batch_id:
|
||
log.error(" ERROR: Failed to start batch (no batchId returned)")
|
||
log.info(" Waiting 60s before retry...")
|
||
time.sleep(60)
|
||
continue
|
||
|
||
log.info(f" Started batch {batch_id}")
|
||
result = wait_for_batch(base_url, batch_id, engine, deadline=deadline)
|
||
|
||
if result:
|
||
total_games += games_per_batch
|
||
# Log deck results if available
|
||
for deck in result.get("decks", []):
|
||
wr = deck.get("winRate", "?")
|
||
name = deck.get("deckName", "?")
|
||
log.info(f" {name}: {wr}% WR")
|
||
else:
|
||
log.info(" Batch failed, continuing to next...")
|
||
|
||
except Exception as e:
|
||
log.error(f" ERROR starting batch: {e}")
|
||
log.info(" Waiting 60s before retry...")
|
||
time.sleep(60)
|
||
continue
|
||
|
||
# Pause between batches
|
||
if pause_sec > 0:
|
||
log.info(f" Cooling down {pause_sec}s...")
|
||
time.sleep(pause_sec)
|
||
|
||
# ── Summary ───────────────────────────────────────────────
|
||
total_elapsed = time.time() - start_time
|
||
decisions_after, files_after = get_ml_data_summary(base_url)
|
||
new_decisions = decisions_after - decisions_before
|
||
new_files = files_after - files_before
|
||
|
||
log.info("")
|
||
log.info("=" * 60)
|
||
log.info(" BATCH SIMULATION COMPLETE")
|
||
log.info("=" * 60)
|
||
log.info(f" Batches run: {batch_num}")
|
||
log.info(f" Total games: ~{total_games}")
|
||
log.info(f" New ML decisions: {new_decisions} (in {new_files} new files)")
|
||
log.info(f" Total ML data: {decisions_after} decisions")
|
||
log.info(f" Elapsed time: {str(timedelta(seconds=int(total_elapsed)))}")
|
||
log.info("")
|
||
|
||
# ── Auto-update sim weights ───────────────────────────────
|
||
# Runs update_weights.py to nudge learned_weights.json from the
|
||
# decision logs produced during this run. This feeds sim_insights
|
||
# into the deck builder's Ollama prompts on the next deck build.
|
||
# Runs before neural net training so both pipelines benefit from
|
||
# the freshest possible data.
|
||
if cfg["auto_update_weights"]:
|
||
run_update_weights(cfg)
|
||
else:
|
||
log.info("Skipping sim weight update (--no-update-weights flag set)")
|
||
|
||
# ── Auto-train ────────────────────────────────────────────
|
||
if cfg["auto_train"]:
|
||
if decisions_after < 100:
|
||
log.info("Skipping training — too few decisions (need at least 100)")
|
||
return True
|
||
|
||
log.info("Starting ML training pipeline...")
|
||
log.info(f" Epochs: {cfg['train_epochs']}, LR: {cfg['train_lr']}, "
|
||
f"Batch: {cfg['train_batch_size']}, Patience: {cfg['train_patience']}")
|
||
|
||
try:
|
||
start_training(
|
||
base_url,
|
||
epochs=cfg["train_epochs"],
|
||
lr=cfg["train_lr"],
|
||
batch_size=cfg["train_batch_size"],
|
||
patience=cfg["train_patience"],
|
||
)
|
||
log.info("Training started, monitoring progress...")
|
||
success = wait_for_training(base_url)
|
||
if success:
|
||
log.info("")
|
||
log.info("=" * 60)
|
||
log.info(" TRAINING COMPLETE — model updated!")
|
||
log.info("=" * 60)
|
||
else:
|
||
log.info("Training finished with errors. Check the Training tab for details.")
|
||
except Exception as e:
|
||
log.error(f"ERROR starting training: {e}")
|
||
return False
|
||
|
||
log.info("")
|
||
log.info("Overnight run finished. You can close this window.")
|
||
return True
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(
|
||
description="Commander AI Lab — Overnight AFK Runner",
|
||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||
epilog="""
|
||
Examples:
|
||
# Run DeepSeek sims for 11.5 hours, then update weights + train:
|
||
python overnight-run.py
|
||
|
||
# Run 20 batches of 50 games each, then update weights + train:
|
||
python overnight-run.py --batches 20 --games 50
|
||
|
||
# Run for 6 hours with Java engine:
|
||
python overnight-run.py --engine java --hours 6 --decks "Deck A" "Deck B" "Deck C"
|
||
|
||
# Just run sims, skip both weight update and training:
|
||
python overnight-run.py --no-train --no-update-weights
|
||
|
||
# Custom training settings:
|
||
python overnight-run.py --epochs 200 --lr 0.0005 --patience 20
|
||
""",
|
||
)
|
||
parser.add_argument("--url", default="http://localhost:8080",
|
||
help="Lab server URL (default: http://localhost:8080)")
|
||
parser.add_argument("--engine", choices=["deepseek", "java"], default="deepseek",
|
||
help="Sim engine (default: deepseek)")
|
||
parser.add_argument("--games", type=int, default=30,
|
||
help="Games per batch (default: 30)")
|
||
parser.add_argument("--batches", type=int, default=0,
|
||
help="Number of batches (0 = run until time limit)")
|
||
parser.add_argument("--hours", type=float, default=11.5,
|
||
help="Max hours to run sims (default: 11.5)")
|
||
parser.add_argument("--pause", type=int, default=10,
|
||
help="Seconds between batches (default: 10)")
|
||
parser.add_argument("--decks", nargs="+", default=[],
|
||
help="Deck names (auto-detected if not specified)")
|
||
parser.add_argument("--no-train", action="store_true",
|
||
help="Skip auto-training after sims")
|
||
parser.add_argument("--no-update-weights", action="store_true",
|
||
help="Skip updating learned_weights.json after sims")
|
||
parser.add_argument("--weights-min-games", type=int, default=10,
|
||
help="Minimum games required for weight update (default: 10)")
|
||
parser.add_argument("--weights-lr", type=float, default=0.05,
|
||
help="Learning rate for sim weight update (default: 0.05)")
|
||
parser.add_argument("--epochs", type=int, default=100,
|
||
help="Training epochs (default: 100)")
|
||
parser.add_argument("--lr", type=float, default=0.001,
|
||
help="Training learning rate (default: 0.001)")
|
||
parser.add_argument("--batch-size", type=int, default=256,
|
||
help="Training batch size (default: 256)")
|
||
parser.add_argument("--patience", type=int, default=15,
|
||
help="Early stopping patience (default: 15)")
|
||
parser.add_argument("--threads", type=int, default=4,
|
||
help="Java engine threads (default: 4)")
|
||
parser.add_argument("--clock", type=int, default=300,
|
||
help="Java engine clock (default: 300)")
|
||
args = parser.parse_args()
|
||
|
||
cfg = dict(DEFAULT_CONFIG)
|
||
cfg["lab_url"] = args.url
|
||
cfg["engine"] = args.engine
|
||
cfg["games_per_batch"] = args.games
|
||
cfg["num_batches"] = args.batches
|
||
cfg["time_limit_hours"] = args.hours
|
||
cfg["pause_between_batches"] = args.pause
|
||
cfg["auto_train"] = not args.no_train
|
||
cfg["auto_update_weights"] = not args.no_update_weights
|
||
cfg["weights_min_games"] = args.weights_min_games
|
||
cfg["weights_lr"] = args.weights_lr
|
||
cfg["train_epochs"] = args.epochs
|
||
cfg["train_lr"] = args.lr
|
||
cfg["train_batch_size"] = args.batch_size
|
||
cfg["train_patience"] = args.patience
|
||
cfg["java_threads"] = args.threads
|
||
cfg["java_clock"] = args.clock
|
||
|
||
if args.decks:
|
||
if args.engine == "java":
|
||
cfg["java_decks"] = args.decks
|
||
else:
|
||
cfg["ds_decks"] = args.decks
|
||
|
||
success = run_overnight(cfg)
|
||
sys.exit(0 if success else 1)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|