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 its API is exported to C through a GLib-based wrapper. This wrapper can be loaded through GObject Introspection so ite can be used in many other languages. libipuz has been tested in python and javascript.
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 a json object, reading the raw structure is not very convenient. In addition, a robust set of mutators are provided for manipulating / creating a crossword while keeping common crossword heuristics valid.
Note
The gobject-introspection based languages are currently a little raw as we shake out all the rough edges of the API.
Fundamentals
Puzzles in libipuz are all represented by the IpuzPuzzle
object. This is the parent class of all puzzle types, and has common
properties associated with all puzzles, such as title, author, etc.
The IpuzCrossword
object is the parent class of all
crossword-like games and provides basic access to a crossword-like
grid. It also provides structure and access to clues and styles.
Note
We only support a limited number of ipuz kinds, though the full set of puzzle types is planned. Sudoku-style games are currently under development. See this document for more details.
Mutations
Changes in puzzles can often be very complex. Small changes in the grid can have large impact on the overall puzzle structure. For example, changing a cell from a normal cell to a block can result in the grid and clues needing renumbering, clues disappearing or appearing, enumeration lengths changing, etc. Symmetry requirements 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
caller make any changes they want to a puzzle, even if it results in a
non-standard grid. Once a set of changes is made, callers can then
call the _fix()
category of functions in order to enforce well-known
heuristics within the puzzle. This allows the user 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 of 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 ("test.ipuz", &error);
if (error != NULL)
{
g_warning ("Error Loading file: %s\n", error->message);
return;
}
guesses = ipuz_guesses_new_from_board (ipuz_crossword_get_board (IPUZ_CROSSWORD (puzzle)), FALSE);
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 clues to fill the open spaces.
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.set_size (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 clues to match
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')