1
/* ipuz-cell.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/ipuz-cell.h>
24

            
25

            
26
IPuzCell *
27
ipuz_cell_new (void)
28
{
29
  IPuzCell *cell = g_new0 (IPuzCell, 1);
30

            
31
  cell->cell_type = IPUZ_CELL_NORMAL;
32

            
33
  return cell;
34
}
35

            
36
gboolean
37
1259
ipuz_cell_equal (IPuzCell *a,
38
                 IPuzCell *b)
39
{
40
1259
  g_assert (a != NULL && b != NULL);
41

            
42
1259
  return (a->cell_type == b->cell_type
43
1259
          && a->number == b->number
44
1257
          && g_strcmp0 (a->label, b->label) == 0
45
1257
          && g_strcmp0 (a->solution, b->solution) == 0
46
1257
          && g_strcmp0 (a->saved_guess, b->saved_guess) == 0
47
1257
          && g_strcmp0 (a->initial_val, b->initial_val) == 0
48
2518
          && g_strcmp0 (a->style_name, b->style_name) == 0);
49
}
50

            
51
IPuzCell *
52
ipuz_cell_copy (const IPuzCell *cell)
53
{
54
  IPuzCell *new_cell;
55

            
56
  g_return_val_if_fail (cell != NULL, NULL);
57

            
58
  new_cell = ipuz_cell_new ();
59
  new_cell->cell_type = cell->cell_type;
60
  new_cell->number = cell->number;
61
  new_cell->label = g_strdup (cell->label);
62
  new_cell->solution = g_strdup (cell->solution);
63
  new_cell->saved_guess = g_strdup (cell->saved_guess);
64
  new_cell->initial_val = g_strdup (cell->initial_val);
65
  new_cell->style_name = g_strdup (cell->style_name);
66
  if (cell->style)
67
    new_cell->style = ipuz_style_ref (cell->style);
68

            
69
  return new_cell;
70
}
71

            
72
void
73
ipuz_cell_free (IPuzCell *cell)
74
{
75
  g_return_if_fail (cell != NULL);
76

            
77
  g_free (cell->label);
78
  g_free (cell->solution);
79
  g_free (cell->saved_guess);
80
  g_free (cell->initial_val);
81
  g_free (cell->style_name);
82
  g_clear_pointer (&cell->style, ipuz_style_unref);
83
  g_clear_pointer (&cell->clues, g_array_unref);
84

            
85
  g_free (cell);
86
}
87

            
88
void
89
5361
ipuz_cell_clear (IPuzCell *cell)
90
{
91
5361
  g_return_if_fail (cell != NULL);
92

            
93
5361
  g_free (cell->label);
94
5361
  g_free (cell->solution);
95
5361
  g_free (cell->saved_guess);
96
5361
  g_free (cell->initial_val);
97
5361
  g_free (cell->style_name);
98
5361
  g_clear_pointer (&cell->style, ipuz_style_unref);
99
5361
  g_clear_pointer (&cell->clues, g_array_unref);
100

            
101
  /* Force clear this */
102
5361
  memset (cell, 0, sizeof (IPuzCell));
103
}
104

            
105
void
106
898
ipuz_cell_build (IPuzCell    *cell,
107
                 JsonBuilder *builder,
108
                 gboolean     solution,
109
                 const char  *block,
110
                 const char  *empty)
