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

            
22

            
23
#include "libipuz-config.h"
24
#include <libipuz/ipuz-acrostic.h>
25
#include <libipuz/ipuz-charset.h>
26
#include "acrostic-board-dimensions.h"
27
#include "ipuz-magic.h"
28

            
29
enum
30
{
31
  PROP_0,
32
  PROP_QUOTE_STR,
33
  PROP_CHARSET,
34
  N_PROPS
35
};
36

            
37
static GParamSpec *obj_props[N_PROPS] = { NULL, };
38

            
39
typedef struct _IPuzAcrosticPrivate
40
{
41
  gchar *quote_str;
42
  IPuzCharset *charset;
43
  IPuzClue *quote_clue;
44
} IPuzAcrosticPrivate;
45

            
46

            
47
static void                ipuz_acrostic_init         (IPuzAcrostic      *self);
48
static void                ipuz_acrostic_class_init   (IPuzAcrosticClass *klass);
49
static void                ipuz_acrostic_set_property (GObject           *object,
50
                                                       guint              prop_id,
51
                                                       const GValue      *value,
52
                                                       GParamSpec        *pspec);
53
static void                ipuz_acrostic_get_property (GObject           *object,
54
                                                       guint              prop_id,
55
                                                       GValue            *value,
56
                                                       GParamSpec        *pspec);
57
static void                ipuz_acrostic_finalize     (GObject           *object);
58
static void                ipuz_acrostic_clone        (IPuzPuzzle        *src,
59
                                                       IPuzPuzzle        *dest);
60
static gboolean            ipuz_acrostic_equal        (IPuzPuzzle        *puzzle_a,
61
                                                       IPuzPuzzle        *puzzle_b);
62
static void                ipuz_acrostic_fixup        (IPuzPuzzle        *puzzle);
63
static void                ipuz_acrostic_real_fix_all (IPuzCrossword     *self,
64
                                                       const gchar       *first_attribute_name,
65
                                                       va_list            var_args);
66
static const gchar *const *ipuz_acrostic_get_kind_str (IPuzPuzzle        *puzzle);
67

            
68

            
69
1
G_DEFINE_TYPE_WITH_CODE (IPuzAcrostic, ipuz_acrostic, IPUZ_TYPE_CROSSWORD,
70
		         G_ADD_PRIVATE (IPuzAcrostic));
71

            
72
static void
73
13
ipuz_acrostic_init (IPuzAcrostic *self)
74
{
75
  /* Pass */
76
13
}
77

            
78
static void
79
1
ipuz_acrostic_class_init (IPuzAcrosticClass *klass)
80
{
81
1
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
82
1
  IPuzPuzzleClass *puzzle_class = IPUZ_PUZZLE_CLASS (klass);
83
1
  IPuzCrosswordClass *crossword_class = IPUZ_CROSSWORD_CLASS (klass);
84

            
85
1
  object_class->set_property = ipuz_acrostic_set_property;
86
1
  object_class->get_property = ipuz_acrostic_get_property;
87
1
  object_class->finalize = ipuz_acrostic_finalize;
88
1
  puzzle_class->clone = ipuz_acrostic_clone;
89
1
  puzzle_class->equal = ipuz_acrostic_equal;
90
1
  puzzle_class->fixup = ipuz_acrostic_fixup;
91
1
  puzzle_class->get_kind_str = ipuz_acrostic_get_kind_str;
92
1
  crossword_class->fix_all = ipuz_acrostic_real_fix_all;
93

            
94
1
  obj_props[PROP_QUOTE_STR] = g_param_spec_string ("quote-string",
95
                                                   "Quote string",
96
                                                   "Quote string",
97
                                                   NULL,
98
                                                   G_PARAM_READWRITE);
99

            
100
1
  obj_props[PROP_CHARSET] = g_param_spec_pointer ("lang-charset",
101
                                                  "Language Charset",
102
                                                  "Language Charset",
103
                                                  G_PARAM_READWRITE);
104

            
105
1
  g_object_class_install_properties (object_class, N_PROPS, obj_props);
106
1
}
107

            
108
static void
109
7
ipuz_acrostic_set_property (GObject      *object,
110
                            guint         prop_id,
111
                            const GValue *value,
112
                            GParamSpec   *pspec)
