]> git.cworth.org Git - wordgame/blob - rack-fancy.c
Add display of all missed words with control-C
[wordgame] / rack-fancy.c
1 /*
2  * Copyright © 2006 Carl Worth
3  *
4  * This program is free software; you can redistribute it and\/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2, or (at your option)
7  * any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA."
17  */
18 #include <stdlib.h>
19 #include <string.h>
20 #include <goocanvas.h>
21 #include <sys/time.h>
22 #include <time.h>
23 #include <ctype.h>
24 #include <math.h>
25 #include <gdk/gdkkeysyms.h>
26
27 #include "word-game.h"
28 #include "demo-item.h"
29
30 #define RACK_DICT_ENTRY_FOUND (1<<1)
31 #define MAX_TILES 7
32
33 typedef struct _tile
34 {
35     char letter;
36     int rack_index;
37     int x, y;
38     GooCanvasItem *item;
39     gboolean guessed;
40 } tile_t;
41
42 typedef struct _rack
43 {
44     tile_t *tiles[MAX_TILES];
45     int num_tiles;
46     char guess[MAX_TILES+1];
47     int guess_length;
48     dict_t *solution;
49     GooCanvasItem *solution_item;
50     gboolean done;
51 } rack_t;
52
53 #define LETTER_SIZE 60
54 #define LETTER_PAD 5
55
56 static void
57 guess_tile_position (int i, int *x, int *y)
58 {
59     *x = 20 + i * (LETTER_SIZE + LETTER_PAD);
60     *y = LETTER_PAD;
61 }
62
63 static void
64 rack_tile_position (int i, int *x, int *y)
65 {
66     guess_tile_position (i, x, y);
67     *y += (LETTER_SIZE + LETTER_PAD);
68 }
69
70 typedef struct _dict_paint_cursor
71 {
72     cairo_t *cr;
73     int line_height;
74     int x;
75     int y;
76     int max_column_width;
77     int max_y;
78     gboolean show_all;
79 } dict_paint_cursor_t;
80
81 static void
82 dict_paint_action (void *closure, char *word, dict_entry_t *entry)
83 {
84     dict_paint_cursor_t *cursor = closure;
85     cairo_t *cr = cursor->cr;
86     double new_x, new_y;
87
88     if (strlen (word) < 3)
89         return;
90
91     cairo_move_to (cr, cursor->x, cursor->y);
92     if (*entry & RACK_DICT_ENTRY_FOUND || cursor->show_all) {
93         if (*entry & RACK_DICT_ENTRY_FOUND)
94             cairo_set_source_rgb (cr, 0, 0, 0); /* black */
95         else
96             cairo_set_source_rgb (cr, 1, 0, 0); /* red */
97         cairo_show_text (cr, word);
98     } else {
99         int i, length = strlen (word);
100         for (i = 0; i < length; i++)
101             cairo_show_text (cr, "_");
102     }
103     cairo_get_current_point (cr, &new_x, &new_y);
104     if (new_x > cursor->max_column_width)
105         cursor->max_column_width = new_x;
106     cursor->y += cursor->line_height;
107     if (cursor->y > cursor->max_y) {
108         cursor->x = cursor->max_column_width + cursor->line_height / 2;
109         cursor->y = cursor->line_height;
110     }
111 }
112
113 #define SOLUTION_FONT_SIZE      12
114 #define SOLUTION_LINE_HEIGHT    (1.5 * SOLUTION_FONT_SIZE)
115
116 static void
117 dict_paint (cairo_t *cr, void *closure, double width, double height)
118 {
119     rack_t *rack = closure;
120     dict_paint_cursor_t cursor;
121
122     cairo_save (cr);
123     cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
124
125     cursor.cr = cr;
126
127     cairo_select_font_face (cr, "mono", 0, 0);
128     cairo_set_font_size (cr, SOLUTION_FONT_SIZE);
129     cursor.line_height = SOLUTION_LINE_HEIGHT;
130     
131     cursor.x = 0;
132     cursor.y = cursor.line_height;
133
134     cursor.max_column_width = 0;
135     cursor.max_y = height;
136
137     cursor.show_all = rack->done;
138
139     dict_for_each_by_length (rack->solution,
140                              dict_paint_action,
141                              &cursor);
142
143     cairo_restore (cr);
144 }
145
146 static void
147 tile_paint (cairo_t *cr, void *closure, double width, double height)
148 {
149     tile_t *tile = closure;
150
151     cairo_pattern_t *gradient;
152     cairo_text_extents_t extents;
153     int rad = (int) MIN (width / 2, height / 2);
154     int cx = width / 2;
155     int cy = cx;
156     int tx, ty;
157     double spot_angle = M_PI / 4.0;
158     double spot_rad = rad / 2.0;
159     char string[2];
160
161     cairo_save (cr);
162
163     gradient = cairo_pattern_create_radial (cx - spot_rad * cos (spot_angle),
164                                             cy - spot_rad * sin (spot_angle),
165                                             0.0,
166                                             cx - spot_rad * cos (spot_angle),
167                                             cy - spot_rad * sin (spot_angle),
168                                             rad + spot_rad);
169     cairo_pattern_add_color_stop_rgb (gradient, 0.0, 1.0, 1.0, 1.0);
170     cairo_pattern_add_color_stop_rgb (gradient, 1.0, 0.33, 0.33, 0.33);
171
172     cairo_set_source (cr, gradient);
173
174     cairo_arc (cr,
175                cx, cy,
176                rad, 0, 2 * M_PI);
177
178     cairo_fill (cr);
179
180     cairo_select_font_face (cr, "mono",
181                             CAIRO_FONT_SLANT_NORMAL,
182                             CAIRO_FONT_WEIGHT_BOLD);
183     cairo_set_font_size (cr, 1.8 * rad);
184
185     string[0] = tile->letter;
186     string[1] = '\0';
187     cairo_text_extents (cr, string, &extents);
188     tx = cx - extents.width / 2 - extents.x_bearing;
189     ty = cy - extents.height / 2 - extents.y_bearing;
190
191     cairo_set_source_rgb (cr, 0.7, 0.7, 0.7);
192     cairo_move_to (cr, tx + 1, ty + 1);
193     cairo_show_text (cr, string);
194                 
195     cairo_set_source_rgb (cr, 0.33, 0.33, 0.33);
196     cairo_move_to (cr, tx - 1, ty - 1);
197     cairo_show_text (cr, string);
198
199     cairo_set_source_rgb (cr, 0.2, 0.3, 0.8);
200     cairo_move_to (cr, tx, ty);
201     cairo_show_text (cr, string);
202
203     cairo_restore (cr);
204 }
205
206 static void
207 tile_glide_to (tile_t *tile, int x, int y)
208 {
209     goo_canvas_item_animate (tile->item, x, y,
210                              1.0, 0,
211                              500, 40,
212                              GOO_CANVAS_ANIMATE_FREEZE);
213     tile->x = x;
214     tile->y = y;
215 }
216
217 static tile_t *
218 tile_create (GooCanvasItem *parent,
219              char letter, int rack_index)
220 {
221     tile_t *tile;
222
223     tile = g_malloc (sizeof (tile_t));
224     tile->letter = tolower (letter);
225     tile->rack_index = rack_index;
226     rack_tile_position (rack_index, &tile->x, &tile->y);
227     tile->item = goo_demo_item_new (parent,
228                                     tile->x, tile->y,
229                                     LETTER_SIZE, LETTER_SIZE,
230                                     tile_paint,
231                                     tile, NULL);
232
233     tile->guessed = FALSE;
234
235     return tile;
236 }
237
238 static gboolean
239 on_delete_event (GtkWidget *window,
240                  GdkEvent  *event,
241                  gpointer   unused_data)
242 {
243     exit (0);
244 }
245
246 static int
247 rand_within (int num_values)
248 {
249     return (int) ((double) num_values * (rand() / (RAND_MAX + 1.0)));
250 }
251
252 static void
253 shuffle (int *array, int length)
254 {
255     int i, r, tmp;
256
257     for (i = 0; i < length; i++)
258     {
259         r = i + rand_within (length - i);
260         tmp = array[i];
261         array[i] = array[r];
262         array[r] = tmp;
263     }
264 }
265
266 static void
267 rack_init (rack_t       *rack,
268            dict_t       *solution)
269 {
270     int i;
271
272     for (i = 0; i < MAX_TILES; i++)
273         rack->tiles[i] = NULL;
274     rack->num_tiles = 0;
275
276     rack->guess_length = 0;
277     rack->solution = solution;
278     rack->solution_item = NULL;
279     rack->done = FALSE;
280 }
281
282 static void
283 rack_create_tiles (rack_t       *rack,
284                    GooCanvas    *canvas,
285                    char         *word)
286 {
287     int i;
288
289     GooCanvasItem *root = goo_canvas_get_root_item (canvas);
290
291     for (i = 0; i < MIN (MAX_TILES, strlen (word)); i++)
292         rack->tiles[i] = tile_create (root, word[i], i);
293     rack->num_tiles = i;
294 }
295
296 static gboolean
297 rack_shuffle (rack_t *rack)
298 {
299     int indices[MAX_TILES];
300     int i, x, y;
301
302     for (i = 0; i < rack->num_tiles; i++)
303         indices[i] = i;
304
305     shuffle (indices, rack->num_tiles);
306
307     for (i = 0; i < rack->num_tiles; i++) {
308         rack->tiles[i]->rack_index = indices[i];
309         rack_tile_position (indices[i], &x, &y);
310         tile_glide_to (rack->tiles[i], x, y);
311     }
312
313     return TRUE;
314 }
315
316 static void
317 rack_return_tile (rack_t *rack, tile_t *tile)
318 {
319     int x, y;
320
321     rack_tile_position (tile->rack_index, &x, &y);
322     tile_glide_to (tile, x, y);
323     tile->guessed = FALSE;
324     rack->guess_length--;
325     rack->guess[rack->guess_length] = '\0';
326 }
327
328 static void
329 rack_return_all (rack_t *rack)
330 {
331     int i;
332
333     for (i = 0; i < rack->num_tiles; i++) {
334         if (rack->tiles[i]->guessed)
335             rack_return_tile (rack, rack->tiles[i]);
336     }
337     rack->guess_length = 0;
338     rack->guess[0] = '\0';
339 }
340
341 static gboolean
342 on_key_press_event (GtkWidget   *widget,
343                     GdkEventKey *event,
344                     gpointer     user_data)
345 {
346     int i, x, y;
347     char guess_letter;
348     rack_t *rack = user_data;
349
350     if (event->state & GDK_CONTROL_MASK &&
351         event->keyval == GDK_c)
352     {
353         rack->done = TRUE;
354         goo_canvas_item_simple_changed (GOO_CANVAS_ITEM_SIMPLE (rack->solution_item), FALSE);
355     }
356
357     if (event->keyval == GDK_Return) {
358         dict_entry_t *entry;
359         if (rack->guess_length >= 3) {
360             entry = dict_lookup (rack->solution, rack->guess);
361             if (DICT_ENTRY_IS_WORD (entry)) {
362                 *entry = *entry | RACK_DICT_ENTRY_FOUND;
363                 goo_canvas_item_simple_changed (GOO_CANVAS_ITEM_SIMPLE (rack->solution_item), FALSE);
364             } else {
365                 printf ("\a");
366                 fflush (stdout);
367             }
368         }
369         rack_return_all (rack);
370         return TRUE;
371     }
372
373     if (event->keyval == GDK_space) {
374         rack_return_all (rack);
375         rack_shuffle (rack);
376         return TRUE;
377     }
378
379     if (event->keyval == GDK_BackSpace) {
380         gboolean found = FALSE;
381         int found_index;
382         x = 0;
383         for (i = 0; i < rack->num_tiles; i++) {
384             /* XXX: evil stuff here... please refactor a lot */
385             if (rack->guess[rack->guess_length-1] == rack->tiles[i]->letter &&
386                 rack->tiles[i]->guessed &&
387                 rack->tiles[i]->x > x)
388             {
389                 found = TRUE;
390                 found_index = i;
391             }
392         }
393         if (found) {
394             rack_return_tile (rack, rack->tiles[found_index]);
395             return TRUE;
396         }
397         return FALSE;
398     }
399
400     /* XXX: event->string is deprecated, but the non-deprecated
401      * input-method stuff (GtkIMContext) is extremely non-obvious to
402      * use. */
403     guess_letter = tolower (event->string[0]);
404     for (i = 0; i < rack->num_tiles; i++) {
405         if (guess_letter == rack->tiles[i]->letter && 
406             ! rack->tiles[i]->guessed)
407         {
408             guess_tile_position (rack->guess_length, &x, &y);
409             tile_glide_to (rack->tiles[i], x, y);
410             rack->tiles[i]->guessed = TRUE;
411             rack->guess[rack->guess_length++] = guess_letter;
412             rack->guess[rack->guess_length] = '\0';
413             return TRUE;
414         }
415     }
416
417     return FALSE;
418 }
419
420 static GtkWidget *
421 create_window (rack_t *rack)
422 {
423     GtkWidget *window, *scrolled_window;
424
425     window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
426     gtk_window_set_default_size (GTK_WINDOW (window), 500, 500);
427     gtk_widget_show (window);
428     g_signal_connect (window, "delete_event",
429                       (GtkSignalFunc) on_delete_event, NULL);
430
431     gtk_widget_add_events (window, GDK_KEY_PRESS_MASK);
432     g_signal_connect (window, "key_press_event",
433                       (GtkSignalFunc) on_key_press_event, rack);
434
435     scrolled_window = gtk_scrolled_window_new (NULL, NULL);
436     gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
437                                          GTK_SHADOW_IN);
438     gtk_scrolled_window_set_policy  (GTK_SCROLLED_WINDOW (scrolled_window),
439                                      GTK_POLICY_AUTOMATIC,
440                                      GTK_POLICY_AUTOMATIC);
441     gtk_widget_show (scrolled_window);
442     gtk_container_add (GTK_CONTAINER (window), scrolled_window);
443
444     return scrolled_window;
445 }
446
447 static GooCanvas *
448 create_canvas (GtkWidget *parent, rack_t *rack)
449 {
450     GtkWidget *canvas;
451     GooCanvasItem *root;
452
453     canvas = goo_canvas_new ();
454     gtk_widget_set_size_request (canvas, 400, 480);
455     goo_canvas_set_bounds (GOO_CANVAS (canvas), 0, 0, 400, 480);
456     gtk_widget_show (canvas);
457     gtk_container_add (GTK_CONTAINER (parent), canvas);
458
459     root = goo_canvas_get_root_item (GOO_CANVAS (canvas));
460
461     rack->solution_item = goo_demo_item_new (root,
462                                              20,
463                                              LETTER_PAD + 2 * (LETTER_SIZE + 2 * LETTER_PAD),
464                                              400 - 20, 480 - (LETTER_PAD + 2 * (LETTER_SIZE + 2 * LETTER_PAD)),
465                                              dict_paint, rack,
466                                              NULL);
467
468     return GOO_CANVAS (canvas);
469 }
470
471 int
472 main (int argc, char *argv[])
473 {
474     dict_t dict, solution;
475     struct timeval tv;
476     bag_t bag;
477     rack_t rack;
478     char word[8];
479     int i;
480     char *draw;
481     GtkWidget *window;
482     GooCanvas *canvas;
483
484     gettimeofday (&tv, NULL);
485     srand (tv.tv_sec ^ tv.tv_usec);
486
487     bag_init (&bag);
488     bag_shuffle (&bag);
489
490     /* Keep drawing until we get 7 non-blank tiles */
491     i = 0;
492     draw = bag.tiles;
493     while (i < 7) {
494         if (*draw != '?')
495             word[i++] = toupper (*draw);
496         draw++;
497     }
498
499     dict_init (&dict);
500     dict_add_words_from_file (&dict, "words.txt");
501
502     dict_init (&solution);
503     subanagram_expand (word, &dict, &solution);
504
505     gtk_init (&argc, &argv);
506
507     rack_init (&rack, &solution);
508
509     window = create_window (&rack);
510
511     canvas = create_canvas (window, &rack);
512
513     rack_create_tiles (&rack, canvas, word);
514
515     gtk_main ();
516
517     return 0;
518 }