111
{
112
898
  g_return_if_fail (cell != NULL);
113

            
114
898
  if (IPUZ_CELL_IS_NULL (cell))
115
    {
116
194
      json_builder_add_null_value (builder);
117
194
      return;
118
    }
119

            
120
  /* We're printing out the "solution" block */
121
704
  if (solution)
122
    {
123
352
      if (cell->solution != NULL)
124
304
        json_builder_add_string_value (builder, cell->solution);
125
      else
126
48
        json_builder_add_null_value (builder);
127

            
128
352
      return;
129
    }
130

            
131
  /* Short-circuit printing out the cell as an object */
132
352
  if (cell->style == NULL &&
133
326
      cell->initial_val == NULL)
134
    {
135
323
      if (IPUZ_CELL_IS_BLOCK (cell))
136
48
        json_builder_add_string_value (builder, block);
137
275
      else if (cell->label)
138
232
        json_builder_add_string_value (builder, cell->label);
139
      else
140
43
        json_builder_add_int_value (builder, cell->number);
141
    }
142
  else /* Print a more complex cell */
143
    {
144
29
      json_builder_begin_object (builder);
145
29
      json_builder_set_member_name (builder, "cell");
146
29
      if (IPUZ_CELL_IS_BLOCK (cell))
147
        json_builder_add_string_value (builder, block);
148
29
      else if (cell->label)
149
        json_builder_add_string_value (builder, cell->label);
150
      else
151
29
        json_builder_add_int_value (builder, cell->number);
152

            
153
29
      if (cell->style)
154
        {
155
26
          json_builder_set_member_name (builder, "style");
156
          /* If it's a named style, we put a string with the name to
157
           * refer to the global styles. Otherwise, we do a full copy
158
           * of the style. */
159
26
          if (cell->style_name)
160
            json_builder_add_string_value (builder, cell->style_name);
161
          else
162
26
            ipuz_style_build (cell->style, builder);
163
        }
164

            
165
29
      if (cell->initial_val)
166
        {
167
3
          json_builder_set_member_name (builder, "value");
168
3
          json_builder_add_string_value (builder, cell->initial_val);
169
        }
170

            
171
29
      json_builder_end_object (builder);
172
    }
173
}
174

            
175
IPuzCellCellType
176
3653
ipuz_cell_get_cell_type (IPuzCell *cell)
177
{
178
3653
  g_return_val_if_fail (cell != NULL, IPUZ_CELL_NORMAL);
179

            
180
3653
  return cell->cell_type;
181
}
182

            
183
/**
184
 * ipuz_cell_set_cell_type:
185
 * @cell: An `IPuzCell`
186
 * @cell_type: The new cell_type to set
187
 *
188
 * This will set the cell type to @cell_type. Note this just sets the
189
 * cell_type on a singular cell. If you want to honor the symmetry of
190
 * a crossword, you should call ipuz_crossword_set_cell_type()
191
 * instead.
192
 **/
193
void
194
5025
ipuz_cell_set_cell_type (IPuzCell *cell,
195
                         IPuzCellCellType cell_type)
196
{
197
5025
  g_return_if_fail (cell != NULL);
198

            
199
5025
  if (cell->cell_type == cell_type)
200
3206
    return;
201

            
202
1819
  cell->cell_type = cell_type;
203
1819
  if (cell->cell_type != IPUZ_CELL_NORMAL)
204
    {
205
1744
      cell->number = 0;
206
1744
      g_clear_pointer (&cell->label, g_free);
207
1744
      g_clear_pointer (&cell->solution, g_free);
208
1744
      g_clear_pointer (&cell->saved_guess, g_free);
209
1744
      g_clear_pointer (&cell->initial_val, g_free);
210
    }
211

            
212
1819
  if (cell->cell_type == IPUZ_CELL_NULL)
213
    {
214
916
      g_clear_pointer (&cell->style, ipuz_style_unref);
215
916
      g_clear_pointer (&cell->style_name, g_free);
216
    }
217
}
218

            
219
gint
220
15109
ipuz_cell_get_number (IPuzCell *cell)
221
{
222
15109
  g_return_val_if_fail (cell != NULL, 0);
223

            
224
15109
  return cell->number;
225
}
226

            
227
void
228
1052
ipuz_cell_set_number (IPuzCell *cell,
229
                      gint      number)
230
{
231
1052
  g_return_if_fail (cell != NULL);
232

            
233
1052
  cell->cell_type = IPUZ_CELL_NORMAL;
234
1052
  cell->number = number;
235
}
236

            
237
const gchar *
238
14022
ipuz_cell_get_label (IPuzCell *cell)
239
{
240
14022
  g_return_val_if_fail (cell != NULL, NULL);
241

            
242
14022
  return cell->label;
243
}
244

            
245
void
246
1605
ipuz_cell_set_label (IPuzCell *cell,
247
                     const gchar *label)
248
{
249
1605
  g_return_if_fail (cell != NULL);
250

            
251
1605
  g_free (cell->label);
252

            
253
1605
  cell->cell_type = IPUZ_CELL_NORMAL;
254
1605
  cell->label = g_strdup (label);
255
}
256

            
257
const gchar *
258
1152
ipuz_cell_get_solution (IPuzCell *cell)
259
{
260
1152
  g_return_val_if_fail (cell != NULL, NULL);
261

            
262
1152
  return cell->solution;
263
}
264

            
265
void
266
3244
ipuz_cell_set_solution (IPuzCell *cell,
267
                        const gchar *solution)
