Lines
0 %
Functions
12.5 %
Branches
/* ipuz-arrowword.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-config.h"
#include <libipuz/ipuz-arrowword.h>
typedef struct _BlockInfo
{
IPuzCellCoord location;
IPuzClueId top_clue_id;
IPuzClueId bottom_clue_id;
IPuzArrowwordArrow top_arrow;
IPuzArrowwordArrow bottom_arrow;
} BlockInfo;
typedef struct _IPuzArrowwordPrivate
GArray *blocks;
} IPuzArrowwordPrivate;
static void ipuz_arrowword_init (IPuzArrowword *self);
static void ipuz_arrowword_class_init (IPuzArrowwordClass *klass);
static void ipuz_arrowword_fixup (IPuzPuzzle *puzzle);
static void ipuz_arrowword_clone (IPuzPuzzle *src,
IPuzPuzzle *dest);
static gboolean ipuz_arrowword_equal (IPuzPuzzle *puzzle_a,
IPuzPuzzle *puzzle_b);
static BlockInfo *arrowword_find_block_info (IPuzArrowword *self,
IPuzCellCoord coord);
G_DEFINE_TYPE_WITH_CODE (IPuzArrowword, ipuz_arrowword, IPUZ_TYPE_CROSSWORD,
G_ADD_PRIVATE (IPuzArrowword));
static void
ipuz_arrowword_init (IPuzArrowword *self)
IPuzArrowwordPrivate *priv;
priv = ipuz_arrowword_get_instance_private (self);
priv->blocks = g_array_new (FALSE, TRUE, sizeof (BlockInfo));
}
ipuz_arrowword_class_init (IPuzArrowwordClass *klass)
IPuzPuzzleClass *puzzle_class = IPUZ_PUZZLE_CLASS (klass);
puzzle_class->fixup = ipuz_arrowword_fixup;
puzzle_class->clone = ipuz_arrowword_clone;
puzzle_class->equal = ipuz_arrowword_equal;
static IPuzArrowwordArrow
calculate_arrow_direction (IPuzArrowword *self,
IPuzClue *clue)
location = ipuz_clue_get_location (clue, NULL);
g_return_val_if_fail (clue->cells != NULL, IPUZ_ARROWWORD_ARROW_NONE);
for (guint i = 0; i < clue->cells->len; i++)
IPuzCellCoord cell = g_array_index (clue->cells, IPuzCellCoord, i);
/* FIXME(cluesets): We are wedded to across/down here. There's a
* possibility where the puzzle is Clues/Hidden, in which case
* we'd have to infer the clue direction */
/* A gnarly set of if-statements */
if (cell.row == location.row && cell.column == (location.column + 1) &&
clue->direction == IPUZ_CLUE_DIRECTION_ACROSS)
return IPUZ_ARROWWORD_ARROW_RIGHT;
else if (cell.row == location.row && cell.column == (location.column + 1) &&
clue->direction == IPUZ_CLUE_DIRECTION_DOWN)
return IPUZ_ARROWWORD_ARROW_RIGHT_DOWN;
else if (cell.row == (location.row + 1) && cell.column == location.column &&
return IPUZ_ARROWWORD_ARROW_DOWN;
return IPUZ_ARROWWORD_ARROW_DOWN_RIGHT;
else if (cell.row == location.row && cell.column == (location.column - 1) &&
return IPUZ_ARROWWORD_ARROW_LEFT_DOWN;
else if (cell.row == (location.row - 1) && cell.column == location.column &&
return IPUZ_ARROWWORD_ARROW_UP_RIGHT;
return IPUZ_ARROWWORD_ARROW_NONE;
arrowword_fixup_foreach (IPuzCrossword *xword,
IPuzClueDirection direction,
IPuzClue *clue,
IPuzClueId clue_id,
IPuzArrowword *self)
gboolean location_set;
BlockInfo *block_info;
IPuzCell *cell;
location = ipuz_clue_get_location (clue, &location_set);
/* Should we do anything different? Arrowword clues without
* locations won't be displayed. */
if (! location_set)
return;
/* Make sure we're a block. Should we error out here too? */
cell = ipuz_crossword_get_cell (IPUZ_CROSSWORD (self), location);
if (!IPUZ_CELL_IS_BLOCK (cell))
block_info = arrowword_find_block_info (self, location);
if (block_info == NULL)
BlockInfo new_block = {
.location = location,
.top_clue_id = clue_id,
.bottom_arrow = IPUZ_ARROWWORD_ARROW_NONE,
};
new_block.top_arrow = calculate_arrow_direction (self, clue);
new_block.bottom_clue_id.direction = IPUZ_CLUE_DIRECTION_NONE,
g_array_append_val (priv->blocks, new_block);
else
/* This is triggered if we already have a two clues set in a
* location */
if (! IPUZ_CLUE_ID_IS_UNSET (block_info->bottom_clue_id))
g_warning ("more than two clues in a cell is not supported for Arrowwords.");
block_info->bottom_clue_id = clue_id;
block_info->bottom_arrow = calculate_arrow_direction (self, clue);
/* Swap the two if necessary */
if (block_info->top_arrow == IPUZ_ARROWWORD_ARROW_DOWN ||
block_info->top_arrow == IPUZ_ARROWWORD_ARROW_DOWN_RIGHT ||
block_info->bottom_arrow == IPUZ_ARROWWORD_ARROW_UP_RIGHT)
IPuzClueId temp_clue_id = block_info->top_clue_id;
IPuzArrowwordArrow temp_arrow = block_info->top_arrow;
block_info->top_clue_id = block_info->bottom_clue_id;
block_info->top_arrow = block_info->bottom_arrow;
block_info->bottom_clue_id = temp_clue_id;
block_info->bottom_arrow = temp_arrow;
static gint
blocks_compare (gconstpointer a,
gconstpointer b)
const BlockInfo *block_a = (const BlockInfo *) a;
const BlockInfo *block_b = (const BlockInfo *) b;
if (block_a->location.row == block_b->location.row)
return block_a->location.column - block_b->location.column;
return block_a->location.row - block_b->location.row;
ipuz_arrowword_fixup (IPuzPuzzle *puzzle)
IPUZ_PUZZLE_CLASS (ipuz_arrowword_parent_class)->fixup (puzzle);
priv = ipuz_arrowword_get_instance_private (IPUZ_ARROWWORD (puzzle));
ipuz_crossword_foreach_clue (IPUZ_CROSSWORD (puzzle),
(IPuzCrosswordForeachClueFunc) arrowword_fixup_foreach,
puzzle);
/* FIXME(clue-block): according to the spec, the location value
* in the clue is optional. We need to go through all the clues and
* assign a location. */
g_array_sort (priv->blocks, blocks_compare);
ipuz_arrowword_clone (IPuzPuzzle *src,
IPuzPuzzle *dest)
IPuzArrowwordPrivate *src_priv;
IPuzArrowwordPrivate *dest_priv;
IPUZ_PUZZLE_CLASS (ipuz_arrowword_parent_class)->clone (src, dest);
src_priv = ipuz_arrowword_get_instance_private (IPUZ_ARROWWORD (src));
dest_priv = ipuz_arrowword_get_instance_private (IPUZ_ARROWWORD (dest));
g_array_set_size (dest_priv->blocks, 0);
g_array_append_vals (dest_priv->blocks, src_priv->blocks->data, src_priv->blocks->len);
static gboolean
block_info_equal (BlockInfo *a,
BlockInfo *b)
return (ipuz_cell_coord_equal (&a->location, &b->location)
&& ipuz_clue_id_equal (&a->top_clue_id, &b->top_clue_id)
&& ipuz_clue_id_equal (&a->bottom_clue_id, &b->bottom_clue_id)
&& a->top_arrow == b->top_arrow
&& a->bottom_arrow == b->bottom_arrow);
ipuz_arrowword_equal (IPuzPuzzle *puzzle_a,
IPuzPuzzle *puzzle_b)
IPuzArrowwordPrivate *priv_a, *priv_b;
g_return_val_if_fail (IPUZ_IS_ARROWWORD (puzzle_b), FALSE);
priv_a = ipuz_arrowword_get_instance_private (IPUZ_ARROWWORD (puzzle_a));
priv_b = ipuz_arrowword_get_instance_private (IPUZ_ARROWWORD (puzzle_b));
if (priv_a->blocks->len != priv_b->blocks->len)
return FALSE;
for (guint i = 0; i < priv_a->blocks->len; i++)
if (! block_info_equal (&g_array_index (priv_a->blocks, BlockInfo, i),
&g_array_index (priv_b->blocks, BlockInfo, i)))
return IPUZ_PUZZLE_CLASS (ipuz_arrowword_parent_class)->equal (puzzle_a,
puzzle_b);
static BlockInfo *
arrowword_find_block_info (IPuzArrowword *self,
IPuzCellCoord location)
g_assert (IPUZ_IS_ARROWWORD (self));
#if 0
/* FIXME(optimization): We call this mid-construction of the blocks
* array, and since there's no way to easily insert an item into an
* array sorted with glib, we have to do the linear search. A better
* data structure could make this more efficient.
if (g_array_binary_search (priv->blocks, &target, blocks_compare, &index))
return &(g_array_index (priv->blocks, BlockInfo, index));
#endif
for (guint i = 0; i < priv->blocks->len; i++)
BlockInfo *block_info = &(g_array_index (priv->blocks, BlockInfo, i));
if (ipuz_cell_coord_equal (&(block_info->location), &location))
return block_info;
return NULL;
/*
* Public methods
void
ipuz_arrowword_blocks_foreach (IPuzArrowword *self,
IPuzArrowwordForeachFunc func,
gpointer data)
IPuzClue *clue;
IPuzArrowwordPlacement placement;
if (IPUZ_CLUE_ID_IS_UNSET (block_info->bottom_clue_id))
placement = IPUZ_ARROWWORD_PLACEMENT_FILL;
placement = IPUZ_ARROWWORD_PLACEMENT_TOP;
clue = ipuz_crossword_get_clue_by_id (IPUZ_CROSSWORD (self), block_info->top_clue_id);
(*func) (self, clue, block_info->location, placement, block_info->top_arrow, data);
if (placement == IPUZ_ARROWWORD_PLACEMENT_TOP)
placement = IPUZ_ARROWWORD_PLACEMENT_BOTTOM;
clue = ipuz_crossword_get_clue_by_id (IPUZ_CROSSWORD (self), block_info->bottom_clue_id);
(*func) (self, clue, block_info->location, placement, block_info->bottom_arrow, data);
* Public functions
ipuz_arrowword_print (IPuzArrowword *self)
char ESC=27;
priv = ipuz_arrowword_get_instance_private (IPUZ_ARROWWORD (self));
ipuz_crossword_print (IPUZ_CROSSWORD (self));
g_print ("%c[1mBlocks%c[0m\n", ESC, ESC);
BlockInfo block_info = g_array_index (priv->blocks, BlockInfo, i);
g_print ("\tLocation: %u %u\n", block_info.location.row, block_info.location.column);
if (! IPUZ_CLUE_ID_IS_UNSET (block_info.top_clue_id))
clue = ipuz_crossword_get_clue_by_id (IPUZ_CROSSWORD (self), block_info.top_clue_id);
g_print ("\t\tTop Clue: %s\n", ipuz_clue_get_clue_text (clue));
if (! IPUZ_CLUE_ID_IS_UNSET (block_info.bottom_clue_id))
clue = ipuz_crossword_get_clue_by_id (IPUZ_CROSSWORD (self), block_info.bottom_clue_id);
g_print ("\t\tBottom Clue: %s\n", ipuz_clue_get_clue_text (clue));