Added LICENSE.txt for licensing under 3 clause BSD license
[wordblah.git] / wordblah.h
1 #ifndef __WORDBLAH_H
2 #define __WORDBLAH_H
3 #define _XOPEN_SOURCE
4 #include <unistd.h>
5 #include <stdbool.h>
6 #include <string.h>
7 #include <ctype.h>
8 #include <gd.h>
9 #include <gdfontmb.h>
10 #include <gdfontg.h>
11 #include <zlib.h>
12 #include <openssl/conf.h>
13 #include <openssl/evp.h>
14 #include <openssl/err.h>
15 #include "constantstrings.h"
16
17 #define MAX_PUZZLE_SIZE 25
18 #define MAX_CLUE_LENGTH 150
19 #define GRID_PIXELS 37
20
21 /* Enum to define terminal colours */
22 enum COLOR {
23 BLACK = 0,
24 RED= 1,
25 GREEN=2,
26 YELLOW=3,
27 BLUE=4,
28 MAGENTA=5,
29 CYAN=6,
30 WHITE=7
31 };
32
33 enum ATTR {
34 NORMAL = 23,
35 BOLD=1
36 };
37
38 enum ORIENTATION {
39 ACROSS=1,
40 DOWN=2
41 };
42
43 /* for use with the player */
44 enum DIRECTION {
45 DIR_FORWARD = 1,
46 DIR_BACK = -1
47 };
48
49 typedef char String[MAX_CLUE_LENGTH];
50
51 /* The main puzzle struct type */
52 typedef struct {
53 char chars[MAX_PUZZLE_SIZE][MAX_PUZZLE_SIZE];
54 int start_across_word[MAX_PUZZLE_SIZE][MAX_PUZZLE_SIZE];
55 int start_down_word[MAX_PUZZLE_SIZE][MAX_PUZZLE_SIZE];
56 String clue_across[MAX_PUZZLE_SIZE][MAX_PUZZLE_SIZE];
57 String clue_down[MAX_PUZZLE_SIZE][MAX_PUZZLE_SIZE];
58 int grid_size;
59 bool grid_frozen;
60 char hashed_master_password[256];
61 char hashed_solution_password[256];
62 } Puzzle;
63
64 /* The player data struct type - for the player app */
65 typedef struct {
66 Puzzle puzzle;
67 char filename[65535];
68 bool is_loaded;
69 char char_ans[MAX_PUZZLE_SIZE][MAX_PUZZLE_SIZE];
70 int cur_row;
71 int cur_col;
72 bool solution_revealed;
73 enum ORIENTATION current_movement;
74 } MainPlayerData;
75
76 /* compute the hash of a password */
77 void digest_message(const unsigned char *message,
78 size_t message_len, unsigned char **digest, unsigned int *digest_len)
79 {
80 EVP_MD_CTX *mdctx;
81
82 if((mdctx = EVP_MD_CTX_new()) == NULL)
83 goto err;
84
85 if(1 != EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL))
86 goto err;
87
88 if(1 != EVP_DigestUpdate(mdctx, message, message_len))
89 goto err;
90
91 if((*digest = (unsigned char *)
92 OPENSSL_malloc(EVP_MD_size(EVP_sha256()))) == NULL)
93 goto err;
94
95 if(1 != EVP_DigestFinal_ex(mdctx, *digest, digest_len))
96 goto err;
97
98 EVP_MD_CTX_free(mdctx);
99 return;
100 err:
101 EVP_MD_CTX_free(mdctx);
102 ERR_print_errors_fp(stderr);
103 exit (2);
104 }
105
106 /* encode the binary data to readable text format using OpenSSL */
107 void encode_binary (char *encoded, unsigned char *binary_data, unsigned int len)
108 {
109
110 EVP_EncodeBlock ((unsigned char*)encoded,
111 (const unsigned char*)binary_data, len);
112 OPENSSL_free (binary_data);
113 }
114
115 /* get a number from the user */
116 int get_num ()
117 {
118 char s[5];
119 fgets (s, 5, stdin);
120 int n = atoi (s);
121 return n;
122 }
123
124 /* verify solution password */
125 bool verify_solution_password (Puzzle *p, const char* password)
126 {
127 /* no password set */
128 if (strcmp (p->hashed_solution_password, "\0") == 0)
129 return true;
130
131 /* hash the user input password and compare it with the stored password */
132 unsigned char* hashed_sol_password;
133 unsigned int len;
134 digest_message ((const unsigned char *)password, strlen(password),
135 &hashed_sol_password, &len);
136 char hashed_hex_pwd[256] = { (char) NULL };
137 encode_binary (hashed_hex_pwd, hashed_sol_password, len);
138
139 if (strcmp (p->hashed_solution_password, hashed_hex_pwd) == 0)
140 return true;
141
142 return false;
143 }
144
145
146 /* verify master password */
147 bool verify_master_password (Puzzle *p, const char* password)
148 {
149 /* no password set */
150 if (strcmp (p->hashed_master_password, "\0") == 0)
151 return true;
152
153 /* hash the user input password and compare it with the stored password */
154 unsigned char* hashed_mas_password;
155 unsigned int len;
156 digest_message ((const unsigned char *)password, strlen(password),
157 &hashed_mas_password, &len);
158 char hashed_hex_pwd[256] = { (char) NULL };
159 encode_binary (hashed_hex_pwd, hashed_mas_password, len);
160
161 if (strcmp (p->hashed_master_password, hashed_hex_pwd) == 0)
162 return true;
163
164 return false;
165 }
166
167 /* Set or reset solution password for puzzle */
168 void set_solution_password (Puzzle *p, const char *password)
169 {
170 /* if it is a null string, reset the password */
171 if (strcmp (password, "\0") == 0)
172 strcpy (p->hashed_solution_password, "\0");
173 else
174 {
175
176 unsigned char* hashedpwd;
177 unsigned int len;
178 digest_message ((const unsigned char *)password, strlen(password),
179 &hashedpwd, &len);
180 /* the hashedpwd contains binary data - we will convert it to
181 hexadecimal data and store in file */
182
183 encode_binary (p->hashed_solution_password, hashedpwd, len);
184 }
185 }
186
187 /* Set or reset master password for puzzle */
188 void set_master_password (Puzzle *p, const char *password)
189 {
190 /* if it is a null string, reset the password */
191 if (strcmp (password, "\0") == 0)
192 strcpy (p->hashed_master_password, "\0");
193 else
194 {
195
196 unsigned char* hashedpwd;
197 unsigned int len;
198 digest_message ((const unsigned char *)password, strlen(password),
199 &hashedpwd, &len);
200 /* the hashedpwd contains binary data - we will convert it to
201 hexadecimal data and store in file */
202
203 encode_binary (p->hashed_master_password, hashedpwd, len);
204 }
205 }
206
207 /* Output the clues to text file */
208 void export_clues (Puzzle *p, const char *filename)
209 {
210 FILE *outfile = fopen (filename, "w");
211 if (outfile == NULL)
212 {
213 fprintf (stderr, "%s\n", ERROR_WRITING_FILE);
214 exit (1);
215 }
216 /* first the across clues */
217 fprintf (outfile, "ACROSS CLUES\n");
218 for (int i = 0; i < p->grid_size; i ++)
219 {
220 for (int j = 0; j < p->grid_size; j ++)
221 {
222 if (p->start_across_word[i][j] != -1)
223 fprintf (outfile, "%d - %s\n", p->start_across_word[i][j],
224 p->clue_across[i][j]);
225 }
226 }
227 /* now the down clues */
228 fprintf (outfile, "DOWN CLUES\n");
229 for (int i = 0; i < p->grid_size; i ++)
230 {
231 for (int j = 0; j < p->grid_size; j ++)
232 {
233 if (p->start_down_word[i][j] != -1)
234 fprintf (outfile, "%d - %s\n", p->start_down_word[i][j],
235 p->clue_down[i][j]);
236 }
237 }
238 fclose (outfile);
239 }
240
241 /* Output the grid to image - if answerkey is true export filled grid */
242 void export_grid_image (Puzzle *p, const char *filename, bool answerkey)
243 {
244 int img_size = p->grid_size * GRID_PIXELS;
245 FILE * outfile = fopen (filename, "wb");
246 if (outfile == NULL)
247 {
248 fprintf (stderr, "%s\n", ERROR_WRITING_FILE);
249 exit (1);
250 }
251
252 gdImagePtr img = gdImageCreate (img_size, img_size);
253 gdImageColorAllocate (img, 255,255,255);
254 int black = gdImageColorAllocate (img, 0, 0, 0);
255 int blue = gdImageColorAllocate (img, 0, 0, 216);
256 gdFontPtr sm_fnt = gdFontGetMediumBold ();
257 gdFontPtr lg_fnt = gdFontGetGiant ();
258
259 for (int i = 0; i < p->grid_size; i ++)
260 {
261 for (int j = 0; j < p->grid_size; j++)
262 {
263 /* if it is a block, draw the black square */
264 if (p->chars[i][j] == '#')
265 gdImageFilledRectangle (img, j*GRID_PIXELS, i*GRID_PIXELS,
266 j*GRID_PIXELS+GRID_PIXELS,
267 i*GRID_PIXELS+GRID_PIXELS,black);
268 else
269 {
270 /* draw a regular square */
271 gdImageRectangle (img, j*GRID_PIXELS, i*GRID_PIXELS,
272 j*GRID_PIXELS+GRID_PIXELS,
273 i*GRID_PIXELS+GRID_PIXELS, black);
274
275 /* print the numers, if it is either start across word or
276 a down word */
277 if (p->start_across_word[i][j] != -1 ||
278 p->start_down_word[i][j] != -1)
279 {
280 if (p->start_across_word[i][j] != -1)
281 {
282 char str[5];
283 sprintf (str, "%d", p->start_across_word[i][j]);
284 gdImageString (img, sm_fnt, j*GRID_PIXELS+2,
285 i*GRID_PIXELS+2,
286 (unsigned char *)str, blue);
287 }
288 else
289 {
290 char str[5];
291 sprintf (str, "%d", p->start_down_word[i][j]);
292 gdImageString (img, sm_fnt, j*GRID_PIXELS+2,
293 i*GRID_PIXELS+2,
294 (unsigned char *)str, blue);
295 }
296 }
297 /* if answerkey is true, draw the character in the cell */
298 if (answerkey)
299 {
300 gdImageChar (img, lg_fnt, j*GRID_PIXELS+15,
301 i*GRID_PIXELS+10, p->chars[i][j], black);
302 }
303 }
304 }
305 }
306
307 gdImagePng (img, outfile);
308 gdImageDestroy (img);
309 fclose (outfile);
310 }
311
312 /* Set the terminal colour */
313 void set_color (enum COLOR fg, enum COLOR bg, enum ATTR at) {
314 printf ("\x1B[%d;%d;%dm", fg+30, bg+40, at);
315 }
316
317 /* Reset the terminal colour */
318 void reset_color () {
319 printf ("\x1B[0m");
320 }
321
322 /* check if the prev row has a block or not */
323 bool prev_row_block (Puzzle *p, int r, int c)
324 {
325 if (r == 0)
326 return true;
327 if (p->chars[r-1][c] == '#')
328 return true;
329 return false;
330 }
331
332 /* check if the next row has a block or not */
333 bool next_row_block (Puzzle *p, int r, int c)
334 {
335 if (r == p->grid_size-1)
336 return true;
337 if (p->chars[r+1][c] == '#')
338 return true;
339 return false;
340 }
341
342 /* check if the prev col has a block or not */
343 bool prev_col_block (Puzzle *p, int r, int c)
344 {
345 if (c == 0)
346 return true;
347 if (p->chars[r][c-1] == '#')
348 return true;
349 return false;
350 }
351
352 /* check if the next col has a block or not */
353 bool next_col_block (Puzzle *p, int r, int c)
354 {
355 if (c == p->grid_size - 1)
356 return true;
357 if (p->chars[r][c+1] == '#')
358 return true;
359 return false;
360 }
361
362 /* check if previous row is blank or not */
363 bool prev_row_blank (Puzzle *p, int r, int c)
364 {
365 if (r == 0) return true;
366 if (p->chars[r-1][c] == ' ' || p->chars[r-1][c] == '#') return true;
367 return false;
368 }
369 /* check if next row is blank or not */
370 bool next_row_blank (Puzzle *p, int r, int c)
371 {
372 if (r == p->grid_size - 1) return true;
373 if (p->chars[r+1][c] == ' ' || p->chars[r+1][c] == '#') return true;
374 return false;
375 }
376 /* check if previous col is blank or not */
377 bool prev_col_blank (Puzzle *p, int r, int c)
378 {
379 if (c == 0) return true;
380 if (p->chars[r][c-1] == ' ' || p->chars[r][c-1] == '#') return true;
381 return false;
382 }
383 /* check if the next col is blank or not */
384 bool next_col_blank (Puzzle *p, int r, int c)
385 {
386 if (c == p->grid_size -1) return true;
387 if (p->chars[r][c+1] == ' ' || p->chars[r][c+1] == '#') return true;
388 return false;
389 }
390
391 /* set the current row/col to the beginning of word index (across or down) */
392 void set_selection_to_word_start (MainPlayerData *app_data,
393 enum ORIENTATION orient, int word_index)
394 {
395 for (int i = 0; i < app_data->puzzle.grid_size; i ++)
396 {
397 for (int j = 0; j < app_data->puzzle.grid_size; j ++)
398 {
399 if (orient == ACROSS &&
400 app_data->puzzle.start_across_word[i][j] == word_index)
401 {
402 app_data->current_movement = ACROSS;
403 app_data->cur_row = i;
404 app_data->cur_col = j;
405 break;
406 }
407 else if (orient == DOWN &&
408 app_data->puzzle.start_down_word[i][j] == word_index)
409 {
410 app_data->current_movement = DOWN;
411 app_data->cur_row = i;
412 app_data->cur_col = j;
413 break;
414 }
415 }
416 }
417 }
418
419 /* unfreeze the grid - make editing possible to change words */
420 void unfreeze_puzzle (Puzzle *p)
421 {
422 for (int i = 0; i < p->grid_size; i ++)
423 {
424 for (int j = 0; j < p->grid_size; j ++)
425 {
426 if (p->chars[i][j] == '#')
427 p->chars[i][j] = ' ';
428
429 p->start_across_word[i][j] = -1;
430 p->start_down_word[i][j] = -1;
431 }
432 }
433 p->grid_frozen = false;
434 }
435
436 /* freeze the grid - make editing impossible because it finalizes the
437 across and down words in the grid */
438 void freeze_puzzle (Puzzle *p)
439 {
440 int word_num = 1;
441 bool across_word_start, down_word_start;
442 for (int i = 0; i < p->grid_size; i ++)
443 {
444 for (int j = 0; j < p->grid_size; j++)
445 {
446 across_word_start = false;
447 down_word_start = false;
448 /* if it is a blank cell - cover it with a block */
449 if (p->chars[i][j] == ' ' || p->chars[i][j] == '#')
450 p->chars[i][j] = '#';
451 /* it is not a blank cell - check all possibilities */
452 else
453 {
454 bool prev_row = prev_row_blank (p, i, j);
455 bool next_row = next_row_blank (p, i, j);
456 bool prev_col = prev_col_blank (p, i, j);
457 bool next_col = next_col_blank (p, i, j);
458 if (prev_row && ! next_row)
459 down_word_start = true;
460 if (prev_col && ! next_col)
461 across_word_start = true;
462 }
463
464 if (across_word_start == true)
465 p->start_across_word[i][j] = word_num;
466 else
467 p->start_across_word[i][j] = -1;
468 if (down_word_start == true)
469 p->start_down_word[i][j] = word_num;
470 else
471 p->start_down_word[i][j] = -1;
472 if (across_word_start == true || down_word_start == true)
473 word_num ++;
474 }
475 }
476 p->grid_frozen = true;
477 }
478
479 /* reset the entire grid */
480 void init_puzzle (Puzzle *p, int grid_size)
481 {
482 p->grid_size = grid_size;
483 p->grid_frozen = false;
484 for (int i = 0; i < p->grid_size; i ++)
485 {
486 for (int j = 0; j < p->grid_size; j ++)
487 {
488 p->chars[i][j] = ' ';
489 p->start_across_word[i][j] = -1;
490 p->start_down_word[i][j] = -1;
491 strcpy (p->clue_across[i][j], "");
492 strcpy (p->clue_down[i][j], "");
493 }
494 }
495 strcpy (p->hashed_master_password, "\0");
496 strcpy (p->hashed_solution_password, "\0");
497
498 }
499
500 /* save the puzzle to a file */
501 void save_puzzle (Puzzle *puzzle, const char* file)
502 {
503 FILE *outfile;
504 /* First output the uncompressed contents to a temp file */
505 outfile = tmpfile ();
506 if (outfile == NULL)
507 {
508 fprintf (stderr, "%s\n", ERROR_WRITING_FILE);
509 exit (1);
510 }
511 /* grid size is the first field */
512 fprintf (outfile, "%d\n", puzzle->grid_size);
513 /* whether grid is frozen or not */
514 fprintf (outfile, "%d\n", puzzle->grid_frozen);
515 /* the hashed password */
516 fprintf (outfile, "%s\n", puzzle->hashed_master_password);
517 /* the hashed_solution_password */
518 fprintf (outfile, "%s\n", puzzle->hashed_solution_password);
519
520 /* First output the grid characters columns/rows */
521 for (int i = 0; i < puzzle->grid_size; i ++)
522 {
523 for (int j = 0; j < puzzle->grid_size; j ++)
524 fprintf (outfile, "%c", puzzle->chars[i][j]);
525 fprintf (outfile, "\n");
526 }
527
528 /* Next output the start across/down numbers */
529 for (int i = 0; i < puzzle->grid_size; i ++)
530 {
531 for (int j = 0; j < puzzle->grid_size; j++)
532 {
533 fprintf (outfile, "%d ", puzzle->start_across_word[i][j]);
534 fprintf (outfile, "%d ", puzzle->start_down_word[i][j]);
535 }
536 fprintf (outfile, "\n");
537 }
538
539 /* Output the across clues */
540 fprintf (outfile, "ACROSS\n");
541 /* Search the grid for across words */
542 for (int i = 0; i < puzzle->grid_size; i ++)
543 {
544 for (int j = 0; j < puzzle->grid_size; j++)
545 {
546 /* if it is an across word, then put the word index followed by
547 tab character (as separator) and the clue */
548 if (puzzle->start_across_word[i][j] != -1)
549 fprintf (outfile, "%d\t%s\n", puzzle->start_across_word[i][j],
550 puzzle->clue_across[i][j]);
551 }
552 }
553
554 /* Output the down clues */
555 fprintf (outfile, "DOWN\n");
556 /* Search the grid for down words */
557 for (int i = 0; i < puzzle->grid_size; i ++)
558 {
559 for (int j = 0; j < puzzle->grid_size; j++)
560 {
561 /* same as across word, put the word index followed by the tab
562 character and then the clue */
563 if (puzzle->start_down_word[i][j] != -1)
564 fprintf (outfile, "%d\t%s\n", puzzle->start_down_word[i][j],
565 puzzle->clue_down[i][j]);
566 }
567 }
568
569 /* Flush the buffer and rewind to beginning - to read and save into
570 gzip compressed file */
571 fflush (outfile);
572 fseek (outfile, 0, 0);
573
574 /* now compress the file and save it to destination file */
575 gzFile outdestfile = gzopen (file, "wb");
576 if (outdestfile == NULL)
577 {
578 fprintf (stderr, "%s\n", ERROR_WRITING_FILE);
579 fclose (outfile);
580 exit (1);
581 }
582 char buf[128];
583 int num = fread (buf, sizeof(char), sizeof(char)*128, outfile);
584 while (num > 0)
585 {
586 int res = gzwrite (outdestfile, buf, num*sizeof(char) );
587 if (res == 0)
588 {
589 fprintf (stderr, "%s %s\n", ERROR_WRITING_FILE, COMPRESSED);
590 fclose (outfile);
591 exit (1);
592 }
593 num = fread (buf, sizeof(char), sizeof(char)*128, outfile);
594 }
595 gzclose (outdestfile);
596 fclose (outfile);
597
598 }
599
600 /* read the puzzle from a file */
601 Puzzle load_puzzle (const char* file)
602 {
603 Puzzle p;
604 /* First open the GZip file */
605 gzFile insourcefile = gzopen (file, "rb");
606 if (insourcefile == NULL)
607 {
608 fprintf (stderr, "%s %s\n", ERROR_READING_FILE, COMPRESSED);
609 /* return an invalid puzzle */
610 init_puzzle (&p, 0);
611 return p;
612 }
613 /* Open a temporary file to uncompress the contents */
614 FILE *infile = tmpfile ();
615 if (infile == NULL)
616 {
617 fprintf (stderr, "%s\n", ERROR_READING_FILE);
618 init_puzzle (&p, 0);
619 return p;
620 }
621 /* Put the uncompressed content to the temp file */
622 char buf[128];
623 int num = 0;
624 num = gzread (insourcefile, buf, 128);
625 while (num > 0)
626 {
627 int res = fwrite (buf, 1, num, infile);
628 if (res == 0)
629 {
630 fprintf (stderr, "%s\n", ERROR_READING_FILE);
631 fclose (infile);
632 gzclose (insourcefile);
633 /* return an invalid puzzle */
634 init_puzzle (&p, 0);
635 return p;
636 }
637 num = gzread (insourcefile, buf, 128);
638 }
639 /* Close the gzip file */
640 gzclose (insourcefile);
641 /* Flush the temp file buffer and rewind to beginning */
642 fflush (infile);
643 fseek (infile, 0, 0);
644
645 /* Read the temporary file contents to the structure Puzzle */
646 char line[MAX_CLUE_LENGTH+10];
647 fgets (line, MAX_CLUE_LENGTH + 10, infile);
648 p.grid_size = atoi (line);
649 /* if puzzle is invalid or otherwise not proper grid, return an invalid
650 puzzle object */
651 if (p.grid_size == 0)
652 {
653 fprintf (stderr, "%s\n", INVALID_PUZZLE);
654 init_puzzle (&p, 0);
655 return p;
656 }
657 fgets (line, MAX_CLUE_LENGTH + 10, infile);
658 p.grid_frozen = atoi (line) == 0 ? false : true ;
659 fgets (line, MAX_CLUE_LENGTH + 10, infile);
660 if (strlen (line) != 1)
661 strcpy (p.hashed_master_password, strtok (line, "\n"));
662 else
663 strcpy (p.hashed_master_password, "\0");
664 fgets (line, MAX_CLUE_LENGTH + 10, infile);
665 if (strlen (line) != 1)
666 strcpy (p.hashed_solution_password, strtok (line, "\n"));
667 else
668 strcpy (p.hashed_solution_password, "\0");
669
670 /* read each character of the grid */
671 for (int i = 0; i < p.grid_size; i ++ )
672 {
673 fgets (line, MAX_CLUE_LENGTH + 10, infile);
674 for (int j = 0; j < p.grid_size; j ++)
675 p.chars[i][j] = line[j];
676 }
677 /* read the word numbers */
678 for (int i = 0; i < p.grid_size; i ++)
679 {
680 fgets (line, MAX_CLUE_LENGTH + 10, infile);
681 char *token = strtok (line, " ");
682 for (int j = 0; j < p.grid_size; j ++)
683 {
684 if (token != NULL)
685 p.start_across_word[i][j] = atoi (token);
686 token = strtok (NULL, " ");
687 if (token != NULL)
688 p.start_down_word[i][j] = atoi (token);
689 token = strtok (NULL, " ");
690 }
691 }
692 /* read the clues */
693 fgets (line, MAX_CLUE_LENGTH + 10, infile);
694
695 /* across clues */
696 char clues[100][MAX_CLUE_LENGTH];
697 int word_num[100];
698 int c = 0;
699 /* first read the across clues from file */
700 while (1)
701 {
702 fgets (line, MAX_CLUE_LENGTH + 10, infile);
703 /* if reached the end of across clues */
704 if (strcmp (line, "DOWN\n") == 0)
705 break;
706 word_num[c] = atoi (strtok (line, "\t"));
707 char *cl = strtok (NULL, "\n");
708 if (cl != NULL)
709 strcpy (clues[c], cl);
710 else
711 strcpy (clues[c], "\0");
712 c++;
713 }
714 /* set the clue to the correct cell in grid */
715 for (int i = 0; i < p.grid_size; i ++)
716 {
717 for (int j = 0; j < p.grid_size; j ++)
718 {
719 for (int r = 0; r < c; r ++)
720 if (p.start_across_word[i][j] == word_num[r])
721 strcpy (p.clue_across[i][j], clues[r]);
722 }
723 }
724
725 /* down clues */
726 c = 0;
727 while (fgets (line, MAX_CLUE_LENGTH + 10, infile))
728 {
729 word_num[c] = atoi (strtok (line, "\t"));
730 char* cl = strtok (NULL, "\n");
731 if (cl != NULL)
732 strcpy (clues[c], cl);
733 else
734 strcpy (clues[c], "\0");
735 c++;
736 }
737 for (int i = 0; i < p.grid_size; i ++)
738 {
739 for (int j = 0; j < p.grid_size; j ++)
740 {
741 for (int r = 0; r < c; r ++)
742 if (p.start_down_word[i][j] == word_num[r])
743 strcpy (p.clue_down[i][j], clues[r]);
744 }
745 }
746
747 fclose (infile);
748 return p;
749 }
750
751 /* display the puzzle */
752 void print_puzzle (Puzzle *p)
753 {
754 printf ("\n");
755 set_color (WHITE, CYAN, NORMAL);
756 printf (" ");
757 for (int i = 0; i < p->grid_size; i ++)
758 printf ("%3d", i);
759 reset_color ();
760 printf("\n");
761 for (int i = 0; i < p->grid_size; i ++)
762 {
763 set_color (WHITE, CYAN, NORMAL);
764 printf ("%3d ", i);
765 for (int j = 0; j < p->grid_size; j ++)
766 {
767 if (p->chars[i][j] == '#') {
768 set_color (WHITE, BLACK, NORMAL);
769 printf (" ");
770 }
771 else
772 {
773 if (p->start_across_word[i][j] != -1 ||
774 p->start_down_word[i][j] != -1)
775 {
776 set_color (BLUE, WHITE, NORMAL);
777 if (p->start_across_word[i][j] != -1)
778 printf ("%-2d", p->start_across_word[i][j]);
779 else
780 printf ("%-2d", p->start_down_word[i][j]);
781 }
782 else
783 {
784 set_color (BLACK, WHITE,NORMAL);
785 printf (" ");
786 }
787
788 set_color (BLACK, WHITE, BOLD);
789 printf ("%c", p->chars[i][j]);
790 }
791 reset_color ();
792 }
793 printf ("\n");
794 }
795 /* print the clues if set */
796 if (p->grid_frozen == true)
797 {
798 printf ("\x1B[1mACROSS - CLUES\x1B[0m\n");
799 for (int i = 0; i < p->grid_size; i ++)
800 {
801 for (int j = 0; j < p->grid_size; j ++)
802 {
803 if (p->start_across_word[i][j] != -1)
804 {
805 printf ("%d - %s; ", p->start_across_word[i][j],
806 p->clue_across[i][j]);
807 }
808 }
809 }
810 printf ("\n\x1B[1mDOWN - CLUES\x1B[0m\n");
811 for (int i = 0; i < p->grid_size; i ++)
812 {
813 for (int j = 0; j < p->grid_size; j ++)
814 {
815 if (p->start_down_word[i][j] != -1)
816 {
817 printf ("%d - %s; ", p->start_down_word[i][j],
818 p->clue_down[i][j]);
819 }
820 }
821 }
822 printf ("\n");
823 }
824 }
825
826 /* function to check if a word is valid or not */
827 char* is_valid_word (char *word)
828 {
829 if (word == NULL || strlen(word) == 0)
830 return NULL;
831 for (int i = 0; i < strlen (word) - 1; i ++)
832 if (! isalpha (word[i]))
833 return NULL;
834
835 return strtok (word, "\n");
836 }
837
838
839 /* function to set a clue for an across word */
840 bool set_clue (Puzzle *p, String clue, int index, enum ORIENTATION order)
841 {
842 for (int i = 0; i < p->grid_size; i ++)
843 {
844 for (int j = 0; j < p->grid_size; j ++)
845 {
846 if (order == ACROSS)
847 {
848 if (p->start_across_word[i][j] == index)
849 {
850 strcpy (p->clue_across[i][j], clue);
851 return true;
852 }
853 }
854 else if (order == DOWN)
855 {
856 if (p->start_down_word[i][j] == index)
857 {
858 strcpy (p->clue_down[i][j], clue);
859 return true;
860 }
861 }
862 }
863 }
864 return false;
865 }
866
867 /* function to print a menu */
868 void print_menu (enum COLOR fg, enum COLOR bg, const char* title,
869 char **items, int num_items, int padding)
870 {
871 /* clear screen */
872 printf ("\e[1;1H\e[2J");
873 set_color (fg, bg, NORMAL);
874 printf ("\u2554");
875 for (int i = 0; i < padding; i ++)
876 printf ("\u2550");
877 printf ("\u2557");
878 reset_color (); printf ("\n");
879 set_color (fg, bg, NORMAL);
880 printf ("\u2551");
881 set_color (fg, bg, BOLD);
882 printf ("%-*s", padding, title);
883 reset_color ();
884 set_color (fg, bg, NORMAL);
885 printf ("\u2551");
886 reset_color (); printf ("\n");
887 set_color (fg, bg, NORMAL);
888 printf ("\u2560");
889 for (int i = 0; i < padding; i ++)
890 printf ("\u2550");
891 printf ("\u2563");
892 reset_color (); printf ("\n");
893 for (int i = 0; i < num_items; i ++)
894 {
895 set_color (fg, bg, NORMAL);
896 printf ("\u2551%-*s\u2551", padding, items[i]);
897 reset_color (); printf ("\n");
898 }
899 set_color (fg, bg, NORMAL);
900 printf ("\u255A");
901 for (int i = 0; i < padding; i ++)
902 printf ("\u2550");
903 printf ("\u255D");
904 reset_color (); printf ("\n");
905 }
906
907 /* reset the player data, loading from the puzzle file */
908 void reset_player_data (MainPlayerData *app_data, const char *filename)
909 {
910 app_data->puzzle = load_puzzle (filename);
911
912 app_data->is_loaded = app_data->puzzle.grid_frozen;
913 app_data->cur_col = -1;
914 app_data->cur_row = -1;
915 app_data->solution_revealed = false;
916 strcpy (app_data->filename, filename);
917 /* reset the answer keys */
918 for (int i = 0; i < app_data->puzzle.grid_size; i ++)
919 for (int j = 0; j < app_data->puzzle.grid_size; j ++)
920 app_data->char_ans[i][j] = ' ';
921
922 }
923
924 /* save the user grid to a file */
925 void save_user_data (MainPlayerData *app_data, const char *filename)
926 {
927 FILE *outfile;
928 outfile = fopen (filename, "wb");
929 if (outfile == NULL)
930 {
931 fprintf (stderr, ERROR_WRITING_FILE);
932 return;
933 }
934 fprintf (outfile, "%s\n", app_data->filename);
935 for (int i = 0; i < app_data->puzzle.grid_size; i ++)
936 {
937 for (int j = 0; j < app_data->puzzle.grid_size; j ++)
938 fprintf (outfile, "%c", app_data->char_ans[i][j]);
939 fprintf (outfile, "\n");
940 }
941
942 fclose (outfile);
943 }
944
945 /* load the user grid from a file */
946 void load_user_data (MainPlayerData *app_data, const char *filename)
947 {
948 FILE *infile;
949 infile = fopen (filename, "rb");
950 if (infile == NULL)
951 {
952 fprintf (stderr, "%s\n", ERROR_READING_FILE);
953 return;
954 }
955
956 char puzzle_file_name[65535];
957 fgets (puzzle_file_name, 65535, infile);
958 reset_player_data (app_data, strtok (puzzle_file_name, "\n"));
959
960 char line[MAX_PUZZLE_SIZE+10];
961 for (int i = 0; i < app_data->puzzle.grid_size; i ++)
962 {
963 fgets (line, MAX_PUZZLE_SIZE+10, infile);
964 for (int j = 0; j < app_data->puzzle.grid_size; j ++)
965 app_data->char_ans[i][j] = line[j];
966
967 }
968 fclose (infile);
969 }
970
971 /* in the player app, move the current selection index left or right */
972 void move_current_col (MainPlayerData *app_data, enum DIRECTION dir)
973 {
974 int r = app_data->cur_row;
975 int c = app_data->cur_col;
976 if (dir == DIR_FORWARD)
977 {
978 c ++;
979 while (c < app_data->puzzle.grid_size)
980 {
981 if (app_data->puzzle.chars[r][c] == '#')
982 c ++;
983 else
984 break;
985 }
986 if (c < app_data->puzzle.grid_size)
987 app_data->cur_col = c;
988 }
989 else
990 {
991 c --;
992 while (c >= 0)
993 {
994 if (app_data->puzzle.chars[r][c] == '#')
995 c --;
996 else
997 break;
998 }
999 if (c >= 0)
1000 app_data->cur_col = c;
1001 }
1002 }
1003
1004 /* in the player app move the current selection index up or down */
1005 void move_current_row (MainPlayerData *app_data, enum DIRECTION dir)
1006 {
1007 int r = app_data->cur_row;
1008 int c = app_data->cur_col;
1009 if (dir == DIR_FORWARD)
1010 {
1011 r ++;
1012 while (r < app_data->puzzle.grid_size)
1013 {
1014 if (app_data->puzzle.chars[r][c] == '#')
1015 r ++;
1016 else
1017 break;
1018 }
1019 if (r < app_data->puzzle.grid_size)
1020 app_data->cur_row = r;
1021 }
1022 else
1023 {
1024 r --;
1025 while (r >= 0)
1026 {
1027 if (app_data->puzzle.chars[r][c] == '#')
1028 r --;
1029 else
1030 break;
1031 }
1032 if (r >= 0)
1033 app_data->cur_row = r;
1034 }
1035 }
1036
1037 #endif