Spaces:
Running
Running
File size: 6,038 Bytes
96e3e70 61557a0 96e3e70 61557a0 96e3e70 f227b96 96e3e70 61557a0 96e3e70 61557a0 96e3e70 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | from __future__ import annotations
from typing import Iterable
def split_lines(board: str) -> list[str]:
return board.replace("\r", "").split("\n")
def pad_lines(lines: Iterable[str], width: int) -> list[str]:
return [line.ljust(width) for line in lines]
def join_grid(grid: list[list[str]]) -> str:
return "\n".join("".join(row) for row in grid)
def _parse_loopy(problem_ascii: str) -> tuple[list[str], list[tuple[int, int]], list[tuple[int, int]]]:
lines = split_lines(problem_ascii)
rows = max(0, (len(lines) - 3) // 2)
cols = max(0, ((len(lines[0]) if lines else 0) - 3) // 2)
horizontal_edges: list[tuple[int, int]] = []
vertical_edges: list[tuple[int, int]] = []
for row in range(rows + 1):
for col in range(cols):
horizontal_edges.append((1 + 2 * row, 2 + 2 * col))
for row in range(rows):
for col in range(cols + 1):
vertical_edges.append((2 + 2 * row, 1 + 2 * col))
return lines, horizontal_edges, vertical_edges
def _decorated_loopy_dimensions(problem_ascii: str) -> tuple[int, int]:
puzzle_lines, _, _ = _parse_loopy(problem_ascii)
width = len(puzzle_lines[0]) if puzzle_lines else 0
height = len(puzzle_lines)
return width, height
def _is_compact_loopy_board(problem_ascii: str, board_ascii: str) -> bool:
lines = split_lines(board_ascii)
decorated_width, decorated_height = _decorated_loopy_dimensions(problem_ascii)
compact_width = max(0, decorated_width - 2)
compact_height = max(0, decorated_height - 2)
return (
len(lines) == compact_height
and all(len(line) <= compact_width for line in lines)
and compact_width > 0
and compact_height > 0
)
def _expand_compact_loopy_board(problem_ascii: str, board_ascii: str) -> str:
puzzle_lines, _, _ = _parse_loopy(problem_ascii)
decorated_width, _ = _decorated_loopy_dimensions(problem_ascii)
compact_lines = pad_lines(split_lines(board_ascii), max(0, decorated_width - 2))
grid = [list(line) for line in pad_lines(puzzle_lines, decorated_width)]
for row_idx, line in enumerate(compact_lines, start=1):
if row_idx >= len(grid) - 1:
break
for col_idx, char in enumerate(line, start=1):
if col_idx >= len(grid[row_idx]) - 1:
break
grid[row_idx][col_idx] = char
return join_grid(grid)
def normalize_loopy_board(problem_ascii: str, board_ascii: str) -> str:
puzzle_lines, horizontal_edges, vertical_edges = _parse_loopy(problem_ascii)
width = len(puzzle_lines[0]) if puzzle_lines else 0
if _is_compact_loopy_board(problem_ascii, board_ascii):
board_ascii = _expand_compact_loopy_board(problem_ascii, board_ascii)
grid = [list(line) for line in pad_lines(split_lines(board_ascii), width)]
if not grid:
grid = [list(line) for line in pad_lines(puzzle_lines, width)]
# A submitted board with the wrong number of rows/columns must not cause an
# IndexError below; pad to the decorated puzzle's dimensions so edge lookups
# always land inside the grid (a too-small/misshapen board simply stays blank
# in the missing cells and verifies as not solved).
height = len(puzzle_lines)
while len(grid) < height:
grid.append([])
for grid_row in grid:
if len(grid_row) < width:
grid_row.extend(" " * (width - len(grid_row)))
for row, col in horizontal_edges:
current = grid[row][col]
if current != "-":
grid[row][col] = "x"
for row, col in vertical_edges:
current = grid[row][col]
if current != "|":
grid[row][col] = "x"
return join_grid(grid)
def compact_loopy_board(problem_ascii: str, board_ascii: str) -> str:
normalized = normalize_loopy_board(problem_ascii, board_ascii)
lines = split_lines(normalized)
if len(lines) <= 2:
return normalized
return "\n".join(line[1:-1] for line in lines[1:-1])
def _parse_pattern_rows(board_ascii: str) -> tuple[list[str], list[str], list[str]]:
lines = split_lines(board_ascii)
clue_lines: list[str] = []
content_lines: list[str] = []
border_lines: list[str] = []
grid_started = False
for line in lines:
if "|" in line:
grid_started = True
content_lines.append(line)
elif "+" in line and "-" in line:
if grid_started:
border_lines.append(line)
else:
clue_lines.append(line)
else:
clue_lines.append(line)
return clue_lines, content_lines, border_lines
def normalize_pattern_board(board_ascii: str) -> str:
clue_lines, content_lines, border_lines = _parse_pattern_rows(board_ascii)
if not content_lines:
return board_ascii
normalized: list[str] = [*clue_lines]
border_iter = iter(border_lines)
for content in content_lines:
pieces = content.split("|")
if len(pieces) < 3:
normalized.append(content)
continue
next_pieces = [pieces[0]]
for cell in pieces[1:-1]:
next_pieces.append(".." if cell == " " else cell)
next_pieces.append(pieces[-1])
normalized.append("|".join(next_pieces))
try:
normalized.append(next(border_iter))
except StopIteration:
pass
return "\n".join(normalized)
def normalize_board_for_display(*, puzzle_type: str, problem_ascii: str, board_ascii: str) -> str:
if puzzle_type == "loopy":
return normalize_loopy_board(problem_ascii, board_ascii)
if puzzle_type == "pattern":
return normalize_pattern_board(board_ascii)
return board_ascii
def normalize_board_for_submission(*, puzzle_type: str, problem_ascii: str, board_ascii: str) -> str:
if puzzle_type == "loopy":
return compact_loopy_board(problem_ascii, board_ascii)
if puzzle_type == "pattern":
return normalize_pattern_board(board_ascii)
return board_ascii
|