Lines
85.08 %
Functions
84 %
Branches
53.17 %
/* ipuz-clue-sets.c
*
* Copyright 2023 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-config.h"
#include "ipuz-clue-sets.h"
#include <glib/gi18n-lib.h>
typedef struct _ClueSet
{
IPuzClueDirection direction;
IPuzClueDirection original_direction;
gchar *label;
GArray *clues;
} ClueSet;
struct _IPuzClueSets
grefcount ref_count;
guint custom_counter;
GArray *clue_sets;
};
G_DEFINE_BOXED_TYPE (IPuzClueSets, ipuz_clue_sets, ipuz_clue_sets_ref, ipuz_clue_sets_unref);
/* ClueSet functions */
/* This is called from GArray, which gives us the address of one of its
* elements to clear. Since we are storing (IPuzClue *) in each element,
* we need double indirection here.
static void
free_one_clue (IPuzClue **clue_ptr)
ipuz_clue_free (*clue_ptr);
*clue_ptr = NULL;
}
static ClueSet *
clue_set_new (IPuzClueDirection direction,
const gchar *label)
ClueSet *clue_set;
clue_set = g_new0 (ClueSet, 1);
clue_set->direction = direction;
clue_set->original_direction = direction;
clue_set->label = g_strdup (label);
clue_set->clues = g_array_new (FALSE, TRUE, sizeof (IPuzClue *));
g_array_set_clear_func (clue_set->clues, (GDestroyNotify) free_one_clue);
return clue_set;
clue_set_copy (ClueSet *src)
ClueSet *dest;
dest = g_new0 (ClueSet, 1);
dest->direction = src->direction;
dest->original_direction = src->original_direction;
dest->label = g_strdup (src->label);
dest->clues = g_array_new (FALSE, TRUE, sizeof (IPuzClue *));
g_array_set_clear_func (dest->clues, (GDestroyNotify) free_one_clue);
for (guint i = 0; i < src->clues->len; i++)
IPuzClue *clue = g_array_index (src->clues, IPuzClue *, i);
IPuzClue *new_clue = ipuz_clue_copy (clue);
g_array_append_val (dest->clues, new_clue);
return dest;
clue_set_free (ClueSet *clue_set)
g_assert (clue_set);
g_array_unref (clue_set->clues);
g_free (clue_set->label);
g_free (clue_set);
find_clue_set (IPuzClueSets *clue_sets,
IPuzClueDirection direction)
for (guint i = 0; i < clue_sets->clue_sets->len; i++)
clue_set = g_array_index (clue_sets->clue_sets, ClueSet *, i);
if (clue_set->direction == direction)
return NULL;
/* IPuzClueSets */
free_one_clue_set (ClueSet **clue_set)
clue_set_free (*clue_set);
IPuzClueSets *
ipuz_clue_sets_new (void)
IPuzClueSets *clue_sets;
clue_sets = g_new0 (IPuzClueSets, 1);
g_ref_count_init (&clue_sets->ref_count);
clue_sets->clue_sets = g_array_new (FALSE, TRUE, sizeof (ClueSet *));
g_array_set_clear_func (clue_sets->clue_sets, (GDestroyNotify) free_one_clue_set);
return clue_sets;
ipuz_clue_sets_ref (IPuzClueSets *clue_sets)
g_return_val_if_fail (clue_sets != NULL, NULL);
g_ref_count_inc (&clue_sets->ref_count);
void
ipuz_clue_sets_unref (IPuzClueSets *clue_sets)
if (clue_sets == NULL)
return;
if (!g_ref_count_dec (&clue_sets->ref_count))
g_clear_pointer (&clue_sets->clue_sets, g_array_unref);
g_free (clue_sets);
gboolean
ipuz_clue_sets_equal (IPuzClueSets *a,
IPuzClueSets *b)
if (a == NULL && b == NULL)
return TRUE;
if (a == NULL || b == NULL)
return FALSE;
if (a->clue_sets->len != b->clue_sets->len)
for (guint i = 0; i < a->clue_sets->len; i++)
ClueSet *clue_set_a, *clue_set_b;
clue_set_a = g_array_index (a->clue_sets, ClueSet *, i);
clue_set_b = g_array_index (a->clue_sets, ClueSet *, i);
if (clue_set_a->original_direction != clue_set_b->original_direction)
if (g_strcmp0 (clue_set_a->label, clue_set_b->label))
if (clue_set_a->clues)
if (clue_set_b->clues == NULL)
if (clue_set_a->clues->len != clue_set_b->clues->len)
for (guint i = 0; i < clue_set_a->clues->len; i++)
if (! ipuz_clue_equal (g_array_index (clue_set_a->clues, IPuzClue *, i),
g_array_index (clue_set_b->clues, IPuzClue *, i)))
else if (clue_set_b->clues)
ipuz_clue_sets_clone (IPuzClueSets *src,
IPuzClueSets *dest)
/* Clear dest->clue_sets, if necessary */
g_array_set_size (dest->clue_sets, 0);
g_array_set_size (dest->clue_sets, src->clue_sets->len);
dest->custom_counter = src->custom_counter;
for (guint i = 0; i < src->clue_sets->len; i++)
ClueSet *src_clue_set;
ClueSet *dest_clue_set;
src_clue_set = g_array_index (src->clue_sets, ClueSet *, i);
dest_clue_set = clue_set_copy (src_clue_set);
g_array_index (dest->clue_sets, ClueSet *, i) = dest_clue_set;
/* Returns TRUE if there are two clue sets with the same direction and
* label. That indicates an invalid puzzle. */
static gboolean
check_for_dupes (IPuzClueSets *clue_sets,
IPuzClueDirection direction,
const gchar *label,
gboolean *custom_needed)
g_assert (clue_sets);
g_assert (clue_sets->clue_sets);
ClueSet *clue_set = g_array_index (clue_sets->clue_sets, ClueSet *, i);
/* Have we ever had a clue of the same direction? */
if (clue_set->original_direction == direction)
/* Check to see if there's a duplicate through the same
* label */
if (! g_strcmp0 (clue_set->label, label))
*custom_needed = TRUE;
gint
clue_sets_sort_func (gconstpointer a,
gconstpointer b)
ClueSet *clue_set_a = *((ClueSet **) a);
ClueSet *clue_set_b = *((ClueSet **) b);
return (gint) clue_set_a->direction - (gint) clue_set_b->direction;
IPuzClueDirection
ipuz_clue_sets_add_set (IPuzClueSets *clue_sets,
gboolean custom_needed = FALSE;
g_return_val_if_fail (clue_sets != NULL, IPUZ_CLUE_DIRECTION_NONE);
if (check_for_dupes (clue_sets, direction, label, &custom_needed))
return IPUZ_CLUE_DIRECTION_NONE;
clue_set = clue_set_new (direction, label);
/* If there's already a clue_set with its direction set to
* direction, then we use the CUSTOM direction to indicate it's
* different
if (custom_needed)
clue_set->direction = IPUZ_CLUE_DIRECTION_CUSTOM + clue_sets->custom_counter++;
g_array_append_val (clue_sets->clue_sets, clue_set);
g_array_sort (clue_sets->clue_sets, clue_sets_sort_func);
return clue_set->direction;
ipuz_clue_sets_foreach (IPuzClueSets *clue_sets,
IPuzClueSetsForeachFunc func,
gpointer user_data)
g_return_if_fail (clue_sets != NULL);
g_return_if_fail (func != NULL);
(* func) (clue_sets, clue_set->direction, user_data);
guint
ipuz_clue_sets_get_n_clue_sets (IPuzClueSets *clue_sets)
g_return_val_if_fail (clue_sets != NULL, 0);
return clue_sets->clue_sets->len;
ipuz_clue_sets_get_direction (IPuzClueSets *clue_sets,
guint index)
g_return_val_if_fail (index < clue_sets->clue_sets->len, IPUZ_CLUE_DIRECTION_NONE);
clue_set = g_array_index (clue_sets->clue_sets, ClueSet *, index);
/* This will create a clue_set if it doesn't already exist */
ipuz_clue_sets_append_clue (IPuzClueSets *clue_sets,
IPuzClue *clue)
clue_set = find_clue_set (clue_sets, direction);
if (clue_set == NULL)
direction = ipuz_clue_sets_add_set (clue_sets, direction, NULL);
g_return_if_fail (clue_set != NULL);
g_array_append_val (clue_set->clues, clue);
ipuz_clue_sets_remove_clue (IPuzClueSets *clue_sets,
IPuzClue *clue,
gboolean remove_empty)
clues = ipuz_clue_sets_get_clues (clue_sets, direction);
g_return_if_fail (clues != NULL);
for (guint i = 0; i < clues->len; i++)
if (clue == g_array_index (clues, IPuzClue *, i))
g_array_remove_index (clues, i);
break;
/* remove the clue_set if it's empty */
if (remove_empty && clues->len == 0)
g_assert (clue_set->clues == clues);
g_array_remove_index (clue_sets->clue_sets, i);
const gchar *
ipuz_clue_sets_get_label (IPuzClueSets *clue_sets,
g_return_val_if_fail (clue_set != NULL, NULL);
if (clue_set->label)
return clue_set->label;
return ipuz_clue_direction_to_string (clue_set->original_direction);
GArray *
ipuz_clue_sets_get_clues (IPuzClueSets *clue_sets,
return clue_set->clues;
ipuz_clue_sets_get_original_direction (IPuzClueSets *clue_sets,
if (clue_set)
return clue_set->original_direction;
ipuz_clue_sets_unlink_direction (IPuzClueSets *clue_sets,