scenarios/loader.js

import { Board } from "../core/board.js";
import { Registry } from "../core/registry.js";
import { Terrain } from "../core/terrain.js";
import { Agent } from "../core/agent.js";
import { Item } from "../core/item.js";
import { ROWS, COLUMNS } from "../core/board.js";

const BASE_URL = window.location.href.split("/game/")[0] + "/public/scenarios/";

/**
 * Load a board from a scenario JSON file.
 *
 * @param {string} boardPath - path stem, e.g. "tutorial/start"
 * @param {string} [baseURL] - base URL, default "/scenarios/"
 * @returns {Promise<{board: Board, startX: number, startY: number, startInv: string[]}>}
 */
export async function loadBoard(boardPath, baseURL = BASE_URL) {
  const url = `${baseURL}${boardPath}.json`;
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`Failed to load board: ${url} (${response.status})`);
  }
  const contentType = response.headers.get("content-type") ?? "";
  if (
    !contentType.includes("application/json") &&
    !contentType.includes("text/json")
  ) {
    throw new Error(
      `Failed to load board: ${url} (response was not JSON — board file may be missing)`,
    );
  }
  const data = await response.json();
  return buildBoard(data, boardPath);
}

/**
 * Build a Board from a deserialized scenario data object.
 * Also used to restore boards from player.unsavedMaps.
 *
 * @param {object} data  - raw JSON or deserialized save data
 * @param {string} boardPath
 * @returns {{board: Board, startX: number, startY: number, startInv: string[]}}
 */
export function buildBoard(data, boardPath) {
  const board = new Board();
  board.outside = data.outside ?? false;
  board.startX = data.startX ?? 0;
  board.startY = data.startY ?? 0;
  board.scenarioName = data.scenarioName ?? data.name ?? null;
  board.creator = data.creator ?? null;
  board.description = data.description ?? null;
  board.startInv = data.startInv ?? null;
  board.folder = data.folder ?? null;
  board.boardID = boardPath;

  // Parse diagram: diagram[y][x] gives the char for cell (x, y)
  const terrainMap = data.terrain ?? {};
  for (let y = 0; y < ROWS; y++) {
    const row = data.diagram?.[y] ?? "";
    for (let x = 0; x < COLUMNS; x++) {
      const ch = row[x] ?? " ";
      const terrainKey = terrainMap[ch];
      if (terrainKey) {
        const piece = Registry.get(terrainKey);
        if (piece instanceof Terrain) {
          board.cells[x][y].setTerrain(piece);
        }
      }
    }
  }

  // Place pieces (agents/items) from the pieces array
  for (const pd of data.pieces ?? []) {
    if (pd.key == null) continue;
    const piece = Registry.get(pd.key);
    const cell = board.getCellAt(pd.x, pd.y);
    if (!cell || !piece) continue;
    if (piece instanceof Agent) {
      if (!cell.agent) cell.setAgent(piece);
    } else if (piece instanceof Item) {
      cell.addItem(piece);
    } else if (piece instanceof Terrain) {
      cell.setTerrain(piece);
    }
  }

  // Register adjacent board paths
  const boardDir = boardPath.includes("/")
    ? boardPath.split("/").slice(0, -1).join("/")
    : "";
  for (const dirName of ["north", "south", "east", "west", "up", "down"]) {
    const stem = data[dirName];
    if (stem) {
      const fullPath = boardDir ? `${boardDir}/${stem}` : stem;
      board.setAdjacentBoard(dirName, fullPath);
    }
  }
  const startInventory = data.startInv
    ? data.startInv.split(",").map((s) => s.trim())
    : [];

  return {
    board,
    startX: data.startX ?? 0,
    startY: data.startY ?? 0,
    startInv: startInventory,
  };
}