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 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')