1
/* ipuz-crossword.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.h"
25
#include "libipuz-enums.h"
26
#include "ipuz-clue-sets.h"
27
#include "ipuz-magic.h"
28
#include "ipuz-puzzle-info-private.h"
29
#include "rust/ipuz-rust.h"
30

            
31
#include <libipuz/ipuz-puzzle.h>
32
#include <libipuz/ipuz-crossword.h>
33
#include <libipuz/ipuz-board.h>
34
#include <glib/gi18n-lib.h>
35

            
36

            
37
enum
38
{
39
  PROP_0,
40
  PROP_WIDTH,
41
  PROP_HEIGHT,
42
  PROP_SHOWENUMERATIONS,
43
  PROP_CLUE_PLACEMENT,
44
  PROP_BOARD,
45
  PROP_GUESSES,
46
  N_PROPS
47
};
48
static GParamSpec *obj_props[N_PROPS] = { NULL, };
49

            
50

            
51
typedef struct _IPuzCrosswordPrivate
52
{
53
  gint width;
54
  gint height;
55
  gboolean showenumerations;
56
  IPuzClueSets *clue_sets;
57

            
58
  IPuzBoard *board;
59
  IPuzGuesses *guesses;
60

            
61
  gboolean uses_extensions;
62
  gboolean has_solution;
63
  gboolean has_saved;
64
  IPuzCluePlacement clue_placement;
65
  /*
66
"zones": [ GroupSpec, ... ]
67
		Arbitrarily-shaped entries overlaid on the grid
68
"answer": "entry"
69
		The final answer to the puzzle
70
"answers": [ "entry", ... ]
71
		List of final answers to the puzzle
72
"enumeration": Enumeration
73
		Enumeration of the final answer to the puzzle
74
"enumerations": [ Enumeration, ... ]
75
		List of enumerations for final answers to the puzzle
76
"misses": { "entry": "hint", ... }
77
		List of hints to be given for misses
78
  */
79
} IPuzCrosswordPrivate;
80

            
81

            
82
static void                ipuz_crossword_init                      (IPuzCrossword      *self);
83
static void                ipuz_crossword_class_init                (IPuzCrosswordClass *klass);
84
static void                ipuz_crossword_finalize                  (GObject            *object);
85
static void                ipuz_crossword_set_property              (GObject            *object,
86
                                                                     guint               prop_id,
87
                                                                     const GValue       *value,
88
                                                                     GParamSpec         *pspec);
89
static void                ipuz_crossword_get_property              (GObject            *object,
90
                                                                     guint               prop_id,
91
                                                                     GValue             *value,
92
                                                                     GParamSpec         *pspec);
93
static void                ipuz_crossword_set_property              (GObject            *object,
94
                                                                     guint               prop_id,
95
                                                                     const GValue       *value,
96
                                                                     GParamSpec         *pspec);
97
static void                ipuz_crossword_get_property              (GObject            *object,
98
                                                                     guint               prop_id,
99
                                                                     GValue             *value,
100
                                                                     GParamSpec         *pspec);
101
static void                ipuz_crossword_load_node                 (IPuzPuzzle         *puzzle,
102
                                                                     const char         *member_name,
103
                                                                     JsonNode           *node);
104
static void                ipuz_crossword_post_load_node            (IPuzPuzzle         *puzzle,
105
                                                                     const char         *member_name,
106
                                                                     JsonNode           *node);
107
static void                ipuz_crossword_fixup                     (IPuzPuzzle         *puzzle);
108
static void                ipuz_crossword_build                     (IPuzPuzzle         *puzzle,
109
                                                                     JsonBuilder        *builder);
110
static IPuzPuzzleFlags     ipuz_crossword_get_flags                 (IPuzPuzzle         *puzzle);
111
static void                ipuz_crossword_calculate_info            (IPuzPuzzle         *puzzle,
112
                                                                     IPuzPuzzleInfo      *info);
113
static void                ipuz_crossword_clone                     (IPuzPuzzle         *src,
114
                                                                     IPuzPuzzle         *dest);
115
static const gchar *const *ipuz_crossword_get_kind_str              (IPuzPuzzle         *puzzle);
116
static void                ipuz_crossword_set_style                 (IPuzPuzzle         *puzzle,
117
                                                                     const gchar        *style_name,
118
                                                                     IPuzStyle          *style);
119
static gboolean            ipuz_crossword_equal                     (IPuzPuzzle         *puzzle_a,
120
                                                                     IPuzPuzzle         *puzzle_b);
121
static void                ipuz_crossword_real_fix_symmetry         (IPuzCrossword      *self,
122
                                                                     IPuzSymmetry        symmetry,
123
                                                                     GArray             *coords);
124
static void                ipuz_crossword_real_fix_numbering        (IPuzCrossword      *self);
125
static void                ipuz_crossword_real_fix_clues            (IPuzCrossword      *self);
126
static void                ipuz_crossword_real_fix_enumerations     (IPuzCrossword      *self);
127
static void                ipuz_crossword_real_fix_styles           (IPuzCrossword      *self);
128
static void                ipuz_crossword_real_fix_all              (IPuzCrossword      *self,
129
                                                                     const gchar        *first_attribute_name,
130
                                                                     va_list             var_args);
131
static gboolean            ipuz_crossword_real_clue_continues_up    (IPuzCrossword      *self,
132
                                                                     IPuzCellCoord       coord);
133
static gboolean            ipuz_crossword_real_clue_continues_down  (IPuzCrossword      *self,
134
                                                                     IPuzCellCoord       coord);
135
static gboolean            ipuz_crossword_real_clue_continues_left  (IPuzCrossword      *self,
136
                                                                     IPuzCellCoord       coord);
137
static gboolean            ipuz_crossword_real_clue_continues_right (IPuzCrossword      *self,
138
                                                                     IPuzCellCoord       coord);
139
static void                ipuz_crossword_real_mirror_cell          (IPuzCrossword      *self,
140
                                                                     IPuzCellCoord       src_coord,
141
                                                                     IPuzCellCoord       dest_coord,
142
                                                                     IPuzSymmetry        symmetry,
143
                                                                     IPuzSymmetryOffset  symmetry_offset);
144
static gboolean            ipuz_crossword_real_check_mirror         (IPuzCrossword      *self,
145
                                                                     IPuzCellCoord       src_coord,
146
                                                                     IPuzCellCoord       target_coord,
147
                                                                     IPuzSymmetry        symmetry,
148
                                                                     IPuzSymmetryOffset  symmetry_offset);
149
static gboolean            ipuz_crossword_real_set_size             (IPuzCrossword      *self,
150
                                                                     gint                width,
151
                                                                     gint                height);
152
static IPuzClue           *calculate_clue                           (IPuzCrossword      *self,
153
                                                                     IPuzClueDirection   direction,
154
                                                                     IPuzCellCoord       coord,
155
                                                                     gint                number);
156

            
157

            
158
6
G_DEFINE_TYPE_WITH_CODE (IPuzCrossword, ipuz_crossword, IPUZ_TYPE_PUZZLE,
159
                         G_ADD_PRIVATE (IPuzCrossword));
160

            
161

            
162
static void
163
43
ipuz_crossword_init_clues (IPuzCrossword *self)
164
{
165
  IPuzCrosswordPrivate *priv;
166

            
167
43
  priv = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (self));
168

            
169
43
  g_clear_pointer (&priv->clue_sets, ipuz_clue_sets_unref);
170
43
  priv->clue_sets = ipuz_clue_sets_new ();
171
43
}
172

            
173
static void
174
39
ipuz_crossword_init (IPuzCrossword *self)
175
{
176
  IPuzCrosswordPrivate *priv;
177

            
178
39
  priv = ipuz_crossword_get_instance_private (self);
179

            
180
39
  priv->showenumerations = FALSE;
181
39
  priv->width = 0;
182
39
  priv->height = 0;
183
39
  priv->board = ipuz_board_new ();
184
39
  ipuz_crossword_init_clues (self);
185

            
186
39
  ipuz_noop ();
187
39
}
188

            
189
static void
190
6
ipuz_crossword_class_init (IPuzCrosswordClass *klass)
191
{
192
6
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
193
6
  IPuzPuzzleClass *puzzle_class = IPUZ_PUZZLE_CLASS (klass);
194
6
  IPuzCrosswordClass *crossword_class = IPUZ_CROSSWORD_CLASS (klass);
195

            
196
6
  object_class->finalize = ipuz_crossword_finalize;
197
6
  object_class->set_property = ipuz_crossword_set_property;
198
6
  object_class->get_property = ipuz_crossword_get_property;
199
6
  puzzle_class->load_node = ipuz_crossword_load_node;
200
6
  puzzle_class->post_load_node = ipuz_crossword_post_load_node;
201
6
  puzzle_class->fixup = ipuz_crossword_fixup;
202
6
  puzzle_class->build = ipuz_crossword_build;
203
6
  puzzle_class->get_flags = ipuz_crossword_get_flags;
204
6
  puzzle_class->calculate_info = ipuz_crossword_calculate_info;
205
6
  puzzle_class->clone = ipuz_crossword_clone;
206
6
  puzzle_class->get_kind_str = ipuz_crossword_get_kind_str;
207
6
  puzzle_class->set_style = ipuz_crossword_set_style;
208
6
  puzzle_class->equal = ipuz_crossword_equal;
209
6
  crossword_class->fix_symmetry = ipuz_crossword_real_fix_symmetry;
210
6
  crossword_class->fix_numbering = ipuz_crossword_real_fix_numbering;
211
6
  crossword_class->fix_clues = ipuz_crossword_real_fix_clues;
212
6
  crossword_class->fix_enumerations = ipuz_crossword_real_fix_enumerations;
213
6
  crossword_class->fix_styles = ipuz_crossword_real_fix_styles;
214
6
  crossword_class->fix_all = ipuz_crossword_real_fix_all;
215
6
  crossword_class->clue_continues_up = ipuz_crossword_real_clue_continues_up;
216
6
  crossword_class->clue_continues_down = ipuz_crossword_real_clue_continues_down;
217
6
  crossword_class->clue_continues_left = ipuz_crossword_real_clue_continues_left;
218
6
  crossword_class->clue_continues_right = ipuz_crossword_real_clue_continues_right;
219
6
  crossword_class->mirror_cell = ipuz_crossword_real_mirror_cell;
220
6
  crossword_class->check_mirror = ipuz_crossword_real_check_mirror;
221

            
222
6
  obj_props[PROP_WIDTH] = g_param_spec_int ("width",
223
                                            _("width"),
224
                                            _("Width of the puzzle grid"),
225
                                            0, 65536, 0,
226
                                            G_PARAM_READWRITE);
227
6
  obj_props[PROP_HEIGHT] = g_param_spec_int ("height",
228
                                             _("height"),
229
                                             _("height of the puzzle grid"),
230
                                             0, 65536, 0,
231
                                             G_PARAM_READWRITE);
232
6
  obj_props[PROP_SHOWENUMERATIONS] = g_param_spec_boolean ("showenumerations",
233
                                                           _("Show Enumerations"),
234
                                                           _("Show enumerations with clues"),
235
                                                           FALSE,
236
                                                           G_PARAM_READWRITE);
237
6
  obj_props[PROP_CLUE_PLACEMENT] = g_param_spec_enum ("clue-placement",
238
                                                      _("Clue Placement"),
239
                                                      _("Where to put clues"),
240
                                                      I_TYPE_PUZ_CLUE_PLACEMENT,
241
                                                      IPUZ_CLUE_PLACEMENT_NULL,
242
                                                      G_PARAM_READWRITE);
243
6
  obj_props[PROP_BOARD] = g_param_spec_object ("board",
244
                                               _("Board"),
245
                                               _("The crossword board"),
246
                                               IPUZ_TYPE_BOARD,
247
                                               G_PARAM_READABLE);
248
6
  obj_props[PROP_GUESSES] = g_param_spec_boxed ("guesses",
249
                                                _("Guesses"),
250
                                                _("The guesses associated with this crossword"),
251
                                                IPUZ_TYPE_GUESSES,
252
                                                G_PARAM_READWRITE);
253
6
  g_object_class_install_properties (object_class, N_PROPS, obj_props);
254
6
}
255

            
256
/* free all memory */
257
static void
258
39
ipuz_crossword_finalize (GObject *object)
259
{
260
  IPuzCrosswordPrivate *priv;
261

            
262
39
  g_return_if_fail (object != NULL);
263

            
264
39
  priv = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (object));
265

            
266
39
  g_clear_pointer (&priv->clue_sets, ipuz_clue_sets_unref);
267

            
268
39
  g_object_unref (G_OBJECT (priv->board));
269
39
  ipuz_guesses_unref (priv->guesses);
270

            
271
39
  G_OBJECT_CLASS (ipuz_crossword_parent_class)->finalize (object);
272
}
273

            
274
static void
275
26
ipuz_crossword_set_property (GObject      *object,
276
                             guint         prop_id,
277
                             const GValue *value,
278
                             GParamSpec   *pspec)
279
{
280
  IPuzCrosswordPrivate *priv;
281

            
282
26
  priv = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (object));