268
{
269
3244
  g_return_if_fail (cell != NULL);
270

            
271
3244
  g_free (cell->solution);
272

            
273
3244
  cell->cell_type = IPUZ_CELL_NORMAL;
274
3244
  cell->solution = g_strdup (solution);
275
}
276

            
277
const gchar *
278
ipuz_cell_get_saved_guess (IPuzCell *cell)
279
{
280
  g_return_val_if_fail (cell != NULL, NULL);
281

            
282
  return cell->saved_guess;
283
}
284

            
285
/**
286
 * ipuz_cell_set_saved_guess:
287
 * @cell: an @IPuzCell
288
 * @saved_guess: a guess
289
 *
290
 * Sets a user guess for a cell for @cell. This should almost never be
291
 * called, as it is use to preset the solution if you save a
292
 * Crossword. You should use IPuzGuesses to capture the transient
293
 * state of a puzzle.
294
 **/
295
void
296
ipuz_cell_set_saved_guess (IPuzCell    *cell,
297
                           const gchar *saved_guess)
298
{
299
  g_return_if_fail (cell != NULL);
300

            
301
  g_free (cell->saved_guess);
302

            
303
  cell->cell_type = IPUZ_CELL_NORMAL;
304
  cell->saved_guess = g_strdup (saved_guess);
305
}
306

            
307
const gchar *
308
ipuz_cell_get_initial_val (IPuzCell *cell)
309
{
310
  g_return_val_if_fail (cell != NULL, NULL);
311

            
312
  return cell->initial_val;
313
}
314

            
315
void
316
30
ipuz_cell_set_initial_val (IPuzCell *cell,
317
                           const gchar *initial_val)
318
{
319
30
  g_return_if_fail (cell != NULL);
320

            
321
30
  g_free (cell->initial_val);
322

            
323
30
  cell->initial_val = g_strdup (initial_val);
324
}
325

            
326
IPuzStyle *
327
8600
ipuz_cell_get_style (IPuzCell *cell)
328
{
329
8600
  g_return_val_if_fail (cell != NULL, NULL);
330

            
331
8600
  return cell->style;
332
}
333

            
334
/**
335
 * ipuz_cell_set_style:
336
 * @cell: An @IPuzCell
337
 * @style: An @IPuzStyle
338
 * @style_name: The name of the style, or NULL
339
 *
340
 * Sets the style for a given cell.
341
 *
342
 * Note: @style_name is used for a named style within the puzzle. No
343
 * checking is done on style_name: It's up to the caller to check that
344
 * the name exists within the puzzle.
345
 **/
346
void
347
274
ipuz_cell_set_style (IPuzCell    *cell,
348
                     IPuzStyle   *style,
349
                     const gchar *style_name)
350
{
351
274
  gchar *new_style_name = NULL;
352

            
353
274
  g_return_if_fail (cell != NULL);
354

            
355
274
  if (style)
356
    {
357
266
      ipuz_style_ref (style);
358
266
      new_style_name = g_strdup (style_name);
359
    }
360

            
361
274
  g_clear_pointer (&cell->style, ipuz_style_unref);
362
274
  g_clear_pointer (&cell->style_name, g_free);
363

            
364
274
  cell->style = style;
365
274
  cell->style_name = new_style_name;
366
}
367

            
368
/* Internal function. Do not call */
369
/* FIXME: Make private */
370
void
371
6458
ipuz_cell_set_clue (IPuzCell *cell,
372
                    IPuzClue *clue)
373
{
374
6458
  g_return_if_fail (cell != NULL);
375
6458
  g_return_if_fail (clue != NULL);
376

            
377
6458
  if (cell->clues == NULL)
378
3189
    cell->clues = g_array_new (FALSE, TRUE, sizeof (IPuzClue *));
379

            
380
8406
  for (guint i = 0; i < cell->clues->len; i++)
381
    {
382
3198
      IPuzClue *old_clue = g_array_index (cell->clues, IPuzClue *, i);
383
3198
      if (old_clue->direction == clue->direction)
384
        {
385
1250
	  g_array_remove_index_fast (cell->clues, i);
386
1250
	  break;
387
	}
388
    }
389

            
390
6458
  g_array_append_val (cell->clues, clue);
391
}
392

            
393
void
394
614
ipuz_cell_clear_clues (IPuzCell *cell)
395
{
396
614
  g_return_if_fail (cell != NULL);
397

            
398
614
  if (cell->clues)
399
462
    g_array_set_size (cell->clues, 0);
400
}
401

            
402
void
403
26
ipuz_cell_clear_clue_direction (IPuzCell          *cell,
404
                                IPuzClueDirection  direction)
