API reference

Every public name in the pencil_puzzles package. The user guide shows how they fit together.

Functions

generate(genre: str, shape: tuple[int, int] | None = None, *, difficulty: Difficulty | str | None = None, seed: int | None = None, limits: Limits = Limits(), cancel: CancellationToken | None = None) → Generated

A puzzle with exactly one solution, at exactly difficulty (any level if None).

Without shape, the puzzle has the genre’s standard size. Without seed, the seed is random; it is kept in Generated.seed. The same seed and package version always give the same puzzle.

solve(puzzle: Puzzle, *, limits: Limits = Limits(), cancel: CancellationToken | None = None) → Solution

The puzzle’s only solution.

rate(puzzle: Puzzle, *, limits: Limits = Limits(), cancel: CancellationToken | None = None) → Rating

The human techniques the puzzle needs.

check(puzzle: Puzzle, answer: Solution, *, finished: bool = False, limits: Limits = Limits(), cancel: CancellationToken | None = None) → list[Violation]

Every rule the answer breaks; empty when it breaks none.

With finished, unmarked cells and undrawn bridges first take the genre’s default for a finished answer, such as no bridge in hashi.

hint(puzzle: Puzzle, answer: Solution | None = None, *, limits: Limits = Limits(), cancel: CancellationToken | None = None) → Step | None

The next step a solver would take from answer, or None if it is already solved.

genres() → tuple[str, ...]

The supported genre ids.

Puzzles

class Puzzle

A puzzle’s clues. Use Puzzle.from_id or Puzzle.from_text to load one.

genre: str
shape: tuple[int, int]
id: str
static from_id(puzzle_id: str) → Puzzle

Read a puzzle ID such as sudoku:4x4:....

static from_text(source: str) → Puzzle

Read a puzzle in the shared text format.

to_text() → str

The puzzle in the shared text format.

class SudokuPuzzle
clues: tuple[tuple[int, ...], ...]

Digits, 0 for empty cells.

class NurikabePuzzle
clues: tuple[tuple[int, ...], ...]
class FillominoPuzzle
clues: tuple[tuple[int, ...], ...]
class HashiPuzzle
clues: tuple[tuple[int, ...], ...]

Island numbers, 0 for empty cells.

class HitoriPuzzle
clues: tuple[tuple[int, ...], ...]
class KakuroPuzzle
clues: tuple[tuple[tuple[int, int] | None, ...], ...]

Per cell: None for a white cell, (down, across) sums for a block, 0 for a missing sum.

class KakurasuPuzzle
clues: KakurasuClues
class KakurasuClues(rows: tuple[int, ...], cols: tuple[int, ...])

Kakurasu clues: one sum per row and one per column.

rows: tuple[int, ...]

Alias for field number 0

cols: tuple[int, ...]

Alias for field number 1

Every puzzle class is a frozen dataclass. str(puzzle) gives the grid laid out for reading. The names givens, numbers, cells, row_clues and col_clues still work and are deprecated; use clues.

Answers and results

class Solution(genre: str, cells: tuple[tuple[int | str | Mark | None, ...], ...])

A complete or partial answer: numbers for sudoku, fillomino and kakuro (0 for unmarked, None for kakuro blocks), each cell’s text for hashi (".", an island number, "-", "=", "|" or "H"), Mark values otherwise.

genre: str
cells: tuple[tuple[int | str | Mark | None, ...], ...]
property shape: tuple[int, int]
class Generated(puzzle: Puzzle, solution: Solution, rating: Rating, seed: int, generator_version: int, attempts: int)

A generated puzzle with its solution, rating and how to reproduce it.

puzzle: Puzzle
solution: Solution
rating: Rating
seed: int
generator_version: int
attempts: int
class Rating(level: Difficulty, techniques: tuple[str, ...], steps: int, score: int, rater_version: int)

A puzzle’s difficulty, from solving it with human techniques.

level: Difficulty
techniques: tuple[str, ...]
steps: int
score: int

Total size of the step explanations. It changes between rater versions and does not measure solving time.

rater_version: int
class Step(level: Difficulty, technique: str, removed: tuple[tuple[int, int, str] | tuple[int, int, int, int, str], ...])

One deduction: the technique and the candidates it removes, as (row, col, value) for a cell, or (row, col, row2, col2, value) for the bridges between two hashi islands.

level: Difficulty
technique: str
removed: tuple[tuple[int, int, str] | tuple[int, int, int, int, str], ...]
class Violation(rule: int, place: dict[str, Any], values: dict[str, int])

A broken rule: its number on the genre page, where, and the values involved.

rule: int
place: dict[str, Any]
values: dict[str, int]
class Difficulty(*values)

How hard a puzzle is: the level of the hardest human technique it needs.

EASY = 'easy'
MEDIUM = 'medium'
HARD = 'hard'
EXPERT = 'expert'
class Mark(*values)

A cell in a nurikabe, kakurasu or hitori answer.

SHADED = '#'
UNSHADED = '-'
UNMARKED = '.'

Limits and cancellation

class Limits(work: int = 100000000, attempts: int = 10000)

Bounds on an operation. Running out raises an error; a result is the same under any limits.

work: int = 100000000
attempts: int = 10000
class CancellationToken
cancel()
cancelled

Pass a CancellationToken as cancel to any function. Calling cancel() from any thread stops every operation it was passed to with CancelledError, and cancelled becomes True.

Errors

Every error is a subclass of PencilPuzzlesError. Arguments of the wrong type raise TypeError or ValueError.

Error

Raised when

UnsupportedRequestError

The genre, shape, difficulty or another argument is not supported

AttemptsExhaustedError

generate found no puzzle within Limits.attempts

BudgetExceededError

The work ran past Limits.work

CancelledError

The CancellationToken was cancelled

UnratedPuzzleError

The supported techniques cannot finish the puzzle

NoSolutionError

The puzzle has no solution

MultipleSolutionsError

The puzzle has more than one solution

ParseError

Puzzle text, an ID or an answer could not be read

exception PencilPuzzlesError(message: str, **context: Any)

Base class for every error; extra context is available as attributes.

exception UnsupportedRequestError(message: str, **context: Any)

Unknown genre, unsupported shape or difficulty, or an invalid argument.

exception AttemptsExhaustedError(message: str, **context: Any)

No puzzle was found within Limits.attempts.

exception BudgetExceededError(message: str, **context: Any)

The work ran past Limits.work.

exception CancelledError(message: str, **context: Any)

The operation’s CancellationToken was cancelled.

exception UnratedPuzzleError(message: str, **context: Any)

The puzzle is unique, but the supported techniques cannot finish it.

exception NoSolutionError(message: str, **context: Any)

The puzzle has no solution.

exception MultipleSolutionsError(message: str, **context: Any)

The puzzle has more than one solution; witnesses holds two of them.

exception ParseError(message: str, **context: Any)

Puzzle text, an ID or an answer could not be read.