283

            
284
26
  switch (prop_id)
285
    {
286
    case PROP_WIDTH:
287
      ipuz_crossword_real_set_size (IPUZ_CROSSWORD (object),
288
                                    g_value_get_int (value),
289
                                    priv->height);
290
      break;
291
    case PROP_HEIGHT:
292
      ipuz_crossword_real_set_size (IPUZ_CROSSWORD (object),
293
                                    priv->width,
294
                                    g_value_get_int (value));
295
      break;
296
26
    case PROP_SHOWENUMERATIONS:
297
26
      priv->showenumerations = g_value_get_boolean (value);
298
26
      break;
299
    case PROP_CLUE_PLACEMENT:
300
      priv->clue_placement = g_value_get_enum (value);
301
      break;
302
    case PROP_GUESSES:
303
      ipuz_crossword_set_guesses (IPUZ_CROSSWORD (object), (IPuzGuesses *)g_value_get_boxed (value));
304
      break;
305
    default:
306
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
307
      break;
308
    }
309
26
}
310

            
311
static void
312
23
ipuz_crossword_get_property (GObject    *object,
313
                             guint       prop_id,
314
                             GValue     *value,
315
                             GParamSpec *pspec)
316
{
317
  IPuzCrosswordPrivate *priv;
318

            
319
23
  priv = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (object));
320

            
321
23
  switch (prop_id)
322
    {
323
4
    case PROP_WIDTH:
324
4
      g_value_set_int (value, priv->width);
325
4
      break;
326
4
    case PROP_HEIGHT:
327
4
      g_value_set_int (value, priv->height);
328
4
      break;
329
7
    case PROP_SHOWENUMERATIONS:
330
7
      g_value_set_boolean (value, priv->showenumerations);
331
7
      break;
332
4
    case PROP_CLUE_PLACEMENT:
333
4
      g_value_set_enum (value, priv->clue_placement);
334
4
      break;
335
4
    case PROP_BOARD:
336
4
      g_value_set_object (value, priv->board);
337
4
      break;
338
    case PROP_GUESSES:
339
      g_value_set_boxed (value, priv->guesses);
340
      break;
341
    default:
342
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
343
      break;
344
    }
345
23
}
346

            
347
static void
348
32
load_dimensions (IPuzCrossword *self,
349
                 JsonNode      *node)
350
{
351
32
  gint width = -1;
352
32
  gint height = -1;
353

            
354
32
  if (!JSON_NODE_HOLDS_OBJECT (node))
355
    return;
356

            
357
64
  g_autoptr(JsonReader) reader = json_reader_new (node);
358
32
  if (json_reader_read_member (reader, "width"))
359
32
    width = json_reader_get_int_value (reader);
360
32
  json_reader_end_member (reader);
361

            
362
32
  if (json_reader_read_member (reader, "height"))
363
32
    height = json_reader_get_int_value (reader);
364
32
  json_reader_end_member (reader);
365

            
366
32
  if (width > 0 && height > 0)
367
32
    ipuz_crossword_real_set_size (self, width, height);
368
}
369

            
370
static void
371
738
load_clues_foreach (JsonArray *array,
372
                    guint      index,
373
                    JsonNode  *element_node,
374
                    gpointer   user_data)
375
{
376
  IPuzClue *clue;
377

            
378
738
  clue = ipuz_clue_new_from_json (element_node);
379
738
  GArray *clues = (GArray *) user_data;
380
738
  g_array_append_val (clues, clue);
381
738
}
382

            
383
static void
384
30
load_clues (IPuzCrossword *self,
385
            JsonNode      *node)
386
{
387
  IPuzCrosswordPrivate *priv;
388
30
  JsonObjectIter iter = {0, };
389
30
  const gchar *member_name = NULL;
390
  JsonNode *member_node;
391

            
392
30
  if (!JSON_NODE_HOLDS_OBJECT (node))
393
    return;
394

            
395
30
  priv = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (self));
396

            
397
30
  json_object_iter_init (&iter, json_node_get_object (node));
398
85
  while (json_object_iter_next (&iter, &member_name, &member_node))
399
    {
400
      IPuzClueDirection direction;
401
      GArray *clues;
402
      gchar **strv;
403

            
404
      /* conditions for parsing clues. It has to be a direction we know, and has
405
       * to be an array. We should look into better error handling here.
406
       * FIXME(error-handling): See issue #26
407
       */
408
55
      if (! JSON_NODE_HOLDS_ARRAY (member_node))
409
        continue;
410

            
411
55
      strv = g_strsplit_set (member_name, ":", 2);
412
55
      if (strv == NULL || strv[0] == NULL)
413
        continue;
414

            
415
55
      direction = ipuz_clue_sets_add_set (priv->clue_sets,
416
                                          ipuz_clue_direction_from_string (strv[0]),
417
55
                                          strv[1]);
418
55
      clues = ipuz_clue_sets_get_clues (priv->clue_sets, direction);
419
55
      g_strfreev (strv);
420

            
421
55
      if (direction == IPUZ_CLUE_DIRECTION_NONE)
422
        continue;
423

            
424
      /* Load the clues */
425
55
      json_array_foreach_element (json_node_get_array (member_node), load_clues_foreach, clues);
426

            
427
      /* The json section of each clue doesn't contain the direction. Go back and set it
428
       * as a second pass.
429
       */
430
793
      for (guint i = 0; i < clues->len; i++)
431
738
        ipuz_clue_set_direction (g_array_index (clues, IPuzClue *, i), direction);
432
    }
433
}
434

            
435
static void
436
load_clue_placement (IPuzCrossword *self,
437
                     JsonNode      *node)
438
{
439
  IPuzCrosswordPrivate *priv;
440
  const gchar *clueplacement = NULL;
441

            
442
  priv = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (self));
443

            
444
  clueplacement = json_node_get_string (node);
445
  if (clueplacement == NULL)
446
    priv->clue_placement = IPUZ_CLUE_PLACEMENT_NULL;
447
  else if (g_strcmp0 (clueplacement, "before") == 0)
448
    priv->clue_placement = IPUZ_CLUE_PLACEMENT_BEFORE;
449
  else if (g_strcmp0 (clueplacement, "after") == 0)
450
    priv->clue_placement = IPUZ_CLUE_PLACEMENT_AFTER;
451
  else if (g_strcmp0 (clueplacement, "blocks") == 0)
452
    priv->clue_placement = IPUZ_CLUE_PLACEMENT_BLOCKS;
453
  else
454
    priv->clue_placement = IPUZ_CLUE_PLACEMENT_NULL;
455
}
456

            
457
static void
458
358
ipuz_crossword_load_node (IPuzPuzzle *puzzle,
459
                          const char *member_name,
460
                          JsonNode   *node)
461
{
462

            
463
358
  g_return_if_fail (member_name != NULL);
464
358
  g_return_if_fail (node != NULL);
465

            
466
358
  if (strcmp (member_name, "dimensions") == 0)
467
    {
468
32
      load_dimensions (IPUZ_CROSSWORD (puzzle), node);
469
32
      return;
470
    }
471
326
  if (strcmp (member_name, "clues") == 0)
472
    {
473
30
      load_clues (IPUZ_CROSSWORD (puzzle), node);
474
30
      return;
475
    }
476
296
  if (strcmp (member_name, "clueplacement") == 0)
477
    {
478
      load_clue_placement (IPUZ_CROSSWORD (puzzle), node);
479
      return;
480
    }
481

            
482
296
  IPUZ_PUZZLE_CLASS (ipuz_crossword_parent_class)->load_node (puzzle, member_name, node);
483
}
484

            
485

            
486
static void
487
358
ipuz_crossword_post_load_node (IPuzPuzzle *puzzle,
488
                               const char *member_name,
489
                               JsonNode   *node)
490
{
491
  IPuzCrosswordPrivate *priv;
492

            
493
358
  g_return_if_fail (member_name != NULL);
494
358
  g_return_if_fail (node != NULL);
495

            
496
358
  priv = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (puzzle));
497

            
498
358
  if (strcmp (member_name, "puzzle") == 0)
499
    {
500
32
      g_autofree gchar *block, *empty;
501
32
      g_object_get (G_OBJECT (puzzle),
502
                    "block", &block,
503
                    "empty", &empty,
504
                    NULL);
505
32
      ipuz_board_parse_puzzle (priv->board, node, block, empty);
506
32
      return;
507
    }
508
326
  if (strcmp (member_name, "solution") == 0)
509
    {
510
30
      g_autofree gchar *block, *charset_str;
511
30
      g_object_get (G_OBJECT (puzzle),
512
                    "block", &block,
513
                    "charset-str", &charset_str,
514
                    NULL);
515
30
      ipuz_board_parse_solution (priv->board, node, block, charset_str);
516
30
      priv->has_solution = TRUE;
517
30
      return;
518
    }
519
296
  if (strcmp (member_name, "saved") == 0)
520
    {
521
      /* FIXME(saved): Load the saved guesses from disk */
522
    }
523
}
524

            
525
/* This will try its best to determine the cells of the clues when
526
 * there aren't any provided by then puzzle. There are times we can't
527
 * detect anything, and clues won't have cells associated with
528
 * them. That happens with some puzzles (such as alphabetic) */
529
static void
530
191
autodetect_cells_for_clue (IPuzClue     *clue,
531
                           IPuzClueSets *clue_sets,
532
                           IPuzBoard    *board)
533
{
534
  IPuzCell *cell;
535
  IPuzCellCoord coord;
536
  const GArray *cells;
537
  IPuzClueDirection direction;
538

            
539
191
  cells = ipuz_clue_get_cells (clue);
540
191
  g_assert (cells->len == 0);
541

            
542
191
  cell = ipuz_board_get_cell_by_clue (board, clue, &coord);
543

            
544
191
  if (cell == NULL)
545
    /* We have a clue with a label/number that's not in the
546
     * grid.
547
     */
548
    return;
549

            
550
191
  direction = ipuz_clue_sets_get_original_direction (clue_sets,
551
                                                     ipuz_clue_get_direction (clue));
552

            
553
191
  if (! IPUZ_CLUE_DIRECTION_HEADING (direction))
554
    return;
555

            
556
1438
  while (cell != NULL && ipuz_cell_get_cell_type (cell) == IPUZ_CELL_NORMAL)
557
    {
558
1247
      ipuz_clue_append_cell (clue, coord);
559
1247
      ipuz_cell_set_clue (cell, clue);
560

            
561
1247
      switch (direction)
562
        {
563
646
        case IPUZ_CLUE_DIRECTION_ACROSS:
564
646
          coord.column ++;
565
646
          break;
566
601
        case IPUZ_CLUE_DIRECTION_DOWN:
567
601
          coord.row ++;
568
601
          break;
569
        case IPUZ_CLUE_DIRECTION_DIAGONAL:
570
          coord.column ++;
571
          coord.row ++;
572
          break;
573
        case IPUZ_CLUE_DIRECTION_DIAGONAL_UP:
574
          coord.column ++;
575
          coord.row --;
576
          break;
577
        case IPUZ_CLUE_DIRECTION_DIAGONAL_DOWN_LEFT:
578
          coord.column --;
579
          coord.row ++;
580
          break;
581
        case IPUZ_CLUE_DIRECTION_DIAGONAL_UP_LEFT:
582
          coord.column --;
583
          coord.row --;
584
          break;
585
        default:
586
          /* We don't know which way to go from here. Just include the
587
           * first cell */
588
          break;
589
        }
590
1247
      cell = ipuz_board_get_cell (board, coord);
591
    }
592
}
593

            
594
/* This function makes sure that each clue has its "cells" set correctly.
595
 * Either its explicitly set by the puzzle, or implicitly done by the board
596
 * itself. Either way, we need to link back from the Cell to the Clue.
597
 *
598
 * Question: Should we move this to IPuzClue? We should also split it in half.
599
 * */
600
static void
601
865
crossword_ensure_cells (IPuzClue     *clue,
602
                        IPuzClueSets *clue_sets,
603
                        IPuzBoard    *board)
604
{
605
  const GArray *cells;
606
  IPuzCellCoord coord;
607
  IPuzCell *cell;
608

            
609
865
  cells = ipuz_clue_get_cells (clue);
610

            
611
  /* Try to autodetect the cells */
612
865
  if (cells->len == 0)
613
191
    autodetect_cells_for_clue (clue, clue_sets, board);
614

            
615
  /* the clue already has cells loaded. */
616
865
  if (cells->len > 0)
617
    {
618
6076
      for (guint i = 0; i < cells->len; i ++)
619
        {
620
5211
          coord = g_array_index (clue->cells, IPuzCellCoord, i);
621
5211
          cell = ipuz_board_get_cell (board, coord);
622
5211
          ipuz_cell_set_clue (cell, clue);
623
        }
624
865
      return;
625
    }
626
}
627

            
628
/* Makes sure cells have their style associated with them */
629
static void
630
32
ipuz_crossword_fixup_cells (IPuzCrossword *self)
631
{
632
  IPuzCrosswordPrivate *priv;
633
32
  g_autoptr(GHashTable) styles = NULL;
634
  guint row, col;
635

            
636
32
  priv = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (self));