405
{
406
26
  g_return_if_fail (cell != NULL);
407

            
408
26
  if (cell->clues == NULL)
409
    return;
410

            
411
26
  for (guint i = 0; i < cell->clues->len; i++)
412
    {
413
26
      IPuzClue *clue = g_array_index (cell->clues, IPuzClue *, i);
414
26
      g_assert (clue != NULL);
415

            
416
26
      if (clue->direction == direction)
417
        {
418
26
          g_array_remove_index_fast (cell->clues, i);
419
26
          return;
420
        }
421
    }
422
}
423

            
424
const IPuzClue *
425
ipuz_cell_get_clue (IPuzCell          *cell,
426
                    IPuzClueDirection  direction)
427
{
428
  g_return_val_if_fail (cell != NULL, NULL);
429

            
430
  if (cell->clues)
431
    {
432
      for (guint i = 0; i < cell->clues->len; i++)
433
        {
434
          IPuzClue *clue = g_array_index (cell->clues, IPuzClue *, i);
435
          g_assert (clue != NULL);
436

            
437
          if (clue->direction == direction)
438
            return clue;
439
        }
440
    }
441

            
442
  return NULL;
443
}
444

            
445

            
446
/* Basically, every node type is a valid puzzle cell, but not every value is
447
 * valid.
448
 * Some notes:
449
 * Null -> NULL cell type
450
 * Number > 0 -> number
451
 * Number == 0 and empty = "0" -> empty
452
 * string == empty -> empty
453
 * string == block -> block
454
 * string -> label, otherwise
455
 * Object -> "it's complicated"
456
 */
457
static void
458
3936
ipuz_cell_parse_puzzle_value (IPuzCell    *cell,
459
                              JsonNode    *node,
460
                              const gchar *block,
461
                              const gchar *empty)
462
{
463
3936
  GType value_type = json_node_get_value_type (node);
464

            
465
3936
  if (value_type == G_TYPE_INT64)
466
    {
467
2228
      int number = json_node_get_int (node);
468

            
469
2228
      ipuz_cell_set_cell_type (cell, IPUZ_CELL_NORMAL);
470

            
471
2228
      if ((number == 0) && (g_strcmp0 (empty, "0") == 0))
472
1814
        return;
473

            
474
414
      ipuz_cell_set_number (cell, number);
475
      }
476
1708
  else if (value_type == G_TYPE_STRING)
477
    {
478
1708
      const gchar *str = json_node_get_string (node);
479
1708
      if (g_strcmp0 (str, empty) == 0)
480
        ipuz_cell_set_cell_type (cell, IPUZ_CELL_NORMAL);
481
1708
      else if (g_strcmp0 (str, block) == 0)
482
741
        ipuz_cell_set_cell_type (cell, IPUZ_CELL_BLOCK);
483
      else
484
        {
485
967
          ipuz_cell_set_cell_type (cell, IPUZ_CELL_NORMAL);
486
967
          ipuz_cell_set_label (cell, str);
487
        }
488
     }
489
  else
490
    {
491
      /* Not sure what to do with booleans, floats, etc */
492
    }
493
}
494

            
495
void
496
4856
ipuz_cell_parse_puzzle (IPuzCell    *cell,
497
                        JsonNode    *node,
498
                        const gchar *block,
499
                        const gchar *empty)
