]> git.cworth.org Git - kub/blob - kub.c
a4d5dceae3e750fd2e87afe64bf6fbce5638497f
[kub] / kub.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <time.h>
4 #include <gtk/gtk.h>
5 #include <librsvg/rsvg.h>
6 #include <librsvg/rsvg-cairo.h>
7
8 /*
9  *   TILE_NUMBER_X_OFFSET = 3
10  * //
11  * || TILE_NUMBER_WIDTH = 34
12  * ||/   /
13  * ||   |  TILE_WIDTH = 40
14  * ||   | /
15  * |     |
16  * +-----+ - TILE_NUMBER_Y_OFFSET = 3   -
17  * |+---+| -                            |
18  * ||   ||                              |
19  * ||   ||   TILE_NUMBER_HEIGHT = 24    |
20  * |+---+| -                            |
21  * |  _  | _                            |-TILE_HEIGHT = 50
22  * | / \ |                              |
23  * ||   ||   TILE_SUN_HEIGHT = 20       |
24  * | \_/ | _                            |
25  * |     |   TILE_SUN_Y_OFFSET = 3      |
26  * +-----+ -                            -
27  * ||   | TILE_SUN_WIDTH = 20
28  * ||
29  * || TILE_SUN_X_OFFSET = 10
30  */
31 #define TILE_WIDTH              40
32 #define TILE_HEIGHT             50
33
34 #define TILE_NUMBER_X_OFFSET    3
35 #define TILE_NUMBER_Y_OFFSET    3
36 #define TILE_NUMBER_WIDTH       34
37 #define TILE_NUMBER_HEIGHT      24
38
39 #define TILE_SUN_X_OFFSET       10
40 #define TILE_SUN_Y_OFFSET       3
41 #define TILE_SUN_WIDTH          20
42 #define TILE_SUN_HEIGHT         20
43
44 #define FATAL_ERROR(msg)                                                \
45     do { fprintf (stderr, "Error: %s\n", msg); exit (1); } while (0)
46
47 char *colors[] = {"Black", "Blue", "Red", "Yellow"};
48
49 typedef enum {BLACK, BLUE, RED, YELLOW} color_t;
50
51 typedef struct tile {
52     color_t color;
53     int number;
54     int x;
55     int y;
56     int selected;
57 } tile_t;
58
59 #define DECK_MAX_TILES 104
60
61 typedef struct deck {
62     tile_t tiles[DECK_MAX_TILES];
63     int num_tiles;
64 } deck_t;
65
66 #define TILE_GROUP_MAX_TILES DECK_MAX_TILES
67
68 typedef struct tile_group {
69     tile_t tiles[TILE_GROUP_MAX_TILES];
70     int num_tiles;
71 } tile_group_t;
72
73 #define BOARD_MAX_TILE_GROUPS (DECK_MAX_TILES / 3)
74
75 typedef struct board {
76     tile_group_t groups[BOARD_MAX_TILE_GROUPS];
77     int num_groups;
78 } board_t;
79
80 typedef struct player {
81     tile_group_t hand;
82 } player_t;
83
84 typedef struct selection_box {
85     int x1, x2, y1, y2, visible;
86 } selection_box_t;
87
88 #define GAME_MAX_PLAYERS 4
89 #define GAME_WINDOW_DEFAULT_WIDTH  800
90 #define GAME_WINDOW_DEFAULT_HEIGHT 600
91
92 typedef struct game {
93     player_t players[GAME_MAX_PLAYERS];
94     int num_players;
95     board_t board;
96     deck_t deck;
97     selection_box_t selection_box;
98     RsvgHandle *blanktile;
99     RsvgHandle *selectedtile;
100
101     int current_tile;
102     int select_mode;
103     int diff_x, diff_y;
104     int click_x, click_y;
105     int release_x, release_y;    /*Currently unused*/
106 } game_t;
107
108 static void selection_box_init(selection_box_t *box)
109 {
110     box->x1 = 0;
111     box->x2 = 0;
112     box->y1 = 0;
113     box->y2 = 0;
114     box->visible = 0;
115 }
116
117 static void selection_box_draw(selection_box_t *box, cairo_t *cr)
118 {
119     cairo_rectangle (cr, box->x1, box->y1, box->x2 - box->x1, box->y2 - box->y1);
120     cairo_set_source_rgba(cr, 0.0, 0.1, 0.2, 0.5);
121     cairo_fill (cr);
122 }
123
124 static void tile_init (tile_t *tile, color_t color, int number)
125 {
126     tile->color = color;
127     tile->number = number;
128     tile->x = 0;
129     tile->y = 0;
130     tile->selected = 0;
131 }
132
133 static void tile_set_x_y (tile_t *tile, int x, int y)
134 {
135     tile->x = x;
136     tile->y = y;
137 }
138
139 static void tile_print(tile_t tile)
140 {
141     printf("%6s %2d\n", colors[tile.color], tile.number + 1);
142 }
143
144 static void tile_draw(game_t *game, tile_t *tile, cairo_t *cr, GdkRegion *region)
145 {
146     char number_string[3];
147     int len;
148     GdkRectangle rectangle;
149
150     rectangle.x = tile->x - 1;
151     rectangle.y = tile->y - 1;
152     rectangle.width = TILE_WIDTH + 2;
153     rectangle.height = TILE_HEIGHT + 2;
154     if (gdk_region_rect_in (region, &rectangle) == GDK_OVERLAP_RECTANGLE_OUT)
155         return;
156
157     len = snprintf (number_string, 3, "%d", tile->number + 1);
158     if (len < 0 || len >= 3)
159         FATAL_ERROR ("snprintf failed");
160
161     cairo_save(cr);
162     cairo_translate(cr, tile->x, tile->y);
163
164     if (tile->selected)
165         rsvg_handle_render_cairo (game->selectedtile, cr);
166     else
167         rsvg_handle_render_cairo (game->blanktile, cr);
168
169     if (tile->color == BLACK)
170         cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
171     if (tile->color == BLUE)
172         cairo_set_source_rgb (cr, 0.0, 0.0, 1.0);
173     if (tile->color == RED)
174         cairo_set_source_rgb (cr, 1.0, 0.0, 0.0);
175     if (tile->color == YELLOW)
176         cairo_set_source_rgb (cr, 1.0, .843, 0.0);
177     if (tile->number + 1 > 9)
178         cairo_move_to (cr, 1, 25);
179     else
180         cairo_move_to (cr, 10, 25);
181     cairo_set_font_size(cr, 25);
182     cairo_show_text (cr, number_string);
183
184     cairo_restore(cr);
185 }
186
187 static void tile_group_init(tile_group_t *tile_group)
188 {
189     tile_group->num_tiles = 0;
190 }
191
192 static void board_init(board_t *board)
193 {
194     int i;
195     board->num_groups = 0;
196
197     for (i = 0; i <= BOARD_MAX_TILE_GROUPS; ++i)
198     {
199         tile_group_init(&board->groups[i]);
200     }
201 }
202
203 static void player_init(player_t *player)
204 {
205     tile_group_init(&player->hand);
206 }
207
208
209 /* If tile_one < tile_two, then return value will be negative
210    if they are equal, 0 will be returned,
211    if tile_one > tile_two, then return value will be positive */
212 static int tile_compare(const void *one, const void *two)
213 {
214     const tile_t *tile_one = one;
215     const tile_t *tile_two = two;
216     return tile_one->number - tile_two->number;
217 }
218
219 static int tile_group_is_run_one(tile_group_t *tile_group)
220 {
221     int i;
222     qsort (&tile_group->tiles[0], tile_group->num_tiles,
223            sizeof (tile_t), tile_compare);
224
225     if (tile_group->num_tiles > 13 || tile_group->num_tiles < 3)
226     {
227         printf("fail run - invalid num tiles; ");
228         return 0;
229     }
230     for (i = 0; i < tile_group->num_tiles - 1; ++i)
231     {
232         if(tile_group->tiles[i].color != tile_group->tiles[i + 1].color)
233         {
234             printf("fail run - colors don't match; ");
235             return 0;
236         }
237         if( tile_group->tiles[i].number != tile_group->tiles[i + 1].number -1 &&
238             i+1 != tile_group->num_tiles)
239         {
240             printf("fail run - invalid number sequence; ");
241             return 0;
242         }
243     }
244     return 1;
245 }
246
247
248 static int tile_group_is_run_two(tile_group_t *tile_group)
249 {
250     int i;
251     int lowest = 14, highest = 0;
252     color_t run_color;
253
254     /* By definition, a run must have at least 3 tiles. Also, it's
255      * impossible for any group of tiles with more than 13 tiles to be
256      * a run, (there are only 13 unique numbers so a group with more
257      * than 13 tiles must have some duplicates).
258      */
259     if (tile_group->num_tiles < 3 || tile_group->num_tiles > 13)
260     {
261         return 0;
262     }
263
264     /* Loop through all tiles in the group, ensuring that they are all
265      * the same color and finding the highest and lowest number in the
266      * group. */
267     run_color = tile_group->tiles[0].color;
268
269     for (i = 0; i < tile_group->num_tiles; i++)
270     {
271         if (tile_group->tiles[i].color != run_color)
272             return 0;
273         if (tile_group->tiles[i].number > highest)
274         {
275             highest = tile_group->tiles[i].number;
276         }
277         if (tile_group->tiles[i].number < lowest)
278         {
279             lowest = tile_group->tiles[i].number;
280         }
281     }
282
283     /* For a run, the difference between the highest and lowest tiles
284      * will always be one less than the number of tiles in the
285      * group. If not then we know it's not a run.
286      */
287     if (highest - lowest != tile_group->num_tiles - 1)
288     {
289         return 0;
290     }
291
292     /* XXX: There's a bug here. We're guessing that at this point
293      * anything we're looking at must be a run. This would be correct
294      * if there were no duplicate tiles, but since there are
295      * duplicates this us quite broken. For example consider two
296      * sequences of entirely red tiles:
297      *
298      * This is a run:   1, 2, 3, 4
299      * But this is not: 1, 3, 4, 4
300      *
301      * As currently written, this function will consider both of these
302      * groups to be a run. One possible fix is to throw away the
303      * highest - lowest heuristic and instead simply sort the tiles up
304      * front and ensure the difference between each adjacent pair is
305      * exactly 1.
306      */
307     return 1;
308 }
309
310 static int tile_group_is_set(tile_group_t *tile_group)
311 {
312     int i;
313     color_t seen_color[4];
314     for (i = 0; i < 4; i++)
315         seen_color[i] = 0;
316
317     if (tile_group->num_tiles > 4 || tile_group->num_tiles < 3)
318     {
319         printf("fail set - invalid num tiles; ");
320         return 0;
321     }
322     for (i = 0; i <= tile_group->num_tiles - 1; ++i)
323     {
324         if (tile_group->tiles[i].number != tile_group->tiles[i + 1].number &&
325             i+1 != tile_group->num_tiles)
326         {
327             printf("fail set - numbers don't match; ");
328             return 0;
329         }
330         seen_color[tile_group->tiles[i].color] += 1;
331     }
332     for (i = 0; i < 4; i++)
333     {
334         if (seen_color[i] > 1)
335         {
336             printf("fail set - repeat color; ");
337             return 0;
338         }
339     }
340     return 1;
341 }
342
343 static void deck_deal(game_t *game, deck_t *deck)
344 {
345     tile_t temp;
346     int rand_tile;
347     int i, j, newline;
348
349     printf ("How many players(1-4) should I deal in? ");
350     game->num_players = getchar();
351     if (game->num_players == EOF)
352     {
353         printf ("\nGoodbye.\n");
354         exit (1);
355     }
356     newline = getchar();
357     game->num_players -= '0';
358
359     for (i = 0; i < game->num_players; ++i)
360     {
361         for (j = 0; j < 14; ++j)
362         {
363             rand_tile = ((deck->num_tiles + 1.0) * rand()) / (RAND_MAX + 1.0);
364             temp = deck->tiles[rand_tile];
365             deck->tiles[rand_tile] = deck->tiles[deck->num_tiles - 1];
366             game->players[i].hand.tiles[j] = temp;
367             deck->num_tiles -= 1;
368             game->players[i].hand.num_tiles += 1;
369         }
370     }
371     printf ("Game dealt for %d player(s)\n", game->num_players);
372 }
373
374 static void deck_init(deck_t *deck)
375 {
376     int h, i, j;
377     deck->num_tiles = 0;
378     for (h = 0; h <= 1; ++h)
379     {
380         for (i = 0; i <= 3; ++i)
381         {
382             for (j = 0; j <= 12; ++j)
383             {
384                 tile_init (&deck->tiles[deck->num_tiles++], i, j);
385                 printf ("There are %d tiles in the deck\n", deck->num_tiles);
386             }
387         }
388     }
389 }
390
391 static void deck_shuffle(deck_t *deck)
392 {
393     tile_t temp;
394     int rand_tile;
395     int last;
396     for (last = deck->num_tiles; last > 0; --last)
397     {
398         rand_tile = ((last + 1.0) * rand()) / (RAND_MAX + 1.0);
399         temp = deck->tiles[rand_tile];
400         deck->tiles[rand_tile] = deck->tiles[last - 1];
401         deck->tiles[last - 1] = temp;
402     }
403 }
404
405 static void deck_print(deck_t *deck)
406 {
407     int h, i, j;
408     for (h = 0; h < 2; ++h)
409     {
410         for (i = 0; i < 4; ++i)
411         {
412             for (j = 0; j < 13; ++j)
413             {
414                 tile_print(deck->tiles[j + (i * 13) + (h * 52)]);
415             }
416         }
417     }
418     printf ("There are %d tiles in the deck\n" , deck->num_tiles);
419 }
420
421 static void deck_spread(deck_t *deck)
422 {
423     int i, j;
424     for (i = 0; i < 8; i++)
425     {
426         for (j = 0; j < 13; j++)
427         {
428             deck->tiles[j + (i * 13)].x = j * 50;
429             deck->tiles[j + (i * 13)].y = i * 60;
430         }
431     }
432 }
433
434 static void deck_draw(game_t *game, cairo_t *cr, GdkRegion *region)
435 {
436     int i;
437     for (i = 0; i < game->deck.num_tiles; i++)
438     {
439         tile_draw(game, &game->deck.tiles[i], cr, region);
440     }
441 }
442
443 static void hand_print(game_t *game, int player)
444 {
445     int i;
446     for (i = 0; i < game->players[player].hand.num_tiles; i++)
447     {
448         tile_print(game->players[player].hand.tiles[i]);
449     }
450 }
451
452 static void hand_draw(game_t *game, int player, cairo_t *cr, GdkRegion *region)
453 {
454     int i;
455     int gwdw = GAME_WINDOW_DEFAULT_WIDTH;
456     int gwdh = GAME_WINDOW_DEFAULT_HEIGHT;
457     for (i = 0; i < game->players[player].hand.num_tiles; i++)
458     {
459         tile_set_x_y(&game->players[player].hand.tiles[i],
460                      ((gwdw / game->players[player].hand.num_tiles)) * i,
461                      (gwdh - TILE_HEIGHT - 6) );
462     }
463     for (i = 0; i < game->players[player].hand.num_tiles; i++)
464     {
465         tile_draw(game, &game->players[player].hand.tiles[i], cr, region);
466     }
467 }
468
469 static void game_init(game_t *game)
470 {
471     int i;
472     GError *error = NULL;
473
474     game->num_players = 0;
475
476     for (i = 0; i < GAME_MAX_PLAYERS; ++i)
477     {
478         player_init(&game->players[i]);
479         game->num_players += 1;
480     }
481     
482     selection_box_init(&game->selection_box);
483     board_init(&game->board);
484     deck_init(&game->deck);
485     deck_shuffle(&game->deck);
486
487     game->selectedtile = rsvg_handle_new_from_file ("tiles/selectedtile.svg", &error);
488     if (error)
489         FATAL_ERROR (error->message);
490
491     game->blanktile = rsvg_handle_new_from_file ("tiles/blanktile.svg", &error);
492     if (error)
493         FATAL_ERROR (error->message);
494
495     game->current_tile = game->deck.num_tiles - 1;
496     game->select_mode = 1;
497
498     game->diff_x = game->diff_y = 0;
499 }
500
501 static gboolean on_expose_event (GtkWidget *widget, GdkEventExpose *event, game_t *game)
502 {
503     cairo_t *cr;
504
505     cr = gdk_cairo_create (widget->window);
506
507     deck_draw(game, cr, event->region);
508
509     if (game->selection_box.visible)
510         selection_box_draw(&game->selection_box, cr);
511
512     hand_draw(game, 0, cr, event->region);
513
514     cairo_destroy (cr);
515
516     return TRUE;
517 }
518
519 static gboolean on_key_press_event (GtkWidget *widget, GdkEventKey *event, game_t *game)
520 {
521     printf ("You pressed key %d\n", event->keyval);
522
523     return TRUE;
524 }
525
526 static gboolean on_button_press_event (GtkWidget *widget, GdkEventButton *event, game_t *game)
527 {
528     int i, tile_x, tile_y;
529     tile_t *curr_tile = &game->deck.tiles[game->current_tile];    
530
531     for (i = 0; i < game->deck.num_tiles; i++)
532     {
533         tile_x = game->deck.tiles[i].x;
534         tile_y = game->deck.tiles[i].y;
535         if (event->x >= tile_x && event->x <= (tile_x + TILE_WIDTH) &&
536             event->y >= tile_y && event->y <= (tile_y + TILE_HEIGHT) )
537         {
538             game->select_mode = 0;
539
540             curr_tile = &game->deck.tiles[game->current_tile];
541             game->deck.tiles[game->current_tile].selected = 0;
542             gtk_widget_queue_draw_area (widget, curr_tile->x - 1, curr_tile->y - 1, TILE_WIDTH + 1, TILE_HEIGHT + 2);
543
544             game->current_tile = i;
545             curr_tile = &game->deck.tiles[game->current_tile];
546             if (!curr_tile->selected)
547                 curr_tile->selected = 1;
548             else
549                 curr_tile->selected = 0;
550             gtk_widget_queue_draw_area (widget, curr_tile->x - 1, curr_tile->y - 1, TILE_WIDTH + 1, TILE_HEIGHT + 2);
551
552             game->diff_x = event->x - tile_x;
553             game->diff_y = event->y - tile_y;
554         }
555     }
556     if (game->select_mode)
557     {
558         game->deck.tiles[game->current_tile].selected = 0;
559         gtk_widget_queue_draw_area (widget, curr_tile->x - 1, curr_tile->y - 1, TILE_WIDTH + 1, TILE_HEIGHT + 2);
560
561         game->selection_box.visible = 1;
562         /*These next two lines are likely to be replaced by...*/
563         game->click_x = event->x;
564         game->click_y = event->y;
565         /*...these two lines*/
566         game->selection_box.x1 = event->x;
567         game->selection_box.x2 = event->x;
568         game->selection_box.y1 = event->y;
569         game->selection_box.y2 = event->y;
570     }
571     return TRUE;
572 }
573
574 static gboolean on_button_release_event (GtkWidget *widget, GdkEventButton *event, game_t *game)
575 {
576     if (game->select_mode)
577     {
578         game->select_mode = 0;
579         selection_box_t *box;
580         box = &game->selection_box;
581
582         int x_min = MIN(box->x1, box->x2);
583         int x_max = MAX(box->x1, box->x2);
584         int y_min = MIN(box->y1, box->y2);
585         int y_max = MAX(box->y1, box->y2);
586         int width = abs(box->x2 - box->x1);
587         int height = abs(box->y2 - box->y1);
588
589         box->visible = 0;       
590         gtk_widget_queue_draw_area (widget, x_min, y_min, width, height);
591
592         tile_group_t group;
593         group.num_tiles = 0;
594
595         int i, tile_x, tile_y, tile_x2, tile_y2;
596         for (i = 0; i < game->deck.num_tiles; i++)
597         {
598             tile_x = game->deck.tiles[i].x;
599             tile_y = game->deck.tiles[i].y;
600             tile_x2 = tile_x + TILE_WIDTH;
601             tile_y2 = tile_y + TILE_HEIGHT;
602             if (/*If top-left corner*/
603                 (tile_x >= x_min && tile_x <= x_max &&
604                  tile_y >= y_min && tile_y <= y_max) ||
605                 /*or bottom-right corner*/
606                 (tile_x2 >= x_min && tile_x2 <= x_max &&
607                  tile_y2 >= y_min && tile_y2 <= y_max) ||
608                 /*or bottom-left corner*/
609                 (tile_x >= x_min && tile_x <= x_max &&
610                  tile_y2 >= y_min && tile_y2 <= y_max) ||
611                 /*or top-right corner of tile selected*/
612                 (tile_x2 >= x_min && tile_x2 <= x_max &&
613                  tile_y >= y_min && tile_y <= y_max) )
614             {
615                 group.tiles[group.num_tiles] = game->deck.tiles[i];
616                 group.num_tiles++;
617             }
618         }
619         printf("is run %d\n", tile_group_is_run_one(&group) );
620         printf("is set %d\n", tile_group_is_set(&group) );
621         for (i = 0; i < group.num_tiles; i++)
622             tile_print(group.tiles[i]);
623     }    
624     game->select_mode = 1;
625
626     return TRUE;
627 }
628
629 static gboolean on_button_motion_event (GtkWidget *widget, GdkEventMotion *event,
630                                         game_t *game, cairo_t *cr)
631 {
632     if (game->select_mode)
633     {
634         selection_box_t *box;
635         box = &game->selection_box;
636         box->visible = 1;
637
638         gtk_widget_queue_draw_area ( widget,  MIN(box->x1, box->x2), MIN(box->y1, box->y2), abs(box->x2 - box->x1), abs(box->y2 - box->y1) );
639
640         box->x2 = event->x;
641         box->y2 = event->y;
642
643         gtk_widget_queue_draw_area ( widget, MIN(box->x1, box->x2), MIN(box->y1, box->y2), abs(box->x2 - box->x1), abs(box->y2 - box->y1) );
644     }
645     else
646     {
647         tile_t *tile;
648         tile = &game->deck.tiles[game->current_tile];
649
650         /* First, invalidate the region where the tile currently is. */
651         gtk_widget_queue_draw_area (widget, tile->x - 1, tile->y - 1, TILE_WIDTH + 1, TILE_HEIGHT + 2);
652
653     /* Then, move the tile */
654         tile->x = event->x - game->diff_x;
655         tile->y = event->y - game->diff_y;
656
657     /* Finally, invalidate the region where the tile is now. */
658         gtk_widget_queue_draw_area (widget, tile->x - 1, tile->y - 1, TILE_WIDTH + 1, TILE_HEIGHT + 2);
659     }
660     return TRUE;
661 }
662
663 int main(int argc, char *argv[])
664 {
665     GtkWidget *window;
666     game_t game;
667
668     srand(time(NULL));
669
670     gtk_init (&argc, &argv);
671
672     game_init(&game);
673     deck_print(&game.deck);
674     deck_spread(&game.deck);
675     deck_deal(&game, &game.deck);
676     //hand_print(&game, 0); //With Zero being passed, will print hand for player 1(players[0])
677     //deck_print(&game.deck);
678
679     /* Create a new window */
680     window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
681     gtk_window_set_default_size (GTK_WINDOW (window),
682                                  GAME_WINDOW_DEFAULT_WIDTH,
683                                  GAME_WINDOW_DEFAULT_HEIGHT);
684
685     gtk_widget_set_events (window,
686                            GDK_EXPOSURE_MASK |
687                            GDK_KEY_PRESS_MASK |
688                            GDK_BUTTON_MOTION_MASK |
689                            GDK_BUTTON_PRESS_MASK |
690                            GDK_BUTTON_RELEASE_MASK);
691
692     g_signal_connect (G_OBJECT (window), "delete_event",
693                       G_CALLBACK (gtk_main_quit), NULL);
694     g_signal_connect (G_OBJECT (window), "expose_event",
695                       G_CALLBACK (on_expose_event), &game);
696     g_signal_connect (G_OBJECT (window), "key_press_event",
697                       G_CALLBACK (on_key_press_event), &game);
698     g_signal_connect (G_OBJECT (window), "button_press_event",
699                       G_CALLBACK (on_button_press_event), &game);
700     g_signal_connect (G_OBJECT (window), "button_release_event",
701                       G_CALLBACK (on_button_release_event), &game);
702     g_signal_connect (G_OBJECT (window), "motion_notify_event",
703                       G_CALLBACK (on_button_motion_event), &game);
704
705
706     gtk_widget_show_all (window);
707     gtk_main ();
708
709     return 0;
710
711 }