Skip to main content

Import puzzles from other apps

pencilmark reads the common ways puzzles are shared. Pass any of them to pencilmark.rate, solve or describe, or put them one per line in a file for the command line; Other formats gives the exact rules of each.

Text grids​

Grids copied from Simple Sudoku, SadMan or a forum post work as they are, separators and all:

import pencilmark

grid = """
. . . | . 4 . | 8 . 3
. 6 . | . . 8 | . . 2
9 . 8 | 6 . 5 | . . 1
------+-------+------
2 3 5 | . . . | . . .
. . . | 7 . . | 2 . .
. . . | . . . | . 9 6
------+-------+------
. . 2 | . . . | . . .
8 . 1 | 2 . . | 7 . .
. 9 . | . . . | . . .
"""
pencilmark.rate(grid)["band"] # 'Hard'

The command line reads one puzzle per line, so give it the 81-character form there: pencilmark.to_string(grid, "plain").

sudokuwiki.org strings​

The packed strings of sudokuwiki.org's solvers (S9B… for classics, L9B… for killers, and others) are read directly, in Python and on the command line:

$ echo 'S9Bepepepep04ep08ep03ep06epepep08epep0209ep0806ep05epep01020305epepepepepepepepep07epep02epepepepepepepepep0906epep02epepepepepep08ep0102epep07epepep09epepepepepepep' | pencilmark rate -
Hard 5.9 10.7 X-Wing

pencilmark.to_string(puzzle, "sudokuwiki") writes one, to open a puzzle in their solver.

SudokuPad, f-puzzles, Penpa+ and Logic Masters links are read directly. Here links.txt holds one line, the README's classic as an f-puzzles link, so the card is the classic's:

$ pencilmark rate links.txt # one link per line
Hard 5.9 10.7 X-Wing
card = pencilmark.rate("https://f-puzzles.com/?load=N4IgzglgXgpiBcAC…")

f-puzzles ?load= links, Penpa+ links and bare SudokuPad payloads are decoded offline. SudokuPad short links, f-puzzles ?id= links, short-link services and Logic Masters pages are fetched: one request each, identified as pencilmark, with robots.txt obeyed and a pause between requests to the same site.

A rule pencilmark cannot map exactly is refused with the list of what it could not read; nothing is dropped silently. The error is pencilmark.sources.UnsupportedPuzzle, and its message names each element, for example "pink line" or "generic line (meaning in rules text)": a line whose meaning only the rules text gives.

For those, infer=True lets the reader try the possible meanings of each unexplained element and keep the reading under which the puzzle has exactly one solution (and agrees with its embedded solution, when the link has one); the rules text breaks ties. It is slower, and opt-in because it is an inference, however well checked:

from pencilmark import sources

puzzle = sources.load("https://sudokupad.app/…", infer=True) # a puzzle JSON dict
card = pencilmark.rate(puzzle)

ipuz files​

$ pencilmark rate killer.ipuz # docs/examples/killer-4x4.json written with to_ipuz
Very Easy 0.1 0.8 Hidden Single

pencilmark.from_ipuz and to_ipuz convert in both directions for classic, diagonal, windoku, jigsaw and killer puzzles.

Save in the puzzle format​

to_export turns any of these inputs into a sudoku-puzzle file, with the variant name and rules filled in, its solution, and the title and source a link carries:

exp = pencilmark.to_export("https://f-puzzles.com/?load=N4IgzglgXgpiBcAC…", title="Imported killer")
open("killer.json", "w").write(pencilmark.dumps(exp))

On the command line, pencilmark export links.txt --pretty does the same for every line.