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 /* get a number from the user */
113 /* verify password */
114 bool verify_password (Puzzle
*p
, const char* password
)
116 /* no password set */
117 if (strcmp (p
->hashed_password
, "\0") == 0)
120 /* hash the user input password and compare it with the stored password */
121 unsigned char* hashed_password
;
123 digest_message ((const unsigned char *)password
, strlen(password
),
124 &hashed_password
, &len
);
126 if (strcmp (p
->hashed_password
, (const char*) hashed_password
) == 0)
128 OPENSSL_free (hashed_password
);
131 OPENSSL_free (hashed_password
);
135 /* Set or reset password for puzzle */
136 void set_puzzle_password (Puzzle
*p
, const char *password
)
138 /* if it is a null string, reset the password */
139 if (strcmp (password
, "\0") == 0)
141 strcpy (p
->hashed_password
, "\0");
142 strcpy (p
->salt
, "\0");
147 unsigned char* hashedpwd
;
149 digest_message ((const unsigned char *)password
, strlen(password
),
151 strcpy (p
->hashed_password
, (const char *)hashedpwd
);
152 strcpy (p
->salt
, "\0");
153 OPENSSL_free (hashedpwd
);
157 /* Output the clues to text file */
158 void export_clues (Puzzle
*p
, const char *filename
)
160 FILE *outfile
= fopen (filename
, "w");
163 fprintf (stderr
, "%s\n", ERROR_WRITING_FILE
);
166 /* first the across clues */
167 fprintf (outfile
, "ACROSS CLUES\n");
168 for (int i
= 0; i
< p
->grid_size
; i
++)
170 for (int j
= 0; j
< p
->grid_size
; j
++)
172 if (p
->start_across_word
[i
][j
] != -1)
173 fprintf (outfile
, "%d - %s\n", p
->start_across_word
[i
][j
],
174 p
->clue_across
[i
][j
]);
177 /* now the down clues */
178 fprintf (outfile
, "DOWN CLUES\n");
179 for (int i
= 0; i
< p
->grid_size
; i
++)
181 for (int j
= 0; j
< p
->grid_size
; j
++)
183 if (p
->start_down_word
[i
][j
] != -1)
184 fprintf (outfile
, "%d - %s\n", p
->start_down_word
[i
][j
],
191 /* Output the grid to image - if answerkey is true export filled grid */
192 void export_grid_image (Puzzle
*p
, const char *filename
, bool answerkey
)
194 int img_size
= p
->grid_size
* GRID_PIXELS
;
195 FILE * outfile
= fopen (filename
, "wb");
198 fprintf (stderr
, "%s\n", ERROR_WRITING_FILE
);
202 gdImagePtr img
= gdImageCreate (img_size
, img_size
);
203 gdImageColorAllocate (img
, 255,255,255);
204 int black
= gdImageColorAllocate (img
, 0, 0, 0);
205 int blue
= gdImageColorAllocate (img
, 0, 0, 216);
206 gdFontPtr sm_fnt
= gdFontGetMediumBold ();
207 gdFontPtr lg_fnt
= gdFontGetGiant ();
209 for (int i
= 0; i
< p
->grid_size
; i
++)
211 for (int j
= 0; j
< p
->grid_size
; j
++)
213 /* if it is a block, draw the black square */
214 if (p
->chars
[i
][j
] == '#')
215 gdImageFilledRectangle (img
, j
*GRID_PIXELS
, i
*GRID_PIXELS
,
216 j
*GRID_PIXELS
+GRID_PIXELS
,
217 i
*GRID_PIXELS
+GRID_PIXELS
,black
);
220 /* draw a regular square */
221 gdImageRectangle (img
, j
*GRID_PIXELS
, i
*GRID_PIXELS
,
222 j
*GRID_PIXELS
+GRID_PIXELS
,
223 i
*GRID_PIXELS
+GRID_PIXELS
, black
);
225 /* print the numers, if it is either start across word or
227 if (p
->start_across_word
[i
][j
] != -1 ||
228 p
->start_down_word
[i
][j
] != -1)
230 if (p
->start_across_word
[i
][j
] != -1)
233 sprintf (str
, "%d", p
->start_across_word
[i
][j
]);
234 gdImageString (img
, sm_fnt
, j
*GRID_PIXELS
+2,
236 (unsigned char *)str
, blue
);
241 sprintf (str
, "%d", p
->start_down_word
[i
][j
]);
242 gdImageString (img
, sm_fnt
, j
*GRID_PIXELS
+2,
244 (unsigned char *)str
, blue
);
247 /* if answerkey is true, draw the character in the cell */
250 gdImageChar (img
, lg_fnt
, j
*GRID_PIXELS
+15,
251 i
*GRID_PIXELS
+10, p
->chars
[i
][j
], black
);
257 gdImagePng (img
, outfile
);
258 gdImageDestroy (img
);
262 /* Set the terminal colour */
263 void set_color (enum COLOR fg
, enum COLOR bg
, enum ATTR at
) {
264 printf ("\x1B[%d;%d;%dm", fg
+30, bg
+40, at
);
267 /* Reset the terminal colour */
268 void reset_color () {
272 /* check if previous row is blank or not */
273 bool prev_row_blank (Puzzle
*p
, int r
, int c
)
275 if (r
== 0) return true;
276 if (p
->chars
[r
-1][c
] == ' ' || p
->chars
[r
-1][c
] == '#') return true;
279 /* check if next row is blank or not */
280 bool next_row_blank (Puzzle
*p
, int r
, int c
)
282 if (r
== p
->grid_size
- 1) return true;
283 if (p
->chars
[r
+1][c
] == ' ' || p
->chars
[r
+1][c
] == '#') return true;
286 /* check if previous col is blank or not */
287 bool prev_col_blank (Puzzle
*p
, int r
, int c
)
289 if (c
== 0) return true;
290 if (p
->chars
[r
][c
-1] == ' ' || p
->chars
[r
][c
-1] == '#') return true;
293 /* check if the next col is blank or not */
294 bool next_col_blank (Puzzle
*p
, int r
, int c
)
296 if (c
== p
->grid_size
-1) return true;
297 if (p
->chars
[r
][c
+1] == ' ' || p
->chars
[r
][c
+1] == '#') return true;
301 /* unfreeze the grid - make editing possible to change words */
302 void unfreeze_puzzle (Puzzle
*p
)
304 for (int i
= 0; i
< p
->grid_size
; i
++)
306 for (int j
= 0; j
< p
->grid_size
; j
++)
308 if (p
->chars
[i
][j
] == '#')
309 p
->chars
[i
][j
] = ' ';
311 p
->start_across_word
[i
][j
] = -1;
312 p
->start_down_word
[i
][j
] = -1;
315 p
->grid_frozen
= false;
318 /* freeze the grid - make editing impossible because it finalizes the
319 across and down words in the grid */
320 void freeze_puzzle (Puzzle
*p
)
323 bool across_word_start
, down_word_start
;
324 for (int i
= 0; i
< p
->grid_size
; i
++)
326 for (int j
= 0; j
< p
->grid_size
; j
++)
328 across_word_start
= false;
329 down_word_start
= false;
330 /* if it is a blank cell - cover it with a block */
331 if (p
->chars
[i
][j
] == ' ' || p
->chars
[i
][j
] == '#')
332 p
->chars
[i
][j
] = '#';
333 /* it is not a blank cell - check all possibilities */
336 bool prev_row
= prev_row_blank (p
, i
, j
);
337 bool next_row
= next_row_blank (p
, i
, j
);
338 bool prev_col
= prev_col_blank (p
, i
, j
);
339 bool next_col
= next_col_blank (p
, i
, j
);
340 if (prev_row
&& ! next_row
)
341 down_word_start
= true;
342 if (prev_col
&& ! next_col
)
343 across_word_start
= true;
346 if (across_word_start
== true)
347 p
->start_across_word
[i
][j
] = word_num
;
349 p
->start_across_word
[i
][j
] = -1;
350 if (down_word_start
== true)
351 p
->start_down_word
[i
][j
] = word_num
;
353 p
->start_down_word
[i
][j
] = -1;
354 if (across_word_start
== true || down_word_start
== true)
358 p
->grid_frozen
= true;
361 /* reset the entire grid */
362 void init_puzzle (Puzzle
*p
, int grid_size
)
364 p
->grid_size
= grid_size
;
365 p
->grid_frozen
= false;
366 for (int i
= 0; i
< p
->grid_size
; i
++)
368 for (int j
= 0; j
< p
->grid_size
; j
++)
370 p
->chars
[i
][j
] = ' ';
371 p
->start_across_word
[i
][j
] = -1;
372 p
->start_down_word
[i
][j
] = -1;
373 strcpy (p
->clue_across
[i
][j
], "");
374 strcpy (p
->clue_down
[i
][j
], "");
377 strcpy (p
->hashed_password
, "\0");
378 strcpy (p
->salt
, "\0");
382 /* save the puzzle to a file */
383 void save_puzzle (Puzzle
*puzzle
, const char* file
) {
385 /* First output the uncompressed contents to a temp file */
386 outfile
= tmpfile ();
389 fprintf (stderr
, "%s\n", ERROR_WRITING_FILE
);
392 /* grid size is the first field */
393 fprintf (outfile
, "%d\n", puzzle
->grid_size
);
394 /* whether grid is frozen or not */
395 fprintf (outfile
, "%d\n", puzzle
->grid_frozen
);
396 /* the hashed password */
397 fprintf (outfile
, "%s\n", puzzle
->hashed_password
);
399 fprintf (outfile
, "%s\n", puzzle
->salt
);
401 /* First output the grid characters columns/rows */
402 for (int i
= 0; i
< puzzle
->grid_size
; i
++)
404 for (int j
= 0; j
< puzzle
->grid_size
; j
++)
405 fprintf (outfile
, "%c", puzzle
->chars
[i
][j
]);
406 fprintf (outfile
, "\n");
409 /* Next output the start across/down numbers */
410 for (int i
= 0; i
< puzzle
->grid_size
; i
++)
412 for (int j
= 0; j
< puzzle
->grid_size
; j
++)
414 fprintf (outfile
, "%d ", puzzle
->start_across_word
[i
][j
]);
415 fprintf (outfile
, "%d ", puzzle
->start_down_word
[i
][j
]);
417 fprintf (outfile
, "\n");
420 /* Output the across clues */
421 fprintf (outfile
, "ACROSS\n");
422 /* Search the grid for across words */
423 for (int i
= 0; i
< puzzle
->grid_size
; i
++)
425 for (int j
= 0; j
< puzzle
->grid_size
; j
++)
427 /* if it is an across word, then put the word index followed by
428 tab character (as separator) and the clue */
429 if (puzzle
->start_across_word
[i
][j
] != -1)
430 fprintf (outfile
, "%d\t%s\n", puzzle
->start_across_word
[i
][j
],
431 puzzle
->clue_across
[i
][j
]);
435 /* Output the down clues */
436 fprintf (outfile
, "DOWN\n");
437 /* Search the grid for down words */
438 for (int i
= 0; i
< puzzle
->grid_size
; i
++)
440 for (int j
= 0; j
< puzzle
->grid_size
; j
++)
442 /* same as across word, put the word index followed by the tab
443 character and then the clue */
444 if (puzzle
->start_down_word
[i
][j
] != -1)
445 fprintf (outfile
, "%d\t%s\n", puzzle
->start_down_word
[i
][j
],
446 puzzle
->clue_down
[i
][j
]);
450 /* Flush the buffer and rewind to beginning - to read and save into
451 gzip compressed file */
453 fseek (outfile
, 0, 0);
455 /* now compress the file and save it to destination file */
456 gzFile outdestfile
= gzopen (file
, "wb");
457 if (outdestfile
== NULL
)
459 fprintf (stderr
, "%s\n", ERROR_WRITING_FILE
);
464 int num
= fread (buf
, sizeof(char), sizeof(char)*128, outfile
);
467 int res
= gzwrite (outdestfile
, buf
, num
*sizeof(char) );
470 fprintf (stderr
, "%s %s\n", ERROR_WRITING_FILE
, COMPRESSED
);
474 num
= fread (buf
, sizeof(char), sizeof(char)*128, outfile
);
476 gzclose (outdestfile
);
481 /* read the puzzle from a file */
482 Puzzle
load_puzzle (const char* file
) {
483 /* First open the GZip file */
484 gzFile insourcefile
= gzopen (file
, "rb");
485 if (insourcefile
== NULL
)
487 fprintf (stderr
, "%s %s\n", ERROR_READING_FILE
, COMPRESSED
);
490 /* Open a temporary file to uncompress the contents */
491 FILE *infile
= tmpfile ();
494 fprintf (stderr
, "%s\n", ERROR_READING_FILE
);
497 /* Put the uncompressed content to the temp file */
500 num
= gzread (insourcefile
, buf
, 128);
503 int res
= fwrite (buf
, 1, num
, infile
);
506 fprintf (stderr
, "%s\n", ERROR_READING_FILE
);
508 gzclose (insourcefile
);
511 num
= gzread (insourcefile
, buf
, 128);
513 /* Close the gzip file */
514 gzclose (insourcefile
);
515 /* Flush the temp file buffer and rewind to beginning */
517 fseek (infile
, 0, 0);
519 /* Read the temporary file contents to the structure Puzzle */
521 char line
[MAX_CLUE_LENGTH
+10];
522 fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
);
523 p
.grid_size
= atoi (line
);
524 fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
);
525 p
.grid_frozen
= atoi (line
) == 0 ? false : true ;
526 fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
);
527 if (strlen (line
) != 1)
528 strcpy (p
.hashed_password
, strtok (line
, "\n"));
530 strcpy (p
.hashed_password
, "\0");
531 fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
);
532 if (strlen (line
) != 1)
533 strcpy (p
.salt
, strtok (line
, "\n"));
535 strcpy (p
.salt
, "\0");
537 /* read each character of the grid */
538 for (int i
= 0; i
< p
.grid_size
; i
++ )
540 fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
);
541 for (int j
= 0; j
< p
.grid_size
; j
++)
542 p
.chars
[i
][j
] = line
[j
];
544 /* read the word numbers */
545 for (int i
= 0; i
< p
.grid_size
; i
++)
547 fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
);
548 char *token
= strtok (line
, " ");
549 for (int j
= 0; j
< p
.grid_size
; j
++)
552 p
.start_across_word
[i
][j
] = atoi (token
);
553 token
= strtok (NULL
, " ");
555 p
.start_down_word
[i
][j
] = atoi (token
);
556 token
= strtok (NULL
, " ");
560 fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
);
563 char clues
[100][MAX_CLUE_LENGTH
];
566 /* first read the across clues from file */
569 fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
);
570 /* if reached the end of across clues */
571 if (strcmp (line
, "DOWN\n") == 0)
573 word_num
[c
] = atoi (strtok (line
, "\t"));
574 char *cl
= strtok (NULL
, "\n");
576 strcpy (clues
[c
], cl
);
578 strcpy (clues
[c
], "\0");
581 /* set the clue to the correct cell in grid */
582 for (int i
= 0; i
< p
.grid_size
; i
++)
584 for (int j
= 0; j
< p
.grid_size
; j
++)
586 for (int r
= 0; r
< c
; r
++)
587 if (p
.start_across_word
[i
][j
] == word_num
[r
])
588 strcpy (p
.clue_across
[i
][j
], clues
[r
]);
594 while (fgets (line
, MAX_CLUE_LENGTH
+ 10, infile
))
596 word_num
[c
] = atoi (strtok (line
, "\t"));
597 char* cl
= strtok (NULL
, "\n");
599 strcpy (clues
[c
], cl
);
601 strcpy (clues
[c
], "\0");
604 for (int i
= 0; i
< p
.grid_size
; i
++)
606 for (int j
= 0; j
< p
.grid_size
; j
++)
608 for (int r
= 0; r
< c
; r
++)
609 if (p
.start_down_word
[i
][j
] == word_num
[r
])
610 strcpy (p
.clue_down
[i
][j
], clues
[r
]);
618 /* display the puzzle */
619 void print_puzzle (Puzzle
*p
)
622 set_color (WHITE
, CYAN
, NORMAL
);
624 for (int i
= 0; i
< p
->grid_size
; i
++)
628 for (int i
= 0; i
< p
->grid_size
; i
++)
630 set_color (WHITE
, CYAN
, NORMAL
);
632 for (int j
= 0; j
< p
->grid_size
; j
++)
634 if (p
->chars
[i
][j
] == '#') {
635 set_color (WHITE
, BLACK
, NORMAL
);
640 if (p
->start_across_word
[i
][j
] != -1 ||
641 p
->start_down_word
[i
][j
] != -1)
643 set_color (BLUE
, WHITE
, NORMAL
);
644 if (p
->start_across_word
[i
][j
] != -1)
645 printf ("%-2d", p
->start_across_word
[i
][j
]);
647 printf ("%-2d", p
->start_down_word
[i
][j
]);
651 set_color (BLACK
, WHITE
,NORMAL
);
655 set_color (BLACK
, WHITE
, BOLD
);
656 printf ("%c", p
->chars
[i
][j
]);
662 /* print the clues if set */
663 if (p
->grid_frozen
== true)
665 printf ("\x1B[1mACROSS - CLUES\x1B[0m\n");
666 for (int i
= 0; i
< p
->grid_size
; i
++)
668 for (int j
= 0; j
< p
->grid_size
; j
++)
670 if (p
->start_across_word
[i
][j
] != -1)
672 printf ("%d - %s; ", p
->start_across_word
[i
][j
],
673 p
->clue_across
[i
][j
]);
677 printf ("\n\x1B[1mDOWN - CLUES\x1B[0m\n");
678 for (int i
= 0; i
< p
->grid_size
; i
++)
680 for (int j
= 0; j
< p
->grid_size
; j
++)
682 if (p
->start_down_word
[i
][j
] != -1)
684 printf ("%d - %s; ", p
->start_down_word
[i
][j
],
693 /* function to check if a word is valid or not */
694 char* is_valid_word (char *word
)
696 if (word
== NULL
|| strlen(word
) == 0)
698 for (int i
= 0; i
< strlen (word
) - 1; i
++)
699 if (! isalpha (word
[i
]))
702 return strtok (word
, "\n");
706 /* function to set a clue for an across word */
707 bool set_clue (Puzzle
*p
, String clue
, int index
, enum ORIENTATION order
)
709 for (int i
= 0; i
< p
->grid_size
; i
++)
711 for (int j
= 0; j
< p
->grid_size
; j
++)
715 if (p
->start_across_word
[i
][j
] == index
)
717 strcpy (p
->clue_across
[i
][j
], clue
);
721 else if (order
== DOWN
)
723 if (p
->start_down_word
[i
][j
] == index
)
725 strcpy (p
->clue_down
[i
][j
], clue
);
734 /* function to print a menu */
735 void print_menu (enum COLOR fg
, enum COLOR bg
, const char* title
,
736 char **items
, int num_items
, int padding
)
739 printf ("\e[1;1H\e[2J");
740 set_color (fg
, bg
, NORMAL
);
742 for (int i
= 0; i
< padding
; i
++)
745 reset_color (); printf ("\n");
746 set_color (fg
, bg
, NORMAL
);
748 set_color (fg
, bg
, BOLD
);
749 printf ("%-*s", padding
, title
);
751 set_color (fg
, bg
, NORMAL
);
753 reset_color (); printf ("\n");
754 set_color (fg
, bg
, NORMAL
);
756 for (int i
= 0; i
< padding
; i
++)
759 reset_color (); printf ("\n");
760 for (int i
= 0; i
< num_items
; i
++)
762 set_color (fg
, bg
, NORMAL
);
763 printf ("\u2551%-*s\u2551", padding
, items
[i
]);
764 reset_color (); printf ("\n");
766 set_color (fg
, bg
, NORMAL
);
768 for (int i
= 0; i
< padding
; i
++)
771 reset_color (); printf ("\n");
774 /* reset the player data, from the new file */
775 void reset_player_data (MainPlayerData
*app_data
, const char *filename
)
777 app_data
->puzzle
= load_puzzle (filename
);
779 app_data
->is_loaded
= app_data
->puzzle
.grid_frozen
;
780 app_data
->cur_col
= -1;
781 app_data
->cur_row
= -1;
782 strcpy (app_data
->filename
, filename
);
783 /* reset the answer keys */
784 for (int i
= 0; i
< app_data
->puzzle
.grid_size
; i
++)
785 for (int j
= 0; j
< app_data
->puzzle
.grid_size
; j
++)
786 app_data
->char_ans
[i
][j
] = ' ';
790 /* in the player app, move the current selection index left or right */
791 void move_current_col (MainPlayerData
*app_data
, enum DIRECTION dir
)
793 int r
= app_data
->cur_row
;
794 int c
= app_data
->cur_col
;
795 if (dir
== DIR_FORWARD
)
798 while (c
< app_data
->puzzle
.grid_size
)
800 if (app_data
->puzzle
.chars
[r
][c
] == '#')
805 if (c
< app_data
->puzzle
.grid_size
)
806 app_data
->cur_col
= c
;
813 if (app_data
->puzzle
.chars
[r
][c
] == '#')
819 app_data
->cur_col
= c
;
823 /* in the player app move the current selection index up or down */
824 void move_current_row (MainPlayerData
*app_data
, enum DIRECTION dir
)
826 int r
= app_data
->cur_row
;
827 int c
= app_data
->cur_col
;
828 if (dir
== DIR_FORWARD
)
831 while (r
< app_data
->puzzle
.grid_size
)
833 if (app_data
->puzzle
.chars
[r
][c
] == '#')
838 if (r
< app_data
->puzzle
.grid_size
)
839 app_data
->cur_row
= r
;
846 if (app_data
->puzzle
.chars
[r
][c
] == '#')
852 app_data
->cur_row
= r
;