Intro to libipuz
Intro
LIBIPUZ is a library for loading, manipulating, and saving puzzles. It loads .ipuz files and provides a programatic interface to manipulate them. It is written in C and Rust and has a C-based API using glib. This API can be accessed through GObject Introspection, and thus can be used in many other languages. It has been tested in C, Python, and Typescript.
The primary goal of libipuz is to provide an API that loosely mirrors the ipuz structure while providing additional functionality. While ipuz files can be loaded directly into memory as json objects, working with the raw structure is often inconvenient. In addition, a robust set of mutators are provided for manipulating / creating a puzzle while maintaining common heuristics.
Fundamentals
Puzzles in libipuz are all represented by the IpuzPuzzle
object. This is the parent class of all puzzle kinds and provides
common properties associated with all puzzles, such as title, author, etc.
The IpuzGrid
object is the parent class of all grid-based
puzzles and provides basic access to a rectangular grid of cells. The
IpuzClues
interface can be used to read the clues of
crossword-style puzzles.
Note
We currently only support a limited number of ipuz kinds: Crosswords, Acrostics, and Nonograms, The full set of puzzle types is planned, and Sudoku-style games are currently under development. See this document for current status.
Mutations
Changes in puzzles can often be very complex. For gridded puzzles, small changes can have a large impact on the overall puzzle structure. For example, consider the act of changing a cell from a normal cell to a block in a crossword. This can result in the grid and clues needing renumbering, clues disappearing or appearing, enumeration lengths changing, etc. Symmetry requirements may also need to be maintained.
On the other hand, it occasionally is interesting to have a board with non-standard semantics. As an example, Alphabetic jigsaw crosswords don’t use clue numbers.
Consequentially, the overall philosophy of libipuz is to let the
developer make any changes they want to a puzzle, even if it results
in a non-standard puzzle. Once a set of changes is made, developers
can call the _fix()
category of functions in order to enforce
well-known heuristics. This allows them to choose which heuristics to
apply and how to apply them. The resulting puzzle should be a well
formed puzzle, suitable for another round of mutation.
Simple Examples
Here are a few simple examples using libipuz:
C
This snippet will load a puzzle from disk and set a guess of “A” for the first cell.
g_autoptr (IpuzPuzzle) puzzle = NULL;
g_autoptr (IpuzGuesses) guesses = NULL;
g_autoptr (GError) error = NULL;
IpuzCellCoord coord = {
.row = 0,
.column = 0,
};
puzzle = ipuz_puzzle_new_from_file (filename, &error);
if (error != NULL)
{
g_warning ("Error Loading file: %s", error->message);
return;
}
guesses = ipuz_grid_create_guesses (IPUZ_GRID (puzzle));
ipuz_guesses_set_guess (guesses, &coord, "A");
Python
This is a more complex example of creating the simple crossword that
appears in the ipuz spec. In this, we set the size
of the puzzle and change the solution/type of the cells. Then, we call
of the _fix()
functions. This will apply valid numbering to the grid
and create empty clues to fill the open spaces. We then fill in the
clues with appropriate text.
Once done, it will save the puzzle to disk.
simple.ipuz created through libipuz
import gi
gi.require_version('Ipuz', '1.0')
from gi.repository import (Ipuz)
# Define the puzzle
cells = [ [ "C", "A", "#" ], [ "B", "O", "T" ], [ None, "L", "O" ] ]
across_clues = [ "OR neighbor",
"Droid",
"Behold!" ]
down_clues = [ "Trucker's radio",
"MSN competitor",
"A preposition" ]
# Create a crossword
c = Ipuz.Crossword ()
c.resize (3, 3)
c.set_title ('Example crossword')
c.set_author ('Angela Avery')
# Populate the grid
coord = Ipuz.CellCoord()
for row in range (0, 3):
for column in range (0, 3):
val = cells[row][column]
coord.row = row
coord.column = column
cell = c.get_cell(coord)
if val == '#':
cell.set_cell_type(Ipuz.CellType.BLOCK)
elif val == None:
cell.set_cell_type(Ipuz.CellType.NULL)
else:
cell.set_solution(val)
if row == column:
style = Ipuz.Style()
style.set_shapebg(Ipuz.StyleShape.CIRCLE)
cell.set_style (style, None)
# Update the numbering of the grid and create blank clues to match the numbers
c.fix_numbering ()
c.fix_clues ()
# Set the text of each clue
clue_id = Ipuz.ClueId()
clue_id.direction = Ipuz.ClueDirection.ACROSS
for i in range(0, len (across_clues)):
clue_id.index = i
c.get_clue_by_id(clue_id).set_clue_text(across_clues[i])
clue_id.direction = Ipuz.ClueDirection.DOWN
for i in range(0, len (down_clues)):
clue_id.index = i
c.get_clue_by_id(clue_id).set_clue_text(down_clues[i])
# Save the puzzle to disk
c.save_to_file ('simple.ipuz')