# ShipGames bot playtest: on every push to main, GitHub's own computers open your game in headless Chrome, run
# window.runBatchBotSimulations(runs, maxSeconds) and send the results to ShipGames for the build. Free for public repos,
# costs nothing on ShipGames, and bot runs never count as plays. Setup: https://shipgames.io/learn/bot-playtests
# 1. Your game exposes window.runBatchBotSimulations(runs, maxSeconds) -> { strategy: [{ outcome, simDurationSec, ... }] }.
# 2. Repo secret SHIPGAMES_TOKEN: a maker token from shipgames.io/account (the "ship" permission).
# 3. Optional repo variables: SHIPGAMES_GAME (your game's slug; default the repo name), SHIPGAMES_ENTRY (default index.html).
name: Bot playtest
on:
  push:
    branches: [main]
  workflow_dispatch:
permissions:
  contents: read
concurrency:
  group: bot-playtest
  cancel-in-progress: true
jobs:
  playtest:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    env:
      SHIPGAMES_TOKEN: ${{ secrets.SHIPGAMES_TOKEN }}
      GAME: ${{ vars.SHIPGAMES_GAME || github.event.repository.name }}
      ENTRY: ${{ vars.SHIPGAMES_ENTRY || 'index.html' }}
      RUNS: "3"
      SECONDS: "75"
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Install headless Chrome
        run: |
          mkdir -p "$RUNNER_TEMP/pt" && cd "$RUNNER_TEMP/pt"
          npm init -y >/dev/null && npm i --no-audit --no-fund playwright@1.48.2 >/dev/null
          npx playwright install --with-deps chromium
      - name: Run the bots
        run: |
          cat > "$RUNNER_TEMP/pt/playtest.mjs" <<'SCRIPT'
          // ShipGames bot playtest: serves this repo, opens the game in headless Chromium, runs
          // window.runBatchBotSimulations(RUNS, SECONDS) and posts the results to ShipGames.
          import { createServer } from "node:http";
          import { readFile, writeFile } from "node:fs/promises";
          import { extname, join, normalize } from "node:path";
          import { chromium } from "playwright";

          const ROOT = process.cwd(), ENTRY = process.env.ENTRY || "index.html";
          const GAME = process.env.GAME, TOKEN = process.env.SHIPGAMES_TOKEN || "";
          const RUNS = Number(process.env.RUNS || 3), SECONDS = Number(process.env.SECONDS || 75);
          const API = process.env.SHIPGAMES_API || "https://shipgames.io/api/v1";
          const TYPES = { ".html": "text/html", ".js": "text/javascript", ".mjs": "text/javascript", ".css": "text/css", ".json": "application/json",
            ".png": "image/png", ".jpg": "image/jpeg", ".svg": "image/svg+xml", ".wasm": "application/wasm", ".mp3": "audio/mpeg", ".ogg": "audio/ogg", ".wav": "audio/wav" };
          const server = createServer(async (req, res) => {
            const path = normalize(decodeURIComponent(new URL(req.url, "http://x").pathname)).replace(/^(\.\.[/\\])+/, "");
            try { const body = await readFile(join(ROOT, path.endsWith("/") ? path + "index.html" : path)); res.writeHead(200, { "Content-Type": TYPES[extname(path)] || "application/octet-stream" }); res.end(body); }
            catch { res.writeHead(404); res.end(); }
          }).listen(0, "127.0.0.1");
          await new Promise((r) => server.once("listening", r));
          const url = `http://127.0.0.1:${server.address().port}/${ENTRY}`;
          const browser = await chromium.launch(process.env.CHANNEL ? { channel: process.env.CHANNEL } : {});
          let results = null, problem = null;
          try {
            const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
            page.on("pageerror", (e) => console.log("page error:", e.message));
            await page.goto(url, { waitUntil: "load", timeout: 30000 });
            const has = await page.waitForFunction(() => typeof window.runBatchBotSimulations === "function", null, { timeout: 15000 }).then(() => true, () => false);
            if (!has) problem = "this game doesn't expose window.runBatchBotSimulations(runs, maxSeconds)";
            else results = await page.evaluate(([n, s]) => window.runBatchBotSimulations(n, s), [RUNS, SECONDS]);
          } finally { await browser.close(); server.close(); }
          if (problem) { console.log(`::warning::Bot playtest skipped: ${problem}.`); process.exit(0); }
          if (!results || typeof results !== "object") { console.log("::error::runBatchBotSimulations returned nothing."); process.exit(1); }
          await writeFile("playtest-results.json", JSON.stringify(results, null, 2));
          for (const [strategy, runs] of Object.entries(results)) {
            const list = Array.isArray(runs) ? runs : [];
            console.log(`${strategy}: ${list.length} runs, outcomes ${list.map((r) => r.outcome ?? "?").join(", ")}`);
          }
          if (!TOKEN || !GAME) { console.log("::notice::No SHIPGAMES_TOKEN secret (or game): results kept in the run's log and artifact only."); process.exit(0); }
          const r = await fetch(`${API}/games/${encodeURIComponent(GAME)}/playtest`, { method: "POST",
            headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", "User-Agent": "shipgames-playtest" },
            body: JSON.stringify({ results, commit: process.env.GITHUB_SHA || undefined }) });
          const j = await r.json().catch(() => ({}));
          if (!r.ok) { console.log(`::error::ShipGames refused the results (${r.status}): ${j.error ?? ""}`); process.exit(1); }
          console.log(`ShipGames: build ${j.build}, ${j.survived_runs} of ${j.total_runs} runs survived (${j.survival_pct}%).`);
          SCRIPT
          node "$RUNNER_TEMP/pt/playtest.mjs"
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playtest-results
          path: playtest-results.json
          if-no-files-found: ignore