637

            
638
32
  g_object_get (G_OBJECT (self),
639
                "styles", &styles,
640
                NULL);
641

            
642
32
  if (styles == NULL)
643
28
    return;
644

            
645
56
  for (row = 0; row < (guint) priv->height; row++)
646
    {
647
740
      for (col = 0; col < (guint) priv->width; col++)
648
        {
649
          IPuzCell *cell;
650
688
          IPuzCellCoord coord = { .row = row, .column = col };
651

            
652
688
          cell = ipuz_crossword_get_cell (self, coord);
653
688
          if (cell->style_name)
654
            {
655
              IPuzStyle *style;
656

            
657
              /* FIXME: we shouldn't touch this directly. Instad, we
658
               * should add a get_style_name() function */
659
243
              style = g_hash_table_lookup (styles, cell->style_name);
660
243
              ipuz_cell_set_style (cell, style, cell->style_name);
661
            }
662

            
663
688
          if (IPUZ_CELL_IS_BLOCK (cell) && cell->style != NULL)
664
71
            priv->uses_extensions = TRUE;
665

            
666
688
          if (cell->saved_guess != NULL)
667
            priv->has_saved = TRUE;
668
        }
669
    }
670
}
671

            
672
static  void
673
63
ipuz_crossword_fixup_clues_helper (IPuzClueSets            *clue_sets,
674
                                   IPuzClueDirection        direction,
675
                                   gpointer                 user_data)
676
{
677
  IPuzCrosswordPrivate *priv;
678
  GArray *clues;
679

            
680
63
  priv = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (user_data));
681
63
  clues = ipuz_clue_sets_get_clues (clue_sets, direction);
682

            
683
928
  for (guint i = 0; i < clues->len; i++)
684
    {
685
      IPuzClue *clue;
686

            
687
865
      clue = g_array_index (clues, IPuzClue *, i);
688
865
      crossword_ensure_cells (clue, clue_sets, priv->board);
689
865
      if (priv->showenumerations)
690
441
        ipuz_clue_ensure_enumeration (clue);
691
    }
692
63
}
693

            
694
/* Make sure every clue has its "cells" set.
695
 * Update the enumeration if necessary
696
 */
697
static void
698
36
ipuz_crossword_fixup_clues (IPuzCrossword *self)
699
{
700
  IPuzCrosswordPrivate *priv;
701

            
702
36
  priv = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (self));
703

            
704
36
  ipuz_clue_sets_foreach (priv->clue_sets,
705
                          ipuz_crossword_fixup_clues_helper,
706
                          self);
707
36
}
708

            
709
static void
710
32
ipuz_crossword_fixup (IPuzPuzzle *puzzle)
711
{
712
  IPuzCrossword *self;
713

            
714
32
  self = IPUZ_CROSSWORD (puzzle);
715

            
716
32
  ipuz_crossword_fixup_clues (self);
717
32
  ipuz_crossword_fixup_cells (self);
718

            
719
32
  IPUZ_PUZZLE_CLASS (ipuz_crossword_parent_class)->fixup (puzzle);
720
32
}
721

            
722
static void
723
2
build_dimensions (IPuzCrossword *self,
724
                  JsonBuilder   *builder)
725
{
726
  IPuzCrosswordPrivate *priv;
727

            
728
2
  priv = ipuz_crossword_get_instance_private (self);
729

            
730
2
  json_builder_set_member_name (builder, "dimensions");
731
2
  json_builder_begin_object (builder);
732
2
  json_builder_set_member_name (builder, "width");
733
2
  json_builder_add_int_value (builder, priv->width);
734
2
  json_builder_set_member_name (builder, "height");
735
2
  json_builder_add_int_value (builder, priv->height);
736
2
  json_builder_end_object (builder);
737
2
}
738

            
739
static void
740
3
build_clues (IPuzCrossword *self,
741
             JsonBuilder   *builder,
742
             const gchar   *member,
743
             GArray        *clues)
744
{
745
  guint i;
746

            
747
3
  if (clues->len == 0)
748
    return;
749
3
  json_builder_set_member_name (builder, member);
750
3
  json_builder_begin_array (builder);
751
60
  for (i = 0; i < clues->len; i++)
752
    {
753
      IPuzClue *clue;
754
57
      clue = g_array_index (clues, IPuzClue *, i);
755
57
      ipuz_clue_build (clue, builder);
756
    }
757
3
  json_builder_end_array (builder);
758
}
759

            
760
typedef struct
761
{
762
  IPuzPuzzle *puzzle;
763
  JsonBuilder *builder;
764
} BuildHelperTuple;
765

            
766
static void
767
3
build_helper (IPuzClueSets      *clue_sets,
768
              IPuzClueDirection  direction,
769
              gpointer           user_data)
770
{
771
3
  BuildHelperTuple *tuple = user_data;
772
  IPuzClueDirection original_direction;
773
  const gchar *label;
774
  GArray *clues;
775

            
776
3
  original_direction = ipuz_clue_sets_get_original_direction (clue_sets, direction);
777
3
  label = ipuz_clue_sets_get_label (clue_sets, direction);
778
3
  clues = ipuz_clue_sets_get_clues (clue_sets, direction);
779

            
780
3
  if (label)
781
    {
782
3
      g_autofree gchar *member = NULL;
783
3
      member = g_strconcat (ipuz_clue_direction_to_string (original_direction),
784
                            ":", label, NULL);
785
3
      build_clues (IPUZ_CROSSWORD (tuple->puzzle), tuple->builder,
786
                   member, clues);
787
    }
788
  else
789
    {
790
      build_clues (IPUZ_CROSSWORD (tuple->puzzle), tuple->builder,
791
                   ipuz_clue_direction_to_string (original_direction),
792
                   clues);
793
    }
794
3
}
795

            
796

            
797
static void
798
2
ipuz_crossword_build (IPuzPuzzle  *puzzle,
799
                      JsonBuilder *builder)
800
{
801
  IPuzCrosswordPrivate *priv;
802
2
  g_autofree gchar *block = NULL;
803
2
  g_autofree gchar *empty = NULL;
804

            
805
2
  priv = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (puzzle));
806

            
807
2
  g_object_get (G_OBJECT (puzzle),
808
                "block", &block,
809
                "empty", &empty,
810
                NULL);
811

            
812
  /* We chain to the parent class first to get meta-information. Not
813
   * every parsing section can handle this coming at the end. */
814
2
  IPUZ_PUZZLE_CLASS (ipuz_crossword_parent_class)->build (puzzle, builder);
815

            
816
2
  build_dimensions (IPUZ_CROSSWORD (puzzle), builder);
817
2
  json_builder_set_member_name (builder, "showenumerations");
818
2
  json_builder_add_boolean_value (builder, priv->showenumerations);
819
2
  if (priv->clue_placement != IPUZ_CLUE_PLACEMENT_NULL)
820
    {
821
      const gchar *clueplacement = NULL;
822

            
823
      switch (priv->clue_placement)
824
        {
825
        case IPUZ_CLUE_PLACEMENT_BEFORE:
826
          clueplacement = "before";
827
          break;
828
        case IPUZ_CLUE_PLACEMENT_AFTER:
829
          clueplacement = "after";
830
          break;
831
        case IPUZ_CLUE_PLACEMENT_BLOCKS:
832
          clueplacement = "blocks";
833
          break;
834
        default:
835
          break;
836
        }
837
      if (clueplacement)
838
        {
839
          json_builder_set_member_name (builder, "clueplacement");
840
          json_builder_add_string_value (builder, clueplacement);
841
        }
842
    }
843

            
844
2
  ipuz_board_build_puzzle (priv->board, builder, block, empty);
845
2
  ipuz_board_build_solution (priv->board, builder, block);
846

            
847

            
848
2
  if (ipuz_clue_sets_get_n_clue_sets (priv->clue_sets) > 0)
849
    {
850
      BuildHelperTuple tuple;
851
2
      tuple.builder = builder;
852
2
      tuple.puzzle = puzzle;
853

            
854
2
      json_builder_set_member_name (builder, "clues");
855
2
      json_builder_begin_object (builder);
856

            
857
2
      ipuz_clue_sets_foreach (priv->clue_sets, build_helper, &tuple);
858

            
859
2
      json_builder_end_object (builder);
860
    }
861
2
}
862

            
863
static void
864
450
invalid_chars_foreach_cb (IPuzCrossword *crossword,
865
                          IPuzCell      *cell,
866
                          IPuzCellCoord  coord,
867
                          gpointer       user_data)
868
{
869
450
  IPuzPuzzleFlags *flags = user_data;
870
  const gchar *solution;
871
  IPuzCharset *charset;
872

            
873
450
  if (!IPUZ_CELL_IS_GUESSABLE (cell))
874
128
    return;
875

            
876
  /* if we've already detected invalid chars, we can exit */
877
322
  if (*flags & IPUZ_PUZZLE_FLAG_INVALID_CHARS)
878
    return;
879

            
880
322
  solution = ipuz_cell_get_solution (cell);
881
322
  if (solution == NULL)
882
    return;
883

            
884
322
  charset = ipuz_puzzle_get_charset (IPUZ_PUZZLE (crossword));
885

            
886
644
  for (const gchar *ptr = solution; *ptr; ptr = g_utf8_next_char (ptr))
887
    {
888
322
      gunichar c = g_utf8_get_char (ptr);
889
322
      if (ipuz_charset_get_char_count (charset, c) == 0)
890
        {
891
          *flags |= IPUZ_PUZZLE_FLAG_INVALID_CHARS;
892
          return;
893
        }
894
    }
895
}
896

            
897
static IPuzPuzzleFlags
898
2
ipuz_crossword_get_flags (IPuzPuzzle *puzzle)
899
{
900
  IPuzCrosswordPrivate *priv;
901
  guint flags;
902

            
903
2
  priv = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (puzzle));
904

            
905
2
  flags = IPUZ_PUZZLE_CLASS (ipuz_crossword_parent_class)->get_flags (puzzle);
906

            
907
  /* Do we have any clues at all? */
908
2
  for (guint i = 0; i < ipuz_clue_sets_get_n_clue_sets (priv->clue_sets); i++)
909
    {
910
      GArray *clues;
911

            
912
2
      clues = ipuz_clue_sets_get_clues (priv->clue_sets,
913
                                        ipuz_clue_sets_get_direction (priv->clue_sets, i));
914
2
      if (clues->len > 0)
915
        {
916
2
          flags |= IPUZ_PUZZLE_FLAG_HAS_CLUES;
917
2
          break;
918
        }
919
    }
920

            
921
2
  if (priv->uses_extensions)
922
    flags |= IPUZ_PUZZLE_FLAG_USES_EXTENSIONS;
923

            
924
2
  if (priv->has_solution)
925
2
    flags |= IPUZ_PUZZLE_FLAG_HAS_SOLUTION;
926

            
927
2
  if (priv->has_saved)
928
    flags |= IPUZ_PUZZLE_FLAG_HAS_SAVED;
929

            
930
2
  ipuz_crossword_foreach_cell (IPUZ_CROSSWORD (puzzle),
931
                               invalid_chars_foreach_cb,
932
                               &flags);
933

            
934
2
  return flags;
935
}
936

            
937
/* Used for calculate_info */
938
typedef struct
939
{
940
  IPuzPuzzle *puzzle;
941
  IPuzPuzzleInfo *info;
942
  IPuzCharsetBuilder *solution_chars_builder;
943
  IPuzCharsetBuilder *clue_lengths_builder;
944
} InfoTuple;
945

            
946
static void
947
450
calculate_cells_foreach_cb (IPuzCrossword *crossword,
948
                            IPuzCell      *cell,
949
                            IPuzCellCoord  coord,
950
                            gpointer       user_data)
951
{
952
450
  InfoTuple *info_tuple = user_data;
953

            
954
450
  if (IPUZ_CELL_IS_BLOCK (cell))
955
128
    info_tuple->info->cell_stats.block_count ++;
956
322
  else if (IPUZ_CELL_IS_NORMAL (cell))
957
322
    info_tuple->info->cell_stats.normal_count ++;
958
  else if (IPUZ_CELL_IS_NULL (cell))
959
    info_tuple->info->cell_stats.null_count ++;
960
  else
961
    g_assert_not_reached ();
962

            
963
450
  if (IPUZ_CELL_IS_NORMAL (cell))
964
    {
965
      const gchar *solution;
966

            
967
322
      solution = ipuz_cell_get_solution (cell);
968

            
969
322
      if (solution)
970
322
        ipuz_charset_builder_add_text (info_tuple->solution_chars_builder,
971
                                       solution);
972
    }
973
450
}
974

            
975
static void
976
76
calculate_clues_foreach_cb (IPuzCrossword     *xword,
977
                            IPuzClueDirection  direction,
978
                            IPuzClue          *clue,
979
                            IPuzClueId         clue_id,
980
                            gpointer           user_data)
