testspace / submodules /flowfree /convert_freeflow.py
nilshoehing's picture
Upload TopoBench Space app
67acd34 verified
Raw
History Blame Contribute Delete
6.91 kB
# #!/usr/bin/env python3
# import re
# import sys
# from itertools import chain
# """
# Usage:
# # From a file
# python convert_flowfree.py puzzle.txt
# # Or pipe the raw generator output
# printf "3 5 5\n(2, 0) (2, 2)\n(3, 3) (1, 3)\n(1, 0) (1, 4)\n" | python convert_flowfree.py
# Notes:
# - Expects lines like:
# <num_colors> <cols> <rows>
# (x1, y1) (x2, y2)
# ...
# for exactly <num_colors> lines after the header.
# - Coordinates are zero-based and are (col, row).
# - Colors are labeled A, B, C, ... (wraps to aA... if >52 colors).
# """
# HEADER_RE = re.compile(r"^\s*(\d+)\s+(\d+)\s+(\d+)\s*$")
# PAIR_RE = re.compile(
# r"\(\s*(\d+)\s*,\s*(\d+)\s*\)\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)"
# )
# def letter_labels(n):
# base = [chr(i) for i in range(ord('A'), ord('Z')+1)]
# extra = [chr(i) for i in range(ord('a'), ord('z')+1)]
# # If still more needed, go to double letters: AA, AB, ...
# if n <= len(base) + len(extra):
# return list(chain(base, extra))[:n]
# labels = list(chain(base, extra))
# i = 0
# while len(labels) < n:
# for ch in base:
# labels.append(base[i % len(base)] + ch)
# if len(labels) == n:
# break
# i += 1
# return labels
# def parse_blocks(lines):
# """
# Yields (num_colors, cols, rows, pairs_list) for each detected puzzle block.
# pairs_list is a list of ((x1,y1),(x2,y2)) of length num_colors.
# """
# i = 0
# while i < len(lines):
# m = HEADER_RE.match(lines[i])
# if not m:
# i += 1
# continue
# num_colors, cols, rows = map(int, m.groups())
# i += 1
# pairs = []
# while i < len(lines) and len(pairs) < num_colors:
# pm = PAIR_RE.search(lines[i])
# if pm:
# x1, y1, x2, y2 = map(int, pm.groups())
# pairs.append(((x1, y1), (x2, y2)))
# i += 1
# else:
# # skip noise lines between pairs
# i += 1
# if len(pairs) == num_colors:
# yield num_colors, cols, rows, pairs
# # else: header without enough pairs; skip and keep scanning
# def grid_from_puzzle(cols, rows, pairs, labels):
# grid = [['.' for _ in range(cols)] for _ in range(rows)]
# for label, ((x1, y1), (x2, y2)) in zip(labels, pairs):
# for (x, y) in [(x1, y1), (x2, y2)]:
# if not (0 <= x < cols and 0 <= y < rows):
# raise ValueError(f"Coordinate {(x,y)} out of bounds for {cols}x{rows}")
# if grid[y][x] != '.':
# raise ValueError(f"Cell {(x,y)} already occupied (overlap).")
# grid[y][x] = label
# return grid
# def print_grid(grid):
# for row in grid:
# print(''.join(row))
# def main():
# data = sys.stdin.read() if sys.stdin and not sys.stdin.isatty() else None
# if data is None:
# if len(sys.argv) < 2:
# print("Provide a file or pipe the generator output to stdin.", file=sys.stderr)
# sys.exit(1)
# with open(sys.argv[1], 'r', encoding='utf-8') as f:
# data = f.read()
# lines = data.splitlines()
# any_printed = False
# for num_colors, cols, rows, pairs in parse_blocks(lines):
# labels = letter_labels(num_colors)
# grid = grid_from_puzzle(cols, rows, pairs, labels)
# print('"""')
# print_grid(grid)
# print('"""')
# any_printed = True
# if not any_printed:
# print("No valid puzzle blocks found.", file=sys.stderr)
# sys.exit(2)
# if __name__ == "__main__":
# main()
#!/usr/bin/env python3
import re
import sys
from itertools import chain
HEADER_RE = re.compile(r"^\s*(\d+)\s+(\d+)\s+(\d+)\s*$")
COORD_RE = re.compile(r"\(\s*(\d+)\s*,\s*(\d+)\s*\)")
def letter_labels(n):
base = [chr(i) for i in range(ord('A'), ord('Z')+1)]
extra = [chr(i) for i in range(ord('a'), ord('z')+1)]
if n <= len(base) + len(extra):
return list(chain(base, extra))[:n]
labels = list(chain(base, extra))
i = 0
while len(labels) < n:
for ch in base:
labels.append(base[i % len(base)] + ch)
if len(labels) == n:
break
i += 1
return labels
def parse_blocks(lines):
"""
Yields (num_colors, cols, rows, paths_list) per puzzle block.
paths_list: list of lists of (x,y). For endpoints format, each list has len 2.
For solution format, each list may have len > 2 (full path).
"""
i = 0
while i < len(lines):
m = HEADER_RE.match(lines[i])
if not m:
i += 1
continue
num_colors, cols, rows = map(int, m.groups())
i += 1
paths = []
while i < len(lines) and len(paths) < num_colors:
coords = COORD_RE.findall(lines[i])
if coords:
path = [(int(x), int(y)) for x, y in coords]
paths.append(path)
i += 1 # always advance; skip noise automatically
if len(paths) == num_colors:
yield num_colors, cols, rows, paths
# else: header without enough lines -> skip and keep scanning
def build_grid(cols, rows):
return [['.' for _ in range(cols)] for _ in range(rows)]
def place_paths_on_grid(cols, rows, paths, labels):
"""
Fills the grid with labels along either endpoints (len 2) or full paths (len > 2).
"""
grid = build_grid(cols, rows)
for label, path in zip(labels, paths):
for (x, y) in path:
if not (0 <= x < cols and 0 <= y < rows):
raise ValueError(f"Coordinate {(x,y)} out of bounds for {cols}x{rows}")
if grid[y][x] != '.' and grid[y][x] != label:
raise ValueError(f"Cell {(x,y)} already occupied by '{grid[y][x]}' (overlap).")
grid[y][x] = label
return grid
def print_grid(grid):
for row in grid:
print(''.join(row))
def read_all_input():
# Prefer stdin if piped; else read from file arg.
if sys.stdin and not sys.stdin.isatty():
return sys.stdin.read()
if len(sys.argv) < 2:
print("Provide a file or pipe the generator/solution output to stdin.", file=sys.stderr)
sys.exit(1)
with open(sys.argv[1], 'r', encoding='utf-8') as f:
return f.read()
def main():
data = read_all_input()
lines = data.splitlines()
any_printed = False
for num_colors, cols, rows, paths in parse_blocks(lines):
labels = letter_labels(num_colors)
grid = place_paths_on_grid(cols, rows, paths, labels)
print('"""')
print_grid(grid)
print('"""')
any_printed = True
if not any_printed:
print("No valid puzzle blocks found.", file=sys.stderr)
sys.exit(2)
if __name__ == "__main__":
main()