GNOME Crosswords
year three
Agenda
Part 1
Crosswords
What is GNOME Crosswords?
GNOME Crosswords is two things: A standalone crossword game for solving crosswords, and a separate authoring tool for writing puzzles
Started 3.5 years ago:
What is GNOME Crosswords?
Special thanks to Federico, Tanmay, Davide, Philip, �and Pranjal who help me maintain this!
What is GNOME Crosswords?
Thanks Pranjal and GSoC!
What is GNOME Crosswords?
A Visual Tour
The Game
Try it yourself!
Download from flathub
A brief tour — main screen
A brief tour — puzzle selection
A brief tour — standard (American) crosswords
A brief tour — cryptic, rebus, and barred
Cryptic crossword
Standard with rebus
Barred crossword
A brief tour — arroword and Filippine crosswords
Arrowword crossword
Filippine puzzle
A brief tour — acrostic puzzle
Thanks Tanmay and GSoC!
A brief tour — adaptive layout support
Fits on a 300x300 display!
A brief tour — preferences
Part 2
Writing Crosswords
How do you write Crosswords?
With standard (American) crosswords, creating a grid is really hard
On the other hand, writing clues is tricky for cryptic crosswords
A fundamental rule of crosswords:
�Every cell needs at least two paths to its answer
Crossword setters use the term unch — short for "unchecked" — to describe when there is only one path to answer a cell.
Unches are to be avoided.
Standard and Cryptic crosswords satisfy this rule in different ways.
Acrostics also have at least two paths.
Standard Crosswords
Every cell has an across and down clue to let you know you have the right answer.
Ⓒircled cells have three paths!
L. A. Times – Jul 3, 2024
UNCH: Avoid this pattern
Standard Crosswords — clues
L. A. Times – Jul 3, 2024
1ac. Beneath
1dn. As many as
Assorted clues:
The answer to 1ac could be UNDER or BELOW
We need to try to solve 1dn to be sure
Standard Crosswords — puzzle theme
L. A. Times – Jul 3, 2024
58ac. Closely knit community that provides social
support, or what 16, 23, 36, and 46 across
contain?
Assorted clues:
FOUNDFAMILY describes the words in circles
Standard Crosswords — tools
To aid creating grids:
L. A. Times – Jul 3, 2024
Items in bold already implemented
Cryptic Crosswords — clues
With cryptics, clues are used to provide redundancy. Every clue has a straight definition and some wordplay.
Times of London – Jun 30, 2024
UNCH: Avoid this pattern
Cryptic Crosswords — clues
Times of London – Jun 30, 2024
3dn. Check the gas pedal (8)
THROTTLE
Assorted clues:
18ac. Leader from Harlech, Wales, appointed (4, 6)
LECH WALESA
Cryptic Crosswords — tools
To aid writing cryptic clues:
Times of London – Jun 30, 2024
Items in bold already implemented
Thanks Pratham and GSoC!
A Visual Tour
The Editor
A brief tour — initial greeter
A brief tour — grid editor
Puzzle writing modes
A brief tour — grid editor: word suggestions
Word list suggestions
A brief tour — grid editor: autofill
Autofill controls
A brief tour — clue editor
Cryptic clue tools:
anagrams and alternations
A brief tour — metadata
Part 3
Writing Crossword Writers
What makes writing a crossword editor hard?
Example: Small edits can lead to big changes in the puzzle
User presses the '#' key
Change (0,0) to a BLOCK
Numbering is wrong
Symmetry isn't maintained
Enumerations are broken
Initial result
What we want
Clues are different
Styles could be different
Writing the Editor:
Design Requirements
Unidirectional data flow architecture
This approach is highly testable and predictable. Actions and results can be replayed
Writing the Editor:
Implementation rules
Converting action-model-view
to GTK
Point 1: All state is kept in a single struct. Updates are discrete
user input
State 1
State 2
Dispatched to rendering
Dispatched to rendering
user input
State 3
state_2 = grid_state_guess (self->state,
&self->state->cursor,
"#"));
update_all (self, state_2);
self->state =
grid_state_replace (self->state, state_2);
User presses '#' key
state changes from State1 to State2
Point 1: All state is kept in a single struct.
Updates are discrete
States need to be representable as a value. You need to be able to serialize it
typedef struct
{
IpuzCrossword *xword;
GridStateBehavior behavior;
/* Current keyboard state */
IpuzCellCoord cursor;
IpuzClueId clue;
/* Modifiers */
GridRevealMode reveal_mode;
CrosswordsQuirks *quirks;
} GridState;
Every state has its own copy of the puzzle object
Point 1: All state is kept in a single struct. Updates are discrete
State 1
user input
Dispatched to rendering
GridState *
grid_state_guess (GridState *state,
IpuzCellCoord *coord,
const gchar *guess)
{
…
IpuzCell *cell =
ipuz_crossword_get_cell (state->xword, coord);
ipuz_cell_set_cell_type (cell, IPUZ_CELL_BLOCK);
ipuz_crossword_fix_symmetry (state->xword,
IPUZ_SYMMETRY_ROTATIONAL_QUARTER,
coord);
ipuz_crossword_fix_numbering (state->xword);
ipuz_crossword_fix_clues (state->xword);
ipuz_crossword_fix_enumerations (state->xword);
…
user input
Dispatched to rendering
Update puzzle
Fix puzzle
When changing the puzzle:
1. First make all changes to it
2. Then fix the puzzle to follow convention
State 2
Point 1: All state is kept in a single struct. Updates are discrete
State 1
user input
Dispatched to rendering
Push value on Undo/Redo Stack
user input
Dispatched to rendering
Update puzzle
Fix puzzle
Push value on Undo/Redo Stack
Every undoable state is pushed to an Undo/Redo stack object
This object stores the current active state of the editor
State 2
Point 2: Widgets are stateless and commutative
Colin Walters: “Immutable” → reprovisionable, anti-hysteresis�https://blog.verbum.org/2020/08/22/immutable-→-reprovisionable-anti-hysteresis/
Widgets have anti-hysteresis properties.
When given a state, they behave and render the same independent of the path taken to get to that state
Point 2: Widgets are stateless and commutative
Writing commutative widgets:
Anti-hysteresis style widgets are significantly easier to reason about
Point 2: Widgets are stateless and
commutative
State 1
user input
Dispatched to rendering
Push value on Undo/Redo Stack
user input
Update puzzle
Fix puzzle
Calculate GridLayout
Call _update() on all widgets
An intermediate grid representation is needed to avoid each cell calculating its appearance from first principles
Push value on Undo/Redo Stack
State 2
Point 3: Control is centralized in a
top-down dispatch model
State 1
Dispatched to rendering
Update puzzle
Fix puzzle
Calculate GridLayout
Call _update() on all widgets
Push value on Undo/Redo Stack
EditWindow controls
widget
widget
widget
widget
widget
All widgets emit a ::changed signal
widget
widget
widget
Composite widgets propagate their children's ::changed signals
State 2
::changed
Point 4: Complexity is concentrated
Examples:
edit-window-controller.c (dispatch):
Translates between messy world of gtk and values
grid-state.c (crossword behavior):
Handles all actions on the crossword
puzzle-set-model.c (files):
Translates between messy world of file systems and puzzles
word-list.c (word lookup):
Finds potential word matches
Widgets and data structures are kept as simple as possible
Questions?
Download
on flathub!
Bonus slides
Issues with traditional application development
We took inspiration from the Unidirectional Data Flow architecture!
Cryptic Crosswords — clues
Times of London – Jun 30, 2024
3dn. Check the gas pedal (8)
THROTTLE
26ac. Second son, crossing pitch, means to walk tall (6)
STILTS
Assorted clues:
18ac. Leader from Harlech, Wales, appointed (4, 6)
LECH WALESA