981
{
982
76
  InfoTuple *info_tuple = user_data;
983
  const GArray *cells;
984

            
985
  /* Only set this flag if there is text for a clue */
986
76
  if (ipuz_clue_get_clue_text (clue))
987
76
    info_tuple->info->flags |= IPUZ_PUZZLE_FLAG_HAS_CLUES;
988

            
989
76
  cells = ipuz_clue_get_cells (clue);
990
76
  if (cells)
991
76
    ipuz_charset_builder_add_character (info_tuple->clue_lengths_builder,
992
76
                                        (gunichar) cells->len);
993
76
}
994

            
995
static void
996
2
calculate_pangram (IPuzPuzzle     *puzzle,
997
                   IPuzPuzzleInfo *info)
998
{
999
  IPuzCharsetIter *iter;
  IPuzCharset *charset;
2
  info->pangram_count = G_MAXUINT;
2
  charset = ipuz_puzzle_get_charset (puzzle);
2
  for (iter = ipuz_charset_iter_first (charset);
54
       iter;
52
       iter = ipuz_charset_iter_next (iter))
    {
      IPuzCharsetIterValue value;
52
      value = ipuz_charset_iter_get_value (iter);
52
      info->pangram_count =
52
        MIN (info->pangram_count, ipuz_charset_get_char_count (info->solution_chars, value.c));
    }
2
  if (info->pangram_count == G_MAXUINT)
    info->pangram_count = 0;
2
}
static void
2
ipuz_crossword_calculate_info (IPuzPuzzle     *puzzle,
                               IPuzPuzzleInfo *info)
{
  InfoTuple info_tuple;
2
  g_assert (IPUZ_IS_PUZZLE (puzzle));
2
  g_assert (IPUZ_IS_PUZZLE_INFO (info));
2
  IPUZ_PUZZLE_CLASS (ipuz_crossword_parent_class)->calculate_info (puzzle, info);
2
  info_tuple.puzzle = puzzle;
2
  info_tuple.info = info;
2
  info_tuple.solution_chars_builder = ipuz_charset_builder_new ();
2
  info_tuple.clue_lengths_builder = ipuz_charset_builder_new ();
2
  ipuz_crossword_foreach_cell (IPUZ_CROSSWORD (puzzle),
                               calculate_cells_foreach_cb,
                               &info_tuple);
2
  info->solution_chars = ipuz_charset_builder_build (info_tuple.solution_chars_builder);
2
  ipuz_crossword_foreach_clue (IPUZ_CROSSWORD (puzzle),
                               calculate_clues_foreach_cb,
                               &info_tuple);
2
  info->clue_lengths = ipuz_charset_builder_build (info_tuple.clue_lengths_builder);
2
  calculate_pangram (puzzle, info);
2
  info->flags = ipuz_puzzle_get_flags (puzzle);
2
}
/*
 * Copying boards is painful. They don't exist on their own but refer
 * to other portions of the crossword. There's not really a good way to
 * just make fresh copies of cells, as they refer to styles and clues
 * within the puzzle. We can copy the board and cells on the surface,
 * then refix them up afterwards. Regardless of which, Boards don't
 * exist outside the context of their Crossword in practice.
 */
static void
2
ipuz_crossword_deep_clone_board (IPuzCrossword *src,
                                 IPuzCrossword *dest)
{
  IPuzCrosswordPrivate *dest_priv;
2
  GHashTable *styles = NULL;
  guint row, column;
2
  dest_priv = ipuz_crossword_get_instance_private (dest);
2
  ipuz_board_resize (dest_priv->board, dest_priv->width, dest_priv->height);
2
  g_object_get (G_OBJECT (dest),
                "styles", &styles,
                NULL);
29
  for (row = 0; row < (guint) dest_priv->height; row++)
    {
476
      for (column = 0; column < (guint) dest_priv->width; column++)
        {
449
          IPuzCellCoord coord = { .row = row, .column = column };
          IPuzCell *src_cell, *dest_cell;
          IPuzStyle *style;
449
          src_cell = ipuz_crossword_get_cell (src, coord);
449
          dest_cell = ipuz_crossword_get_cell (dest, coord);
449
          dest_cell->cell_type = src_cell->cell_type;
449
          dest_cell->number = src_cell->number;
449
          dest_cell->label = g_strdup (src_cell->label);
449
          dest_cell->solution = g_strdup (src_cell->solution);
449
          dest_cell->initial_val = g_strdup (src_cell->initial_val);
449
          dest_cell->saved_guess = g_strdup (src_cell->saved_guess);
449
          dest_cell->style_name = g_strdup (src_cell->style_name);
449
          if (dest_cell->style_name && styles)
            {
              style = g_hash_table_lookup (styles, dest_cell->style_name);
              if (style)
                dest_cell->style = ipuz_style_ref (style);
            }
          /* update the clues. We will have already copied the clues before we attempt to do this */
449
	  if (dest_cell->clues == NULL)
449
            dest_cell->clues = g_array_new (FALSE, TRUE, sizeof (IPuzClue *));
449
	  if (src_cell->clues)
	    {
673
	      for (guint i = 0; i < src_cell->clues->len; i++)
	        {
	          IPuzClueId clue_id;
	          IPuzClue *src_clue, *dest_clue;
369
	          src_clue = g_array_index (src_cell->clues, IPuzClue *, i);
369
		  clue_id.direction = src_clue->direction;
369
	          clue_id.index = ipuz_crossword_find_clue (src, src_clue);
369
	          dest_clue = ipuz_crossword_get_clue_by_id (dest, clue_id);
369
	          g_array_append_val (dest_cell->clues, dest_clue);
	        }
	    }
          /* There's a chance the original crossword has a weird state
           * with a style_name and an independent style. We keep that
           * inconsistent state rather than trying to clean it up. */
449
          if (dest_cell->style == NULL)
449
            dest_cell->style = ipuz_style_copy (src_cell->style);
        }
    }
2
}
static void
2
ipuz_crossword_clone (IPuzPuzzle *src,
                      IPuzPuzzle *dest)
{
  IPuzCrosswordPrivate *src_priv, *dest_priv;
2
  if (src == NULL)
    return;
2
  g_assert (src != NULL);
2
  src_priv = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (src));
2
  dest_priv = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (dest));
  /* Chain up to start to get the meta information */
2
  IPUZ_PUZZLE_CLASS (ipuz_crossword_parent_class)->clone (src, dest);
2
  dest_priv->width = src_priv->width;
2
  dest_priv->height = src_priv->height;
2
  dest_priv->showenumerations = src_priv->showenumerations;
2
  dest_priv->clue_placement = src_priv->clue_placement;
2
  ipuz_clue_sets_clone (src_priv->clue_sets, dest_priv->clue_sets);
2
  ipuz_crossword_deep_clone_board (IPUZ_CROSSWORD (src),
                                   IPUZ_CROSSWORD (dest));
2
  dest_priv->guesses = ipuz_guesses_copy (src_priv->guesses);
2
  dest_priv->uses_extensions = src_priv->uses_extensions;
2
  dest_priv->has_solution = src_priv->has_solution;
2
  dest_priv->has_saved = src_priv->has_saved;
}
static const gchar *const *
1
ipuz_crossword_get_kind_str (IPuzPuzzle *puzzle)
{
  static const char *kind_str[] =
    {
      "http://ipuz.org/crossword#1",
      NULL
    };
1
  return kind_str;
}
typedef struct
{
  gpointer old_style;
  gpointer new_style;
} StyleSwapTuple;
static void
foreach_fixup_style (IPuzCrossword *crossword,
                     IPuzCell      *cell,
                     IPuzCellCoord  coord,
                     gpointer       user_data)
{
  StyleSwapTuple *tuple;
  tuple = (StyleSwapTuple *) user_data;
  if (cell->style == tuple->old_style)
    ipuz_cell_set_style (cell, tuple->new_style, NULL);
}
static void
3
ipuz_crossword_set_style (IPuzPuzzle  *puzzle,
                          const gchar *style_name,
                          IPuzStyle   *style)
{
  StyleSwapTuple tuple;
3
  tuple.old_style = ipuz_puzzle_get_style (puzzle, style_name);
3
  tuple.new_style = style;
  /* old_style may be unreffed after this call. Do not do anything
   * with it */
3
  IPUZ_PUZZLE_CLASS (ipuz_crossword_parent_class)->set_style (puzzle, style_name, style);
3
  if (tuple.old_style != NULL)
    ipuz_crossword_foreach_cell (IPUZ_CROSSWORD (puzzle),
                                 foreach_fixup_style, &tuple);
3
}
static gboolean
3
ipuz_crossword_equal (IPuzPuzzle *puzzle_a,
                      IPuzPuzzle *puzzle_b)
{
  IPuzCrosswordPrivate *priv_a, *priv_b;
3
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (puzzle_b), FALSE);
3
  priv_a = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (puzzle_a));
3
  priv_b = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (puzzle_b));
  /* clue_sets can never be NULL because it is initialized in
   * ipuz_crossword_init_clues */
3
  if (! ipuz_clue_sets_equal (priv_a->clue_sets, priv_b->clue_sets))
    return FALSE;