113
{
114
  IPuzAcrosticPrivate *priv;
115

            
116
7
  g_return_if_fail (object != NULL);
117

            
118
7
  priv = ipuz_acrostic_get_instance_private (IPUZ_ACROSTIC (object));
119

            
120
7
  switch (prop_id)
121
    {
122
      case PROP_QUOTE_STR:
123
        ipuz_acrostic_set_quote_str (IPUZ_ACROSTIC (object), g_value_get_string (value));
124
        break;
125
7
      case PROP_CHARSET:
126
7
        if (priv->charset != NULL)
127
2
          ipuz_charset_unref (priv->charset);
128
7
        priv->charset = g_value_get_pointer (value);
129
7
        break;
130
      default:
131
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
132
        break;
133
    }
134
}
135

            
136
static void
137
ipuz_acrostic_get_property (GObject    *object,
138
                            guint       prop_id,
139
                            GValue     *value,
140
                            GParamSpec *pspec)
141
{
142
  IPuzAcrosticPrivate *priv;
143

            
144
  g_return_if_fail (object != NULL);
145

            
146
  priv = ipuz_acrostic_get_instance_private (IPUZ_ACROSTIC (object));
147

            
148
  switch (prop_id)
149
    {
150
      case PROP_QUOTE_STR:
151
        g_value_set_string (value, priv->quote_str);
152
        break;
153
      case PROP_CHARSET:
154
        g_value_set_pointer (value, priv->charset);
155
        break;
156
      default:
157
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
158
        break;
159
    }
160
}
161

            
162
static void
163
13
ipuz_acrostic_finalize (GObject *object)
164
{
165
  IPuzAcrosticPrivate *priv;
166

            
167
13
  g_return_if_fail (object != NULL);
168

            
169
13
  priv = ipuz_acrostic_get_instance_private (IPUZ_ACROSTIC (object));
170

            
171
13
  g_clear_pointer (&priv->quote_str, g_free);
172
13
  if (priv->charset != NULL)
173
5
    ipuz_charset_unref (priv->charset);
174
13
  ipuz_clue_free (priv->quote_clue);
175

            
176
13
  G_OBJECT_CLASS (ipuz_acrostic_parent_class)->finalize (object);
177
}
178

            
179
static void
180
1
ipuz_acrostic_clone (IPuzPuzzle *src,
181
                     IPuzPuzzle *dest)
182
{
183
  IPuzAcrosticPrivate *src_priv, *dest_priv;
184

            
185
1
  g_assert (src != NULL);
186
1
  g_assert (dest != NULL);
187

            
188
1
  src_priv = ipuz_acrostic_get_instance_private (IPUZ_ACROSTIC (src));
189
1
  dest_priv = ipuz_acrostic_get_instance_private (IPUZ_ACROSTIC (dest));
190

            
191
1
  IPUZ_PUZZLE_CLASS (ipuz_acrostic_parent_class)->clone (src, dest);
192

            
193
1
  dest_priv->quote_clue = ipuz_clue_copy (src_priv->quote_clue);
194
1
}
195

            
196
static IPuzClue *
197
11
calculate_quote_clue (IPuzAcrostic *self)
198
{
199
11
  IPuzCrossword *xword = IPUZ_CROSSWORD (self);
200
11
  IPuzClue *quote_clue = ipuz_clue_new ();
201

            
202
11
  guint rows = ipuz_crossword_get_height (xword);
203
11
  guint columns = ipuz_crossword_get_width (xword);
204
  guint row, column;
205

            
206
89
  for (row = 0; row < rows; row++)
207
    {
208
1293
      for (column = 0; column < columns; column++)
209
        {
210
          IPuzCell *cell;
211
1215
          IPuzCellCoord coord = {
212
           .row = row,
213
           .column = column,
214
         };
215

            
216
1215
         cell = ipuz_crossword_get_cell (xword, coord);
217

            
218
1215
         if (IPUZ_CELL_IS_GUESSABLE (cell))
219
           {
220
1012
             g_array_append_val (quote_clue->cells, coord);
221
           }
222
       }
223
    }
224

            
225
11
  return quote_clue;
226
}
227

            
228
static IPuzClue *
229
7
extract_quote_clue (IPuzAcrostic *self)
230
{
231
12
  for (guint n = 0; n < ipuz_crossword_get_n_clue_sets (IPUZ_CROSSWORD (self)); n++)
232
    {
233
      GArray *clues;
234

            
235
7
      clues = ipuz_crossword_get_clues (IPUZ_CROSSWORD (self),
236
                                        ipuz_crossword_clue_set_get_dir (IPUZ_CROSSWORD (self), n));
237
7
      g_assert (clues);
238
114
      for (guint i = 0; i < clues->len; i++)
239
        {
240
109
          IPuzClue *clue = g_array_index (clues, IPuzClue *, i);
241
109
          if (g_strcmp0 (ipuz_clue_get_clue_text (clue), _IPUZ_ACROSTIC_QUOTE_STR) == 0)
242
            {
243
              IPuzClue *quote_clue;
244
2
              quote_clue = ipuz_clue_copy (clue);
245
2
              ipuz_crossword_unlink_clue (IPUZ_CROSSWORD (self), clue);
246

            
247
2
              ipuz_clue_set_direction (quote_clue, IPUZ_CLUE_DIRECTION_NONE);
248
2
              ipuz_clue_set_clue_text (quote_clue, NULL);
249
2
              return quote_clue;
250
            }
251
        }
252
    }
253

            
254
5
  return NULL;
255
}
256

            
257
static void
258
7
fix_quote_clue (IPuzAcrostic *self)
259
{
260
  IPuzAcrosticPrivate *priv;
261

            
262
7
  priv = ipuz_acrostic_get_instance_private (self);
263

            
264
7
  priv->quote_clue = extract_quote_clue (self);
265

            
266
7
  if (priv->quote_clue == NULL)
267
5
    priv->quote_clue = calculate_quote_clue (self);
268
7
}
269

            
270
static gboolean
271
2
ipuz_acrostic_equal (IPuzPuzzle *puzzle_a,
272
                     IPuzPuzzle *puzzle_b)
