1
/* ipuz-arrowword.c
2
 *
3
 * Copyright 2022 Jonathan Blandford <jrb@gnome.org>
4
 *
5
 * This library is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU Lesser General Public
7
 * License as published by the Free Software Foundation; either
8
 * version 2.1 of the License, or (at your option) any later version.
9
 *
10
 * This library is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
 * Lesser General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Lesser General Public
16
 * License along with this library; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18
 *
19
 * SPDX-License-Identifier: (LGPL-2.1-or-later OR MIT)
20
 */
21

            
22

            
23
#include "libipuz-config.h"
24
#include <libipuz/ipuz-arrowword.h>
25

            
26

            
27
typedef struct _BlockInfo
28
{
29
  IPuzCellCoord location;
30
  IPuzClueId top_clue_id;
31
  IPuzClueId bottom_clue_id;
32
  IPuzArrowwordArrow top_arrow;
33
  IPuzArrowwordArrow bottom_arrow;
34
} BlockInfo;
35

            
36
typedef struct _IPuzArrowwordPrivate
37
{
38
  GArray *blocks;
39
} IPuzArrowwordPrivate;
40

            
41

            
42
static void       ipuz_arrowword_init       (IPuzArrowword      *self);
43
static void       ipuz_arrowword_class_init (IPuzArrowwordClass *klass);
44
static void       ipuz_arrowword_fixup      (IPuzPuzzle         *puzzle);
45
static void       ipuz_arrowword_clone      (IPuzPuzzle         *src,
46
                                             IPuzPuzzle         *dest);
47
static gboolean   ipuz_arrowword_equal      (IPuzPuzzle         *puzzle_a,
48
                                             IPuzPuzzle         *puzzle_b);
49
static BlockInfo *arrowword_find_block_info (IPuzArrowword      *self,
50
                                             IPuzCellCoord       coord);
51

            
52

            
53
G_DEFINE_TYPE_WITH_CODE (IPuzArrowword, ipuz_arrowword, IPUZ_TYPE_CROSSWORD,
54
                         G_ADD_PRIVATE (IPuzArrowword));
55

            
56

            
57
static void
58
ipuz_arrowword_init (IPuzArrowword *self)
59
{
60
  IPuzArrowwordPrivate *priv;
61

            
62
  priv = ipuz_arrowword_get_instance_private (self);
63
  priv->blocks = g_array_new (FALSE, TRUE, sizeof (BlockInfo));
64
}
65

            
66
static void
67
ipuz_arrowword_class_init (IPuzArrowwordClass *klass)
68
{
69
  IPuzPuzzleClass *puzzle_class = IPUZ_PUZZLE_CLASS (klass);
70

            
71
  puzzle_class->fixup = ipuz_arrowword_fixup;
72
  puzzle_class->clone = ipuz_arrowword_clone;
73
  puzzle_class->equal = ipuz_arrowword_equal;
74
}
75

            
76
static IPuzArrowwordArrow
77
calculate_arrow_direction (IPuzArrowword *self,
78
                           IPuzClue      *clue)
