Created
August 14, 2024 21:01
-
-
Save Mercerenies/7da15bd8663d8ccde11c5b5bb7d22bb3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """Javagrid code generator. | |
| This module accepts a file formatted like the examples on | |
| https://esolangs.org/wiki/Javagrid (with the dashes, at-signs, and | |
| vertical bars separating cells) and produces JSON data in the format | |
| expected by localStorage on https://stefan-hering.github.io/. | |
| """ | |
| from __future__ import annotations | |
| import itertools | |
| import json | |
| from pathlib import Path | |
| from functools import cached_property | |
| from typing import Iterator, Iterable, NamedTuple, TypedDict, Sequence | |
| from dataclasses import dataclass | |
| import argparse | |
| class Grid: | |
| """2D grid of characters.""" | |
| _data: list[str] | |
| def __init__(self, s: str | list[str]) -> None: | |
| if isinstance(s, str): | |
| s = s.split("\n") | |
| self._data = s | |
| @property | |
| def height(self) -> int: | |
| return len(self._data) | |
| @cached_property | |
| def width(self) -> int: | |
| return max((len(line) for line in self._data), default=0) | |
| def __getitem__(self, indices: tuple[int, int]) -> str: | |
| """Returns the character at the given (row, column), or a | |
| space if out-of-bounds.""" | |
| try: | |
| return self._data[indices[0]][indices[1]] | |
| except IndexError: | |
| return ' ' | |
| def rect(self, rect: Rect) -> Grid: | |
| """Returns a subgrid containing only the given rectangle.""" | |
| new_grid_data: list[str] = [] | |
| for y in range(rect.start.y, rect.end.y): | |
| row = "".join(self[y, x] for x in range(rect.start.x, rect.end.x)) | |
| new_grid_data.append(row.rstrip()) | |
| return Grid(new_grid_data) | |
| def __contains__(self, index: tuple[int, int]) -> bool: | |
| """Returns true if the index is within bounds for this grid.""" | |
| y, x = index | |
| return 0 <= y < self.height and 0 <= x < self.width | |
| def __iter__(self) -> Iterator[Point]: | |
| """Iterates over indices, in row-major order.""" | |
| for y in range(self.height): | |
| for x in range(self.width): | |
| yield Point(y, x) | |
| def items(self) -> Iterator[tuple[Point, str]]: | |
| """Iterates over indices and corresponding items, in row-major | |
| order.""" | |
| return ((p, self[p]) for p in self) | |
| def __str__(self) -> str: | |
| return "\n".join(self._data) | |
| class Point(NamedTuple): | |
| y: int | |
| x: int | |
| @dataclass(frozen=True) | |
| class Rect: | |
| """Half-open axis-aligned 2D rectangle.""" | |
| start: Point | |
| end: Point | |
| def __post_init__(self) -> None: | |
| if self.start.y > self.end.y or self.start.x > self.end.x: | |
| raise ValueError("Invalid rectangle {}".format(self)) | |
| @dataclass(kw_only=True, frozen=True) | |
| class RowColInfo: | |
| """Information about rows and columns in a grid.""" | |
| rows: tuple[int, ...] | |
| cols: tuple[int, ...] | |
| @classmethod | |
| def from_corners(cls, grid: Grid, corners: Sequence[Point]) -> RowColInfo: | |
| rows = {corner.y for corner in corners} | |
| cols = {corner.x for corner in corners} | |
| # Validate that the corners are consistent. | |
| for row in rows: | |
| for col in cols: | |
| if grid[row, col] != '@': | |
| raise ValueError("Expected @ at ({}, {})".format(row, col)) | |
| return cls( | |
| rows=(-1,) + tuple(sorted(rows)) + (grid.height,), | |
| cols=(-1,) + tuple(sorted(cols)) + (grid.width,), | |
| ) | |
| def corners(self) -> Iterator[Point]: | |
| for y in self.rows: | |
| for x in self.cols: | |
| yield Point(y, x) | |
| def rectangles(self) -> Iterator[Iterable[Rect]]: | |
| for y1, y2 in itertools.pairwise(self.rows): | |
| row = [] | |
| for x1, x2 in itertools.pairwise(self.cols): | |
| row.append(Rect(Point(y1 + 1, x1 + 1), Point(y2, x2))) | |
| yield row | |
| class JavagridProgram(TypedDict): | |
| cells: list[list[str]] | |
| settings: JavagridSettings | |
| class JavagridSettings(TypedDict): | |
| width: str | |
| height: str | |
| params: str | |
| startX: str | |
| startY: str | |
| def find_corners(grid: Grid) -> list[Point]: | |
| return [p for p, ch in grid.items() if ch == '@'] | |
| def argparser() -> argparse.ArgumentParser: | |
| parser = argparse.ArgumentParser(description="Javagrid code generator") | |
| parser.add_argument('input_file', type=str, help='Input file path') | |
| parser.add_argument('--startx', type=int, default=0, help='Starting X coordinate') | |
| parser.add_argument('--starty', type=int, default=0, help='Starting Y coordinate') | |
| parser.add_argument('--params', type=str, default='[]', help='Input parameters') | |
| parser.add_argument('--output-javascript', default=False, action='store_true', help='Output JavaScript loader code (default is plain JSON data)') # noqa: E501 | |
| return parser | |
| def get_cells(grid: Grid, grid_info: RowColInfo) -> list[list[str]]: | |
| result: list[list[str]] = [] | |
| for row in grid_info.rectangles(): | |
| result.append([str(grid.rect(rect)).rstrip() for rect in row]) | |
| return result | |
| def compile_program(cells: list[list[str]], args: argparse.Namespace) -> JavagridProgram: | |
| return { | |
| 'cells': cells, | |
| 'settings': { | |
| 'height': str(len(cells)), | |
| 'width': str(len(cells[0]) if cells else 0), | |
| 'params': args.params, | |
| 'startX': str(args.startx), | |
| 'startY': str(args.starty), | |
| }, | |
| } | |
| def output_javascript(compiled_program: str) -> None: | |
| print("Copy-paste this into your JavaScript developer console to load the grid into the playground:") | |
| print() | |
| print("""var grids=JSON.parse(localStorage.savedGrids);grids.includes("tmp")||grids.push("tmp"),localStorage.savedGrids=JSON.stringify(grids),localStorage["grid-tmp"]=%s;""" % json.dumps(compiled_program)) # noqa: E501 | |
| if __name__ == "__main__": | |
| args = argparser().parse_args() | |
| grid = Grid(Path(args.input_file).read_text(encoding="utf-8")) | |
| corners = find_corners(grid) | |
| grid_lines = RowColInfo.from_corners(grid, corners) | |
| cells = get_cells(grid, grid_lines) | |
| compiled_program = json.dumps(compile_program(cells=cells, args=args)) | |
| if args.output_javascript: | |
| output_javascript(compiled_program) | |
| else: | |
| print(compiled_program) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment