# Editing the Acrostic Grid

**Status:** Approved | January 2025

**Author:** Jonathan Blandford

**Reviewer:** Tanmay Patil

## Overview

Acrostic puzzles have some unique constraints that need to be
satisfied before they're a _well formed_ puzzle. The editor lets you
edit parts of the constraints putting the puzzle in _partially formed_
or _error_ states. Changing some of the constraints can be
particularly destructive. It's not clear what grid editing actions
should exist and what's an acceptable level of destruction. In
addition, it's not clear what the correct behavior for the `_fix()`
functions — and the UI — should be.

## Problem Statement

Acrostics have the following constraints:

1. The quote string can be read from the grid (and vice versa)
2. The first letter of each answer spell out the source string
3. Every cell in the grid is used exactly once for an answer, and has
   a 1:1 mapping between a clue and a cell.

We introduce the concept of the acrostic being in a _well formed_,
_partially formed_, or an _error state_ in the editor.

When all the contraints are met and the puzzle is complete, it's in a
well-formed state.  When constraints are all possible to be met, but
we don't have a full set of answers, the puzzle is in a partially
formed state. When we have contradictions in the set of characters
then we're in an error state.

We don't have the APIs currently to handle the partially formed and
error states. Consequentially the puzzle is hard to work with in the
editor.

### Existing fix functions

`IpuzAcrostic` has the following fix functions:

* **fix_quote():** Syncs between the grid and to the quote
  string. Takes an argument to indicate direction of
  synchronization. Will (potentially) resize the board and clears all
  quote strings.
* **fix_source():** Syncs between the clues and the source
  string. Takes an argument to indicate direction of
  synchronization. Changes the number of clues in the puzzle and
  clears them.
* **set_answers():** Takes a list of answers that satisfies the
  constraints set by the quote and source strings. It will set the
  clues. Unlike the `quote_str` and `source_str`, the answers are
  stored as the clue cells and aren't kept as a lookaside value.
* **fix_labels():** Updates the labels of every cell to match that of
  the clue.

## Proposal

**tl;dr:** Never let the puzzle get into an error state in the editor
as the result of a user action.

### Supported User Actions

We want to support the following user actions in the editor:

* Edit the quote string
* Edit the source string
* Edit and write individual answers
* Use the autofill functionality on either a portion of the answer space, or the full puzzle.

#### Edit the quote string

_The user edits the quote string_

Editing the quote string will change the size and shape of the grid,
as well as the valid characters for the grid. It will also
(potentially) invalidate any clues that currently exists. It's
possible to make minor changes to a quote and keep the puzzle largely
the same. Alternatively, it's also possible to put the puzzle into an
error state invalidating everything.

When the user changes the quote string, we should see if the source
string still works with the original quote. If so we are in a
partially formed state, otherwise we're in an error state. We can then
choose the actions in one of the following two options:

**Option 1: Partially Formed**

1. Take a snapshot of all existing answers in the puzzle.
1. Recreate the grid with the new quote string
1. Go through each answer serially as they exists, and see if it can
   be used with the quote letters. Add back the ones that work. Add
   any clues as well.
1. Update the puzzle labels to not include clues
1. Write the puzzle to the undo stack.

**Option 2: Error State**

1. Possibly warn the user about a destructive change?
1. Recreate the grid with the new quote string
1. Clear the source string
1. Clear all answers
1. Update the puzzle labels to not include clues
1. Write the puzzle to the undo stack.

That would make the callback look something like:

```C
static void
quote_string_changed_cb (quote_str)
{
  answer_list = snapshot_answers ();
  set_quote_str (quote_str);
  fix_quote_str ();
  state = check_acrostic_state ();
  if (state == ERROR)
    {
       set_source_str ("");
       fix_source_str ();
       // state should be partial now, by definition
    }
  else if (state == PARTIAL)
    {
       for (guint i = 0; i < answer_list->len; i++)
         {
           // This call will fail if there aren't enough letters
           set_answer (i, answer_list[i]);
         }
       // Recheck to see if all the letters are used
       state = check_acrostic_state ();
    }

  if (state == WELL_FORMED)
    fix_labels (CLUES);
  else
    fix_labels (NO_CLUES);

  push_change ();
}
```

