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