273
{
274
  IPuzAcrosticPrivate *priv_a, *priv_b;
275

            
276
2
  g_return_val_if_fail (IPUZ_IS_ACROSTIC (puzzle_b), FALSE);
277

            
278
2
  priv_a = ipuz_acrostic_get_instance_private (IPUZ_ACROSTIC (puzzle_a));
279
2
  priv_b = ipuz_acrostic_get_instance_private (IPUZ_ACROSTIC (puzzle_b));
280

            
281
2
  if (! ipuz_clue_equal (priv_a->quote_clue, priv_b->quote_clue))
282
    return FALSE;
283

            
284
2
  return IPUZ_PUZZLE_CLASS (ipuz_acrostic_parent_class)->equal (puzzle_a,
285
                                                                puzzle_b);
286
}
287

            
288
static void
289
7
ipuz_acrostic_fixup (IPuzPuzzle *puzzle)
290
{
291
7
  IPUZ_PUZZLE_CLASS (ipuz_acrostic_parent_class) -> fixup (puzzle);
292

            
293
7
  fix_quote_clue (IPUZ_ACROSTIC (puzzle));
294
7
}
295

            
296
static void
297
3
ipuz_acrostic_real_fix_all (IPuzCrossword *self,
298
                            const gchar   *first_attribute_name,
299
                            va_list        var_args)
300

            
301
{
302
  const gchar *attribute_name;
303
  IPuzAcrosticSyncDirection direction;
304
  va_list var_args_copy;
305

            
306
3
  va_copy (var_args_copy, var_args);
307
3
  attribute_name = first_attribute_name;
308

            
309
6
  while (attribute_name)
310
    {
311
3
      if (! g_strcmp0 (attribute_name, "sync-direction"))
312
        {
313
3
          direction = va_arg (var_args_copy, IPuzAcrosticSyncDirection);
314
3
          ipuz_acrostic_fix_quote_str (IPUZ_ACROSTIC (self), direction);
315
        }
316

            
317
3
      attribute_name = va_arg (var_args_copy, const gchar *);
318
    }
319
3
  va_end (var_args_copy);
320

            
321
  /* Don't chain up. Just fix styles from the parent class as its the
322
   * only one that makes sense. */
323
3
  ipuz_crossword_fix_styles (IPUZ_CROSSWORD (self));
324
3
}
325

            
326
static const gchar *const *
327
1
ipuz_acrostic_get_kind_str (IPuzPuzzle *puzzle)
328
{
329
  static const char *kind_str[] =
330
    {
331
      "http://ipuz.org/acrostic#1",
332
      NULL
333
    };
334

            
335
1
  return kind_str;
336
}
337

            
338
/*
339
 * Public Methods
340
 */
