12 #include <openssl/conf.h>
13 #include <openssl/evp.h>
14 #include <openssl/err.h>
15 #include "constantstrings.h"
17 #define MAX_PUZZLE_SIZE 25
18 #define MAX_CLUE_LENGTH 150
19 #define GRID_PIXELS 37
21 /* Enum to define terminal colours */
43 /* for use with the player */
49 typedef char String
[MAX_CLUE_LENGTH
];
51 /* The main puzzle struct type */
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
];
60 char hashed_password
[256];
64 /* The player data struct type - for the player app */
69 char char_ans
[MAX_PUZZLE_SIZE
][MAX_PUZZLE_SIZE
];
74 /* compute the hash of a password */
75 void digest_message(const unsigned char *message
,
76 size_t message_len
, unsigned char **digest
, unsigned int *digest_len
)
80 if((mdctx
= EVP_MD_CTX_new()) == NULL
)
83 if(1 != EVP_DigestInit_ex(mdctx
, EVP_sha256(), NULL
))
86 if(1 != EVP_DigestUpdate(mdctx
, message
, message_len
))
89 if((*digest
= (unsigned char *)
90 OPENSSL_malloc(EVP_MD_size(EVP_sha256()))) == NULL
)
93 if(1 != EVP_DigestFinal_ex(mdctx
, *digest
, digest_len
))
96 EVP_MD_CTX_free(mdctx
);
99 EVP_MD_CTX_free(mdctx
);
100 ERR_print_errors_fp(stderr
);
104 /* convert the hashed binary password to hexadecimal representation and
105 free the hashed binary password */
106 void to_hexadecimal (char *hex
, unsigned char *binary_pwd
, unsigned int len
)
109 /* keep reference to beginning of the hashed password */
110 unsigned char *binary_pw_begin
= binary_pwd
;
111 for (int i
= 0; i
< len
; i
++)
113 sprintf (buf
, "%02x", (*binary_pwd
)&0xff);
117 /* free the hashed password */
118 OPENSSL_free (binary_pw_begin
);
121 /* get a number from the user */
130 /* verify password */
131 bool verify_password (Puzzle
*p
, const char* password
)
133 /* no password set */
134 if (strcmp (p
->hashed_password
, "\0") == 0)
137 /* hash the user input password and compare it with the stored password */
138 unsigned char* hashed_password
;
140 digest_message ((const unsigned char *)password
, strlen(password
),
141 &hashed_password
, &len
);
142 char hashed_hex_pwd
[256] = { (char) NULL
};
143 to_hexadecimal (hashed_hex_pwd
, hashed_password
, len
);
145 if (strcmp (p
->hashed_password
, hashed_hex_pwd
) == 0)
151 /* Set or reset password for puzzle */
152 void set_puzzle_password (Puzzle
*p
, const char *password
)
154 /* if it is a null string, reset the password */
155 if (strcmp (password
, "\0") == 0)
157 strcpy (p
->hashed_password
, "\0");
158 strcpy (p
->salt
, "\0");
163 unsigned char* hashedpwd
;
165 digest_message ((const unsigned char *)password
, strlen(password
),
167 /* the hashedpwd contains binary data - we will convert it to
168 hexadecimal data and store in file */
170 to_hexadecimal (p
->hashed_password
, hashedpwd
, len
);
171 printf ("%s\n", p
->hashed_password
);
173 strcpy (p
->salt
, "\0");
178 /* Output the clues to text file */
179 void export_clues (Puzzle
*p
, const char *filename
)
181 FILE *outfile
= fopen (filename
, "w");
184 fprintf (stderr
, "%s\n", ERROR_WRITING_FILE
);
187 /* first the across clues */
188 fprintf (outfile
, "ACROSS CLUES\n");
189 for (int i
= 0; i
< p
->grid_size
; i
++)
191 for (int j
= 0; j
< p
->grid_size
; j
++)
193 if (p
->start_across_word
[i
][j
] != -1)
194 fprintf (outfile
, "%d - %s\n", p
->start_across_word
[i
][j
],
195 p
->clue_across
[i
][j
]);
198 /* now the down clues */
199 fprintf (outfile
, "DOWN CLUES\n");
200 for (int i
= 0; i
< p
->grid_size
; i
++)
202 for (int j
= 0; j
< p
->grid_size
; j
++)
204 if (p
->start_down_word
[i
][j
] != -1)
205 fprintf (outfile
, "%d - %s\n", p
->start_down_word
[i
][j
],
212 /* Output the grid to image - if answerkey is true export filled grid */
213 void export_grid_image (Puzzle
*p
, const char *filename
, bool answerkey
)
215 int img_size
= p
->grid_size
* GRID_PIXELS
;
216 FILE * outfile
= fopen (filename
, "wb");
219 fprintf (stderr
, "%s\n", ERROR_WRITING_FILE
);
223 gdImagePtr img
= gdImageCreate (img_size
, img_size
);
224 gdImageColorAllocate (img
, 255,255,255);
225 int black
= gdImageColorAllocate (img
, 0, 0, 0);
226 int blue
= gdImageColorAllocate (img
, 0, 0, 216);
227 gdFontPtr sm_fnt
= gdFontGetMediumBold ();
228 gdFontPtr lg_fnt
= gdFontGetGiant ();
230 for (int i
= 0; i
< p
->grid_size
; i
++)
232 for (int j
= 0; j
< p
->grid_size
; j
++)
234 /* if it is a block, draw the black square */
235 if (p
->chars
[i
][j
] == '#')
236 gdImageFilledRectangle (img
, j
*GRID_PIXELS
, i
*GRID_PIXELS
,
237 j
*GRID_PIXELS
+GRID_PIXELS
,
238 i
*GRID_PIXELS
+GRID_PIXELS
,black
);
241 /* draw a regular square */
242 gdImageRectangle (img
, j
*GRID_PIXELS
, i
*GRID_PIXELS
,
243 j
*GRID_PIXELS
+GRID_PIXELS
,
244 i
*GRID_PIXELS
+GRID_PIXELS
, black
);
246 /* print the numers, if it is either start across word or
248 if (p
->start_across_word
[i
][j
] != -1 ||
249 p
->start_down_word
[i
][j
] != -1)
251 if (p
->start_across_word
[i
][j
] != -1)
254 sprintf (str
, "%d", p
->start_across_word
[i
][j
]);
255 gdImageString (img
, sm_fnt
, j
*GRID_PIXELS
+2,
257 (unsigned char *)str
, blue
);
262 sprintf (str
, "%d", p
->start_down_word
[i
][j
]);
263 gdImageString (img
, sm_fnt
, j
*GRID_PIXELS
+2,
265 (unsigned char *)str
, blue
);
268 /* if answerkey is true, draw the character in the cell */
271 gdImageChar (img
, lg_fnt
, j
*GRID_PIXELS
+15,
272 i
*GRID_PIXELS
+10, p
->chars
[i
][j
], black
);
278 gdImagePng (img
, outfile
);
279 gdImageDestroy (img
);
283 /* Set the terminal colour */
284 void set_color (enum COLOR fg
, enum COLOR bg
, enum ATTR at
) {
285 printf ("\x1B[%d;%d;%dm", fg
+30, bg
+40, at
);
288 /* Reset the terminal colour */
289 void reset_color () {
293 /* check if previous row is blank or not */
294 bool prev_row_blank (Puzzle
*p
, int r
, int c
)
296 if (r
== 0) return true;
297 if (p
->chars
[r
-1][c
] == ' ' || p
->chars
[r
-1][c
] == '#') return true;
300 /* check if next row is blank or not */
301 bool next_row_blank (Puzzle
*p
, int r
, int c
)
303 if (r
== p
->grid_size
- 1) return true;
304 if (p
->chars
[r
+1][c
] == ' ' || p
->chars
[r
+1][c
] == '#') return true;
307 /* check if previous col is blank or not */
308 bool prev_col_blank (Puzzle
*p
, int r
, int c
)
310 if (c
== 0) return true;
311 if (p
->chars
[r
][c
-1] == ' ' || p
->chars
[r
][c
-1] == '#') return true;
314 /* check if the next col is blank or not */
315 bool next_col_blank (Puzzle
*p
, int r
, int c
)
317 if (c
== p
->grid_size
-1) return true;
318 if (p
->chars
[r
][c
+1] == ' ' || p
->chars
[r
][c
+1] == '#') return true;
322 /* unfreeze the grid - make editing possible to change words */
323 void unfreeze_puzzle (Puzzle
*p
)
325 for (int i
= 0; i
< p
->grid_size
; i
++)
327 for (int j
= 0; j
< p
->grid_size
; j
++)
329 if (p
->chars
[i
][j
] == '#')
330 p
->chars
[i
][j
] = ' ';
332 p
->start_across_word
[i
][j
] = -1;
333 p
->start_down_word
[i
][j
] = -1;
336 p
->grid_frozen
= false;
339 /* freeze the grid - make editing impossible because it finalizes the
340 across and down words in the grid */
341 void freeze_puzzle (Puzzle
*p
)
344 bool across_word_start
, down_word_start
;
345 for (int i
= 0; i
< p
->grid_size
; i
++)
347 for (int j
= 0; j
< p
->grid_size
; j
++)
349 across_word_start
= false;
350 down_word_start
= false;
351 /* if it is a blank cell - cover it with a block */
352 if (p
->chars
[i
][j
] == ' ' || p
->chars
[i
][j
] == '#')
353 p
->chars
[i
][j
] = '#';
354 /* it is not a blank cell - check all possibilities */
357 bool prev_row
= prev_row_blank (p
, i
, j
);
358 bool next_row
= next_row_blank (p
, i
, j
);
359 bool prev_col
= prev_col_blank (p
, i
, j
);
360 bool next_col
= next_col_blank (p
, i
, j
);
361 if (prev_row
&& ! next_row
)
362 down_word_start
= true;
363 if (prev_col
&& ! next_col
)
364 across_word_start
= true;
367 if (across_word_start
== true)
368 p
->start_across_word
[i
][j
] = word_num
;
370 p
->start_across_word
[i
][j
] = -1;
371 if (down_word_start
== true)
372 p
->start_down_word
[i
][j
] = word_num
;
374 p
->start_down_word
[i
][j
] = -1;
375 if (across_word_start
== true || down_word_start
== true)
379 p
->grid_frozen
= true;
382 /* reset the entire grid */
383 void init_puzzle (Puzzle
*p
, int grid_size
)
385 p
->grid_size
= grid_size
;
386 p
->grid_frozen
= false;
387 for (int i
= 0; i
< p
->grid_size
; i
++)
389 for (int j
= 0; j
< p
->grid_size
; j
++)
391 p
->chars
[i
][j
] = ' ';
392 p
->start_across_word
[i
][j
] = -1;
393 p
->start_down_word
[i
][j
] = -1;
394 strcpy (p
->clue_across
[i
][j
], "");
395 strcpy (p
->clue_down
[i
][j
], "");
398 strcpy (p
->hashed_password
, "\0");
399 strcpy (p
->salt
, "\0");
403 /* save the puzzle to a file */
404 void save_puzzle (Puzzle
*puzzle
, const char* file
) {
406 /* First output the uncompressed contents to a temp file */
407 outfile
= tmpfile ();
410 fprintf (stderr
, "%s\n", ERROR_WRITING_FILE
);
413 /* grid size is the first field */
414 fprintf (outfile
, "%d\n", puzzle
->grid_size
);
415 /* whether grid is frozen or not */
416 fprintf (outfile
, "%d\n", puzzle
->grid_frozen
);
417 /* the hashed password */
418 fprintf (outfile
, "%s\n", puzzle
->hashed_password
);
420 fprintf (outfile
, "%s\n", puzzle
->salt
);
422 /* First output the grid characters columns/rows */
423 for (int i
= 0; i
< puzzle
->grid_size
; i
++)
425 for (int j
= 0; j
< puzzle
->grid_size
; j
++)
426 fprintf (outfile
, "%c", puzzle
->chars
[i
][j
]);
427 fprintf (outfile
, "\n");
430 /* Next output the start across/down numbers */
431 for (int i
= 0; i
< puzzle
->grid_size
; i
++)
433 for (int j
= 0; j
< puzzle
->grid_size
; j
++)
435 fprintf (outfile
, "%d ", puzzle
->start_across_word
[i
][j
]);
436 fprintf (outfile
, "%d ", puzzle
->start_down_word
[i
][j
]);
438 fprintf (outfile
, "\n");
441 /* Output the across clues */
442 fprintf (outfile
, "ACROSS\n");
443 /* Search the grid for across words */
444 for (int i
= 0; i
< puzzle
->grid_size
; i
++)
446 for (int j
= 0; j
< puzzle
->grid_size
; j
++)
448 /* if it is an across word, then put the word index followed by
449 tab character (as separator) and the clue */
450 if (puzzle
->start_across_word
[i
][j
] != -1)
451 fprintf (outfile
, "%d\t%s\n", puzzle
->start_across_word
[i
][j
],
452 puzzle
->clue_across
[i
][j
]);
456 /* Output the down clues */
457 fprintf (outfile
, "DOWN\n");
458 /* Search the grid for down words */
459 for (int i
= 0; i
< puzzle
->grid_size
; i
++)
461 for (int j
= 0; j
< puzzle
->grid_size
; j
++)
463 /* same as across word, put the word index followed by the tab
464 character and then the clue */
465 if (puzzle
->start_down_word
[i
][j
] != -1)
466 fprintf (outfile
, "%d\t%s\n", puzzle
->start_down_word
[i
][j
],
467 puzzle
->clue_down
[i
][j
]);
471 /* Flush the buffer and rewind to beginning - to read and save into
472 gzip compressed file */
474 fseek (outfile
, 0, 0);
476 /* now compress the file and save it to destination file */
477 gzFile outdestfile
= gzopen (file
, "wb");
478 if (outdestfile
== NULL
)
480 fprintf (stderr
, "%s\n", ERROR_WRITING_FILE
);
485 int num
= fread (buf
, sizeof(char), sizeof(char)*128, outfile
);
488 int res
= gzwrite (outdestfile
, buf
, num
*sizeof(char) );
491 fprintf (stderr
, "%s %s\n", ERROR_WRITING_FILE
, COMPRESSED
);
495 num
= fread (buf
, sizeof(char), sizeof(char)*128, outfile
);
497 gzclose (outdestfile
);
502 /* read the puzzle from a file */
503 Puzzle
load_puzzle (const char* file
) {
504 /* First open the GZip file */
505 gzFile insourcefile
= gzopen (file
, "rb");
506 if (insourcefile
== NULL
)
508 fprintf (stderr
, "%s %s\n", ERROR_READING_FILE
, COMPRESSED
);
511 /* Open a temporary file to uncompress the contents */
512 FILE *infile
= tmpfile ();
515 fprintf (stderr
, "%s\n", ERROR_READING_FILE
);
518 /* Put the uncompressed content to the temp file */
521 num
= gzread (insourcefile
, buf
, 128);
524 int res
= fwrite (buf
, 1, num
, infile
);
527 fprintf (stderr
, "%s\n", ERROR_READING_FILE
);
529 gzclose (insourcefile
);
532 num
= gzread (insourcefile
, buf
, 128);
534 /* Close the gzip file */
535 gzclose (insourcefile
);
536 /* Flush the temp file buffer and rewind to beginning */
538 fseek (infile
, 0, 0);
540 /* Read the temporary file contents to the structure Puzzle */
542 char line
[MAX_CLUE_LENGTH
+10];
543 fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
);
544 p
.grid_size
= atoi (line
);
545 fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
);
546 p
.grid_frozen
= atoi (line
) == 0 ? false : true ;
547 fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
);
548 if (strlen (line
) != 1)
549 strcpy (p
.hashed_password
, strtok (line
, "\n"));
551 strcpy (p
.hashed_password
, "\0");
552 fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
);
553 if (strlen (line
) != 1)
554 strcpy (p
.salt
, strtok (line
, "\n"));
556 strcpy (p
.salt
, "\0");
558 /* read each character of the grid */
559 for (int i
= 0; i
< p
.grid_size
; i
++ )
561 fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
);
562 for (int j
= 0; j
< p
.grid_size
; j
++)
563 p
.chars
[i
][j
] = line
[j
];
565 /* read the word numbers */
566 for (int i
= 0; i
< p
.grid_size
; i
++)
568 fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
);
569 char *token
= strtok (line
, " ");
570 for (int j
= 0; j
< p
.grid_size
; j
++)
573 p
.start_across_word
[i
][j
] = atoi (token
);
574 token
= strtok (NULL
, " ");
576 p
.start_down_word
[i
][j
] = atoi (token
);
577 token
= strtok (NULL
, " ");
581 fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
);
584 char clues
[100][MAX_CLUE_LENGTH
];
587 /* first read the across clues from file */
590 fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
);
591 /* if reached the end of across clues */
592 if (strcmp (line
, "DOWN\n") == 0)
594 word_num
[c
] = atoi (strtok (line
, "\t"));
595 char *cl
= strtok (NULL
, "\n");
597 strcpy (clues
[c
], cl
);
599 strcpy (clues
[c
], "\0");
602 /* set the clue to the correct cell in grid */
603 for (int i
= 0; i
< p
.grid_size
; i
++)
605 for (int j
= 0; j
< p
.grid_size
; j
++)
607 for (int r
= 0; r
< c
; r
++)
608 if (p
.start_across_word
[i
][j
] == word_num
[r
])
609 strcpy (p
.clue_across
[i
][j
], clues
[r
]);
615 while (fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
))
617 word_num
[c
] = atoi (strtok (line
, "\t"));
618 char* cl
= strtok (NULL
, "\n");
620 strcpy (clues
[c
], cl
);
622 strcpy (clues
[c
], "\0");
625 for (int i
= 0; i
< p
.grid_size
; i
++)
627 for (int j
= 0; j
< p
.grid_size
; j
++)
629 for (int r
= 0; r
< c
; r
++)
630 if (p
.start_down_word
[i
][j
] == word_num
[r
])
631 strcpy (p
.clue_down
[i
][j
], clues
[r
]);
639 /* display the puzzle */
640 void print_puzzle (Puzzle
*p
)
643 set_color (WHITE
, CYAN
, NORMAL
);
645 for (int i
= 0; i
< p
->grid_size
; i
++)
649 for (int i
= 0; i
< p
->grid_size
; i
++)
651 set_color (WHITE
, CYAN
, NORMAL
);
653 for (int j
= 0; j
< p
->grid_size
; j
++)
655 if (p
->chars
[i
][j
] == '#') {
656 set_color (WHITE
, BLACK
, NORMAL
);
661 if (p
->start_across_word
[i
][j
] != -1 ||
662 p
->start_down_word
[i
][j
] != -1)
664 set_color (BLUE
, WHITE
, NORMAL
);
665 if (p
->start_across_word
[i
][j
] != -1)
666 printf ("%-2d", p
->start_across_word
[i
][j
]);
668 printf ("%-2d", p
->start_down_word
[i
][j
]);
672 set_color (BLACK
, WHITE
,NORMAL
);
676 set_color (BLACK
, WHITE
, BOLD
);
677 printf ("%c", p
->chars
[i
][j
]);
683 /* print the clues if set */
684 if (p
->grid_frozen
== true)
686 printf ("\x1B[1mACROSS - CLUES\x1B[0m\n");
687 for (int i
= 0; i
< p
->grid_size
; i
++)
689 for (int j
= 0; j
< p
->grid_size
; j
++)
691 if (p
->start_across_word
[i
][j
] != -1)
693 printf ("%d - %s; ", p
->start_across_word
[i
][j
],
694 p
->clue_across
[i
][j
]);
698 printf ("\n\x1B[1mDOWN - CLUES\x1B[0m\n");
699 for (int i
= 0; i
< p
->grid_size
; i
++)
701 for (int j
= 0; j
< p
->grid_size
; j
++)
703 if (p
->start_down_word
[i
][j
] != -1)
705 printf ("%d - %s; ", p
->start_down_word
[i
][j
],
714 /* function to check if a word is valid or not */
715 char* is_valid_word (char *word
)
717 if (word
== NULL
|| strlen(word
) == 0)
719 for (int i
= 0; i
< strlen (word
) - 1; i
++)
720 if (! isalpha (word
[i
]))
723 return strtok (word
, "\n");
727 /* function to set a clue for an across word */
728 bool set_clue (Puzzle
*p
, String clue
, int index
, enum ORIENTATION order
)
730 for (int i
= 0; i
< p
->grid_size
; i
++)
732 for (int j
= 0; j
< p
->grid_size
; j
++)
736 if (p
->start_across_word
[i
][j
] == index
)
738 strcpy (p
->clue_across
[i
][j
], clue
);
742 else if (order
== DOWN
)
744 if (p
->start_down_word
[i
][j
] == index
)
746 strcpy (p
->clue_down
[i
][j
], clue
);
755 /* function to print a menu */
756 void print_menu (enum COLOR fg
, enum COLOR bg
, const char* title
,
757 char **items
, int num_items
, int padding
)
760 printf ("\e[1;1H\e[2J");
761 set_color (fg
, bg
, NORMAL
);
763 for (int i
= 0; i
< padding
; i
++)
766 reset_color (); printf ("\n");
767 set_color (fg
, bg
, NORMAL
);
769 set_color (fg
, bg
, BOLD
);
770 printf ("%-*s", padding
, title
);
772 set_color (fg
, bg
, NORMAL
);
774 reset_color (); printf ("\n");
775 set_color (fg
, bg
, NORMAL
);
777 for (int i
= 0; i
< padding
; i
++)
780 reset_color (); printf ("\n");
781 for (int i
= 0; i
< num_items
; i
++)
783 set_color (fg
, bg
, NORMAL
);
784 printf ("\u2551%-*s\u2551", padding
, items
[i
]);
785 reset_color (); printf ("\n");
787 set_color (fg
, bg
, NORMAL
);
789 for (int i
= 0; i
< padding
; i
++)
792 reset_color (); printf ("\n");
795 /* reset the player data, from the new file */
796 void reset_player_data (MainPlayerData
*app_data
, const char *filename
)
798 app_data
->puzzle
= load_puzzle (filename
);
800 app_data
->is_loaded
= app_data
->puzzle
.grid_frozen
;
801 app_data
->cur_col
= -1;
802 app_data
->cur_row
= -1;
803 strcpy (app_data
->filename
, filename
);
804 /* reset the answer keys */
805 for (int i
= 0; i
< app_data
->puzzle
.grid_size
; i
++)
806 for (int j
= 0; j
< app_data
->puzzle
.grid_size
; j
++)
807 app_data
->char_ans
[i
][j
] = ' ';
811 /* in the player app, move the current selection index left or right */
812 void move_current_col (MainPlayerData
*app_data
, enum DIRECTION dir
)
814 int r
= app_data
->cur_row
;
815 int c
= app_data
->cur_col
;
816 if (dir
== DIR_FORWARD
)
819 while (c
< app_data
->puzzle
.grid_size
)
821 if (app_data
->puzzle
.chars
[r
][c
] == '#')
826 if (c
< app_data
->puzzle
.grid_size
)
827 app_data
->cur_col
= c
;
834 if (app_data
->puzzle
.chars
[r
][c
] == '#')
840 app_data
->cur_col
= c
;
844 /* in the player app move the current selection index up or down */
845 void move_current_row (MainPlayerData
*app_data
, enum DIRECTION dir
)
847 int r
= app_data
->cur_row
;
848 int c
= app_data
->cur_col
;
849 if (dir
== DIR_FORWARD
)
852 while (r
< app_data
->puzzle
.grid_size
)
854 if (app_data
->puzzle
.chars
[r
][c
] == '#')
859 if (r
< app_data
->puzzle
.grid_size
)
860 app_data
->cur_row
= r
;
867 if (app_data
->puzzle
.chars
[r
][c
] == '#')
873 app_data
->cur_row
= r
;