Lines
39.46 %
Functions
57.69 %
Branches
31.25 %
/* ipuz-guesses.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 "ipuz-guesses.h"
#include "ipuz-cell.h"
#include "ipuz-magic.h"
#include "libipuz.h"
struct _IPuzGuesses
{
grefcount ref_count;
GArray *cells;
guint rows;
guint columns;
gchar *puzzle_id;
};
typedef struct _IPuzGuessCell
IPuzCellCellType cell_type;
gchar *guess;
} IPuzGuessCell;
G_DEFINE_BOXED_TYPE (IPuzGuesses, ipuz_guesses, ipuz_guesses_ref, ipuz_guesses_unref)
static void
array_row_clear_func (GArray **cell_row)
g_array_free (*cell_row, TRUE);
*cell_row = NULL;
}
array_cell_clear_func (IPuzGuessCell *cell)
g_clear_pointer (&cell->guess, g_free);
static GArray *
new_row_array (void)
GArray *row_array;
row_array = g_array_new (FALSE, TRUE, sizeof (IPuzGuessCell));
g_array_set_clear_func (row_array, (GDestroyNotify) array_cell_clear_func);
return row_array;
static IPuzGuesses *
ipuz_guesses_new (void)
IPuzGuesses *guesses;
guesses = (IPuzGuesses *) g_new0 (IPuzGuesses, 1);
g_ref_count_init (&guesses->ref_count);
guesses->cells = g_array_new (FALSE, TRUE, sizeof (GArray*));
g_array_set_clear_func (guesses->cells, (GDestroyNotify) array_row_clear_func);
return guesses;
/**
* ipuz_guesses_new_from_board:
* @board: A IPuzBoard to use as the model
* @copy_guesses: Whether to prepopulate the guesses from @board
* Creates a new IPuzGuesses object modeled after @board. If
* @copy_guesses is %TRUE then any initial saved guesses are copied
* into the new `IPuzGuesses`
* Returns (transfer full): a `IPuzGuesses`
**/
IPuzGuesses *
ipuz_guesses_new_from_board (IPuzBoard *board,
gboolean copy_guesses)
guint row, column;
g_return_val_if_fail (IPUZ_IS_BOARD (board), NULL);
guesses = ipuz_guesses_new ();
guesses->rows = ipuz_board_get_height (board);
guesses->columns = ipuz_board_get_width (board);
for (row = 0; row < guesses->rows; row++)
GArray *new_row = new_row_array ();
g_array_set_size (new_row, guesses->columns);
g_array_append_val (guesses->cells, new_row);
for (column = 0; column < guesses->columns; column++)
IPuzCellCoord coord = { .row = row, .column = column };
IPuzGuessCell *cell = &(g_array_index (new_row, IPuzGuessCell, column));;
IPuzCell *board_cell = ipuz_board_get_cell (board, coord);
cell->cell_type = ipuz_cell_get_cell_type (board_cell);
if (board_cell->cell_type == IPUZ_CELL_NORMAL)
/* It's not clear which takes priority when there's saved
* guesses and initial val. Feels like a nonsensical corner
* case. */
if (board_cell->initial_val)
cell->cell_type = IPUZ_CELL_NULL;
if (copy_guesses && board_cell->saved_guess)
cell->guess = g_strdup (board_cell->saved_guess);
ipuz_guesses_parse_row (GArray *row,
JsonNode *node)
JsonArray *array;
guint len;
if (!JSON_NODE_HOLDS_ARRAY (node))
return;
array = json_node_get_array (node);
len = json_array_get_length (array);
g_array_set_size (row, len);
for (guint i = 0; i < len; i++)
JsonNode *element_node;
IPuzGuessCell *cell;
element_node = json_array_get_element (array, i);
cell = &(g_array_index (row, IPuzGuessCell, i));
if (JSON_NODE_HOLDS_NULL (element_node))
else if (JSON_NODE_HOLDS_VALUE (element_node))
const gchar *guess;
guess = json_node_get_string (element_node);
/* At some point, we may need to support BLOCK characters
* other than "#" */
if (g_strcmp0 (guess, _IPUZ_DEFAULT_BLOCK) == 0)
cell->cell_type = IPUZ_CELL_BLOCK;
else
cell->cell_type = IPUZ_CELL_NORMAL;
if (guess != NULL && strlen (guess) > 0)
cell->guess = g_strdup (guess);
ipuz_guesses_parse_saved (IPuzGuesses *guesses,
for (guint i = 0; i < json_array_get_length (array); i++)
GArray *new_row;
if (! JSON_NODE_HOLDS_ARRAY (node))
continue;
new_row = new_row_array ();
ipuz_guesses_parse_row (new_row, element_node);
if (new_row->len == 0)
g_array_unref (new_row);
guesses->columns = MAX (guesses->columns, new_row->len);
guesses->rows = guesses->cells->len;
ipuz_guesses_new_from_json (JsonNode *root,
GError **error)
IPuzGuesses *guesses = NULL;
JsonNode *node;
JsonObject *obj;
g_return_val_if_fail (root != NULL, NULL);
if (!JSON_NODE_HOLDS_OBJECT (root))
if (error)
*error = g_error_new (IPUZ_ERROR, IPUZ_ERROR_INVALID_FILE, "The first element isn't an object");
return NULL;
obj = json_node_get_object (root);
node = json_object_get_member (obj, "saved");
ipuz_guesses_parse_saved (guesses, node);
ipuz_guesses_new_from_file (const char *filename,
GError *tmp_error = NULL;
g_autoptr(JsonParser) parser = NULL;
g_return_val_if_fail (filename != NULL, NULL);
parser = json_parser_new ();
json_parser_load_from_file (parser, filename, &tmp_error);
if (tmp_error != NULL)
g_propagate_error (error, tmp_error);
return ipuz_guesses_new_from_json (json_parser_get_root (parser), error);
ipuz_guesses_new_from_stream (GInputStream *stream,
GCancellable *cancellable,
JsonParser *parser;
g_return_val_if_fail (stream != NULL, NULL);
json_parser_load_from_stream (parser, stream, cancellable, &tmp_error);
/* The variable naming in this function is confusing. We persist.. */
static JsonNode *
ipuz_guesses_to_json (IPuzGuesses *guesses)
JsonNode *root, *column_node;
JsonObject *root_obj;
JsonArray *column_node_array;
root = json_node_new (JSON_NODE_OBJECT);
root_obj = json_object_new();
json_node_take_object (root, root_obj);
if (guesses->puzzle_id)
json_object_set_string_member (root_obj, "puzzle-id", guesses->puzzle_id);
column_node = json_node_new (JSON_NODE_ARRAY);
column_node_array = json_array_new ();
json_node_take_array (column_node, column_node_array);
json_object_set_member (root_obj, "saved", column_node);
GArray *row_array = g_array_index (guesses->cells, GArray *, row);
JsonNode *row_node = json_node_new (JSON_NODE_ARRAY);
JsonArray *row_node_array = json_array_new ();
json_node_take_array (row_node, row_node_array);
json_array_add_element (column_node_array, row_node);
for (column = 0; column < row_array->len; column++)
IPuzGuessCell *cell = &(g_array_index (row_array, IPuzGuessCell, column));
switch (cell->cell_type)
case IPUZ_CELL_NULL:
json_array_add_null_element (row_node_array);
break;
case IPUZ_CELL_BLOCK:
json_array_add_string_element (row_node_array, _IPUZ_DEFAULT_BLOCK);
case IPUZ_CELL_NORMAL:
if (cell->guess == NULL)
json_array_add_string_element (row_node_array, "");
json_array_add_string_element (row_node_array, cell->guess);
return root;
gboolean
ipuz_guesses_save_to_file (IPuzGuesses *guesses,
const gchar *filename,
g_autoptr (JsonNode) root = NULL;
g_autoptr (JsonGenerator) generator = NULL;
g_return_val_if_fail (guesses != NULL, FALSE);
g_return_val_if_fail (filename != NULL, FALSE);
generator = json_generator_new ();
json_generator_set_pretty (generator, TRUE);
root = ipuz_guesses_to_json (guesses);
json_generator_set_root (generator, root);
if (! root)
return FALSE;
return json_generator_to_file (generator, filename, error);
ipuz_guesses_ref (IPuzGuesses *guesses)
g_return_val_if_fail (guesses != NULL, NULL);
g_ref_count_inc (&guesses->ref_count);
void
ipuz_guesses_unref (IPuzGuesses *guesses)
if (guesses == NULL)
if (!g_ref_count_dec (&guesses->ref_count))
/* Free data */
g_array_unref (guesses->cells);
g_free (guesses->puzzle_id);
g_free (guesses);
ipuz_guesses_copy (IPuzGuesses *src)
IPuzGuesses *dest;
if (src == NULL)
dest = ipuz_guesses_new ();
dest->rows = src->rows;
dest->columns = src->columns;
dest->puzzle_id = g_strdup (src->puzzle_id);
for (row = 0; row < src->rows; row++)
GArray *src_row = g_array_index (src->cells, GArray *, row);
GArray *dest_row = new_row_array ();
g_array_set_size (dest_row, src->columns);
g_array_append_val (dest->cells, dest_row);
for (column = 0; column < src->columns; column++)
IPuzGuessCell *src_cell = &(g_array_index (src_row, IPuzGuessCell, column));
IPuzGuessCell *dest_cell = &(g_array_index (dest_row, IPuzGuessCell, column));
dest_cell->cell_type = src_cell->cell_type;
if (src_cell->cell_type == IPUZ_CELL_NORMAL)
dest_cell->guess = g_strdup (src_cell->guess);
return dest;
ipuz_guesses_equal (IPuzGuesses *a,
IPuzGuesses *b)
if (a == NULL && b == NULL)
return TRUE;
if (a == NULL || b == NULL)
if (a->rows != b->rows || a->columns != b->columns)
for (row = 0; row < a->rows; row++)
GArray *row_a, *row_b;
row_a = g_array_index (a->cells, GArray *, row);
row_b = g_array_index (b->cells, GArray *, row);
for (column = 0; column < a->columns; column++)
IPuzGuessCell cell_a, cell_b;
cell_a = g_array_index (row_a, IPuzGuessCell, column);
cell_b = g_array_index (row_b, IPuzGuessCell, column);
if (cell_a.cell_type != cell_b.cell_type)
if (cell_a.cell_type == IPUZ_CELL_NORMAL &&
g_strcmp0 (cell_a.guess, cell_b.guess) != 0)
guint
ipuz_guesses_get_width (IPuzGuesses *guesses)
g_return_val_if_fail (guesses != NULL, 0);
return guesses->columns;
ipuz_guesses_get_height (IPuzGuesses *guesses)
return guesses->rows;
const gchar *
ipuz_guesses_get_guess (IPuzGuesses *guesses,
IPuzCellCoord coord)
if (coord.row >= guesses->rows || coord.column >= guesses->columns)
row_array = g_array_index (guesses->cells, GArray *, coord.row);
g_assert (row_array);
cell = &(g_array_index (row_array, IPuzGuessCell, coord.column));
return cell->guess;
ipuz_guesses_set_guess (IPuzGuesses *guesses,
IPuzCellCoord coord,
const gchar *guess)
g_return_if_fail (guesses != NULL);
g_return_if_fail (cell->cell_type == IPUZ_CELL_NORMAL);
/* FIXME(cleanup): We should wrie an internal get_cell function to
* cleanup these getters.
IPuzCellCellType
ipuz_guesses_get_cell_type (IPuzGuesses *guesses,
g_return_val_if_fail (guesses != NULL, IPUZ_CELL_NORMAL);
return IPUZ_CELL_NORMAL;
return cell->cell_type;
/* Returns the percentage filled out. Not the percentage correct
gfloat
ipuz_guesses_get_percent (IPuzGuesses *guesses)
gint guessed = 0;
gint total = 0;
g_return_val_if_fail (guesses != NULL, 0.0);
if (cell->cell_type == IPUZ_CELL_NORMAL)
total ++;
if (cell->guess != NULL)
guessed++;
if (total == 0)
return 0.0;
return ((gfloat) guessed) / total;
* ipuz_guesses_get_checksum:
* @guesses: An `IPuzGuess`
* @salt: used to seed the checksum, or NULL
* Returns a SHA1 HASH representing the current state of the
* puzzle. Used to
* Returns (transfer full): a newly allocated checksum of the solution
gchar *
ipuz_guesses_get_checksum (IPuzGuesses *guesses,
const gchar *salt)
g_autoptr (GString) str = NULL;
str = g_string_new (NULL);
g_string_append (str, cell->guess);
g_string_append (str, _IPUZ_DEFAULT_EMPTY);
if (salt)
g_string_append (str, salt);
return g_compute_checksum_for_string (G_CHECKSUM_SHA1, str->str, str->len);
ipuz_guesses_print (IPuzGuesses *guesses)
for (guint i = 0; i < guesses->columns + 1; i++)
g_print ("██");
g_print ("\n");
for (row = 0; row < guesses->rows; row ++)
g_print ("█");
for (column = 0; column < guesses->columns; column ++)
switch (ipuz_guesses_get_cell_type (guesses, coord))
g_print ("▓▓");
g_print ("▞▚");
g_print (" ");
g_print ("█\n█");
const gchar *guess = NULL;
guess = ipuz_guesses_get_guess (guesses, coord);
g_print ("▚▞");
if (guess)
g_print (" %s", guess);
g_print ("█\n");
for (column = 0; column < guesses->columns + 1; column++)
g_print ("\n\n");