|
4 | 4 | from typing import Optional |
5 | 5 |
|
6 | 6 | from .api import analyze, build_reveal_trace, from_string, is_valid, solve, to_string |
7 | | -from .crosscheck import sat_solve |
| 7 | +from .crosscheck import sat_solve, cnf_dimacs_lines |
8 | 8 | from .explain import explain |
9 | 9 | from .canonical import canonical_form |
10 | 10 | from .generate import generate |
11 | 11 | from .rating import rate |
| 12 | +from .formats import detect_format, read_grids, write_grids |
12 | 13 | from statistics import mean |
13 | 14 |
|
14 | 15 |
|
@@ -122,6 +123,44 @@ def cmd_check(ns: argparse.Namespace) -> int: |
122 | 123 | return 0 |
123 | 124 |
|
124 | 125 |
|
| 126 | +def cmd_convert(ns: argparse.Namespace) -> int: |
| 127 | + infmt = ns.in_format or detect_format(ns.in_path) |
| 128 | + outfmt = ns.out_format or detect_format(ns.out_path) |
| 129 | + grids = read_grids(ns.in_path, infmt) |
| 130 | + write_grids(ns.out_path, grids, outfmt) |
| 131 | + print(f"# converted {len(grids)} grids {infmt} → {outfmt}", file=sys.stderr) |
| 132 | + return 0 |
| 133 | + |
| 134 | + |
| 135 | +def cmd_to_cnf(ns: argparse.Namespace) -> int: |
| 136 | + grid = from_string(_read_grid_arg(ns)) |
| 137 | + outp = pathlib.Path(ns.out_path) |
| 138 | + outp.parent.mkdir(parents=True, exist_ok=True) |
| 139 | + with outp.open("w", encoding="utf-8") as handle: |
| 140 | + for line in cnf_dimacs_lines(grid): |
| 141 | + handle.write(line) |
| 142 | + if not line.endswith("\n"): |
| 143 | + handle.write("\n") |
| 144 | + return 0 |
| 145 | + |
| 146 | + |
| 147 | +def cmd_explain_file(ns: argparse.Namespace) -> int: |
| 148 | + inp = pathlib.Path(ns.in_path) |
| 149 | + outp = pathlib.Path(ns.out_path) |
| 150 | + grids = read_grids(str(inp), ns.in_format) |
| 151 | + outp.parent.mkdir(parents=True, exist_ok=True) |
| 152 | + written = 0 |
| 153 | + with outp.open("w", encoding="utf-8") as handle: |
| 154 | + for s in grids: |
| 155 | + grid = from_string(s) |
| 156 | + data = explain(grid, max_steps=ns.max_steps) |
| 157 | + obj = {"grid": s, **data} |
| 158 | + handle.write(json.dumps(obj, separators=(",", ":"), sort_keys=True) + "\n") |
| 159 | + written += 1 |
| 160 | + print(f"# wrote {written} explanations to {outp}", file=sys.stderr) |
| 161 | + return 0 |
| 162 | + |
| 163 | + |
125 | 164 | def cmd_explain(ns: argparse.Namespace) -> int: |
126 | 165 | grid = from_string(_read_grid_arg(ns)) |
127 | 166 | data = explain(grid, max_steps=ns.max_steps) |
@@ -383,6 +422,26 @@ def main(argv: Optional[list[str]] = None) -> int: |
383 | 422 | check_parser.add_argument("--json", action="store_true", help="output JSON") |
384 | 423 | check_parser.set_defaults(func=cmd_check) |
385 | 424 |
|
| 425 | + convert_parser = sub.add_parser("convert", help="convert between txt/csv/jsonl formats") |
| 426 | + convert_parser.add_argument("--in", dest="in_path", required=True) |
| 427 | + convert_parser.add_argument("--out", dest="out_path", required=True) |
| 428 | + convert_parser.add_argument("--in-format", dest="in_format", choices=["txt", "csv", "jsonl"]) |
| 429 | + convert_parser.add_argument("--out-format", dest="out_format", choices=["txt", "csv", "jsonl"]) |
| 430 | + convert_parser.set_defaults(func=cmd_convert) |
| 431 | + |
| 432 | + tocnf_parser = sub.add_parser("to-cnf", help="export one puzzle to DIMACS CNF") |
| 433 | + tocnf_parser.add_argument("--grid", help="81-char string; 0/./- for blanks") |
| 434 | + tocnf_parser.add_argument("--file", help="path to a file with 9 lines of 9 chars") |
| 435 | + tocnf_parser.add_argument("--out", dest="out_path", required=True) |
| 436 | + tocnf_parser.set_defaults(func=cmd_to_cnf) |
| 437 | + |
| 438 | + explainf_parser = sub.add_parser("explain-file", help="explain many puzzles into NDJSON") |
| 439 | + explainf_parser.add_argument("--in", dest="in_path", required=True) |
| 440 | + explainf_parser.add_argument("--out", dest="out_path", required=True) |
| 441 | + explainf_parser.add_argument("--in-format", dest="in_format", choices=["txt", "csv", "jsonl"]) |
| 442 | + explainf_parser.add_argument("--max-steps", type=int, default=200) |
| 443 | + explainf_parser.set_defaults(func=cmd_explain_file) |
| 444 | + |
386 | 445 | explain_parser = sub.add_parser( |
387 | 446 | "explain", help="human-style steps (naked/hidden single, locked candidates)" |
388 | 447 | ) |
|
0 commit comments