Something went wrong. Try again.
Web frontend and supporting services for lance.blue
Something went wrong. Try again.
5.0 kB · 132 lines
Python
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133#!/usr/bin/env python3"""Read the boards the daily scenarios name, and write the card's tile data.
The card draws a small minimap of the real terrain beside the schematic board.That terrain lives in MegaMek's `.board` files, which are in the arena imageand not on the website, so it is read here at authoring time and committed —the same arrangement as daily.json.
scripts/boards.py --megamek <dir> --out web/src/content/boards.json
What is encoded is only what the tile draws: one character per hex for terrainclass, one for level, and an exit mask per road hex. Everything else in a`.board` file — themes, elevations of individual terrain layers, the fluff — isdropped, because the tile is a summary and so is this.
Format, from megamek/common/board/Board.java's own reader:
size W H hex <coords> <level> "<terrain>" "<theme>"
The coordinate on a hex line is ignored by MegaMek itself (Board.java says soin a comment): hexes are consumed in file order, so hex n is at(n % width, n / width). Terrain is `;`-separated `name:level[:exits]`."""
import argparseimport jsonimport pathlibimport reimport sys
# What the tile draws, in the order a reader should notice it. The character is# what goes in the payload.OPEN, LIGHT_WOODS, HEAVY_WOODS, WATER, ROAD, BUILDING = ".", "w", "W", "~", "=", "#"
LEVELS = "abcdefghijklmnopqrs" # -9..9, so a level is always one character
def classify(terrain: str) -> tuple[str, int]: """The one class a hex reads as, and its road exit mask.""" kinds, exits = {}, 0 for part in terrain.split(";"): if not part: continue bits = part.split(":") name = bits[0] level = bits[1] if len(bits) > 1 else "1" kinds[name] = level if name in ("road", "bridge") and len(bits) > 2: try: exits = int(bits[2]) except ValueError: pass # Most-to-least significant for a glance: a building on a road is a # building, a road through woods is a road. if "building" in kinds or "fuel_tank" in kinds: return BUILDING, exits if "road" in kinds or "bridge" in kinds or "pavement" in kinds: return ROAD, exits if "water" in kinds: return WATER, exits if "woods" in kinds: return (HEAVY_WOODS if kinds["woods"] not in ("1",) else LIGHT_WOODS), exits if "jungle" in kinds: return HEAVY_WOODS, exits return OPEN, exits
def read_board(path: pathlib.Path) -> dict: width = height = 0 terrain, levels, roads = [], [], {} for line in path.read_text(errors="replace").splitlines(): line = line.strip() if line.startswith("size "): _, w, h = line.split() width, height = int(w), int(h) elif line.startswith("hex "): parts = re.findall(r'"[^"]*"|\S+', line) level = int(parts[2]) kind, exits = classify(parts[3].strip('"')) index = len(terrain) terrain.append(kind) levels.append(LEVELS[max(-9, min(9, level)) + 9]) if kind == ROAD and exits: roads[index] = f"{exits:x}" if not width or len(terrain) != width * height: raise SystemExit(f"{path}: {len(terrain)} hexes for a {width}x{height} board") return { "w": width, "h": height, "t": "".join(terrain), "l": "".join(levels), # Only the road hexes that declare an exit mask; the rest join up by # sitting next to each other, which the renderer can infer. "r": {str(k): v for k, v in roads.items()}, }
def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--megamek", required=True) ap.add_argument("--scenarios", required=True) ap.add_argument("--out", required=True) args = ap.parse_args()
# Every board any scenario names. wanted = set() for mms in sorted(pathlib.Path(args.scenarios).glob("*.mms")): for m in re.finditer(r"(?:^map:[ \t]+|^\s*- file:[ \t]*)(\S.*)$", mms.read_text(), re.M): name = m.group(1).strip().strip('"') # `map:` followed by a block opens a grid; only a # scalar or a `- file:` names a board. if name and not name.endswith(":") and name.endswith(".board"): wanted.add(name)
boards = {} for name in sorted(wanted): path = pathlib.Path(args.megamek) / "data" / "boards" / name if not path.exists(): print(f"boards: {name} is not in {args.megamek}/data/boards", file=sys.stderr) return 1 boards[name] = read_board(path)
out = pathlib.Path(args.out) out.write_text(json.dumps({"boards": boards}, indent=1, sort_keys=True) + "\n") total = sum(len(b["t"]) for b in boards.values()) print(f"wrote {out} ({len(boards)} boards, {total} hexes)") return 0
if __name__ == "__main__": raise SystemExit(main())