#### Edit the source string

_The user edits the source string_

We should make setting of a source to be secondary to the quote, and
prevent the user from writing one that's not a subset of its
characters.

Editing the source string will change the number of answers, or
as well as the valid characters for the grid. It will also
(potentially) invalidate any clues that currently exists.

1. Take a snapshot of all existing answers in the puzzle.
1. Recreate the answers with the new first letters
1. Go through each answer serially as they exists, and see if it can
   be used with the new initial letters and quote letters. Add back
   the ones that work.
1. Write the answers to the puzzle
1. Update the puzzle labels to not include clues
1. Write the puzzle to the undo stack.

That would make the callback look something like:

```C
static void
source_string_changed_cb (source_str)
{
  answer_list = snapshot_answers ();
  set_source_str (quote_str);
  fix_grid ();
  state = check_acrostic_state ();
  if (state == ERROR)
    {
       // We shouldn't let you set a source_str that's invalid
       g_assert_not_reached();
    }
  else if (state == PARTIAL)
    {
       for (guint i = 0; i < answer_list->len; i++)
         {
           set_answer (i, answer_list[i]);
         }
       fix_labels (NO_CLUES);
    }
  else // Well formed puzzle
    {
      fix_labels (NO_CLUES);
    }
  push_change ();
}
```

#### Edit an answer

This is a little simpler from the perspective of the puzzle, though
perhaps a more complex API. The one thing we do is update the
answer. One challenge is that we don't want to have the puzzle in an error state, which means the answer widget should only emit

```C
static void
source_answer_changed_cb (index, new_answer)
{
  set_answer (index, new_answer);
  state = check_acrostic_state ();

  if (state == ERROR)
    g_assert_not_reached();
  else if (state == PARTIAL)
    fix_labels (NO_CLUES);
  else // Well formed puzzle
    fix_labels (NO_CLUES);
  push_change ();
}
```


### Libipuz changes

We need to be able to represent the puzzle in libipuz when it's in a
partially-formed state. This is for two reasons. First, we need to be
able to push the puzzle to the undo stack when it's in this form. We
have to capture those changes when they occur. Second, the user may
want to save an acrostic while in the middle of creating it.

As a convention, I propose we don't update the labels in the editor to
include the clue numbers when we're in a partially formed state. That
will avoid the numbers bouncing around when editing, and give a visual
indication that it's not done. We may want to write them out when
saving.

To implement this we should:

* Change `set_answers()` to accept a partial list of answers, or
  potentially just one answer.
* Change `fix_labels()` to take an argument about whether it should
  map the clues, or just include a cell number.

:::{note}
It's possible to manually set the puzzle into an _error
state_ and save it through raw calls to the library, or through
manually editing the file. The editor shouldn't allow that, and
should fix up the puzzle as best as possible when loading from disk.
:::

### Post push

Once a new acrostic puzzle has been pushed, we need to prepare it for
updating widgets. One important thing to do is to recalculate the
Charset of characters in the puzzle, and the Charset of the current
set of answers. If they're identical, then the puzzle is well
formed. That will be useful to pass to the various `update()`
functions.

## Actions

- [ ] Create widgets for editing an acrostic grid.
    - [ ] Widget for `quote_str`
    - [ ] Widget for `source_str`
    - [ ] Widget for `answers`. The autofill fill feature will be
          embedded in the answer widget.
- [ ] Setup callbacks for widgets
- [ ] Post push propagation and validation
- [X] _(libipuz)_ Change `fix_labels()` to take an enum indicating how to take the
- [ ] _(libipuz)_ Add an `set_answer()` function equivalent for just one answer.
- [X] _(libipuz)_ Add `ipuz_charset_subset()`


## Other Thoughts

One other long-standing action is to make `IpuzAcrostic` inherit from
`IpuzGrid`. We don't really use any of the functions from
`IpuzCrossword` other than `fix_all()` and `fix_style)_`.

It's good to not refactor too much at once so we will do that as a
separate action. Bbut care should be taken in the implementation to
make sure that we don't make that task harder.