341

            
342
IPuzPuzzle *
343
5
ipuz_acrostic_new (void)
344
{
345
  IPuzPuzzle *acrostic;
346
  IPuzCharsetBuilder *builder;
347
  IPuzCharset *charset;
348

            
349
5
  builder = ipuz_charset_builder_new_for_language ("C");
350
5
  charset = ipuz_charset_builder_build (builder);
351
5
  builder = NULL;
352

            
353
5
  acrostic = g_object_new (IPUZ_TYPE_ACROSTIC,
354
                           "lang-charset", charset,
355
                           NULL);
356

            
357
5
  return acrostic;
358
}
359

            
360
static gchar*
361
9
sanitize_quote_str (const gchar  *quote_str,
362
                    IPuzAcrostic *self)
363
{
364
  const gchar *p;
365
9
  GString *string = NULL;
366
9
  gchar *sanitized = NULL;
367
  IPuzAcrosticPrivate *priv;
368

            
369
9
  priv = ipuz_acrostic_get_instance_private (self);
370

            
371
9
  string = g_string_new (NULL);
372

            
373
157
  for (p = quote_str; p[0]; p = g_utf8_next_char (p))
374
    {
375
      gunichar c;
376

            
377
148
      c = g_utf8_get_char (p);
378

            
379
148
      if (ipuz_charset_get_char_count (priv->charset, g_unichar_toupper (c)))
380
114
        g_string_append_unichar (string, g_unichar_toupper (c));
381
      else
382
34
        g_string_append_unichar (string, ' ');
383
    }
384

            
385
9
  sanitized = g_string_free (string, FALSE);
386

            
387
  /* remove leading and trailing whitespaces */
388
9
  sanitized = g_strstrip (sanitized);
389

            
390
9
  return sanitized;
391
}
392

            
393
void
394
9
ipuz_acrostic_set_quote_str (IPuzAcrostic *self,
395
                             const gchar  *quote_str)
