Check for invalid puzzle file added
[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 /* First open the GZip file */
604 gzFile insourcefile = gzopen (file, "rb");
605 if (insourcefile == NULL)
606 {
607 fprintf (stderr, "%s %s\n", ERROR_READING_FILE, COMPRESSED);
608 exit (1);
609 }
610 /* Open a temporary file to uncompress the contents */
611 FILE *infile = tmpfile ();
612 if (infile == NULL)
613 {
614 fprintf (stderr, "%s\n", ERROR_READING_FILE);
615 exit (1);
616 }
617 /* Put the uncompressed content to the temp file */
618 char buf[128];
619 int num = 0;
620 num = gzread (insourcefile, buf, 128);
621 while (num > 0)
622 {
623 int res = fwrite (buf, 1, num, infile);
624 if (res == 0)
625 {
626 fprintf (stderr, "%s\n", ERROR_READING_FILE);
627 fclose (infile);
628 gzclose (insourcefile);
629 exit (1);
630 }
631 num = gzread (insourcefile, buf, 128);
632 }
633 /* Close the gzip file */
634 gzclose (insourcefile);
635 /* Flush the temp file buffer and rewind to beginning */
636 fflush (infile);
637 fseek (infile, 0, 0);
638
639 /* Read the temporary file contents to the structure Puzzle */
640 Puzzle p;
641 char line[MAX_CLUE_LENGTH+10];
642 fgets (line, MAX_CLUE_LENGTH + 10, infile);
643 p.grid_size = atoi (line);
644 /* if puzzle is invalid or otherwise not proper grid, return an invalid
645 puzzle object */
646 if (p.grid_size == 0)
647 {
648 init_puzzle (&p, 0);
649 return p;
650 }
651 fgets (line, MAX_CLUE_LENGTH + 10, infile);
652 p.grid_frozen = atoi (line) == 0 ? false : true ;
653 fgets (line, MAX_CLUE_LENGTH + 10, infile);
654 if (strlen (line) != 1)
655 strcpy (p.hashed_master_password, strtok (line, "\n"));
656 else
657 strcpy (p.hashed_master_password, "\0");
658 fgets (line, MAX_CLUE_LENGTH + 10, infile);
659 if (strlen (line) != 1)
660 strcpy (p.hashed_solution_password, strtok (line, "\n"));
661 else
662 strcpy (p.hashed_solution_password, "\0");
663
664 /* read each character of the grid */
665 for (int i = 0; i < p.grid_size; i ++ )
666 {
667 fgets (line, MAX_CLUE_LENGTH + 10, infile);
668 for (int j = 0; j < p.grid_size; j ++)
669 p.chars[i][j] = line[j];
670 }
671 /* read the word numbers */
672 for (int i = 0; i < p.grid_size; i ++)
673 {
674 fgets (line, MAX_CLUE_LENGTH + 10, infile);
675 char *token = strtok (line, " ");
676 for (int j = 0; j < p.grid_size; j ++)
677 {
678 if (token != NULL)
679 p.start_across_word[i][j] = atoi (token);
680 token = strtok (NULL, " ");
681 if (token != NULL)
682 p.start_down_word[i][j] = atoi (token);
683 token = strtok (NULL, " ");
684 }
685 }
686 /* read the clues */
687 fgets (line, MAX_CLUE_LENGTH + 10, infile);
688
689 /* across clues */
690 char clues[100][MAX_CLUE_LENGTH];
691 int word_num[100];
692 int c = 0;
693 /* first read the across clues from file */
694 while (1)
695 {
696 fgets (line, MAX_CLUE_LENGTH + 10, infile);
697 /* if reached the end of across clues */
698 if (strcmp (line, "DOWN\n") == 0)
699 break;
700 word_num[c] = atoi (strtok (line, "\t"));
701 char *cl = strtok (NULL, "\n");
702 if (cl != NULL)
703 strcpy (clues[c], cl);
704 else
705 strcpy (clues[c], "\0");
706 c++;
707 }
708 /* set the clue to the correct cell in grid */
709 for (int i = 0; i < p.grid_size; i ++)
710 {
711 for (int j = 0; j < p.grid_size; j ++)
712 {
713 for (int r = 0; r < c; r ++)
714 if (p.start_across_word[i][j] == word_num[r])
715 strcpy (p.clue_across[i][j], clues[r]);
716 }
717 }
718
719 /* down clues */
720 c = 0;
721 while (fgets (line, MAX_CLUE_LENGTH + 10, infile))
722 {
723 word_num[c] = atoi (strtok (line, "\t"));
724 char* cl = strtok (NULL, "\n");
725 if (cl != NULL)
726 strcpy (clues[c], cl);
727 else
728 strcpy (clues[c], "\0");
729 c++;
730 }
731 for (int i = 0; i < p.grid_size; i ++)
732 {
733 for (int j = 0; j < p.grid_size; j ++)
734 {
735 for (int r = 0; r < c; r ++)
736 if (p.start_down_word[i][j] == word_num[r])
737 strcpy (p.clue_down[i][j], clues[r]);
738 }
739 }
740
741 fclose (infile);
742 return p;
743 }
744
745 /* display the puzzle */
746 void print_puzzle (Puzzle *p)
747 {
748 printf ("\n");
749 set_color (WHITE, CYAN, NORMAL);
750 printf (" ");
751 for (int i = 0; i < p->grid_size; i ++)
752 printf ("%3d", i);
753 reset_color ();
754 printf("\n");
755 for (int i = 0; i < p->grid_size; i ++)
756 {
757 set_color (WHITE, CYAN, NORMAL);
758 printf ("%3d ", i);
759 for (int j = 0; j < p->grid_size; j ++)
760 {
761 if (p->chars[i][j] == '#') {
762 set_color (WHITE, BLACK, NORMAL);
763 printf (" ");
764 }
765 else
766 {
767 if (p->start_across_word[i][j] != -1 ||
768 p->start_down_word[i][j] != -1)
769 {
770 set_color (BLUE, WHITE, NORMAL);
771 if (p->start_across_word[i][j] != -1)
772 printf ("%-2d", p->start_across_word[i][j]);
773 else
774 printf ("%-2d", p->start_down_word[i][j]);
775 }
776 else
777 {
778 set_color (BLACK, WHITE,NORMAL);
779 printf (" ");
780 }
781
782 set_color (BLACK, WHITE, BOLD);
783 printf ("%c", p->chars[i][j]);
784 }
785 reset_color ();
786 }
787 printf ("\n");
788 }
789 /* print the clues if set */
790 if (p->grid_frozen == true)
791 {
792 printf ("\x1B[1mACROSS - CLUES\x1B[0m\n");
793 for (int i = 0; i < p->grid_size; i ++)
794 {
795 for (int j = 0; j < p->grid_size; j ++)
796 {
797 if (p->start_across_word[i][j] != -1)
798 {
799 printf ("%d - %s; ", p->start_across_word[i][j],
800 p->clue_across[i][j]);
801 }
802 }
803 }
804 printf ("\n\x1B[1mDOWN - CLUES\x1B[0m\n");
805 for (int i = 0; i < p->grid_size; i ++)
806 {
807 for (int j = 0; j < p->grid_size; j ++)
808 {
809 if (p->start_down_word[i][j] != -1)
810 {
811 printf ("%d - %s; ", p->start_down_word[i][j],
812 p->clue_down[i][j]);
813 }
814 }
815 }
816 printf ("\n");
817 }
818 }
819
820 /* function to check if a word is valid or not */
821 char* is_valid_word (char *word)
822 {
823 if (word == NULL || strlen(word) == 0)
824 return NULL;
825 for (int i = 0; i < strlen (word) - 1; i ++)
826 if (! isalpha (word[i]))
827 return NULL;
828
829 return strtok (word, "\n");
830 }
831
832
833 /* function to set a clue for an across word */
834 bool set_clue (Puzzle *p, String clue, int index, enum ORIENTATION order)
835 {
836 for (int i = 0; i < p->grid_size; i ++)
837 {
838 for (int j = 0; j < p->grid_size; j ++)
839 {
840 if (order == ACROSS)
841 {
842 if (p->start_across_word[i][j] == index)
843 {
844 strcpy (p->clue_across[i][j], clue);
845 return true;
846 }
847 }
848 else if (order == DOWN)
849 {
850 if (p->start_down_word[i][j] == index)
851 {
852 strcpy (p->clue_down[i][j], clue);
853 return true;
854 }
855 }
856 }
857 }
858 return false;
859 }
860
861 /* function to print a menu */
862 void print_menu (enum COLOR fg, enum COLOR bg, const char* title,
863 char **items, int num_items, int padding)
864 {
865 /* clear screen */
866 printf ("\e[1;1H\e[2J");
867 set_color (fg, bg, NORMAL);
868 printf ("\u2554");
869 for (int i = 0; i < padding; i ++)
870 printf ("\u2550");
871 printf ("\u2557");
872 reset_color (); printf ("\n");
873 set_color (fg, bg, NORMAL);
874 printf ("\u2551");
875 set_color (fg, bg, BOLD);
876 printf ("%-*s", padding, title);
877 reset_color ();
878 set_color (fg, bg, NORMAL);
879 printf ("\u2551");
880 reset_color (); printf ("\n");
881 set_color (fg, bg, NORMAL);
882 printf ("\u2560");
883 for (int i = 0; i < padding; i ++)
884 printf ("\u2550");
885 printf ("\u2563");
886 reset_color (); printf ("\n");
887 for (int i = 0; i < num_items; i ++)
888 {
889 set_color (fg, bg, NORMAL);
890 printf ("\u2551%-*s\u2551", padding, items[i]);
891 reset_color (); printf ("\n");
892 }
893 set_color (fg, bg, NORMAL);
894 printf ("\u255A");
895 for (int i = 0; i < padding; i ++)
896 printf ("\u2550");
897 printf ("\u255D");
898 reset_color (); printf ("\n");
899 }
900
901 /* reset the player data, loading from the puzzle file */
902 void reset_player_data (MainPlayerData *app_data, const char *filename)
903 {
904 app_data->puzzle = load_puzzle (filename);
905
906 app_data->is_loaded = app_data->puzzle.grid_frozen;
907 app_data->cur_col = -1;
908 app_data->cur_row = -1;
909 app_data->solution_revealed = false;
910 strcpy (app_data->filename, filename);
911 /* reset the answer keys */
912 for (int i = 0; i < app_data->puzzle.grid_size; i ++)
913 for (int j = 0; j < app_data->puzzle.grid_size; j ++)
914 app_data->char_ans[i][j] = ' ';
915
916 }
917
918 /* save the user grid to a file */
919 void save_user_data (MainPlayerData *app_data, const char *filename)
920 {
921 FILE *outfile;
922 outfile = fopen (filename, "wb");
923 if (outfile == NULL)
924 {
925 fprintf (stderr, ERROR_WRITING_FILE);
926 return;
927 }
928 fprintf (outfile, "%s\n", app_data->filename);
929 for (int i = 0; i < app_data->puzzle.grid_size; i ++)
930 {
931 for (int j = 0; j < app_data->puzzle.grid_size; j ++)
932 fprintf (outfile, "%c", app_data->char_ans[i][j]);
933 fprintf (outfile, "\n");
934 }
935
936 fclose (outfile);
937 }
938
939 /* load the user grid from a file */
940 void load_user_data (MainPlayerData *app_data, const char *filename)
941 {
942 FILE *infile;
943 infile = fopen (filename, "rb");
944 if (infile == NULL)
945 {
946 fprintf (stderr, "%s\n", ERROR_READING_FILE);
947 return;
948 }
949
950 char puzzle_file_name[65535];
951 fgets (puzzle_file_name, 65535, infile);
952 reset_player_data (app_data, strtok (puzzle_file_name, "\n"));
953
954 char line[MAX_PUZZLE_SIZE+10];
955 for (int i = 0; i < app_data->puzzle.grid_size; i ++)
956 {
957 fgets (line, MAX_PUZZLE_SIZE+10, infile);
958 for (int j = 0; j < app_data->puzzle.grid_size; j ++)
959 app_data->char_ans[i][j] = line[j];
960
961 }
962 fclose (infile);
963 }
964
965 /* in the player app, move the current selection index left or right */
966 void move_current_col (MainPlayerData *app_data, enum DIRECTION dir)
967 {
968 int r = app_data->cur_row;
969 int c = app_data->cur_col;
970 if (dir == DIR_FORWARD)
971 {
972 c ++;
973 while (c < app_data->puzzle.grid_size)
974 {
975 if (app_data->puzzle.chars[r][c] == '#')
976 c ++;
977 else
978 break;
979 }
980 if (c < app_data->puzzle.grid_size)
981 app_data->cur_col = c;
982 }
983 else
984 {
985 c --;
986 while (c >= 0)
987 {
988 if (app_data->puzzle.chars[r][c] == '#')
989 c --;
990 else
991 break;
992 }
993 if (c >= 0)
994 app_data->cur_col = c;
995 }
996 }
997
998 /* in the player app move the current selection index up or down */
999 void move_current_row (MainPlayerData *app_data, enum DIRECTION dir)
1000 {
1001 int r = app_data->cur_row;
1002 int c = app_data->cur_col;
1003 if (dir == DIR_FORWARD)
1004 {
1005 r ++;
1006 while (r < app_data->puzzle.grid_size)
1007 {
1008 if (app_data->puzzle.chars[r][c] == '#')
1009 r ++;
1010 else
1011 break;
1012 }
1013 if (r < app_data->puzzle.grid_size)
1014 app_data->cur_row = r;
1015 }
1016 else
1017 {
1018 r --;
1019 while (r >= 0)
1020 {
1021 if (app_data->puzzle.chars[r][c] == '#')
1022 r --;
1023 else
1024 break;
1025 }
1026 if (r >= 0)
1027 app_data->cur_row = r;
1028 }
1029 }
1030
1031 #endif