Lines
92.26 %
Functions
100 %
Branches
59.87 %
/* ipuz-board.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-board.h>
struct _IPuzBoard
{
GObject parent_instance;
GArray *cells;
guint rows;
guint columns;
};
static void ipuz_board_init (IPuzBoard *self);
static void ipuz_board_class_init (IPuzBoardClass *klass);
static void ipuz_board_finalize (GObject *object);
G_DEFINE_TYPE (IPuzBoard, ipuz_board, G_TYPE_OBJECT);
/*
* Class Methods
/* This is called from GArray, which gives us the address of one of its
* elements to clear. Since we are storing (GArray *) each element,
* we need double indirection here.
static void
cells_clear_func (GArray **cell_row)
g_array_free (*cell_row, TRUE);
*cell_row = NULL;
}
ipuz_board_init (IPuzBoard *self)
g_return_if_fail (self != NULL);
self->cells = g_array_new (FALSE, TRUE, sizeof (GArray*));
g_array_set_clear_func (self->cells, (GDestroyNotify) cells_clear_func);
self->rows = 0;
self->columns = 0;
ipuz_board_class_init (IPuzBoardClass *klass)
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->finalize = ipuz_board_finalize;
ipuz_board_finalize (GObject *object)
IPuzBoard *self;
g_return_if_fail (object != NULL);
self = IPUZ_BOARD (object);
g_array_free (self->cells, TRUE);
G_OBJECT_CLASS (ipuz_board_parent_class)->finalize (object);
* Public functions
IPuzBoard *
ipuz_board_new ()
return (IPuzBoard *) g_object_new (IPUZ_TYPE_BOARD, NULL);
void
ipuz_board_build_puzzle (IPuzBoard *board,
JsonBuilder *builder,
const char *block,
const char *empty)
guint r, c;
g_return_if_fail (IPUZ_IS_BOARD (board));
g_return_if_fail (JSON_IS_BUILDER (builder));
if (board->rows == 0 || board->columns == 0)
return;
json_builder_set_member_name (builder, "puzzle");
json_builder_begin_array (builder);
for (r = 0; r < board->rows; r++)
for (c = 0; c < board->columns; c++)
IPuzCellCoord coord = { .row = r, .column = c };
IPuzCell *cell = ipuz_board_get_cell (board, coord);
ipuz_cell_build (cell, builder, FALSE, block, empty);
json_builder_end_array (builder);
ipuz_board_build_solution (IPuzBoard *board,
const char *block)
json_builder_set_member_name (builder, "solution");
ipuz_cell_build (cell, builder, TRUE, block, NULL);
/**
* ipuz_board_equal:
* @a: First board to compare.
* @b: Second board to compare.
* Returns: whether the boards have the same dimensions and their
* respective cells are equal.
gboolean
ipuz_board_equal (IPuzBoard *a,
IPuzBoard *b)
g_assert (IPUZ_IS_BOARD (a));
g_assert (IPUZ_IS_BOARD (b));
if (a->rows != b->rows || a->columns != b->columns)
return FALSE;
for (r = 0; r < a->rows; r++)
for (c = 0; c < a->columns; c++)
IPuzCell *cell_a = ipuz_board_get_cell (a, coord);
IPuzCell *cell_b = ipuz_board_get_cell (b, coord);
if (!ipuz_cell_equal (cell_a, cell_b))
return TRUE;
ipuz_board_resize (IPuzBoard *board,
guint new_width,
guint new_height)
guint old_width, old_height;
g_return_if_fail (new_width > 0);
g_return_if_fail (new_height > 0);
/* Calculate the old dimensions from the existing array.
old_height = board->rows;
old_width = board->columns;
if (new_width == old_width && new_height == old_height)
/* If we are growing vertically, add more rows. Otherwise, if we're shrinking,
* we count on g_array_set_size to do the right thing for us. */
if (new_height > old_height)
for (guint i = 0; i < new_height - old_height; i++)
GArray *new_row;
new_row = g_array_new (FALSE, TRUE, sizeof (IPuzCell));
g_array_set_clear_func (new_row, (GDestroyNotify) ipuz_cell_clear);
g_array_append_val (board->cells, new_row);
else
g_array_set_size (board->cells, new_height);
/* Resize all the rows to be the right size. This will clear memory if rows
* shrink or fill it with empty cells if they grow.
for (guint i = 0; i < board->cells->len; i++)
GArray *row;
row = g_array_index (board->cells, GArray *, i);
g_array_set_size (row, new_width);
board->rows = new_height;
board->columns = new_width;
ipuz_board_parse_puzzle_row (GArray *row,
JsonArray *array,
const gchar *block,
const gchar *empty)
guint n_rows, array_len;
g_return_if_fail (row != NULL);
g_return_if_fail (array != NULL);
g_return_if_fail (block != NULL);
array_len = json_array_get_length (array);
n_rows = row->len;
for (guint i = 0; i < MIN (n_rows, array_len); i++)
JsonNode *node;
IPuzCell *cell;
node = json_array_get_element (array, i);
cell = & (g_array_index (row, IPuzCell, i));
ipuz_cell_parse_puzzle (cell, node, block, empty);
ipuz_board_parse_puzzle (IPuzBoard *board,
JsonNode *node,
JsonArray *array;
guint array_len;
g_return_if_fail (node != NULL);
g_return_if_fail (empty != NULL);
/* bail out on anything other than an JsonArray */
if (! JSON_NODE_HOLDS_ARRAY (node))
array = json_node_get_array (node);
for (guint i = 0; i < MIN (board->rows, array_len); i++)
JsonNode *row_node;
row_node = json_array_get_element (array, i);
if (JSON_NODE_HOLDS_ARRAY (row_node))
ipuz_board_parse_puzzle_row (g_array_index (board->cells, GArray *, i),
json_node_get_array (row_node),
block,
empty);
* We look for valid strings that aren't in the block element, and are in the
* charset if it exists.
ipuz_board_parse_solution_row (GArray *row,
guint columns,
const gchar *charset)
for (guint i = 0; i < MIN (columns, array_len); i++)
ipuz_cell_parse_solution (cell, node, block, charset);
ipuz_board_parse_solution (IPuzBoard *board,
ipuz_board_parse_solution_row (g_array_index (board->cells, GArray *, i),
board->columns,
charset);
* ipuz_board_get_cell:
* @board: An `IPuzBoard`
* @coord: Coordinates for the cell.
* Retrieves the cell at @coord. If the coordinates are
* outside the bounds of the board then it will return %NULL
* The coordinate system of the @board is similar to that of s spreadsheet. The
* origin is the upper left corner. Row's increase vertically downward, and
* columns increase horizontally.
* Returns: (nullable) (transfer none): The cell at @coord.
**/
IPuzCell *
ipuz_board_get_cell (IPuzBoard *board,
IPuzCellCoord coord)
GArray *row_array;
g_return_val_if_fail (IPUZ_IS_BOARD (board), NULL);
if (coord.row >= board->rows || coord.column >= board->columns)
return NULL;
row_array = g_array_index (board->cells, GArray *, coord.row);
g_assert (row_array);
return &(g_array_index (row_array, IPuzCell, coord.column));
* ipuz_board_get_width:
* @board: An `IpuzBoard`
* Returns the width of @board
* Returns: The width of @board
guint
ipuz_board_get_width (IPuzBoard *board)
g_return_val_if_fail (IPUZ_IS_BOARD (board), 0);
return board->columns;
* ipuz_board_get_height:
* Returns the height of @board
* Returns: The height of @board
ipuz_board_get_height (IPuzBoard *board)
return board->rows;
* @clue: The @clue to locate
* @coord: Location to write the coordinates of first cell of @clue, or $NULL
* Retrieves the first cell of @clue from @board. If coord is not %NULL, then
* it will be populated with the coordinates of the first cell.
* Returns: (nullable) (transfer none): The first cell of @clue, or %NULL
ipuz_board_get_cell_by_clue (IPuzBoard *board,
IPuzClue *clue,
IPuzCellCoord *coord)
const GArray *cells;
g_return_val_if_fail (clue != NULL, NULL);
cells = ipuz_clue_get_cells (clue);
g_assert (cells);
/* If the cells on clue have been populated, return the first one */
if (cells->len > 0)
IPuzCellCoord *cell_coord;
cell_coord = &g_array_index (cells, IPuzCellCoord, 0);
if (coord)
*coord = *cell_coord;
return ipuz_board_get_cell (board, *cell_coord);
/* Note: This can be called before clue->cells has been set.
* As a result, we walk the board looking for the first match */
IPuzCellCoord cell_coord = { .row = r, .column = c };
IPuzCell *cell = ipuz_board_get_cell (board, cell_coord);
gint cell_number, clue_number;
const gchar *cell_label, *clue_label;
/* Heuristic. If the labels are non-null and match, or they share a
* number that's non 0, then they match.
* FIXME: We should probably refactor clue / cell equality to a
* separate public function.
cell_number = ipuz_cell_get_number (cell);
clue_number = ipuz_clue_get_number (clue);
cell_label = ipuz_cell_get_label (cell);
clue_label = ipuz_clue_get_label (clue);
if ((cell_label && (g_strcmp0 (cell_label, clue_label) == 0))
|| ((cell_number > 0) && (cell_number == clue_number)))
coord->row = r;
coord->column = c;
return cell;