Revamping the libipuz edit APIs
Status: Reviewed federico@gnome.org | partially-implemented
Summary
Currently, all editing is split between xword-state.c
and
libipuz. We propose a structured way to handle editing to make it
easier to reason about, and to put the majority of the complexity in
libipuz.
Currently, the fix()
API is implemented, but the linters are not.
Problem Statement
There currently exists a mixture of API levels used to mutate puzzles
through the editor. For example, it’s possible to change an individual
cell within the puzzle by calling ipuz_cell_set_*
, or
ipuz_style_set_*
. Similarly, you can change a clue. However, all
these functions bring some challenges with them, that can lead to an
internally inconsistent state:
Changing the cell_type can leave the numbering and clue cells inconsistent.
Changing the symmetry can be super destructive
Even clues are super-tricky: We need to make sure every cell knows which clue it’s part, and vice-versa.
Additionally, there are some conventions we want to enforce that aren’t part of the ipuz spec. In particular:
For crosswords, there are only across/down clues, and every cell is part of at least one clue.
We are correctly numbered. Clue numbers increase by 1, and there are no gaps
We store the clue cells within the puzzle, and they don’t have discontinuities
We tried to work around this in calls from xword-state.c
by
introducing the ipuz_crossword_set_cell_type()
function, as well as
storing the symmetry within the puzzle. This is always immediatedly
followed but a call to ipuz_crossword_calculate_clues()
. This level
of granularity isn’t sufficient for what we need to do and hasn’t aged
well.
Challenges
Subtypes
Arrowword, Filippine, and Barred puzzles inherit from Crossword, and Acrostics don’t inherit from Crosswords at all. They don’t share the same conventions that Crossword has, though there are some commonalitites. All of these will need their own adjustment to editing.
For example, Filippine puzzles only have across clues, and have a vertical line that’s highlighted with the answer. Barred puzzles don’t have blocks or nulls, and expect “T”, “L”, and “TL” styles to exist. Arrowwords need to have the arrow updated. Symmetry makes almost no sense for Acrostics.
NOTE: In addition, both Filippine and Acrostic puzzles will benefit from a locked-down custom editor that doesn’t even let you change the grid directly. They might want to bypass XwordState’s editing system entirely. Given that we called it
XwordState
, the introduction of anAcrosticState
wouldn’t be surprising, but this may be substantial work and sources of errors.
Raw Mode
Some puzzles do need to break the rules. There are fun puzzles with
diagonal clues, or ‘J’ hooks that don’t reveal themselves
immediately. Maybe you do want a barred puzzle with blocks and/or
discontinuities. We may want to consider adding a raw mode to the
editor that just uses the _set()
functions and lets you produce
valid-but-unusual puzzles. This mode would require people to
automatically set the range of cells of clues, for example.
Proposal
libipuz changes
We will have three different APIs for libipuz. First the existing one demarked by the ipuz
_set()
functions. These will let you manipulate the puzzle however you see fit. Example:ipuz_cell_set_label()
We add a set of linting APIs demarked by
_check()
functions. This will return a list of potentially broken attributes that might need fixing. We will have to define a set of types for each check type. Example:ipuz_crossword_check_cell_labels()
.We add a correction API, demarked by
_fix()
functions_fix()
functions may need arguments. For example,fix_symmetry()
will need to take in a list of modified cells to update. Otherwise, we won’t know which direction to change things.
As a convenience, we add an
ipuz_crossword_fix_all()
function to call the fix functions in the correct order.We remove symmetry as a tracked feature of crosswords.
Here’s a diagram of this behavior.
sketch of editing changes
NOTE: We will make both the
_check()
and_fix()
function class methods, so that child types can override or chain to them. Most notably,fix_all()
also needs to be a class_method.fix_all()
will also need to take in arguments, so the class method will require kwargs.
XwordState changes
We move any settings to give us hints about the type of fixes to the Quirks object.
We will introduce a mangaged_mode/raw_mode enum to the Quirks object to track whether we want to fix things up automatically or not.
As another example, symmetry is another quirk setting
All current mutators within xword_state will fix up puzzle based on that mode.
Editor Changes / Raw Mode
There’s a (currently unwritten) style tab in the editor that will let you customize the label / style of grids. We will have to prevent the user from breaking assumptions
The current
edit-
widgets were written to expect the puzzle type and base puzzle to change. We will make it so thatPUZZLE_KIND
is a construct-only property.raw_mode
should be a property that can change, and the widgets have to adjust themselves to adapt.