79
{
80
  IPuzCellCoord location;
81

            
82
  location = ipuz_clue_get_location (clue, NULL);
83
  g_return_val_if_fail (clue->cells != NULL, IPUZ_ARROWWORD_ARROW_NONE);
84

            
85
  for (guint i = 0; i < clue->cells->len; i++)
86
    {
87
      IPuzCellCoord cell = g_array_index (clue->cells, IPuzCellCoord, i);
88

            
89
      /* FIXME(cluesets): We are wedded to across/down here. There's a
90
       * possibility where the puzzle is Clues/Hidden, in which case
91
       * we'd have to infer the clue direction */
92
      /* A gnarly set of if-statements */
93
      if (cell.row == location.row && cell.column == (location.column + 1) &&
94
          clue->direction == IPUZ_CLUE_DIRECTION_ACROSS)
95
        return IPUZ_ARROWWORD_ARROW_RIGHT;
96
      else if (cell.row == location.row && cell.column == (location.column + 1) &&
97
               clue->direction == IPUZ_CLUE_DIRECTION_DOWN)
98
        return IPUZ_ARROWWORD_ARROW_RIGHT_DOWN;
99
      else if (cell.row == (location.row + 1) && cell.column == location.column &&
100
               clue->direction == IPUZ_CLUE_DIRECTION_DOWN)
101
        return IPUZ_ARROWWORD_ARROW_DOWN;
102
      else if (cell.row == (location.row + 1) && cell.column == location.column &&
103
               clue->direction == IPUZ_CLUE_DIRECTION_ACROSS)
104
        return IPUZ_ARROWWORD_ARROW_DOWN_RIGHT;
105
      else if (cell.row == location.row && cell.column == (location.column - 1) &&
106
               clue->direction == IPUZ_CLUE_DIRECTION_DOWN)
107
        return IPUZ_ARROWWORD_ARROW_LEFT_DOWN;
108
      else if (cell.row == (location.row - 1) && cell.column == location.column &&
109
               clue->direction == IPUZ_CLUE_DIRECTION_ACROSS)
110
        return IPUZ_ARROWWORD_ARROW_UP_RIGHT;
111
    }
112

            
113
  return IPUZ_ARROWWORD_ARROW_NONE;
114
}
115

            
116

            
117
static void
118
arrowword_fixup_foreach (IPuzCrossword     *xword,
119
                         IPuzClueDirection  direction,
120
                         IPuzClue          *clue,
121
                         IPuzClueId         clue_id,
122
                         IPuzArrowword     *self)
123
{
124
  IPuzArrowwordPrivate *priv;
125
  IPuzCellCoord location;
126
  gboolean location_set;
127
  BlockInfo *block_info;
128
  IPuzCell *cell;
129

            
130
  priv = ipuz_arrowword_get_instance_private (self);
131

            
132
  location = ipuz_clue_get_location (clue, &location_set);
133
  /* Should we do anything different? Arrowword clues without
134
   * locations won't be displayed. */
135
  if (! location_set)
136
    return;
137

            
138
  /* Make sure we're a block. Should we error out here too? */
139
  cell = ipuz_crossword_get_cell (IPUZ_CROSSWORD (self), location);
140
  if (!IPUZ_CELL_IS_BLOCK (cell))
141
    return;
142

            
143
  block_info = arrowword_find_block_info (self, location);
144
  if (block_info == NULL)
145
    {
146
      BlockInfo new_block = {
147
        .location = location,
148
        .top_clue_id = clue_id,
149
        .bottom_arrow = IPUZ_ARROWWORD_ARROW_NONE,
150
      };
151
      new_block.top_arrow = calculate_arrow_direction (self, clue);
152
      new_block.bottom_clue_id.direction = IPUZ_CLUE_DIRECTION_NONE,
153

            
154
      g_array_append_val (priv->blocks, new_block);
155
    }
156
  else
157
    {
158
      /* This is triggered if we already have a two clues set in a
159
       * location */
160
      if (! IPUZ_CLUE_ID_IS_UNSET (block_info->bottom_clue_id))
161
        {
162
          g_warning ("more than two clues in a cell is not supported for Arrowwords.");
163
          return;
164
        }
165

            
166
      block_info->bottom_clue_id = clue_id;
167
      block_info->bottom_arrow = calculate_arrow_direction (self, clue);
168

            
169
      /* Swap the two if necessary */
170
      if (block_info->top_arrow == IPUZ_ARROWWORD_ARROW_DOWN ||
171
          block_info->top_arrow == IPUZ_ARROWWORD_ARROW_DOWN_RIGHT ||
172
          block_info->bottom_arrow == IPUZ_ARROWWORD_ARROW_UP_RIGHT)
173
        {
174
          IPuzClueId temp_clue_id = block_info->top_clue_id;
175
          IPuzArrowwordArrow temp_arrow = block_info->top_arrow;
176

            
177
          block_info->top_clue_id = block_info->bottom_clue_id;
178
          block_info->top_arrow = block_info->bottom_arrow;
179
          block_info->bottom_clue_id = temp_clue_id;
180
          block_info->bottom_arrow = temp_arrow;
181
        }
182
    }
183
}
184

            
185
static gint
186
blocks_compare (gconstpointer a,
187
                gconstpointer b)