500
{
501
  JsonNodeType node_type;
502

            
503
4856
  g_return_if_fail (cell != NULL);
504
4856
  g_return_if_fail (node != NULL);
505

            
506
4856
  node_type = json_node_get_node_type (node);
507

            
508
4856
  if (node_type == JSON_NODE_NULL)
509
    {
510
912
      ipuz_cell_set_cell_type (cell, IPUZ_CELL_NULL);
511
912
      return;
512
    }
513
3944
  else if (node_type == JSON_NODE_VALUE)
514
    {
515

            
516
3451
      ipuz_cell_parse_puzzle_value (cell, node, block, empty);
517
3451
      return;
518

            
519
    }
520
493
  else if (node_type == JSON_NODE_OBJECT)
521
    {
522
      JsonObject *obj;
523
      JsonNode *element;
524

            
525
493
      obj = json_node_get_object (node);
526

            
527
493
      element = json_object_get_member (obj, "cell");
528
493
      if (element)
529
485
        ipuz_cell_parse_puzzle_value (cell, element, block, empty);
530

            
531
493
      element = json_object_get_member (obj, "style");
532
493
      if (element)
533
        {
534
463
          if (JSON_NODE_HOLDS_VALUE (element))
535
243
            cell->style_name = g_strdup (json_node_get_string (element));
536
220
          else if (JSON_NODE_HOLDS_OBJECT (element))
537
220
            cell->style = ipuz_style_new_from_json (element);
538
        }
539

            
540
493
      element = json_object_get_member (obj, "value");
541
493
      if (element)
542
        {
543
30
          const char *initial_value = json_node_get_string (element);
544
30
          ipuz_cell_set_initial_val (cell, initial_value);
545
        }
546
    }
547
  /* We don't do anything with arrays. */
548
}
549

            
550
/* Validate the solution is in the charset, if it exists. Note, the
551
 * solution could be a rebus in which case we need to go through all
552
 * the characters in solution. Also, the charset can be null, in which
553
 * case we allow all solutions to be valid
554
 */
555
static gboolean
556
3722
check_solution_in_charset (const gchar *solution,
557
                           const gchar *charset)
558
{
559
3722
  const gchar *ptr = solution;
560

            
561
  /* Fail open */
562
3722
  if (solution == NULL || charset == NULL)
563
21
    return TRUE;
564

            
565
7402
  while (*ptr)
566
    {
567
3701
      if (g_utf8_strchr (solution, -1, g_utf8_get_char (ptr)) == NULL)
568
        return FALSE;
569
3701
      ptr = g_utf8_next_char (ptr);
570
    }
571
3701
  return TRUE;
572
}
573
/*
574
 * Ignores all BLOCK / NULL characters. The shape of the board is set
575
 * by the "puzzle" element.
576
 */
577
void
578
4816
ipuz_cell_parse_solution (IPuzCell    *cell,
579
                          JsonNode    *node,
580
                          const gchar *block,
581
                          const gchar *charset)
582
{
583
  JsonNodeType node_type;
584
4816
  const gchar *solution = NULL;
585

            
586
4816
  g_return_if_fail (cell != NULL);
587
4816
  g_return_if_fail (node != NULL);
588

            
589
4816
  node_type = json_node_get_node_type (node);
590

            
591
4816
  if (node_type == JSON_NODE_NULL)
592
    {
593
1094
      return;
594
    }
595
3722
  else if (node_type == JSON_NODE_VALUE)
596
    {
597
3722
      solution = json_node_get_string (node);
598

            
599
3722
      if (! check_solution_in_charset (solution, charset))
600
        return;
601

            
602
3722
      if (g_strcmp0 (solution, block) == 0)
603
553
        return;
604

            
605
3169
      ipuz_cell_set_solution (cell, solution);
606
    }
607
  else if (node_type == JSON_NODE_OBJECT)
608
    {
609
      g_autoptr(JsonReader) reader = json_reader_new (node);
610

            
611
      if (json_reader_read_member (reader, "value"))
612
        {
613
          solution = json_reader_get_string_value (reader);
614

            
615
          if (charset != NULL && strstr (charset, solution) == NULL)
616
            return;
617
          ipuz_cell_set_solution (cell, solution);
618
        }
619
      json_reader_end_member (reader);
620

            
621
      /* Some puzzles in the wild have a "cell" attribute that links it back
622
       * to the cell. I don't think we need to parse this though, and its
623
       * not in the spec.
624
       */
625
    }
626
  else
627
    {
628
      /* Arrays aren't valid solution values AFAICT */
629
    }
630
}
631

            
632
G_DEFINE_BOXED_TYPE (IPuzCell, ipuz_cell, ipuz_cell_copy, ipuz_cell_free);