Lines
61.08 %
Functions
58.14 %
Branches
38.95 %
/* ipuz-clue.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/libipuz.h>
#include "ipuz-misc.h"
IPuzClueId *
ipuz_clue_id_copy (const IPuzClueId *clue_id)
{
IPuzClueId *new_clue_id;
if (clue_id == NULL)
return NULL;
new_clue_id = g_new0 (IPuzClueId, 1);
*new_clue_id = *clue_id;
return new_clue_id;
}
void
ipuz_clue_id_free (IPuzClueId *clue_id)
g_free (clue_id);
gboolean
ipuz_clue_id_equal (const IPuzClueId *a,
const IPuzClueId *b)
g_return_val_if_fail (a != NULL, FALSE);
g_return_val_if_fail (b != NULL, FALSE);
return (a->direction == b->direction
&& a->index == b->index);
IPuzClue *
ipuz_clue_new ()
IPuzClue *clue;
clue = g_new0 (IPuzClue, 1);
clue->number = -1;
clue->cells = g_array_new (FALSE, TRUE, sizeof (IPuzCellCoord));
return clue;
ipuz_clue_copy (const IPuzClue *clue)
IPuzClue *new_clue;
g_return_val_if_fail (clue != NULL, NULL);
new_clue = ipuz_clue_new ();
new_clue->number = clue->number;
new_clue->label = g_strdup (clue->label);
new_clue->clue_text = g_strdup (clue->clue_text);
new_clue->enumeration = clue->enumeration;
new_clue->direction = clue->direction;
g_clear_pointer (&new_clue->cells, g_array_unref);
new_clue->cells = g_array_copy (clue->cells);
new_clue->cells_set = clue->cells_set;
return new_clue;
ipuz_clue_free (IPuzClue *clue)
if (clue == NULL)
return;
g_free (clue->clue_text);
g_free (clue->label);
ipuz_enumeration_unref (clue->enumeration);
g_array_free (clue->cells, TRUE);
g_free (clue);
ipuz_clue_equal (const IPuzClue *clue1,
const IPuzClue *clue2)
if (clue1 == NULL && clue2 == NULL)
return TRUE;
if (clue1 == NULL || clue2 == NULL)
return FALSE;
if (!((clue1->number == clue2->number) &&
(clue1->direction == clue2->direction) &&
(g_strcmp0 (clue1->label, clue2->label) == 0) &&
(g_strcmp0 (clue1->clue_text, clue2->clue_text) == 0)))
if (clue1->cells->len != clue2->cells->len)
if (clue1->cells_set != clue2->cells_set)
return (memcmp (clue1->cells->data, clue2->cells->data, clue1->cells->len * sizeof (IPuzCellCoord)) == 0);
ipuz_clue_build_simple (IPuzClue *clue,
JsonBuilder *builder)
g_return_if_fail (clue != NULL);
json_builder_begin_array (builder);
if (clue->number >=0)
json_builder_add_int_value (builder, clue->number);
json_builder_add_string_value (builder, clue->clue_text);
json_builder_end_array (builder);
ipuz_clue_build_full (IPuzClue *clue,
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "number");
if (clue->label)
json_builder_set_member_name (builder, "label");
json_builder_add_string_value (builder, clue->label);
if (clue->clue_text)
json_builder_set_member_name (builder, "clue");
if (clue->enumeration)
json_builder_set_member_name (builder, "enumeration");
g_autofree gchar *src = ipuz_enumeration_get_src (clue->enumeration);
json_builder_add_string_value (builder, src);
if (clue->location_set)
json_builder_set_member_name (builder, "location");
json_builder_add_int_value (builder, clue->location.column);
json_builder_add_int_value (builder, clue->location.row);
if (clue->cells)
guint i;
json_builder_set_member_name (builder, "cells");
for (i = 0; i < clue->cells->len; i++)
IPuzCellCoord *clue_coord;
clue_coord = &(g_array_index (clue->cells, IPuzCellCoord, i));
json_builder_add_int_value (builder, clue_coord->column);
json_builder_add_int_value (builder, clue_coord->row);
json_builder_end_object (builder);
ipuz_clue_build (IPuzClue *clue,
if (clue->cells_set || clue->label || clue->enumeration)
ipuz_clue_build_full (clue, builder);
else
ipuz_clue_build_simple (clue, builder);
/* Convenience function */
IPuzClueDirection
ipuz_clue_direction_switch (IPuzClueDirection direction)
if (direction == IPUZ_CLUE_DIRECTION_ACROSS)
return IPUZ_CLUE_DIRECTION_DOWN;
if (direction == IPUZ_CLUE_DIRECTION_DOWN)
return IPUZ_CLUE_DIRECTION_ACROSS;
if (direction == IPUZ_CLUE_DIRECTION_DIAGONAL)
return IPUZ_CLUE_DIRECTION_DIAGONAL_UP_LEFT;
if (direction == IPUZ_CLUE_DIRECTION_DIAGONAL_UP_LEFT)
return IPUZ_CLUE_DIRECTION_DIAGONAL;
if (direction == IPUZ_CLUE_DIRECTION_DIAGONAL_UP)
return IPUZ_CLUE_DIRECTION_DIAGONAL_DOWN_LEFT;
if (direction == IPUZ_CLUE_DIRECTION_DIAGONAL_DOWN_LEFT)
return IPUZ_CLUE_DIRECTION_DIAGONAL_UP;
return direction;
gint
ipuz_clue_get_number (IPuzClue *clue)
g_return_val_if_fail (clue != NULL, -1);
return clue->number;
ipuz_clue_set_number (IPuzClue *clue,
gint number)
clue->number = number;
if (clue->number > 0)
g_clear_pointer (&clue->label, g_free);
const gchar *
ipuz_clue_get_label (IPuzClue *clue)
return clue->label;
ipuz_clue_set_label (IPuzClue *clue,
const gchar *label)
clue->label = g_strdup (label);
ipuz_clue_get_clue_text (IPuzClue *clue)
return clue->clue_text;
ipuz_clue_set_clue_text (IPuzClue *clue,
const gchar *clue_text)
clue->clue_text = g_strdup (clue_text);
IPuzEnumeration *
ipuz_clue_get_enumeration (IPuzClue *clue)
return ipuz_enumeration_ref (clue->enumeration);
ipuz_clue_set_enumeration (IPuzClue *clue,
IPuzEnumeration *enumeration)
if (enumeration)
ipuz_enumeration_ref (enumeration);
clue->enumeration = enumeration;
ipuz_clue_get_direction (IPuzClue *clue)
g_return_val_if_fail (clue != NULL, 0);
return clue->direction;
ipuz_clue_set_direction (IPuzClue *clue,
IPuzClueDirection direction)
clue->direction = direction;
IPuzCellCoord
ipuz_clue_get_location(IPuzClue *clue,
gboolean *location_set)
IPuzCellCoord coord = {
.row = 0,
.column = 0,
};
g_return_val_if_fail (clue != NULL, coord);
if (location_set)
*location_set = clue->location_set;
return clue->location;
ipuz_clue_set_location (IPuzClue *clue,
IPuzCellCoord location)
/* FIXME(arrowword): We need to recalculate the cells now */
clue->location = location;
clue->location_set = TRUE;
const GArray *
ipuz_clue_get_cells (IPuzClue *clue)
return clue->cells;
ipuz_clue_append_cell (IPuzClue *clue,
IPuzCellCoord coord)
g_array_append_val (clue->cells, coord);
ipuz_clue_get_first_cell (IPuzClue *clue,
IPuzCellCoord *coord)
g_return_if_fail (coord != NULL);
g_return_if_fail (clue->cells->len != 0);
clue_coord = &(g_array_index (clue->cells, IPuzCellCoord, 0));
coord->row = clue_coord->row;
coord->column = clue_coord->column;
ipuz_clue_get_last_cell (IPuzClue *clue,
clue_coord = &(g_array_index (clue->cells, IPuzCellCoord, clue->cells->len - 1));
ipuz_clue_contains_cell (IPuzClue *clue,
/* FIXME: We should cache this per-cell in the grid.
* */
g_return_val_if_fail (clue != NULL, FALSE);
for (guint i = 0; i < clue->cells->len; i++)
IPuzCellCoord * clue_coord = &(g_array_index (clue->cells, IPuzCellCoord, i));
if (clue_coord->row == coord.row && clue_coord->column == coord.column)
ipuz_clue_ensure_enumeration (IPuzClue *clue)
if (clue->enumeration == NULL)
g_autofree gchar *src = g_strdup_printf ("%u", clue->cells->len);
clue->enumeration = ipuz_enumeration_new (src, IPUZ_VERBOSITY_STANDARD);
/*
* We support the following formats:
* [ number, clue_text, (optional) {"cells":[[]]} ]
* [ "label", clue_text, (optional) {"cells":[[]]} ]
* {"number": "clue": "label": (optional) "enumeration": (optional) "cells":[[]]}
ipuz_clue_parse_cell (JsonNode *node,
gboolean *valid)
JsonArray *coord_array;
if (valid) *valid = FALSE;
if (! JSON_NODE_HOLDS_ARRAY (node))
return coord;
coord_array = json_node_get_array (node);
if (json_array_get_length (coord_array) < 2)
/* WARNING:
* It seems that puzzles store their "cells" coords as [x, y], and we do
* everything as [row, column] which is inverted from this. This will
* Fix it, but should probably be clarified in the spec.
coord.row = json_array_get_int_element (coord_array, 1);
coord.column = json_array_get_int_element (coord_array, 0);
if (valid) *valid = TRUE;
parse_cell_foreach (JsonArray *array,
guint index,
JsonNode *element_node,
IPuzClue *clue)
IPuzCellCoord coord;
gboolean valid;
coord = ipuz_clue_parse_cell (element_node, &valid);
if (valid)
static void
ipuz_clue_parse_cells (IPuzClue *clue,
JsonNode *node)
if (!JSON_NODE_HOLDS_ARRAY (node))
json_array_foreach_element (json_node_get_array (node),
(JsonArrayForeach) parse_cell_foreach,
clue);
clue->cells_set = TRUE;
ipuz_clue_new_from_json (JsonNode *node)
g_return_val_if_fail (node != NULL, NULL);
clue = ipuz_clue_new ();
if (JSON_NODE_HOLDS_VALUE (node))
clue->clue_text = json_node_dup_string (node);
else if (JSON_NODE_HOLDS_ARRAY (node))
JsonArray *array = json_node_get_array (node);
JsonNode *element;
element = json_array_get_element (array, 0);
if (element != NULL && JSON_NODE_HOLDS_VALUE (element))
GValue value = G_VALUE_INIT;
json_node_get_value (element, &value);
if (G_VALUE_HOLDS_STRING (&value))
clue->label = g_value_dup_string (&value);
else if (G_VALUE_HOLDS_INT (&value) || G_VALUE_HOLDS_INT64 (&value))
clue->number = json_node_get_int (element);
g_value_unset (&value);
element = json_array_get_element (array, 1);
clue->clue_text = ipuz_html_to_markup (json_node_get_string (element));
else if (JSON_NODE_HOLDS_OBJECT (node))
JsonObject *object;
object = json_node_get_object (node);
if (json_object_has_member (object, "number"))
clue->number = json_object_get_int_member (object, "number");
if (json_object_has_member (object, "clue"))
clue->clue_text = ipuz_html_to_markup (json_object_get_string_member (object, "clue"));
if (json_object_has_member (object, "label"))
clue->label = g_strdup (json_object_get_string_member (object, "label"));
if (json_object_has_member (object, "enumeration"))
const gchar *src = json_object_get_string_member (object, "enumeration");
if (json_object_has_member (object, "location"))
JsonNode *location = json_object_get_member (object, "location");
if (location && JSON_NODE_HOLDS_ARRAY (location))
clue->location = ipuz_clue_parse_cell (location, &clue->location_set);
if (json_object_has_member (object, "cells"))
JsonNode *cell_value = json_object_get_member (object, "cells");
if (cell_value && JSON_NODE_HOLDS_ARRAY (cell_value))
ipuz_clue_parse_cells (clue, cell_value);
IPuzCellCoord *
ipuz_cell_coord_copy (const IPuzCellCoord *coord)
IPuzCellCoord *copy = g_new (IPuzCellCoord, 1);
*copy = *coord;
return copy;
ipuz_cell_coord_equal (const IPuzCellCoord *a,
const IPuzCellCoord *b)
return (a->row == b->row && a->column == b->column);
* Helper functions
ipuz_clue_direction_to_string (IPuzClueDirection direction)
switch (direction)
case IPUZ_CLUE_DIRECTION_NONE:
return "None";
case IPUZ_CLUE_DIRECTION_ACROSS:
return "Across";
case IPUZ_CLUE_DIRECTION_DOWN:
return "Down";
case IPUZ_CLUE_DIRECTION_DIAGONAL:
return "Diagonal";
case IPUZ_CLUE_DIRECTION_DIAGONAL_UP:
return "Diagonal Up";
case IPUZ_CLUE_DIRECTION_DIAGONAL_DOWN_LEFT:
return "Diagonal Down Left";
case IPUZ_CLUE_DIRECTION_DIAGONAL_UP_LEFT:
return "Diagonal Up Left";
case IPUZ_CLUE_DIRECTION_ZONES:
return "Zones";
case IPUZ_CLUE_DIRECTION_CLUES:
return "Clues";
case IPUZ_CLUE_DIRECTION_HIDDEN:
return "Hidden";
default:
g_assert_not_reached ();
ipuz_clue_direction_from_string (const gchar *str)
g_return_val_if_fail (str != NULL, IPUZ_CLUE_DIRECTION_NONE);
if (g_ascii_strcasecmp (str, "none") == 0)
return IPUZ_CLUE_DIRECTION_NONE;
else if (g_ascii_strcasecmp (str, "across") == 0)
else if (g_ascii_strcasecmp (str, "down") == 0)
else if (g_ascii_strcasecmp (str, "diagonal") == 0)
else if (g_ascii_strcasecmp (str, "diagonal up") == 0)
else if (g_ascii_strcasecmp (str, "diagonal down left") == 0)
else if (g_ascii_strcasecmp (str, "diagonal up left") == 0)
else if (g_ascii_strcasecmp (str, "zones") == 0)
return IPUZ_CLUE_DIRECTION_ZONES;
else if (g_ascii_strcasecmp (str, "clues") == 0)
return IPUZ_CLUE_DIRECTION_CLUES;
else if (g_ascii_strcasecmp (str, "hidden") == 0)
return IPUZ_CLUE_DIRECTION_HIDDEN;
G_DEFINE_BOXED_TYPE (IPuzClueId, ipuz_clue_id, ipuz_clue_id_copy, ipuz_clue_id_free)
G_DEFINE_BOXED_TYPE (IPuzCellCoord, ipuz_cell_coord, ipuz_cell_coord_copy, g_free);
G_DEFINE_BOXED_TYPE (IPuzClue, ipuz_clue, ipuz_clue_copy, ipuz_clue_free);