188
{
189
  const BlockInfo *block_a = (const BlockInfo *) a;
190
  const BlockInfo *block_b = (const BlockInfo *) b;
191

            
192
  if (block_a->location.row == block_b->location.row)
193
    return block_a->location.column - block_b->location.column;
194

            
195
  return block_a->location.row - block_b->location.row;
196
}
197

            
198
static void
199
ipuz_arrowword_fixup (IPuzPuzzle *puzzle)
200
{
201
  IPuzArrowwordPrivate *priv;
202

            
203
  IPUZ_PUZZLE_CLASS (ipuz_arrowword_parent_class)->fixup (puzzle);
204

            
205
  priv = ipuz_arrowword_get_instance_private (IPUZ_ARROWWORD (puzzle));
206

            
207
  ipuz_crossword_foreach_clue (IPUZ_CROSSWORD (puzzle),
208
                               (IPuzCrosswordForeachClueFunc) arrowword_fixup_foreach,
209
                               puzzle);
210
  /* FIXME(clue-block): according to the spec, the location value
211
   * in the clue is optional. We need to go through all the clues and
212
   * assign a location. */
213
  g_array_sort (priv->blocks, blocks_compare);
214
}
215

            
216
static void
217
ipuz_arrowword_clone (IPuzPuzzle *src,
218
                      IPuzPuzzle *dest)
219
{
220
  IPuzArrowwordPrivate *src_priv;
221
  IPuzArrowwordPrivate *dest_priv;
222

            
223
  IPUZ_PUZZLE_CLASS (ipuz_arrowword_parent_class)->clone (src, dest);
224

            
225
  src_priv = ipuz_arrowword_get_instance_private (IPUZ_ARROWWORD (src));
226
  dest_priv = ipuz_arrowword_get_instance_private (IPUZ_ARROWWORD (dest));
227

            
228
  g_array_set_size (dest_priv->blocks, 0);
229
  g_array_append_vals (dest_priv->blocks, src_priv->blocks->data, src_priv->blocks->len);
230
}
231

            
232
static gboolean
233
block_info_equal (BlockInfo *a,
234
                  BlockInfo *b)
235
{
236
  return (ipuz_cell_coord_equal (&a->location, &b->location)
237
          && ipuz_clue_id_equal (&a->top_clue_id, &b->top_clue_id)
238
          && ipuz_clue_id_equal (&a->bottom_clue_id, &b->bottom_clue_id)
239
          && a->top_arrow == b->top_arrow
240
          && a->bottom_arrow == b->bottom_arrow);
241
}
242

            
243
static gboolean
244
ipuz_arrowword_equal (IPuzPuzzle *puzzle_a,
245
                      IPuzPuzzle *puzzle_b)
246
{
247
  IPuzArrowwordPrivate *priv_a, *priv_b;
248

            
249
  g_return_val_if_fail (IPUZ_IS_ARROWWORD (puzzle_b), FALSE);
250

            
251
  priv_a = ipuz_arrowword_get_instance_private (IPUZ_ARROWWORD (puzzle_a));
252
  priv_b = ipuz_arrowword_get_instance_private (IPUZ_ARROWWORD (puzzle_b));
253

            
254
  if (priv_a->blocks->len != priv_b->blocks->len)
255
    return FALSE;
256

            
257
  for (guint i = 0; i < priv_a->blocks->len; i++)
258
    {
259
      if (! block_info_equal (&g_array_index (priv_a->blocks, BlockInfo, i),
260
                              &g_array_index (priv_b->blocks, BlockInfo, i)))
261
          return FALSE;
262
    }
263

            
264
  return IPUZ_PUZZLE_CLASS (ipuz_arrowword_parent_class)->equal (puzzle_a,
265
                                                                 puzzle_b);
266
}
267

            
268
static BlockInfo *
269
arrowword_find_block_info (IPuzArrowword *self,
270
                           IPuzCellCoord  location)