396
{
397
  IPuzAcrosticPrivate *priv;
398

            
399
9
  g_return_if_fail (self != NULL);
400
9
  g_return_if_fail (quote_str != NULL);
401

            
402
9
  priv = ipuz_acrostic_get_instance_private (self);
403

            
404
9
  g_clear_pointer (&priv->quote_str, g_free);
405
9
  priv->quote_str = sanitize_quote_str (quote_str, self);
406
}
407

            
408
const gchar*
409
11
ipuz_acrostic_get_quote_str (IPuzAcrostic *self)
410
{
411
  IPuzAcrosticPrivate *priv;
412

            
413
11
  g_return_val_if_fail (self != NULL, NULL);
414

            
415
11
  priv = ipuz_acrostic_get_instance_private (self);
416

            
417
11
  return priv->quote_str;
418
}
419

            
420
IPuzClue *
421
2
ipuz_acrostic_get_quote_clue (IPuzAcrostic *self)
422
{
423
  IPuzAcrosticPrivate *priv;
424

            
425
2
  priv = ipuz_acrostic_get_instance_private (self);
426

            
427
2
  return priv->quote_clue;
428
}
429

            
430
static void
431
6
update_grid_from_quote_str (IPuzAcrostic *self)
432
{
433
  IPuzAcrosticPrivate *priv;
434
  const gchar *ptr;
435
  guint rows, columns;
436
  IPuzCrossword *xword;
437

            
438
6
  xword = IPUZ_CROSSWORD (self);
439
6
  rows = ipuz_crossword_get_height (xword);
440
6
  columns = ipuz_crossword_get_width (xword);
441

            
442
6
  priv = ipuz_acrostic_get_instance_private (self);
443
6
  ptr = priv->quote_str;
444

            
445
25
  for (guint row = 0; row < rows; row++)
446
    {
447
99
      for (guint column = 0; column < columns; column++)
448
        {
449
          IPuzCell *cell;
450
80
          IPuzCellCoord coord = {
451
            .row = row,
452
            .column = column,
453
          };
454

            
455
80
          cell = ipuz_crossword_get_cell (xword, coord);
456

            
457
80
          ipuz_cell_set_cell_type (cell, IPUZ_CELL_BLOCK);
458

            
459
80
          if (ptr && ptr[0])
460
            {
461
              gunichar c;
462

            
463
80
              c = g_utf8_get_char (ptr);
464

            
465
80
              if (ipuz_charset_get_char_count (priv->charset, c))
466
                {
467
71
                  g_autofree gchar *solution = NULL;
468

            
469
71
                  ipuz_cell_set_cell_type (cell, IPUZ_CELL_NORMAL);
470

            
471
71
                  solution = g_utf8_substring (ptr, 0, 1);
472
71
                  ipuz_cell_set_solution (cell, solution);
473
                }
474

            
475
80
              ptr = g_utf8_next_char (ptr);
476
            }
477
        }
478
    }
479
6
}
480

            
481
static void
482
6
ensure_board_fits_quote_str (IPuzAcrostic *self)
483
{
484
  IPuzAcrosticPrivate *priv;
485
  guint quote_length;
486
  AcrosticBoardDimension dimension;
487

            
488
6
  priv = ipuz_acrostic_get_instance_private (self);
489

            
490
6
  quote_length = g_utf8_strlen (priv->quote_str, -1);
491
6
  dimension = acrostic_board_dimension_from_quote_length (quote_length);
492

            
493
  /* quote_length > IPUZ_ACROSTIC_MAX_QUOTE_STR_LENGTH */
494
6
  g_return_if_fail (dimension.width != 0);
495

            
496
6
  ipuz_crossword_set_size (IPUZ_CROSSWORD (self), dimension.width, dimension.height);
497
}
498

            
499
static void
500
6
sync_quote_str_to_grid (IPuzAcrostic *self)
501
{
502
  IPuzAcrosticPrivate *priv;
503
6
  g_return_if_fail (IPUZ_IS_ACROSTIC (self));
504

            
505
6
  priv = ipuz_acrostic_get_instance_private (self);
506

            
507
6
  if (priv->quote_str != NULL)
508
    {
509
6
      ensure_board_fits_quote_str (self);
510
6
      update_grid_from_quote_str (self);
511

            
512
6
      g_clear_pointer (& priv->quote_clue, ipuz_clue_free);
513
6
      priv->quote_clue = calculate_quote_clue (self);
514
    }
515
}
516

            
517
static void
518
3
sync_grid_to_quote_str (IPuzAcrostic *self)
519
{
520
  IPuzAcrosticPrivate  *priv;
521
3
  GString *quote_str = NULL;
522
  guint rows, columns;
523
  IPuzCrossword *xword;
524

            
525
3
  xword = IPUZ_CROSSWORD (self);
526
3
  rows = ipuz_crossword_get_height (xword);
527
3
  columns = ipuz_crossword_get_width (xword);
528

            
529
3
  priv = ipuz_acrostic_get_instance_private (self);
530

            
531
3
  quote_str = g_string_new (NULL);
532

            
533
12
  for (guint row = 0; row < rows; row++)
534
    {
535
45
      for (guint column = 0; column < columns; column++)
536
        {
537
          IPuzCell *cell;
538
36
          IPuzCellCoord coord = {
539
            .row = row,
540
            .column = column,
541
          };
542

            
543
36
          cell = ipuz_crossword_get_cell (xword, coord);
544

            
545
36
          if (IPUZ_CELL_IS_NORMAL (cell))
546
            {
547
32
              g_string_append_unichar (quote_str,
548
                                       g_utf8_get_char (ipuz_cell_get_solution (cell)));
549
            }
550
          else
551
            {
552
4
              g_string_append_unichar (quote_str, ' ');
553
            }
554
        }
555
    }
556

            
557
3
  g_clear_pointer (&priv->quote_str, g_free);
558
3
  priv->quote_str = g_strchomp (g_string_free (quote_str, FALSE));
559
3
}
560

            
561
void
562
9
ipuz_acrostic_fix_quote_str (IPuzAcrostic              *self,
563
                             IPuzAcrosticSyncDirection  sync_direction)
564
{
565
9
  g_return_if_fail (self != NULL);
566

            
567
9
  if (sync_direction == IPUZ_ACROSTIC_SYNC_QUOTE_STR_TO_GRID)
568
6
    sync_quote_str_to_grid (self);
569
3
  else if (sync_direction == IPUZ_ACROSTIC_SYNC_GRID_TO_QUOTE_STR)
570
3
    sync_grid_to_quote_str (self);
571
  else
572
    g_assert_not_reached ();
573
}