3
  return (priv_a->width == priv_b->width
3
          && priv_a->height == priv_b->height
3
          && priv_a->showenumerations == priv_b->showenumerations
3
          && ipuz_board_equal (priv_a->board, priv_b->board)
3
          && ipuz_guesses_equal (priv_a->guesses, priv_b->guesses)
3
          && priv_a->uses_extensions == priv_b->uses_extensions
3
          && priv_a->has_solution == priv_b->has_solution
3
          && priv_a->has_saved == priv_b->has_saved
3
          && priv_a->clue_placement == priv_b->clue_placement
6
          && IPUZ_PUZZLE_CLASS (ipuz_crossword_parent_class)->equal (puzzle_a, puzzle_b));
}
static gboolean
588
ipuz_crossword_real_clue_continues_up (IPuzCrossword *self,
                                       IPuzCellCoord  coord)
{
588
  if (coord.row > 0)
    {
548
      IPuzCellCoord neighbor_coord = coord;
      IPuzCell *neighbor_cell;
548
      neighbor_coord.row --;
548
      neighbor_cell = ipuz_crossword_get_cell (self, neighbor_coord);
548
      return IPUZ_CELL_IS_NORMAL (neighbor_cell);
    }
40
  return FALSE;
}
static gboolean
564
ipuz_crossword_real_clue_continues_down (IPuzCrossword *self,
                                         IPuzCellCoord  coord)
{
  IPuzCrosswordPrivate *priv;
564
  priv = ipuz_crossword_get_instance_private (self);
564
  if (coord.row < (guint)priv->height - 1)
    {
530
      IPuzCellCoord neighbor_coord = coord;
      IPuzCell *neighbor_cell;
530
      neighbor_coord.row ++;
530
      neighbor_cell = ipuz_crossword_get_cell (self, neighbor_coord);
530
      return IPUZ_CELL_IS_NORMAL (neighbor_cell);
    }
34
  return FALSE;
}
static gboolean
515
ipuz_crossword_real_clue_continues_left (IPuzCrossword *self,
                                         IPuzCellCoord  coord)
{
515
  if (coord.column > 0)
    {
476
      IPuzCellCoord neighbor_coord = coord;
      IPuzCell *neighbor_cell;
476
      neighbor_coord.column --;
476
      neighbor_cell = ipuz_crossword_get_cell (self, neighbor_coord);
476
      return IPUZ_CELL_IS_NORMAL (neighbor_cell);
    }
39
  return FALSE;
}
static gboolean
544
ipuz_crossword_real_clue_continues_right (IPuzCrossword *self,
                                          IPuzCellCoord  coord)
{
  IPuzCrosswordPrivate *priv;
544
  priv = ipuz_crossword_get_instance_private (self);
544
  if (coord.column < (guint)priv->width - 1)
    {
508
      IPuzCellCoord neighbor_coord = coord;
      IPuzCell *neighbor_cell;
508
      neighbor_coord.column ++;
508
      neighbor_cell = ipuz_crossword_get_cell (self, neighbor_coord);
508
      return IPUZ_CELL_IS_NORMAL (neighbor_cell);
    }
36
  return FALSE;
}
static void
17
ipuz_crossword_real_mirror_cell (IPuzCrossword      *self,
                                 IPuzCellCoord       src_coord,
                                 IPuzCellCoord       dest_coord,
                                 IPuzSymmetry        symmetry,
                                 IPuzSymmetryOffset  symmetry_offset)
{
  IPuzCell *src, *dest;
17
  src = ipuz_crossword_get_cell (self, src_coord);
17
  dest = ipuz_crossword_get_cell (self, dest_coord);
17
  ipuz_cell_set_cell_type (dest, ipuz_cell_get_cell_type (src));
17
}
static gboolean
451
ipuz_crossword_real_check_mirror (IPuzCrossword      *self,
                                  IPuzCellCoord       src_coord,
                                  IPuzCellCoord       target_coord,
                                  IPuzSymmetry        symmetry,
                                  IPuzSymmetryOffset  symmetry_offset)
{
  IPuzCell *src, *target;
451
  src = ipuz_crossword_get_cell (self, src_coord);
451
  target = ipuz_crossword_get_cell (self, target_coord);
451
  return (ipuz_cell_get_cell_type (src) == ipuz_cell_get_cell_type (target));
}
static void
3
ipuz_crossword_real_fix_symmetry  (IPuzCrossword *self,
                                   IPuzSymmetry   symmetry,
                                   GArray        *coords)
{
  IPuzCrosswordPrivate *priv;
3
  priv = ipuz_crossword_get_instance_private (self);
  /* Nothing to do */
3
  if (symmetry == IPUZ_SYMMETRY_NONE)
    return;
  /* Quarter-symmetric only makes sense if we're square */
3
  if (priv->width != priv->height)
    g_return_if_fail (symmetry != IPUZ_SYMMETRY_ROTATIONAL_QUARTER);
10
  for (guint i = 0; i < coords->len; i++)
    {
      IPuzCellCoord coord;
      IPuzCellCoord mirror_coord;
7
      coord = g_array_index (coords, IPuzCellCoord, i);
7
      mirror_coord = ipuz_symmetry_calculate (coord, priv->width, priv->height,
                                              symmetry, IPUZ_SYMMETRY_OFFSET_OPPOSITE);
7
      ipuz_crossword_mirror_cell (self, coord, mirror_coord, symmetry, IPUZ_SYMMETRY_OFFSET_OPPOSITE);
      /* Add the other two points as well */
7
      if (symmetry == IPUZ_SYMMETRY_ROTATIONAL_QUARTER ||
          symmetry == IPUZ_SYMMETRY_MIRRORED)
        {
5
          mirror_coord = ipuz_symmetry_calculate (coord, priv->width, priv->height,
                                                  symmetry, IPUZ_SYMMETRY_OFFSET_CW_ADJACENT);
5
          ipuz_crossword_mirror_cell (self, coord, mirror_coord, symmetry, IPUZ_SYMMETRY_OFFSET_CW_ADJACENT);
5
          mirror_coord = ipuz_symmetry_calculate (coord, priv->width, priv->height,
                                                  symmetry, IPUZ_SYMMETRY_OFFSET_CCW_ADJACENT);
5
          ipuz_crossword_mirror_cell (self, coord, mirror_coord, symmetry, IPUZ_SYMMETRY_OFFSET_CCW_ADJACENT);
        }
    }
}
static void
5
ipuz_crossword_real_fix_numbering (IPuzCrossword *self)
{
  IPuzCrosswordPrivate *priv;
  guint row, col;
5
  gint number = 1;
5
  priv = ipuz_crossword_get_instance_private (self);
66
  for (row = 0; row < (guint) priv->height; row++)
    {
900
      for (col = 0; col < (guint) priv->width; col++)
        {
839
          IPuzCellCoord coord = { .row = row, .column = col };
          IPuzCell *cell;
839
          cell = ipuz_crossword_get_cell (self, coord);
839
          if (! IPUZ_CELL_IS_NORMAL (cell))
201
            continue;
          /* Clear the label */
638
          ipuz_cell_set_label (cell, NULL);
1196
          if ((!ipuz_crossword_clue_continues_up (self, coord) && ipuz_crossword_clue_continues_down (self, coord)) ||
779
              (!ipuz_crossword_clue_continues_left (self, coord) && ipuz_crossword_clue_continues_right (self, coord)))
            {
150
              ipuz_cell_set_number (cell, number);
150
              number++;
            }
          else
            {
488
              ipuz_cell_set_number (cell, 0);
            }
        }
    }
5
}
static void
4
match_clue_sets (IPuzClueSets *src_clue_sets,
                 IPuzClueSets *dest_clue_sets)
{
4
  g_assert (src_clue_sets);
4
  g_assert (dest_clue_sets);
10
  for (guint n = 0; n < ipuz_clue_sets_get_n_clue_sets (src_clue_sets); n++)
    {
      IPuzClueDirection direction;
      GArray *src_clues;
      GArray *dest_clues;
6
      direction = ipuz_clue_sets_get_direction (src_clue_sets, n);
6
      src_clues = ipuz_clue_sets_get_clues (src_clue_sets, direction);
6
      dest_clues = ipuz_clue_sets_get_clues (dest_clue_sets, direction);
6
      g_assert (src_clues != NULL);
6
      if (dest_clues == NULL)
        continue;
98
      for (guint i = 0; i < src_clues->len; i++)
        {
          IPuzClue *src_clue;
          const GArray *src_cells;
92
          src_clue = g_array_index (src_clues, IPuzClue *, i);
92
          src_cells = ipuz_clue_get_cells (src_clue);
1939
          for (guint j = 0; j < dest_clues->len; j++)
            {
              IPuzClue *dest_clue;
              const GArray *dest_cells;
1847
              dest_clue = g_array_index (dest_clues, IPuzClue *, j);
1847
              dest_cells = ipuz_clue_get_cells (dest_clue);
1847
              if ((src_cells->len == dest_cells->len) &&
416
                  (memcmp (src_cells->data, dest_cells->data, src_cells->len * sizeof (IPuzCellCoord)) == 0))
                {
73
                  IPuzEnumeration *enumeration = ipuz_clue_get_enumeration (src_clue);
                  /* We don't need to set the label, number, direction, or
                   * location as we will have built that already in
                   * fix_clues () */
73
                  ipuz_clue_set_clue_text (dest_clue, ipuz_clue_get_clue_text (src_clue));
73
                  ipuz_clue_set_enumeration (dest_clue, enumeration);
73
                  ipuz_enumeration_unref (enumeration);
                }
            }
        }
    }
4
}
static void
4
ipuz_crossword_real_fix_clues (IPuzCrossword *self)
{
  IPuzCrosswordPrivate *priv;
4
  g_autoptr (IPuzClueSets) old_clue_sets = NULL;
4
  g_return_if_fail (IPUZ_IS_CROSSWORD (self));
4
  priv = ipuz_crossword_get_instance_private (self);
  /* Keep the old clue_set around to copy over clue labels and
   * enumerations. This comparison is unfortunately n^2, but I don't
   * think there's any way around that. */
4
  old_clue_sets = priv->clue_sets;
4
  priv->clue_sets = NULL;
4
  ipuz_crossword_init_clues (self);
50
  for (guint row = 0; row < (guint) priv->height; row++)
    {
660
      for (guint col = 0; col < (guint) priv->width; col++)
        {
          IPuzCell *cell;
614
          IPuzCellCoord coord = { .row = row, .column = col };
          gint number;
614
          cell = ipuz_crossword_get_cell (self, coord);
	  /* Clear out the old clues */
614
	  ipuz_cell_clear_clues (cell);
614
          number = ipuz_cell_get_number (cell);
614
          if (number > 0)
            {
              IPuzClue *across_clue;
              IPuzClue *down_clue;
120
              across_clue = calculate_clue (self, IPUZ_CLUE_DIRECTION_ACROSS, coord, number);
120
              down_clue = calculate_clue (self, IPUZ_CLUE_DIRECTION_DOWN, coord, number);
120
              if (across_clue)
63
                ipuz_clue_sets_append_clue (priv->clue_sets, IPUZ_CLUE_DIRECTION_ACROSS, across_clue);
120
              if (down_clue)
64
                ipuz_clue_sets_append_clue (priv->clue_sets, IPUZ_CLUE_DIRECTION_DOWN, down_clue);
            }
        }
    }
  /* Set up all the cells for the clues we just created */
4
  ipuz_crossword_fixup_clues (self);
4
  match_clue_sets (old_clue_sets, priv->clue_sets);
}
static void
86
ensure_enumeration (IPuzCrossword     *xword,
                    IPuzClueDirection  direction,
                    IPuzClue          *clue,
                    IPuzClueId         clue_id,
                    gpointer           user_data)
{
  IPuzEnumeration *enumeration;
  const GArray *cells;
86
  g_autofree gchar *str = NULL;
86
  enumeration = ipuz_clue_get_enumeration (clue);
86
  if (enumeration != NULL)
    {
86
      ipuz_enumeration_unref (enumeration);
86
      return;
    }
  /* create an enumeration for clue as if it had no deliminators */
  cells = ipuz_clue_get_cells (clue);
  str = g_strdup_printf ("%u", cells?cells->len:0);
  enumeration = ipuz_enumeration_new (str, IPUZ_VERBOSITY_STANDARD);
  ipuz_clue_set_enumeration (clue, enumeration);
  ipuz_enumeration_unref (enumeration);
}
static void
3
ipuz_crossword_real_fix_enumerations (IPuzCrossword *self)
{
3
  gboolean showenumerations = FALSE;
4
  g_return_if_fail (IPUZ_IS_CROSSWORD (self));
3
  g_object_get (self,
                "showenumerations", &showenumerations,
                NULL);
3
  if (!showenumerations)
1
    return;
2
  ipuz_crossword_foreach_clue (self, ensure_enumeration, NULL);
}
static void
569
foreach_fix_styles (IPuzCrossword *crossword,
                    IPuzCell      *cell,
                    IPuzCellCoord  coord,
                    gpointer       user_data)
{
  IPuzStyle *style;
569
  style = ipuz_cell_get_style (cell);
569
  if (style && ipuz_style_is_empty (style))
    ipuz_cell_set_style (cell, NULL, NULL);
569
}
static void
7
ipuz_crossword_real_fix_styles (IPuzCrossword *self)
{
7
  g_return_if_fail (IPUZ_IS_CROSSWORD (self));
7
  ipuz_crossword_foreach_cell (self, foreach_fix_styles, NULL);
}
static void
3
ipuz_crossword_real_fix_all (IPuzCrossword *self,
                             const gchar   *first_attribute_name,
                             va_list        var_args)
{
  const gchar *attribute_name;
3
  IPuzSymmetry symmetry = IPUZ_SYMMETRY_NONE;
3
  GArray *symmetry_coords = NULL; /* We don't own this */
  va_list var_args_copy;
3
  va_copy (var_args_copy, var_args);
3
  attribute_name = first_attribute_name;
7
  while (attribute_name)
    {
4
      if (! g_strcmp0 (attribute_name, "symmetry-coords"))
        {
2
          if (symmetry_coords != NULL)
            {
              g_warning ("symmetry-coords set multiple times");
              goto out;
            }
2
          symmetry_coords = va_arg (var_args_copy, GArray *);
        }
2
      else if (! g_strcmp0 (attribute_name, "symmetry"))
        {
2
          symmetry = va_arg (var_args_copy, IPuzSymmetry);
        }
4
      attribute_name = va_arg (var_args_copy, const gchar *);
    }
  /* Apply fixes to the puzzle */
3
  if (symmetry_coords != NULL)
2
    ipuz_crossword_fix_symmetry (self, symmetry, symmetry_coords);
3
  ipuz_crossword_fix_numbering (self);
3
  ipuz_crossword_fix_clues (self);
3
  ipuz_crossword_fix_enumerations (self);
3
  ipuz_crossword_fix_styles (self);
3
 out:
3
  va_end (var_args_copy);
3
}
/**
 * Public Methods
 */
IPuzCrossword *
ipuz_crossword_new (void)
{
  IPuzCrossword *xword;
  xword = g_object_new (IPUZ_TYPE_CROSSWORD, NULL);
  return xword;
}
guint
246
ipuz_crossword_get_width (IPuzCrossword *self)
{
  IPuzCrosswordPrivate *priv;
246
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), 0);
246
  priv = ipuz_crossword_get_instance_private (self);
246
  return priv->width;
}
guint
229
ipuz_crossword_get_height (IPuzCrossword *self)
{
  IPuzCrosswordPrivate *priv;
229
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), 0);
229
  priv = ipuz_crossword_get_instance_private (self);
229
  return priv->height;
}
static gboolean
38
ipuz_crossword_real_set_size (IPuzCrossword *self,
                              gint           width,
                              gint           height)
{
  IPuzCrosswordPrivate *priv;
38
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), FALSE);
38
  priv = ipuz_crossword_get_instance_private (self);
38
  if (priv->width == width && priv->height == height)
1
    return FALSE;
37
  priv->width = width;
37
  priv->height = height;
37
  if (priv->width > 0 && priv->height > 0)
37
    ipuz_board_resize (priv->board, width, height);
37
  return TRUE;
}
void
6
ipuz_crossword_set_size   (IPuzCrossword *self,
                           gint           width,
                           gint           height)
{
6
  g_return_if_fail (IPUZ_IS_CROSSWORD (self));
6
  g_return_if_fail (width > 0 && height > 0);
6
  g_object_freeze_notify (G_OBJECT (self));
6
  if (ipuz_crossword_real_set_size (self, width, height))
    {
5
      g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_WIDTH]);
5
      g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_HEIGHT]);
    }
6
  g_object_thaw_notify (G_OBJECT (self));
}
IPuzBoard *
13
ipuz_crossword_get_board (IPuzCrossword *self)
{
  IPuzCrosswordPrivate *priv;
13
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), NULL);
13
  priv = ipuz_crossword_get_instance_private (self);