271
{
272
  IPuzArrowwordPrivate *priv;
273

            
274
  g_assert (IPUZ_IS_ARROWWORD (self));
275
  priv = ipuz_arrowword_get_instance_private (self);
276

            
277
#if 0
278
  /* FIXME(optimization): We call this mid-construction of the blocks
279
   * array, and since there's no way to easily insert an item into an
280
   * array sorted with glib, we have to do the linear search. A better
281
   * data structure could make this more efficient.
282
   */
283
  if (g_array_binary_search (priv->blocks, &target, blocks_compare, &index))
284
    return &(g_array_index (priv->blocks, BlockInfo, index));
285
#endif
286

            
287
  for (guint i = 0; i < priv->blocks->len; i++)
288
    {
289
      BlockInfo *block_info = &(g_array_index (priv->blocks, BlockInfo, i));
290
      if (ipuz_cell_coord_equal (&(block_info->location), &location))
291
        return block_info;
292
    }
293
  return NULL;
294
}
295

            
296
/*
297
 * Public methods
298
 */
299

            
300
void
301
ipuz_arrowword_blocks_foreach (IPuzArrowword            *self,
302
                               IPuzArrowwordForeachFunc  func,
303
                               gpointer                  data)
304
{
305
  IPuzArrowwordPrivate *priv;
306
  g_assert (IPUZ_IS_ARROWWORD (self));
307
  priv = ipuz_arrowword_get_instance_private (self);
308

            
309
  for (guint i = 0; i < priv->blocks->len; i++)
310
    {
311
      BlockInfo *block_info = &(g_array_index (priv->blocks, BlockInfo, i));
312
      IPuzClue *clue;
313
      IPuzArrowwordPlacement placement;
314

            
315
      if (IPUZ_CLUE_ID_IS_UNSET (block_info->bottom_clue_id))
316
        placement = IPUZ_ARROWWORD_PLACEMENT_FILL;
317
      else
318
        placement = IPUZ_ARROWWORD_PLACEMENT_TOP;
319
      clue = ipuz_crossword_get_clue_by_id (IPUZ_CROSSWORD (self), block_info->top_clue_id);
320
      (*func) (self, clue, block_info->location, placement, block_info->top_arrow, data);
321

            
322
      if (placement == IPUZ_ARROWWORD_PLACEMENT_TOP)
323
        {
324
          placement = IPUZ_ARROWWORD_PLACEMENT_BOTTOM;
325
          clue = ipuz_crossword_get_clue_by_id (IPUZ_CROSSWORD (self), block_info->bottom_clue_id);
326
          (*func) (self, clue, block_info->location, placement, block_info->bottom_arrow, data);
327
        }
328
    }
329
}
330

            
331
/*
332
 * Public functions
333
 */
334
void
335
ipuz_arrowword_print (IPuzArrowword *self)
336
{
337
  IPuzArrowwordPrivate *priv;
338
  char ESC=27;
339

            
340
  priv = ipuz_arrowword_get_instance_private (IPUZ_ARROWWORD (self));
341

            
342
  ipuz_crossword_print (IPUZ_CROSSWORD (self));
343

            
344
  g_print ("%c[1mBlocks%c[0m\n", ESC, ESC);
345

            
346
  for (guint i = 0; i < priv->blocks->len; i++)
347
    {
348
      BlockInfo block_info = g_array_index (priv->blocks, BlockInfo, i);
349
      IPuzClue *clue;
350

            
351
      g_print ("\tLocation: %u %u\n", block_info.location.row, block_info.location.column);
352
      if (! IPUZ_CLUE_ID_IS_UNSET (block_info.top_clue_id))
353
        {
354
          clue = ipuz_crossword_get_clue_by_id (IPUZ_CROSSWORD (self), block_info.top_clue_id);
355
          g_print ("\t\tTop Clue: %s\n", ipuz_clue_get_clue_text (clue));
356
        }
357
      if (! IPUZ_CLUE_ID_IS_UNSET (block_info.bottom_clue_id))
358
        {
359
          clue = ipuz_crossword_get_clue_by_id (IPUZ_CROSSWORD (self), block_info.bottom_clue_id);
360
          g_print ("\t\tBottom Clue: %s\n", ipuz_clue_get_clue_text (clue));
361
        }
362
    }
363
}