Crosswords codebase overview

These documents describes the way the two applications are written. It doesn’t go into a lot of detail in each section, but gives a rough overview and hints to understand the codebase better.

This page covers the main game. In addition, it refers to code from the shared internals section. There are five major sections of code:

  • Main application

  • Puzzle sets

  • Puzzle pickers

  • Game board

  • Downloaders and convertors

Main application

The primary window in the game is the PlayWindow. This window has a list that lets the user select puzzle sets. It contains the main GtkStack to swap between views and manages the control flow between them. It also has game controls are included in the top level, which are forwarded to other parts of the application.

It also has a preferences dialog (PlayPreferencesDialog) with the global settings, as well as a help overlay.

The PlayWindow has three game phases that it can be in at any time that determines how it appears:

  • PUZZLE_PHASE_MAIN: Display the list to select puzzle sets

  • PUZZLE_PHASE_PICKER: Display the puzzle picker

  • PUZZLE_PHASE_GAME: Display the game board

Each phase as represented by a widget. The latter two are described in more detail below.

Related code:

  • PlayWindow: play-window (.h,.c)

  • PlayPreferencesDialog: play-preferences-dialog (.h,.c)

  • help overlay: help-overlay (.ui)

Puzzle sets

Puzzle sets represents collection of puzzles based around a theme. They are shipped as a GResource with an internal config file (puzzle.config), defining that collection. They also optionally have puzzles contained within the resource.

These are represented by a PuzzleSet object which is used to control and load aspects of the collection. All configuration settings are loaded from puzzle.config and contained within the PuzzleSetConfig object. It will also load all puzzles from disk — or resource — at creation and store them in PuzzleSetModel. This implements the GListModel interface, which can be used to follow changes to the model.

Puzzle sets are referred to by a globally unique id field defined in the puzzle.config file. This identifier is used in many places. It’s used as a location on disk to store the puzzle set, it’s used as a path within the GResource, and it’s used to refer to the puzzle set through out the code.

The PuzzleSet also acts as a factory, creating both puzzle pickers and the game board (see below). Currently, due to limitations in the current code base, only one PuzzleSet can exist at a time. This will have to be changed before we can present multiple PlayWindows at the same time.

Related code:

  • PuzzleSet: puzzle-set (.h,.c)

  • PuzzleSetConfig: puzzle-set-config (.h,.c)

  • PuzzleSetModel: puzzle-set-model (.h,.c)

Puzzle pickers

The PuzzlePicker widget manages the user’s interaction with the PuzzleSet. It is deceptively complex code. It has to present the puzzles in a way for the user to handle them, and controls the downloader (see below). The picker’s appearance is defined in puzzle.config and is read from the PuzzleSetConfig object.

PuzzlePicker is a base widget with two variants so far: PickerGrid and PickerList. The grid presents a grid view of puzzles with snapshots as can be seen in the cats-and-dogs puzzle set. The list is less puzzle-like and doesn’t require solving prior puzzles before advancing. This list uses a PuzzleSetModel as its base model.

Related code:

  • PuzzlePicker: puzzle-picker (.h,.c)

  • PickerGrid: picker-grid (.h,.c)

  • PickerList: picker-grid (.h,.c)

Game board

The game board is a widget that displays the puzzle. Currently, the only one supported is the PlayXword which lets you play crosswords. In the future, other puzzle types (such as acrostics or word searches) could be supported through their own widget type. It is created and configured by PuzzleSets and put in the main stack.

The PlayXword contains only one widget — the custom PlayXwordColumn. There’s a somewhat complicated (rationale)[play-xword-column.md] for this custom container, but the end result is that changes to it take extra work to implement. The main game widget within the PlayXword is a PlayGrid. It also has custom widgets for displaying clues and rows-of-clues (PlayClueRow and PlayClueList).

The PlayXword controls the XwordState and uses it to update other widgets. It’s entirely stateless. Setting a new IPuzPuzzle on the PlayXword will result in a valid appearance, even if it’s entirely different. To implement undo/redo it has a PuzzleStack that manages the IPuzPuzzle internally.

Related code:

  • PlayXword: play-xword (.h,.c)

  • PlayXwordColumn: play-xword-column (.h,.c)

  • PlayClueList: play-clue-list (.h,.c)

  • PlayClueRow: play-clue-row (.h,.c)

Downloaders and convertors

The PuzzleDownloader is how puzzles are imported by the game. They are initiated by the user pressing the download button in the puzzle picker, or from the user passing a uri in on the command-line.

The downloader has a multi-stage process. First, it can optionally use a command provided by the puzzle set to download a file from a location, and store it in /tmp. This command is configured by the PuzzleSetConfig, and is managed by the PuzzleDownloader object.

Once the downloader has copied the file to /tmp, it is optionally passed through the ipuz-convertor utility to the game’s directory. This convertor knows how to handle .ipuz, .puz, .jpz, and .xd files. It is written to be modular and should be able to be extended to other formats relatively easily.

Related code:

  • PuzzleDownloader: puzzle-downloader (.h,.c)

  • PuzzleDownloaderDialog: puzzle-downloader-dialog (.h,.c)

  • ipuzconvertor: ipuzconvertor (.py,.c)