13
  return priv->board;
}
static gboolean
ipuz_crossword_validate_guesses (IPuzCrossword *self,
                                 IPuzGuesses   *guesses)
{
  IPuzCrosswordPrivate *priv;
  guint row, col;
  priv = ipuz_crossword_get_instance_private (self);
  if ((guint) priv->width != ipuz_guesses_get_width (guesses) ||
      (guint) priv->height != ipuz_guesses_get_height (guesses))
    return FALSE;
  for (row = 0; row < (guint) priv->height; row++)
    {
      for (col = 0; col < (guint) priv->width; col++)
        {
          IPuzCell *cell;
          IPuzCellCellType guess_type;
          IPuzCellCoord coord = { .row = row, .column = col };
          cell = ipuz_crossword_get_cell (self, coord);
          guess_type = ipuz_guesses_get_cell_type (guesses, coord);
          /* We check to make sure that if the cell is guessable, we
           * can write to it. We ignore the remainder of the cells. */
          if (IPUZ_CELL_IS_GUESSABLE (cell) &&
              guess_type != IPUZ_CELL_NORMAL)
            return FALSE;
        }
    }
  return TRUE;
}
gboolean
ipuz_crossword_set_guesses (IPuzCrossword *self,
                            IPuzGuesses   *guesses)
{
  IPuzCrosswordPrivate *priv;
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), FALSE);
  priv = ipuz_crossword_get_instance_private (self);
  if (guesses)
    {
      if (! ipuz_crossword_validate_guesses (self, guesses))
        return FALSE;
      ipuz_guesses_ref (guesses);
    }
  g_clear_pointer (&priv->guesses, ipuz_guesses_unref);
  priv->guesses = guesses;
  g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_GUESSES]);
  return TRUE;
}
IPuzGuesses *
ipuz_crossword_get_guesses (IPuzCrossword *self)
{
  IPuzCrosswordPrivate *priv;
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), NULL);
  priv = ipuz_crossword_get_instance_private (self);
  return priv->guesses;
}
/* Convenience function. */
IPuzCell *
17939
ipuz_crossword_get_cell (IPuzCrossword *self,
                         IPuzCellCoord  coord)
{
  IPuzCrosswordPrivate *priv;
17939
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), NULL);
17939
  priv = ipuz_crossword_get_instance_private (self);
17939
  g_return_val_if_fail (priv->board != NULL, NULL);
17939
  return ipuz_board_get_cell (priv->board, coord);
}
/* Foreach functions */
void
4
ipuz_crossword_foreach_clue (IPuzCrossword                *self,
                             IPuzCrosswordForeachClueFunc  func,
                             gpointer                      data)
{
  IPuzCrosswordPrivate *priv;
4
  g_return_if_fail (IPUZ_IS_CROSSWORD (self));
4
  priv = ipuz_crossword_get_instance_private (IPUZ_CROSSWORD (self));
12
  for (guint i = 0; i < ipuz_clue_sets_get_n_clue_sets (priv->clue_sets); i++)
    {
      IPuzClueDirection direction;
      GArray *clues;
8
      direction = ipuz_clue_sets_get_direction (priv->clue_sets, i);
8
      clues = ipuz_clue_sets_get_clues (priv->clue_sets, direction);
170
      for (guint j = 0; j < clues->len; j++)
        {
          IPuzClue *clue;
162
          IPuzClueId clue_id = {
            .direction = direction,
            .index = j,
          };
162
          clue = g_array_index (clues, IPuzClue *, j);
162
          (func) (self, direction, clue, clue_id, data);
        }
    }
}
void
11
ipuz_crossword_foreach_cell (IPuzCrossword                *self,
                             IPuzCrosswordForeachCellFunc  func,
                             gpointer                      user_data)
{
  IPuzCrosswordPrivate *priv;
  guint row, col;
11
  g_return_if_fail (IPUZ_IS_CROSSWORD (self));
11
  priv = ipuz_crossword_get_instance_private (self);
123
  for (row = 0; row < (guint) priv->height; row++)
    {
1581
      for (col = 0; col < (guint) priv->width; col++)
        {
          IPuzCell *cell;
1469
          IPuzCellCoord coord = { .row = row, .column = col };
1469
          cell = ipuz_crossword_get_cell (self, coord);
1469
          (func) (self, cell, coord, user_data);
        }
    }
}
guint
33
ipuz_crossword_get_n_clue_sets (IPuzCrossword *self)
{
  IPuzCrosswordPrivate *priv;
33
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), 0);
33
  priv = ipuz_crossword_get_instance_private (self);
33
  return ipuz_clue_sets_get_n_clue_sets (priv->clue_sets);
}
IPuzClueDirection
15
ipuz_crossword_clue_set_get_dir (IPuzCrossword *self,
                                 guint          index)
{
  IPuzCrosswordPrivate *priv;
15
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), IPUZ_CLUE_DIRECTION_NONE);
15
  priv = ipuz_crossword_get_instance_private (self);
15
  return ipuz_clue_sets_get_direction (priv->clue_sets, index);
}
const gchar *
ipuz_crossword_clue_set_get_label (IPuzCrossword     *self,
                                   IPuzClueDirection  direction)
{
  IPuzCrosswordPrivate *priv;
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), NULL);
  priv = ipuz_crossword_get_instance_private (self);
  return ipuz_clue_sets_get_label (priv->clue_sets, direction);
}
GArray *
995
ipuz_crossword_get_clues (IPuzCrossword     *self,
                          IPuzClueDirection  direction)
{
  IPuzCrosswordPrivate *priv;
995
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), NULL);
995
  priv = ipuz_crossword_get_instance_private (self);
995
  return ipuz_clue_sets_get_clues (priv->clue_sets, direction);
}
IPuzClueId
ipuz_crossword_get_clue_id (IPuzCrossword  *self,
                            const IPuzClue *clue)
{
  IPuzClueId id = {
    .direction = IPUZ_CLUE_DIRECTION_NONE,
    .index = 0,
  };
  if (clue)
    {
      gboolean found = FALSE;
      GArray *clues;
      guint i;
      clues = ipuz_crossword_get_clues (self, clue->direction);
      if (clues == NULL)
        return id;
      for (i = 0; i < clues->len; i++)
        {
          IPuzClue *tmp_clue = g_array_index (clues, IPuzClue *, i);
          if (ipuz_clue_equal (clue, tmp_clue))
            {
              found = TRUE;
              break;
            }
        }
      if (found)
        {
          id.direction = clue->direction;
          id.index = i;
        }
    }
  return id;
}
IPuzClue *
369
ipuz_crossword_get_clue_by_id (IPuzCrossword *self,
                               IPuzClueId     clue_id)
{
  GArray *clues;
369
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), NULL);
369
  g_return_val_if_fail (! IPUZ_CLUE_ID_IS_UNSET (clue_id), NULL);
369
  clues = ipuz_crossword_get_clues (self, clue_id.direction);
369
  if (clues && clue_id.index < clues->len)
369
    return g_array_index (clues, IPuzClue *, clue_id.index);
  return NULL;
}
static void
2
unlink_clue_from_cells (IPuzCrossword *self,
                        IPuzClue      *clue)
{
2
  if (clue->cells == NULL)
    return;
28
  for (guint i = 0; i < clue->cells->len; i++)
    {
      IPuzCell *cell;
      IPuzCellCoord coord;
26
      coord = g_array_index (clue->cells, IPuzCellCoord, i);
26
      cell = ipuz_crossword_get_cell (self, coord);
26
      if (cell)
26
        ipuz_cell_clear_clue_direction (cell, clue->direction);
    }
}
void
2
ipuz_crossword_unlink_clue (IPuzCrossword *self,
                            IPuzClue      *clue)
{
  IPuzCrosswordPrivate *priv;
2
  g_return_if_fail (IPUZ_IS_CROSSWORD (self));
2
  g_return_if_fail (clue != NULL);
2
  priv = ipuz_crossword_get_instance_private (self);
  //  g_warning ("this function won't work correctly until we add refcounting to clues\n");
2
  unlink_clue_from_cells (self, clue);
2
  ipuz_clue_sets_remove_clue (priv->clue_sets, clue->direction, clue, TRUE);
}
guint
ipuz_crossword_get_n_clues (IPuzCrossword     *self,
                            IPuzClueDirection  direction)
{
  GArray *clues;
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), 0);
  clues = ipuz_crossword_get_clues (self, direction);
  if (clues)
    return clues->len;
  return 0;
}
guint
369
ipuz_crossword_find_clue (IPuzCrossword *self,
                          IPuzClue      *clue)
{
369
  GArray *clues = NULL;
369
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), 0);
369
  g_return_val_if_fail (clue != NULL, 0);
369
  clues = ipuz_crossword_get_clues (self, clue->direction);
4222
  for (guint i = 0; i < clues->len; i ++)
    {
4222
      IPuzClue *tmp_clue = g_array_index (clues, IPuzClue *, i);
4222
      if (ipuz_clue_equal (clue, tmp_clue))
369
        return i;
    }
  return 0;
}
static gchar *
ipuz_crossword_get_string (IPuzCrossword *self,
                           IPuzClueId     clue_id,
                           gboolean       guesses)
{
  IPuzCrosswordPrivate *priv;
  IPuzClue *clue;
  const GArray *cells;
  GString *string;
  guint i;
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), NULL);
  priv = ipuz_crossword_get_instance_private (self);
  clue = ipuz_crossword_get_clue_by_id (self, clue_id);
  if (clue == NULL)
    return NULL;
  string = g_string_new (NULL);
  cells = ipuz_clue_get_cells (clue);
  for (i = 0; i < cells->len; i++)
    {
      IPuzCellCoord coord;
      const char *solution = NULL;
      coord = g_array_index (cells, IPuzCellCoord, i);
      if (guesses)
        {
          solution = ipuz_guesses_get_guess (priv->guesses, coord);
        }
      else
        {
          IPuzCell *cell;
          cell = ipuz_crossword_get_cell (self, coord);
          solution = ipuz_cell_get_solution (cell);
        }
      if (solution && solution[0] !='\0')
        g_string_append (string, solution);
      else
        g_string_append (string, "?");
    }
  return g_string_free (string, FALSE);
}
/**
 * ipuz_crossword_get_clue_string_by_id:
 * @self: An 'IPuzCrossword'
 * @direction: Direction of the clue
 * @number: index of the clue
 *
 * Returns a string containing the solution of the puzzle for a given
 * clue. This string will have '?' characterd embedded within it if
 * there are cells without solutions set yet.
 *
 * Returns: The solution for the puzzle for Clue, or %NULL
 **/
gchar *
ipuz_crossword_get_clue_string_by_id (IPuzCrossword *self,
                                      IPuzClueId     clue_id)
{
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), NULL);
  return ipuz_crossword_get_string (self, clue_id, FALSE);
}
/**
 * ipuz_crossword_get_guess_string_by_id:
 * @self: An 'IPuzCrossword'
 * @direction: Direction of the clue
 * @number: index of the clue
 *
 * Returns a string containing the guess in the puzzle for a given
 * clue. This string will have '?' characterd embedded within it if
 * there are cells not completely filled out.
 *
 * Returns: The solution for the puzzle for Clue, or %NULL
 **/
gchar *
ipuz_crossword_get_guess_string_by_id (IPuzCrossword *self,
                                       IPuzClueId     clue_id)
{
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), NULL);
  return ipuz_crossword_get_string (self, clue_id, TRUE);
}
/**
 * ipuz_crossword_clue_guessed:
 * @self:  An 'IPuzCrossword'
 * @clue: The clue to check
 *
 * Check to see if every field in the clue is guessed. This doesn't
 * account for the correctness of the clue
 *
 * Returns: %TRUE, if every field in the clue is guessed.
 **/
gboolean
ipuz_crossword_clue_guessed (IPuzCrossword *self,
                             IPuzClue      *clue,
                             gboolean      *correct)
{
  IPuzCrosswordPrivate *priv;
  const GArray *cells;
  guint i;
  gboolean guessed = TRUE;
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), FALSE);
  g_return_val_if_fail (clue != NULL, FALSE);
  priv = ipuz_crossword_get_instance_private (self);
  cells = ipuz_clue_get_cells (clue);
  if (cells == NULL || cells->len == 0)
    return FALSE;
  if (priv->guesses == NULL)
    return FALSE;
  if (correct)
    *correct = TRUE;
  for (i = 0; i < cells->len; i++)
    {
      IPuzCellCoord coord;
      IPuzCell *cell;
      const char *guess = NULL;
      const char *solution = NULL;
      coord = g_array_index (cells, IPuzCellCoord, i);
      cell = ipuz_crossword_get_cell (self, coord);
      if (ipuz_cell_get_initial_val (cell))
        continue;
      guess = ipuz_guesses_get_guess (priv->guesses, coord);
      solution = ipuz_cell_get_solution (cell);
      if (guess == NULL || guess[0] == '\0')
        guessed = FALSE;
      if (correct)
        *correct = !g_strcmp0 (solution, guess) && *correct;
    }
  return guessed;
}
IPuzClue *
2
ipuz_crossword_find_clue_by_number (IPuzCrossword     *self,
                                    IPuzClueDirection  direction,
                                    gint               number)
{
2
  GArray *clues = NULL;
2
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), NULL);
2
  clues = ipuz_crossword_get_clues (self, direction);
