Lines
75.2 %
Functions
70 %
Branches
57.27 %
/* ipuz-cell.c
*
* Copyright 2022 Jonathan Blandford <jrb@gnome.org>
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* SPDX-License-Identifier: (LGPL-2.1-or-later OR MIT)
*/
#include <libipuz/ipuz-cell.h>
IPuzCell *
ipuz_cell_new (void)
{
IPuzCell *cell = g_new0 (IPuzCell, 1);
cell->cell_type = IPUZ_CELL_NORMAL;
return cell;
}
gboolean
ipuz_cell_equal (IPuzCell *a,
IPuzCell *b)
g_assert (a != NULL && b != NULL);
return (a->cell_type == b->cell_type
&& a->number == b->number
&& g_strcmp0 (a->label, b->label) == 0
&& g_strcmp0 (a->solution, b->solution) == 0
&& g_strcmp0 (a->saved_guess, b->saved_guess) == 0
&& g_strcmp0 (a->initial_val, b->initial_val) == 0
&& g_strcmp0 (a->style_name, b->style_name) == 0);
ipuz_cell_copy (const IPuzCell *cell)
IPuzCell *new_cell;
g_return_val_if_fail (cell != NULL, NULL);
new_cell = ipuz_cell_new ();
new_cell->cell_type = cell->cell_type;
new_cell->number = cell->number;
new_cell->label = g_strdup (cell->label);
new_cell->solution = g_strdup (cell->solution);
new_cell->saved_guess = g_strdup (cell->saved_guess);
new_cell->initial_val = g_strdup (cell->initial_val);
new_cell->style_name = g_strdup (cell->style_name);
if (cell->style)
new_cell->style = ipuz_style_ref (cell->style);
return new_cell;
void
ipuz_cell_free (IPuzCell *cell)
g_return_if_fail (cell != NULL);
g_free (cell->label);
g_free (cell->solution);
g_free (cell->saved_guess);
g_free (cell->initial_val);
g_free (cell->style_name);
g_clear_pointer (&cell->style, ipuz_style_unref);
g_clear_pointer (&cell->clues, g_array_unref);
g_free (cell);
ipuz_cell_clear (IPuzCell *cell)
/* Force clear this */
memset (cell, 0, sizeof (IPuzCell));
ipuz_cell_build (IPuzCell *cell,
JsonBuilder *builder,
gboolean solution,
const char *block,
const char *empty)
if (IPUZ_CELL_IS_NULL (cell))
json_builder_add_null_value (builder);
return;
/* We're printing out the "solution" block */
if (solution)
if (cell->solution != NULL)
json_builder_add_string_value (builder, cell->solution);
else
/* Short-circuit printing out the cell as an object */
if (cell->style == NULL &&
cell->initial_val == NULL)
if (IPUZ_CELL_IS_BLOCK (cell))
json_builder_add_string_value (builder, block);
else if (cell->label)
json_builder_add_string_value (builder, cell->label);
json_builder_add_int_value (builder, cell->number);
else /* Print a more complex cell */
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "cell");
json_builder_set_member_name (builder, "style");
/* If it's a named style, we put a string with the name to
* refer to the global styles. Otherwise, we do a full copy
* of the style. */
if (cell->style_name)
json_builder_add_string_value (builder, cell->style_name);
ipuz_style_build (cell->style, builder);
if (cell->initial_val)
json_builder_set_member_name (builder, "value");
json_builder_add_string_value (builder, cell->initial_val);
json_builder_end_object (builder);
IPuzCellCellType
ipuz_cell_get_cell_type (IPuzCell *cell)
g_return_val_if_fail (cell != NULL, IPUZ_CELL_NORMAL);
return cell->cell_type;
/**
* ipuz_cell_set_cell_type:
* @cell: An `IPuzCell`
* @cell_type: The new cell_type to set
* This will set the cell type to @cell_type. Note this just sets the
* cell_type on a singular cell. If you want to honor the symmetry of
* a crossword, you should call ipuz_crossword_set_cell_type()
* instead.
**/
ipuz_cell_set_cell_type (IPuzCell *cell,
IPuzCellCellType cell_type)
if (cell->cell_type == cell_type)
cell->cell_type = cell_type;
if (cell->cell_type != IPUZ_CELL_NORMAL)
cell->number = 0;
g_clear_pointer (&cell->label, g_free);
g_clear_pointer (&cell->solution, g_free);
g_clear_pointer (&cell->saved_guess, g_free);
g_clear_pointer (&cell->initial_val, g_free);
if (cell->cell_type == IPUZ_CELL_NULL)
g_clear_pointer (&cell->style_name, g_free);
gint
ipuz_cell_get_number (IPuzCell *cell)
g_return_val_if_fail (cell != NULL, 0);
return cell->number;
ipuz_cell_set_number (IPuzCell *cell,
gint number)
cell->number = number;
const gchar *
ipuz_cell_get_label (IPuzCell *cell)
return cell->label;
ipuz_cell_set_label (IPuzCell *cell,
const gchar *label)
cell->label = g_strdup (label);
ipuz_cell_get_solution (IPuzCell *cell)
return cell->solution;
ipuz_cell_set_solution (IPuzCell *cell,
const gchar *solution)
cell->solution = g_strdup (solution);
ipuz_cell_get_saved_guess (IPuzCell *cell)
return cell->saved_guess;
* ipuz_cell_set_saved_guess:
* @cell: an @IPuzCell
* @saved_guess: a guess
* Sets a user guess for a cell for @cell. This should almost never be
* called, as it is use to preset the solution if you save a
* Crossword. You should use IPuzGuesses to capture the transient
* state of a puzzle.
ipuz_cell_set_saved_guess (IPuzCell *cell,
const gchar *saved_guess)
cell->saved_guess = g_strdup (saved_guess);
ipuz_cell_get_initial_val (IPuzCell *cell)
return cell->initial_val;
ipuz_cell_set_initial_val (IPuzCell *cell,
const gchar *initial_val)
cell->initial_val = g_strdup (initial_val);
IPuzStyle *
ipuz_cell_get_style (IPuzCell *cell)
return cell->style;
* ipuz_cell_set_style:
* @cell: An @IPuzCell
* @style: An @IPuzStyle
* @style_name: The name of the style, or NULL
* Sets the style for a given cell.
* Note: @style_name is used for a named style within the puzzle. No
* checking is done on style_name: It's up to the caller to check that
* the name exists within the puzzle.
ipuz_cell_set_style (IPuzCell *cell,
IPuzStyle *style,
const gchar *style_name)
gchar *new_style_name = NULL;
if (style)
ipuz_style_ref (style);
new_style_name = g_strdup (style_name);
cell->style = style;
cell->style_name = new_style_name;
/* Internal function. Do not call */
/* FIXME: Make private */
ipuz_cell_set_clue (IPuzCell *cell,
IPuzClue *clue)
g_return_if_fail (clue != NULL);
if (cell->clues == NULL)
cell->clues = g_array_new (FALSE, TRUE, sizeof (IPuzClue *));
for (guint i = 0; i < cell->clues->len; i++)
IPuzClue *old_clue = g_array_index (cell->clues, IPuzClue *, i);
if (old_clue->direction == clue->direction)
g_array_remove_index_fast (cell->clues, i);
break;
g_array_append_val (cell->clues, clue);
ipuz_cell_clear_clues (IPuzCell *cell)
if (cell->clues)
g_array_set_size (cell->clues, 0);
ipuz_cell_clear_clue_direction (IPuzCell *cell,
IPuzClueDirection direction)
IPuzClue *clue = g_array_index (cell->clues, IPuzClue *, i);
g_assert (clue != NULL);
if (clue->direction == direction)
const IPuzClue *
ipuz_cell_get_clue (IPuzCell *cell,
return clue;
return NULL;
/* Basically, every node type is a valid puzzle cell, but not every value is
* valid.
* Some notes:
* Null -> NULL cell type
* Number > 0 -> number
* Number == 0 and empty = "0" -> empty
* string == empty -> empty
* string == block -> block
* string -> label, otherwise
* Object -> "it's complicated"
static void
ipuz_cell_parse_puzzle_value (IPuzCell *cell,
JsonNode *node,
const gchar *block,
const gchar *empty)
GType value_type = json_node_get_value_type (node);
if (value_type == G_TYPE_INT64)
int number = json_node_get_int (node);
ipuz_cell_set_cell_type (cell, IPUZ_CELL_NORMAL);
if ((number == 0) && (g_strcmp0 (empty, "0") == 0))
ipuz_cell_set_number (cell, number);
else if (value_type == G_TYPE_STRING)
const gchar *str = json_node_get_string (node);
if (g_strcmp0 (str, empty) == 0)
else if (g_strcmp0 (str, block) == 0)
ipuz_cell_set_cell_type (cell, IPUZ_CELL_BLOCK);
ipuz_cell_set_label (cell, str);
/* Not sure what to do with booleans, floats, etc */
ipuz_cell_parse_puzzle (IPuzCell *cell,
JsonNodeType node_type;
g_return_if_fail (node != NULL);
node_type = json_node_get_node_type (node);
if (node_type == JSON_NODE_NULL)
ipuz_cell_set_cell_type (cell, IPUZ_CELL_NULL);
else if (node_type == JSON_NODE_VALUE)
ipuz_cell_parse_puzzle_value (cell, node, block, empty);
else if (node_type == JSON_NODE_OBJECT)
JsonObject *obj;
JsonNode *element;
obj = json_node_get_object (node);
element = json_object_get_member (obj, "cell");
if (element)
ipuz_cell_parse_puzzle_value (cell, element, block, empty);
element = json_object_get_member (obj, "style");
if (JSON_NODE_HOLDS_VALUE (element))
cell->style_name = g_strdup (json_node_get_string (element));
else if (JSON_NODE_HOLDS_OBJECT (element))
cell->style = ipuz_style_new_from_json (element);
element = json_object_get_member (obj, "value");
const char *initial_value = json_node_get_string (element);
ipuz_cell_set_initial_val (cell, initial_value);
/* We don't do anything with arrays. */
/* Validate the solution is in the charset, if it exists. Note, the
* solution could be a rebus in which case we need to go through all
* the characters in solution. Also, the charset can be null, in which
* case we allow all solutions to be valid
static gboolean
check_solution_in_charset (const gchar *solution,
const gchar *charset)
const gchar *ptr = solution;
/* Fail open */
if (solution == NULL || charset == NULL)
return TRUE;
while (*ptr)
if (g_utf8_strchr (solution, -1, g_utf8_get_char (ptr)) == NULL)
return FALSE;
ptr = g_utf8_next_char (ptr);
/*
* Ignores all BLOCK / NULL characters. The shape of the board is set
* by the "puzzle" element.
ipuz_cell_parse_solution (IPuzCell *cell,
const gchar *solution = NULL;
solution = json_node_get_string (node);
if (! check_solution_in_charset (solution, charset))
if (g_strcmp0 (solution, block) == 0)
ipuz_cell_set_solution (cell, solution);
g_autoptr(JsonReader) reader = json_reader_new (node);
if (json_reader_read_member (reader, "value"))
solution = json_reader_get_string_value (reader);
if (charset != NULL && strstr (charset, solution) == NULL)
json_reader_end_member (reader);
/* Some puzzles in the wild have a "cell" attribute that links it back
* to the cell. I don't think we need to parse this though, and its
* not in the spec.
/* Arrays aren't valid solution values AFAICT */
G_DEFINE_BOXED_TYPE (IPuzCell, ipuz_cell, ipuz_cell_copy, ipuz_cell_free);