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