6
  for (guint i = 0; i < clues->len; i ++)
    {
      IPuzClue *clue;
6
      clue = g_array_index (clues, IPuzClue *, i);
6
      if (clue->number == number)
2
        return clue;
    }
  return NULL;
}
IPuzClue *
ipuz_crossword_find_clue_by_label (IPuzCrossword     *self,
                                   IPuzClueDirection  direction,
                                   const gchar       *label)
{
  GArray *clues = NULL;
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), NULL);
  g_return_val_if_fail (label != NULL, NULL);
  clues = ipuz_crossword_get_clues (self, direction);
  for (guint i = 0; i < clues->len; i ++)
    {
      IPuzClue *clue;
      clue = g_array_index (clues, IPuzClue *, i);
      if (g_strcmp0 (label, clue->label) == 0)
        return clue;
    }
  return NULL;
}
IPuzClue *
240
ipuz_crossword_find_clue_by_coord (IPuzCrossword     *self,
                                   IPuzClueDirection  direction,
                                   IPuzCellCoord      coord)
{
240
 GArray *clues = NULL;
240
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), NULL);
240
  clues = ipuz_crossword_get_clues (self, direction);
240
  if (clues == NULL)
8
    return NULL;
2178
  for (guint i = 0; i < clues->len; i ++)
    {
      IPuzClue *clue;
2037
      clue = g_array_index (clues, IPuzClue *, i);
2037
      if (ipuz_clue_contains_cell (clue, coord))
91
        return clue;
    }
141
  return NULL;
}
static gboolean
ipuz_crossword_game_won_solution (IPuzCrossword *self)
{
  IPuzCrosswordPrivate *priv;
  guint row, col;
  priv = ipuz_crossword_get_instance_private (self);
  for (row = 0; row < (guint) priv->height; row++)
    {
      for (col = 0; col < (guint) priv->width; col++)
        {
          IPuzCell *cell;
          IPuzCellCoord coord = { .row = row, .column = col };
          cell = ipuz_crossword_get_cell (self, coord);
          if (IPUZ_CELL_IS_GUESSABLE (cell))
            {
              const char *guess = ipuz_guesses_get_guess (priv->guesses, coord);
              /* FIXME(charset): There are puzzles where the solution charset
               * doesn't match the solution and we have a bunch of
               * blank solution values. We want to catch that case.
               */
              /* FIXME(charset): should we normalize guess and solution first?*/
              if (guess == NULL)
                return FALSE;
              if (g_strcmp0 (guess, cell->solution))
                return FALSE;
            }
        }
    }
  return TRUE;
}
gboolean
ipuz_crossword_game_won (IPuzCrossword *self)
{
  guint flags;
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), FALSE);
  /* You can't win without playing! */
  if (ipuz_crossword_get_guesses (self) == NULL)
    return FALSE;
  flags = ipuz_puzzle_get_flags (IPUZ_PUZZLE (self));
  if (flags & IPUZ_PUZZLE_FLAG_HAS_SOLUTION)
    return ipuz_crossword_game_won_solution (self);
  if (flags & IPUZ_PUZZLE_FLAG_HAS_CHECKSUM)
    {
      /* FIXME(checksum): need to support this! */
    }
  return FALSE;
}
static void
solution_chars_foreach_cb (IPuzCrossword *crossword,
                           IPuzCell      *cell,
                           IPuzCellCoord  coord,
                           gpointer       user_data)
{
  IPuzCharsetBuilder *builder = user_data;
  ipuz_charset_builder_add_text (builder, ipuz_cell_get_solution (cell));
}
IPuzCharset *
ipuz_crossword_get_solution_chars (IPuzCrossword *self)
{
  IPuzCharsetBuilder *builder;
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), NULL);
  builder = ipuz_charset_builder_new ();
  ipuz_crossword_foreach_cell (self, solution_chars_foreach_cb, builder);
  return ipuz_charset_builder_build (builder);
}
static IPuzClue *
240
calculate_clue (IPuzCrossword     *self,
                IPuzClueDirection  direction,
                IPuzCellCoord      coord,
                gint               number)
{
  IPuzClue *clue;
  IPuzCell *cell;
240
  gboolean valid = FALSE;
240
  guint len = 0;
  /* If we're already in a clue, we don't want to create another one */
240
  if (ipuz_crossword_find_clue_by_coord (self, direction, coord) != NULL)
91
    return NULL;
  /* create a perspective new clue, and see if it's valid. We do this
   * by seeing if we can walk in direction and append the cell to the
   * clue. If our len is > 1, then it means that it's a valid
   * clue. Also, we do a dance between NORMAL cells and GESSABLE cells
   * because we can have a row of all guessable cells that shouldn't
   * be calculated. See the first puzzle as an example of this. */
149
  clue = ipuz_clue_new ();
149
  ipuz_clue_set_direction (clue, direction);
149
  ipuz_clue_set_number (clue, number);
149
  cell = ipuz_crossword_get_cell (self, coord);
736
  while (IPUZ_CELL_IS_NORMAL (cell))
    {
736
      ipuz_clue_append_cell (clue, coord);
736
      len ++;
736
      if (IPUZ_CELL_IS_GUESSABLE (cell))
729
        valid = TRUE;
1109
      if (direction == IPUZ_CLUE_DIRECTION_ACROSS &&
373
          !ipuz_crossword_clue_continues_right (self, coord))
76
        break;
1023
      if (direction == IPUZ_CLUE_DIRECTION_DOWN &&
363
          !ipuz_crossword_clue_continues_down (self, coord))
73
        break;
587
      if (direction == IPUZ_CLUE_DIRECTION_ACROSS)
297
        coord.column ++;
      else
290
        coord.row ++;
587
      cell = ipuz_crossword_get_cell (self, coord);
    }
149
  if (valid && len > 1)
127
    return clue;
22
  ipuz_clue_free (clue);
22
  return NULL;
}
/**
 * ipuz_crossword_get_symmetry:
 * @self: A `IPuzCrossword`
 *
 * Calculates the symmetry of @self. Note, there can be multiple valid
 * calculations for a board. For example, we can't say anything at all
 * about the symmetry for a blank, square board. This function returns
 * the first one that matches.
 *
 * Returns: The apparent symmetry of the puzzle.
 **/
IPuzSymmetry
2
ipuz_crossword_get_symmetry (IPuzCrossword *self)
{
  IPuzCrosswordPrivate *priv;
  guint row, col;
  /* We assume these are true until proved otherwise */
2
  gboolean is_half_rotational = TRUE;
2
  gboolean is_quarter_rotational = TRUE;
2
  gboolean is_horizontal = TRUE;
2
  gboolean is_vertical = TRUE;
2
  gboolean is_mirrored = TRUE;
2
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), IPUZ_SYMMETRY_NONE);
2
  priv = ipuz_crossword_get_instance_private (self);
  /* Quarter-symmetric only makes sense if we're square */
2
  if (priv->width != priv->height)
    is_quarter_rotational = FALSE;
14
  for (row = 0; row < (guint) priv->height; row++)
    {
159
      for (col = 0; col < (guint) priv->width; col++)
        {
147
          IPuzCellCoord coord = { .row = row, .column = col };
          IPuzCellCoord mirror_coord;
147
          if (is_half_rotational)
            {
147
              mirror_coord = ipuz_symmetry_calculate (coord, priv->width, priv->height, IPUZ_SYMMETRY_ROTATIONAL_HALF, IPUZ_SYMMETRY_OFFSET_OPPOSITE);
147
              if (! ipuz_crossword_check_mirror (self, coord, mirror_coord, IPUZ_SYMMETRY_ROTATIONAL_HALF, IPUZ_SYMMETRY_OFFSET_OPPOSITE))
                {
1
                  is_half_rotational = FALSE;
1
                  is_quarter_rotational = FALSE;
                }
            }
147
          if (is_quarter_rotational)
            {
146
              mirror_coord = ipuz_symmetry_calculate (coord, priv->width, priv->height, IPUZ_SYMMETRY_ROTATIONAL_QUARTER, IPUZ_SYMMETRY_OFFSET_CW_ADJACENT);
146
              if (! ipuz_crossword_check_mirror (self, coord, mirror_coord, IPUZ_SYMMETRY_ROTATIONAL_QUARTER, IPUZ_SYMMETRY_OFFSET_CW_ADJACENT))
                is_quarter_rotational = FALSE;
146
              mirror_coord = ipuz_symmetry_calculate (coord, priv->width, priv->height, IPUZ_SYMMETRY_ROTATIONAL_QUARTER, IPUZ_SYMMETRY_OFFSET_CCW_ADJACENT);
146
              if (! ipuz_crossword_check_mirror (self, coord, mirror_coord, IPUZ_SYMMETRY_ROTATIONAL_QUARTER, IPUZ_SYMMETRY_OFFSET_CCW_ADJACENT))
                is_quarter_rotational = FALSE;
            }
147
          if (is_horizontal)
            {
4
              mirror_coord = ipuz_symmetry_calculate (coord, priv->width, priv->height, IPUZ_SYMMETRY_HORIZONTAL, IPUZ_SYMMETRY_OFFSET_OPPOSITE);
4
              if (! ipuz_crossword_check_mirror (self, coord, mirror_coord, IPUZ_SYMMETRY_HORIZONTAL, IPUZ_SYMMETRY_OFFSET_OPPOSITE))
                {
2
                  is_horizontal = FALSE;
2
                  is_mirrored = FALSE;
                }
            }
147
          if (is_vertical)
            {
4
              mirror_coord = ipuz_symmetry_calculate (coord, priv->width, priv->height, IPUZ_SYMMETRY_VERTICAL, IPUZ_SYMMETRY_OFFSET_OPPOSITE);
4
              if (! ipuz_crossword_check_mirror (self, coord, mirror_coord, IPUZ_SYMMETRY_VERTICAL, IPUZ_SYMMETRY_OFFSET_OPPOSITE))
                {
2
                  is_vertical = FALSE;
2
                  is_mirrored = FALSE;
                }
            }
147
          if (is_mirrored)
            {
2
              mirror_coord = ipuz_symmetry_calculate (coord, priv->width, priv->height, IPUZ_SYMMETRY_MIRRORED, IPUZ_SYMMETRY_OFFSET_CW_ADJACENT);
2
              if (! ipuz_crossword_check_mirror (self, coord, mirror_coord, IPUZ_SYMMETRY_MIRRORED, IPUZ_SYMMETRY_OFFSET_CW_ADJACENT))
                is_mirrored = FALSE;
2
              mirror_coord = ipuz_symmetry_calculate (coord, priv->width, priv->height, IPUZ_SYMMETRY_MIRRORED, IPUZ_SYMMETRY_OFFSET_CCW_ADJACENT);
2
              if (! ipuz_crossword_check_mirror (self, coord, mirror_coord, IPUZ_SYMMETRY_MIRRORED, IPUZ_SYMMETRY_OFFSET_CCW_ADJACENT))
                is_mirrored = FALSE;
            }
          /* We've eliminated everything */
147
          if (! is_half_rotational &&
1
              ! is_quarter_rotational &&
1
              ! is_horizontal &&
1
              ! is_vertical &&
              ! is_mirrored)
            {
1
              return IPUZ_SYMMETRY_NONE;
            }
        }
    }
1
  if (is_quarter_rotational)
1
    return IPUZ_SYMMETRY_ROTATIONAL_QUARTER;
  else if (is_half_rotational)
    return IPUZ_SYMMETRY_ROTATIONAL_HALF;
  else if (is_mirrored)
    return IPUZ_SYMMETRY_MIRRORED;
  else if (is_horizontal)
    return IPUZ_SYMMETRY_HORIZONTAL;
  else if (is_vertical)
    return IPUZ_SYMMETRY_VERTICAL;
  else
    g_assert_not_reached ();
}
static void
8
print_clues (GArray   *clues,
             gboolean  showenumerations)
{
  guint i;
108
  for (i = 0; i < clues->len; i++)
    {
100
      IPuzClue *clue = g_array_index (clues, IPuzClue *, i);
100
      g_autoptr (IPuzEnumeration) enumeration = NULL;
100
      enumeration = ipuz_clue_get_enumeration (clue);
100
      g_print ("\t");
100
      if (ipuz_clue_get_number (clue) > 0)
43
        g_print ("%d. ", ipuz_clue_get_number (clue));
57
      else if (ipuz_clue_get_label (clue))
57
        g_print ("%s. ", ipuz_clue_get_label (clue));
100
      if (ipuz_clue_get_clue_text (clue))
90
        g_print ("%s ", ipuz_clue_get_clue_text (clue));
100
      if (showenumerations && enumeration)
        {
68
          g_autofree gchar *display = ipuz_enumeration_get_display (enumeration);
34
          g_print ("(%s)", display);
        }
100
      g_print ("\n");
100
      if (clue->cells->len > 0)
        {
100
          g_print ("\tcells: ");
711
          for (guint j = 0; j < clue->cells->len; j++)
            {
611
              IPuzCellCoord *coord = & (g_array_index (clue->cells, IPuzCellCoord, j));
611
              g_print ("[%u, %u] ", coord->row, coord->column);
            }
100
          g_print ("\n");
        }
    }
8
}
/* Correction API */
void
3
ipuz_crossword_fix_symmetry (IPuzCrossword *self,
                             IPuzSymmetry   symmetry,
                             GArray        *coords)
{
  IPuzCrosswordClass *klass;
3
  g_return_if_fail (IPUZ_IS_CROSSWORD (self));
3
  g_return_if_fail (coords != NULL);
3
  klass = IPUZ_CROSSWORD_GET_CLASS (self);
3
  return klass->fix_symmetry (self, symmetry, coords);
}
void
5
ipuz_crossword_fix_numbering (IPuzCrossword *self)
{
  IPuzCrosswordClass *klass;
5
  g_return_if_fail (IPUZ_IS_CROSSWORD (self));
5
  klass = IPUZ_CROSSWORD_GET_CLASS (self);
5
  return klass->fix_numbering (self);
}
void
4
ipuz_crossword_fix_clues (IPuzCrossword *self)
{
  IPuzCrosswordClass *klass;
4
  g_return_if_fail (IPUZ_IS_CROSSWORD (self));
4
  klass = IPUZ_CROSSWORD_GET_CLASS (self);
4
  return klass->fix_clues (self);
}
void
3
ipuz_crossword_fix_enumerations (IPuzCrossword *self)
{
  IPuzCrosswordClass *klass;
3
  g_return_if_fail (IPUZ_IS_CROSSWORD (self));
3
  klass = IPUZ_CROSSWORD_GET_CLASS (self);
3
  return klass->fix_enumerations (self);
}
void
7
ipuz_crossword_fix_styles (IPuzCrossword *self)
{
  IPuzCrosswordClass *klass;
7
  g_return_if_fail (IPUZ_IS_CROSSWORD (self));
7
  klass = IPUZ_CROSSWORD_GET_CLASS (self);
7
  return klass->fix_styles (self);
}
void
6
ipuz_crossword_fix_all (IPuzCrossword *self,
                        const char    *first_attribute_name,
                        ...)
{
  IPuzCrosswordClass *klass;
  va_list var_args;
6
  g_return_if_fail (IPUZ_IS_CROSSWORD (self));
6
  klass = IPUZ_CROSSWORD_GET_CLASS (self);
6
  va_start (var_args, first_attribute_name);
6
  klass->fix_all (self, first_attribute_name, var_args);
6
  va_end (var_args);
}
gboolean
638
ipuz_crossword_clue_continues_up (IPuzCrossword *self,
                                  IPuzCellCoord  coord)
{
  IPuzCrosswordClass *klass;
638
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), FALSE);
638
  klass = IPUZ_CROSSWORD_GET_CLASS (self);
638
  return klass->clue_continues_up (self, coord);
}
gboolean
618
ipuz_crossword_clue_continues_down (IPuzCrossword *self,
                                    IPuzCellCoord  coord)
{
  IPuzCrosswordClass *klass;
618
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), FALSE);
618
  klass = IPUZ_CROSSWORD_GET_CLASS (self);
618
  return klass->clue_continues_down (self, coord);
}
gboolean
558
ipuz_crossword_clue_continues_left (IPuzCrossword *self,
                                    IPuzCellCoord  coord)
{
  IPuzCrosswordClass *klass;
558
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), FALSE);
558
  klass = IPUZ_CROSSWORD_GET_CLASS (self);
558
  return klass->clue_continues_left (self, coord);
}
gboolean
594
ipuz_crossword_clue_continues_right (IPuzCrossword *self,
                                     IPuzCellCoord  coord)
{
  IPuzCrosswordClass *klass;
594
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), FALSE);
594
  klass = IPUZ_CROSSWORD_GET_CLASS (self);
594
  return klass->clue_continues_right (self, coord);
}
void
17
ipuz_crossword_mirror_cell (IPuzCrossword      *self,
                            IPuzCellCoord       src_coord,
                            IPuzCellCoord       dest_coord,
                            IPuzSymmetry        symmetry,
                            IPuzSymmetryOffset  symmetry_offset)
{
  IPuzCrosswordClass *klass;
17
  g_return_if_fail (IPUZ_IS_CROSSWORD (self));
17
  klass = IPUZ_CROSSWORD_GET_CLASS (self);
17
  klass->mirror_cell (self, src_coord, dest_coord, symmetry, symmetry_offset);
}
gboolean
451
ipuz_crossword_check_mirror (IPuzCrossword      *self,
                             IPuzCellCoord       src_coord,
                             IPuzCellCoord       target_coord,
                             IPuzSymmetry        symmetry,
                             IPuzSymmetryOffset  symmetry_offset)
{
  IPuzCrosswordClass *klass;
451
  g_return_val_if_fail (IPUZ_IS_CROSSWORD (self), FALSE);
451
  klass = IPUZ_CROSSWORD_GET_CLASS (self);
451
  return klass->check_mirror (self, src_coord, target_coord, symmetry, symmetry_offset);
}
/* Public Functions */
void
4
ipuz_crossword_print (IPuzCrossword *self)
{
4
  g_return_if_fail (IPUZ_CROSSWORD (self));
  /* Puzzle */
4
  g_autofree gchar *copyright = NULL;
4
  g_autofree gchar *publisher = NULL;
4
  g_autofree gchar *publication = NULL;
4
  g_autofree gchar *url = NULL;
4
  g_autofree gchar *uniquieid = NULL;
4
  g_autofree gchar *title = NULL;
4
  g_autofree gchar *intro = NULL;
4
  g_autofree gchar *explanation = NULL;
4
  g_autofree gchar *annotation = NULL;
4
  g_autofree gchar *author = NULL;
4
  g_autofree gchar *editor = NULL;
4
  g_autofree gchar *date = NULL;
4
  g_autofree gchar *notes = NULL;
4
  g_autofree gchar *difficulty = NULL;
4
  g_autofree gchar *charset_str = NULL;
4
  g_autofree gchar *origin = NULL;
4
  g_autofree gchar *block = NULL;
4
  g_autofree gchar *empty = NULL;
  /* Crossword */
  gint width;
  gint height;
  gboolean showenumerations;
  IPuzCluePlacement clue_placement;
  IPuzBoard *board;
  IPuzCrosswordPrivate *priv;
4
  char ESC=27;
4
  g_object_get (G_OBJECT (self),
                "title", &title,
                "copyright", &copyright,
                "publisher", &publisher,
                "publication", &publication,
                "url", &url,
                "uniqueid", &uniquieid,
                "intro", &intro,
                "explanation", &explanation,
                "annotation", &annotation,
                "author", &author,
                "editor", &editor,
                "date", &date,
                "notes", &notes,
                "difficulty", &difficulty,
                "charset-str", &charset_str,
                "origin", &origin,
                "block", &block,
                "empty", &empty,
                "showenumerations", &showenumerations,
                "clue-placement", &clue_placement,
                "width", &width,
                "height", &height,
                "board", &board,
                NULL);
4
  priv = ipuz_crossword_get_instance_private (self);
4
  g_print ("\n");
  /* Print the title section */
4
  g_print ("%c[1mTitle: %s%c[0m\n", ESC, title?title:"(null)", ESC);
  /* Print the type */
4
  if (IPUZ_IS_ACROSTIC (self))
1
    g_print ("\tType: Acrostic Puzzle\n");
3
  else if (IPUZ_IS_ARROWWORD (self))
    g_print ("\tType: Arrowword Puzzle\n");
3
  else if (IPUZ_IS_BARRED (self))
    g_print ("\tType: Barred Puzzle\n");
3
  else if (IPUZ_IS_CRYPTIC (self))
1
    g_print ("\tType: Cryptic Crossword Puzzle\n");
2
  else if (IPUZ_IS_FILIPPINE (self))
    g_print ("\tType: Filippine Puzzle\n");
  else /* Default */
2
    g_print ("\tType: Crossword Puzzle\n");
4
  if (author || editor)
    {
3
      if (author) g_print ("\tby %s\t", author);
3
      if (editor) g_print ("\tedited by %s", editor);
3
      g_print ("\n");
    }
4
  if (copyright) g_print ("\tCopyright: %s\n", copyright);
4
  if (title || author || editor || copyright || date) g_print ("\n");
59
  for (int i = 0; i < width + 1; i++)
55
    g_print ("██");
4
  g_print ("\n");
49
  for (int row = 0; row < height; row ++)
    {
45
      g_print ("█");
728
      for (int column = 0; column < width; column ++)
        {
683
          IPuzCellCoord coord = { .row = row, .column = column };
683
          IPuzCell *cell = ipuz_board_get_cell (board, coord);
          gint n;
683
          switch (ipuz_cell_get_cell_type (cell))
            {
115
            case IPUZ_CELL_BLOCK:
115
              g_print ("▓▓");
115
              break;
98
            case IPUZ_CELL_NULL:
98
              g_print ("▞▚");
98
              break;
470
            case IPUZ_CELL_NORMAL:
470
              n = ipuz_cell_get_number (cell);
470
              if (n == 0)
425
                g_print ("  ");
              else
45
                g_print ((n<10?"%d ":"%d"),n);
470
              break;
            }
        }
45
      g_print ("█\n█");
728
      for (int column = 0; column < width; column ++)
        {
683
          const gchar *solution = NULL;
683
          IPuzCellCoord coord = { .row = row, .column = column };
683
          IPuzCell *cell = ipuz_board_get_cell (board, coord);
683
          switch (ipuz_cell_get_cell_type (cell))
            {
115
            case IPUZ_CELL_BLOCK:
115
              g_print ("▓▓");
115
              break;
98
            case IPUZ_CELL_NULL:
98
              g_print ("▚▞");
98
              break;
470
            case IPUZ_CELL_NORMAL:
470
               solution = ipuz_cell_get_solution (cell);
470
              if (solution)
468
                g_print (" %s", solution);
              else
2
                g_print ("  ");
470
              break;
            }
        }
45
      g_print ("█\n");
    }
59
  for (int column = 0; column < width + 1; column++)
55
    g_print ("██");
4
  g_print ("\n\n");
  GArray *clues;
4
  if (ipuz_crossword_get_n_clue_sets (self) > 0)
4
    g_print ("%c[1mClues%c[0m\n", ESC, ESC);
12
  for (guint i = 0; i < ipuz_crossword_get_n_clue_sets (self); i++)
    {
      IPuzClueDirection direction;
      IPuzClueDirection orig_direction;
8
      direction = ipuz_crossword_clue_set_get_dir (self, i);
8
      orig_direction = ipuz_clue_sets_get_original_direction (priv->clue_sets, direction);
8
      clues = ipuz_crossword_get_clues (self, direction);
8
      g_print ("\t%s:", ipuz_clue_direction_to_string (orig_direction));
8
      if (orig_direction != direction)
2
        g_print ("%s\n", ipuz_clue_sets_get_label (priv->clue_sets, direction));
      else
6
        g_print ("\n");
8
      print_clues (clues, showenumerations);
8
      g_print ("\n");
    }
4
  if (ipuz_crossword_get_n_clue_sets (self) > 0)
4
    g_print ("\n");
4
  g_print ("%c[1mDocument Information%c[0m\n", ESC, ESC);
4
  if (date) g_print ("\tDate: %s\n", date);
4
  if (publisher) g_print ("\tPublisher: %s\n", publisher);
4
  if (publication) g_print ("\tPublication: %s\n", publication);
4
  if (url) g_print ("\tURL: %s\n:", url);
4
  if (uniquieid) g_print ("\tUnique ID:%s\n", uniquieid);
4
  if (difficulty) g_print ("\tDifficulty:%s\n", difficulty);
4
  g_print ("\n");
4
  g_print ("%c[1mDisplay Information%c[0m\n", ESC, ESC);
4
  if (block) g_print ("\tBlock string: '%s'\n", block);
4
  if (empty) g_print ("\tEmpty cell string: '%s'\n", empty);
4
  if (charset_str) g_print ("\tValid charset-str: '%s'\n", charset_str);
4
  g_print ("\tShow enumerations: %s\n", showenumerations?"true":"false");
4
  switch (clue_placement)
    {
4
    case IPUZ_CLUE_PLACEMENT_NULL:
4
      g_print ("\tClue placement: null\n");
4
      break;
    case IPUZ_CLUE_PLACEMENT_BEFORE:
      g_print ("\tClue placement: before\n");
      break;
    case IPUZ_CLUE_PLACEMENT_AFTER:
      g_print ("\tClue placement: after\n");
      break;
    case IPUZ_CLUE_PLACEMENT_BLOCKS:
      g_print ("\tClue placement: blocks\n");
      break;
    }
4
  g_print ("\n");
#if 0
  "intro", &intro,
  "explanation", &explanation,
  "annotation", &annotation,
  "origin", &origin,
#endif
4
  g_object_unref (board);
}