]> git.cworth.org Git - grrobot/blob - src/grrobot.c
386a1637956a275586cdb1dfd502349c2729534f
[grrobot] / src / grrobot.c
1 /* grrobot - Ricochet Robot using GTK+, libxsvg, and Cairo
2  *
3  * Copyright © 2003 Carl Worth
4  *
5  * Permission to use, copy, modify, distribute, and sell this software
6  * and its documentation for any purpose is hereby granted without
7  * fee, provided that the above copyright notice appear in all copies
8  * and that both that copyright notice and this permission notice
9  * appear in supporting documentation, and that the name of Carl Worth
10  * not be used in advertising or publicity pertaining to distribution
11  * of the software without specific, written prior permission.
12  * Carl Worth makes no representations about the suitability of this
13  * software for any purpose.  It is provided "as is" without express
14  * or implied warranty.
15  * 
16  * CARL WORTH DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
17  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
18  * NO EVENT SHALL CARL WORTH BE LIABLE FOR ANY SPECIAL, INDIRECT OR
19  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
20  * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
21  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
22  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23  *
24  * Author: Carl Worth <carl@theworths.org>
25  */
26
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include <gtk/gtk.h>
36 #include <gdk/gdkkeysyms.h>
37
38 #include "grr_board_view.h"
39 #include "grr_util.h"
40 #include "args.h"
41
42 typedef struct grr_game {
43     rr_client_t *client;
44     rr_board_t *board;
45
46     GtkWidget *window;
47     grr_board_view_t *board_view;
48     GtkWidget *message_entry;
49
50     GtkTextBuffer *message_buffer;
51     GtkWidget *message_view;
52     int msg_id;
53     int last_move_msg_id;
54     rr_robot_t last_move_robot;
55
56     rr_gamestate_t state;
57 } grr_game_t;
58
59 static void
60 grr_game_pass (grr_game_t *game);
61
62 static void
63 grr_game_zap (grr_game_t *game);
64
65 static void
66 grr_game_next (grr_game_t *game);
67
68 static int
69 grr_game_printf (grr_game_t *game, const char *fmt, ...);
70
71 static int
72 grr_game_vprintf (grr_game_t *game, const char *fmt, va_list ap);
73
74 static int
75 grr_game_print (grr_game_t *game, const char *msg);
76
77 static int
78 grr_game_start_gui (grr_game_t *game, const char *user);
79
80 static void
81 grr_game_read_notices (grr_game_t *game);
82
83 static GSource *
84 grr_game_notices_source_new (grr_game_t *game);
85
86 int 
87 main (int argc, char **argv)
88 {
89     args_t args;
90     rr_status_t status;
91     grr_game_t game;
92     GSource *source;
93
94     char *diagram;
95
96     gtk_init (&argc, &argv);
97     
98     args_parse (&args, argc, argv);
99
100     if (args.file) {
101         game.board = rr_board_create_from_file (args.file);
102         if (game.board == NULL) {
103             fprintf (stderr, "Failed to parse board from %s\n", args.file);
104             return 1;
105         }
106         game.client = NULL;
107     } else {
108         GPollFD *poll_fd;
109
110         game.client = rr_client_create (args.host, args.port, args.user);
111         if (game.client == NULL) {
112             fprintf (stderr, "Failed connecting to %s:%s as %s\n",
113                      args.host, args.port, args.user);
114             return 1;
115         }
116
117         if (args.watch) {
118             status = rr_client_watch (game.client, args.game);
119             if (status == RR_STATUS_NO_GAME) {
120                 fprintf (stderr, "No game %s to watch\n", args.game);
121             }
122         } else {
123             status = rr_client_join (game.client, args.game);
124             if (status == RR_STATUS_NO_GAME) {
125                 status = rr_client_new (game.client, args.game);
126             }
127         }
128
129         game.board = rr_board_create (16, 16);
130         game.msg_id = 0;
131         game.last_move_msg_id = 0;
132         game.last_move_robot = RR_ROBOT_NONE;
133
134         source = grr_game_notices_source_new (&game);
135         poll_fd = g_new (GPollFD, 1);
136         poll_fd->fd = rr_client_fd (game.client);
137         poll_fd->events = G_IO_IN;
138         g_source_add_poll (source, poll_fd);
139
140         g_source_set_priority (source, GDK_PRIORITY_EVENTS);
141         g_source_attach (source, NULL);
142         g_source_unref (source);
143
144         rr_client_show (game.client, &diagram);
145         rr_board_parse (game.board, diagram);
146         free (diagram);
147     }
148
149     return grr_game_start_gui (&game, args.user);
150 }
151
152 typedef struct _GrrNoticeSource {
153     GSource source;
154     grr_game_t *game;
155 } GrrNoticeSource;
156
157 static gboolean
158 grr_game_notices_prepare (GSource   *source,
159                           gint      *timeout)
160 {
161     GrrNoticeSource *src = (GrrNoticeSource*)source;
162     grr_game_t *game = src->game;
163     gboolean retval;
164
165     GDK_THREADS_ENTER ();
166     *timeout = -1;
167     retval = rr_client_notice_pending (game->client);
168     GDK_THREADS_LEAVE ();
169
170     return retval;
171 }
172
173 static gboolean
174 grr_game_notices_check (GSource     *source)
175 {
176     GrrNoticeSource *src = (GrrNoticeSource*)source;
177     grr_game_t *game = src->game;
178     gboolean retval;
179
180     GDK_THREADS_ENTER ();
181     retval = rr_client_notice_pending (game->client);
182     GDK_THREADS_LEAVE ();
183
184     return retval;
185 }
186
187 static gboolean
188 grr_game_notices_dispatch (GSource      *source,
189                            GSourceFunc  callback,
190                            gpointer     user_data)
191 {
192     GrrNoticeSource *src = (GrrNoticeSource*)source;
193     grr_game_t *game = src->game;
194     
195     grr_game_read_notices (game);
196
197     return TRUE;
198 }
199
200 static GSourceFuncs game_notices_funcs = {
201   grr_game_notices_prepare,
202   grr_game_notices_check,
203   grr_game_notices_dispatch,
204   NULL
205 };
206
207 static GSource *
208 grr_game_notices_source_new (grr_game_t *game)
209 {
210     GSource *source = g_source_new (&game_notices_funcs, sizeof (GrrNoticeSource));
211     GrrNoticeSource *src = (GrrNoticeSource*)source;
212     src->game = game;
213     return source;
214 }
215
216 static void
217 grr_game_read_notices (grr_game_t *game)
218 {
219     rr_board_t *board = game->board;
220     rr_status_t status;
221     rr_notice_t *notice;
222
223     while (rr_client_notice_pending (game->client)) {
224         status = rr_client_next_notice (game->client, &notice);
225         if (status) {
226             if (status == RR_STATUS_EOF)
227                 fprintf (stderr, "The server has disconnected, exiting.\n");
228             else
229                 fprintf (stderr, "Error during rr_client_next_notice: %s\n",
230                          rr_status_str (status));
231             gtk_main_quit ();
232             return;
233         }
234         if (!notice) {
235             fprintf (stderr, "Missing notice\n");
236             continue;
237         }
238  
239         switch (notice->type) {
240         case RR_NOTICE_USER:
241             grr_game_printf (game,
242                              "\nGLOBAL: %s has connected.", notice->u.string);
243             break;
244         case RR_NOTICE_QUIT:
245             grr_game_printf (game,
246                              "\nGLOBAL: %s has disconnected.", notice->u.string);
247             break;
248         case RR_NOTICE_GAME:
249             grr_game_printf (game,
250                              "\nGLOBAL: New game available: %s.", notice->u.string);
251             break;
252         case RR_NOTICE_DISPOSE:
253             grr_game_printf (game,
254                              "\nGLOBAL: Game %s has been disposed.", notice->u.string);
255             break;
256         case RR_NOTICE_MESSAGE:
257             grr_game_printf (game, "\n%s> %s",
258                              notice->u.message.username,
259                              notice->u.message.text);
260             break;
261         case RR_NOTICE_BOARD:
262             rr_board_parse (board, notice->u.string);
263             gtk_widget_queue_draw (GTK_WIDGET (game->window));
264             break;
265         case RR_NOTICE_GAMESTATE:
266             game->state = notice->u.gamestate;
267             grr_game_printf (game, "\nGame state changed to: %s.",
268                              rr_gamestate_str (notice->u.gamestate));
269             break;
270         case RR_NOTICE_TURN:
271             /* XXX: Setting game->state here is goofy. Cleaner would
272                be to make the server send a NOTICE GAMESTATE NEW
273                here. */
274             game->state = RR_GAMESTATE_NEW;
275
276             grr_board_view_set_timer (game->board_view, 60.0);
277             grr_game_print (game, "\nNew round!");
278             rr_board_set_goal_target (board, notice->u.target);
279             gtk_widget_queue_draw (GTK_WIDGET (game->window));
280             break;
281         case RR_NOTICE_GAMEOVER:
282             grr_game_printf (game, "\nGame over. New game will begin now.");
283             break;
284         case RR_NOTICE_JOIN:
285             grr_game_printf (game, "\nUser %s has joined the game.",
286                              notice->u.string);
287             break;
288         case RR_NOTICE_WATCH:
289             grr_game_printf (game, "\nUser %s has started watching the game.",
290                              notice->u.string);
291             break;
292         case RR_NOTICE_PART:
293             grr_game_printf (game, "\nUser %s has left the game.",
294                              notice->u.string);
295             break;
296         case RR_NOTICE_BID:
297             grr_game_printf (game, "\nUser %s bids %d.",
298                              notice->u.bid.username,
299                              notice->u.bid.number);
300             break;
301         case RR_NOTICE_REVOKE:
302             grr_game_printf (game, "\nUser %s revokes previous bid.",
303                              notice->u.string);
304             break;
305         case RR_NOTICE_ABANDON:
306             grr_game_printf (game, "\nUser %s is willing to abandon this turn.",
307                              notice->u.string);
308             break;
309         case RR_NOTICE_NOBID:
310             grr_game_printf (game, "\nUser %s sees no point in bidding further this round",
311                              notice->u.string);
312             break;
313         case RR_NOTICE_MOVE:
314             if (game->msg_id == game->last_move_msg_id
315                 && game->last_move_robot == notice->u.move.robot) {
316                 game->last_move_msg_id = grr_game_printf (game, ",%c",
317                                  rr_direction_char (notice->u.move.direction));
318             } else {
319                 game->last_move_msg_id = grr_game_printf (game, "\nMove #%d: %s %c",
320                                  notice->u.move.count,
321                                  rr_robot_str (notice->u.move.robot),
322                                  rr_direction_char (notice->u.move.direction));
323                 game->last_move_robot = notice->u.move.robot;
324             }
325             break;
326         case RR_NOTICE_UNDO:
327             grr_game_print (game, "\nUNDO");
328             break;
329         case RR_NOTICE_RESET:
330             grr_game_print (game, "\nRESET");
331             break;
332         case RR_NOTICE_SCORE:
333             grr_game_printf (game, "\nScore for %s: %d.",
334                              notice->u.bid.username,
335                              notice->u.bid.number);
336             break;
337         case RR_NOTICE_ACTIVATE:
338             grr_game_printf (game, "\nYour turn.",
339                              notice->u.number);
340             break;
341         case RR_NOTICE_ACTIVE:
342             grr_board_view_zap_timer (game->board_view);
343             grr_game_printf (game, "\nUser %s now active to demonstrate solution in %d moves.",
344                              notice->u.bid.username,
345                              notice->u.bid.number);
346             break;
347         case RR_NOTICE_TIMER:
348             grr_board_view_set_timer (game->board_view,
349                                       notice->u.number);
350             grr_board_view_start_timer (game->board_view);
351             break;
352         case RR_NOTICE_POSITION:
353         {
354             int x, y;
355             rr_board_find_robot (board, notice->u.position.robot, &x, &y);
356             rr_board_add_robot (board, notice->u.position.robot,
357                                 notice->u.position.x, notice->u.position.y);
358             grr_board_view_mark_damage (game->board_view, x, y);
359             grr_board_view_mark_damage (game->board_view,
360                                         notice->u.position.x, notice->u.position.y);
361         }
362         break;
363         default:
364             fprintf (stderr, "Unknown notice: %d\n", notice->type);
365             break;
366         }
367         free (notice);
368     }
369 }
370
371 static gboolean
372 grr_game_key_press_callback (GtkWidget *widget,
373                              GdkEventKey *event,
374                              grr_game_t *game)
375 {
376     if (GTK_WIDGET_HAS_FOCUS (GTK_WIDGET (game->message_entry))) {
377         if (event->keyval == GDK_Escape) {
378             gtk_entry_set_text (GTK_ENTRY (game->message_entry), "");
379             gtk_widget_grab_focus (GTK_WIDGET (game->message_view));
380             return TRUE;
381         } else {
382             if ((event->state & GDK_CONTROL_MASK) == 0)
383                 return FALSE;
384         }
385     }
386
387     if (event->state & GDK_CONTROL_MASK) {
388         switch (event->keyval) {
389         case GDK_N:
390         case GDK_n:
391         case GDK_T:
392         case GDK_t:
393             grr_game_next (game);
394             return TRUE;
395         case GDK_P:
396         case GDK_p:
397             grr_game_pass (game);
398             return TRUE;
399         case GDK_Z:
400         case GDK_z:
401             grr_game_zap (game);
402             return TRUE;
403         case GDK_B:
404         case GDK_b:
405             grr_board_view_set_active_robot (game->board_view, RR_ROBOT_BLUE);
406             return TRUE;
407         case GDK_G:
408         case GDK_g:
409             grr_board_view_set_active_robot (game->board_view, RR_ROBOT_GREEN);
410             return TRUE;
411         case GDK_R:
412         case GDK_r:
413             grr_board_view_set_active_robot (game->board_view, RR_ROBOT_RED);
414             return TRUE;
415         case GDK_Y:
416         case GDK_y:
417             grr_board_view_set_active_robot (game->board_view, RR_ROBOT_YELLOW);
418             return TRUE;
419         case GDK_Q:
420         case GDK_q:
421             if (event->state & GDK_CONTROL_MASK) {
422                 gtk_main_quit ();
423             }
424             return TRUE;
425         }
426     }
427
428     switch (event->keyval) {
429     case GDK_BackSpace:
430         grr_board_view_undo (game->board_view);
431         return TRUE;
432     case GDK_Up:
433         grr_board_view_move_active (game->board_view, RR_DIRECTION_NORTH);
434         return TRUE;
435     case GDK_Right:
436         grr_board_view_move_active (game->board_view, RR_DIRECTION_EAST);
437         return TRUE;
438     case GDK_Down:
439         grr_board_view_move_active (game->board_view, RR_DIRECTION_SOUTH);
440         return TRUE;
441     case GDK_Left:
442         grr_board_view_move_active (game->board_view, RR_DIRECTION_WEST);
443         return TRUE;
444     case GDK_Control_L:
445     case GDK_Control_R:
446         return TRUE;
447     }
448
449     gtk_widget_grab_focus (GTK_WIDGET (game->message_entry));
450     return FALSE;
451 }
452
453 /* XXX: Messy, messy. I can't seem to make up my mind whether client
454    functions like such as these should be called here or in
455    grr_board_view. */
456 static void
457 grr_game_pass (grr_game_t *game)
458 {
459     if (game->client == NULL)
460         return;
461
462     if (game->state == RR_GAMESTATE_SHOW)
463         rr_client_pass (game->client);
464     else
465         rr_client_abandon (game->client);
466 }
467
468 static void
469 grr_game_zap (grr_game_t *game)
470 {
471     if (game->client == NULL)
472         return;
473
474     rr_client_nobid (game->client);
475 }
476
477 static void
478 grr_game_next (grr_game_t *game)
479 {
480     if (game->client == NULL)
481         return;
482
483     rr_client_turn (game->client);
484 }
485
486 static void
487 grr_game_pass_callback (GtkWidget *widget,
488                         grr_game_t *game)
489 {
490     grr_game_pass (game);
491 }
492
493 static void
494 grr_game_zap_callback (GtkWidget *widget,
495                         grr_game_t *game)
496 {
497     grr_game_zap (game);
498 }
499
500 static void
501 grr_game_next_callback (GtkWidget *widget,
502                         grr_game_t *game)
503 {
504     grr_game_next (game);
505 }
506
507 static void
508 grr_game_message_entry_callback (GtkWidget *widget,
509                                  grr_game_t *game)
510 {
511     rr_status_t status;
512     const gchar *entry_text;
513     char **response;
514     char *end;
515     long bid;
516     int i;
517
518     if (game->client == NULL)
519         return;
520
521     entry_text = gtk_entry_get_text (GTK_ENTRY (game->message_entry));
522
523     if (entry_text && entry_text[0]) {
524         bid = strtol (entry_text, &end, 10);
525         if (*end == '\0' || isspace (*end)) {
526             if (bid == 0)
527                 rr_client_revoke (game->client);
528             else
529                 rr_client_bid (game->client, bid);
530         } else  if (entry_text[0] == '/') {
531             status = rr_client_request (game->client, entry_text + 1, &response);
532             if (status) {
533                 grr_game_printf (game, "\nERROR: %s.", rr_status_str (status));
534             } else {
535                 if (response[0]) {
536                     grr_game_print (game, "\n");
537                     for (i=0; response[i]; i++)
538                         grr_game_printf (game, "%s ", response[i]);
539                 }
540             }
541         } else {
542             rr_client_message (game->client, entry_text);
543         }
544     }
545
546     gtk_entry_set_text (GTK_ENTRY (game->message_entry), "");
547     gtk_widget_grab_focus (GTK_WIDGET (game->message_view));
548
549 /* XXX: Huh? Why is this triggering valgrind?
550     free (response);
551 */
552
553 }
554
555 static int
556 grr_game_printf (grr_game_t *game, const char *fmt, ...)
557 {
558     int msg_id;
559
560     va_list ap;
561
562     va_start (ap, fmt);
563     msg_id = grr_game_vprintf (game, fmt, ap);
564     va_end (ap);
565
566     return msg_id;
567 }
568
569 static int
570 grr_game_vprintf (grr_game_t *game, const char *fmt, va_list ap)
571 {
572     char *msg;
573     int msg_id;
574
575     grr_sprintf_alloc_va (&msg, fmt, ap);
576     if (msg == NULL)
577         return 0;
578
579     msg_id = grr_game_print (game, msg);
580
581     free (msg);
582
583     return msg_id;
584 }
585
586 static int
587 grr_game_print (grr_game_t *game, const char *msg)
588 {
589     GtkTextIter iter;
590     GtkTextMark *mark;
591
592     /* There might be a lighter way to do this, but this seems to
593        work. */
594
595     gtk_text_buffer_get_end_iter (game->message_buffer, &iter);
596
597     mark = gtk_text_buffer_get_mark (game->message_buffer, "end");
598
599     gtk_text_buffer_move_mark (game->message_buffer, mark, &iter);
600
601     gtk_text_buffer_insert (game->message_buffer, &iter, msg, -1);
602
603     gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (game->message_view),
604                                   mark, 0.0, TRUE, 0.0, 1.0);
605
606     return ++game->msg_id;
607 }
608
609 static int
610 grr_game_start_gui (grr_game_t *game, const char *user)
611 {
612     GtkWidget *board_frame;
613     GtkWidget *vpaned;
614     GtkWidget *sw;
615     GtkWidget *vbox;
616     GtkWidget *hbox;
617     GtkWidget *window;
618     GtkWidget *message_view;
619     GtkWidget *message_entry;
620     GtkWidget *pass_button;
621     GtkWidget *zap_button;
622     GtkWidget *next_button;
623     GtkWidget *user_label;
624
625     game->state = RR_GAMESTATE_NEW;
626
627     game->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
628     window = game->window;
629
630     gtk_window_set_title (GTK_WINDOW (window), "Ricochet Robot");
631     gtk_window_set_default_size (GTK_WINDOW (window), 512, 512);
632     
633     g_signal_connect (G_OBJECT (window), "destroy",
634                       G_CALLBACK (exit), NULL);
635     gtk_container_set_border_width (GTK_CONTAINER (window), 0);
636
637     vbox = gtk_vbox_new (FALSE, 1);
638     gtk_container_add (GTK_CONTAINER (window), vbox);
639     {
640         GtkTextIter iter;
641         game->board_view = grr_board_view_new (game->board);
642         grr_board_view_set_client (GRR_BOARD_VIEW (game->board_view), game->client);
643         game->message_buffer = gtk_text_buffer_new (NULL);
644         gtk_text_buffer_get_iter_at_offset (game->message_buffer,
645                                             &iter, -1);
646         gtk_text_buffer_create_mark (game->message_buffer,
647                                      "end", &iter, FALSE);
648         game->message_view = gtk_text_view_new_with_buffer (game->message_buffer);
649
650         vpaned = gtk_vpaned_new ();
651         gtk_container_set_border_width (GTK_CONTAINER (vpaned), 0);
652         gtk_box_pack_start (GTK_BOX (vbox), vpaned,
653                             TRUE, TRUE, 0);
654         {
655             board_frame = gtk_aspect_frame_new (NULL, 0.5, 0.5, 1.0, FALSE);
656             gtk_paned_pack1 (GTK_PANED (vpaned), board_frame, TRUE, TRUE);
657             {
658                 gtk_container_add (GTK_CONTAINER (board_frame), GTK_WIDGET (game->board_view));
659                 gtk_widget_show (GTK_WIDGET (game->board_view));
660             }
661             gtk_widget_show (board_frame);
662
663             sw = gtk_scrolled_window_new (NULL, NULL);
664             gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
665                                             GTK_POLICY_NEVER,
666                                             GTK_POLICY_AUTOMATIC);
667             gtk_paned_pack2 (GTK_PANED (vpaned), sw, FALSE, TRUE);
668             {
669                 message_view = game->message_view;
670                 gtk_text_view_set_editable (GTK_TEXT_VIEW (message_view), FALSE);
671                 gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (message_view), FALSE);
672                 gtk_container_add (GTK_CONTAINER (sw), message_view);
673                 gtk_widget_show (message_view);
674             }
675             gtk_widget_show (sw);
676         }
677         gtk_widget_show (vpaned);
678
679         hbox = gtk_hbox_new (FALSE, 1);
680         gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
681         {
682             user_label = gtk_label_new (user);
683             gtk_box_pack_start (GTK_BOX (hbox), user_label,
684                                 FALSE, FALSE, 1);
685             gtk_widget_show (user_label);
686
687             game->message_entry = gtk_entry_new ();
688             message_entry = game->message_entry;
689             gtk_box_pack_start (GTK_BOX (hbox), message_entry,
690                                 TRUE, TRUE, 1);
691             gtk_widget_show (message_entry);
692             g_signal_connect (G_OBJECT (message_entry), "activate",
693                               G_CALLBACK (grr_game_message_entry_callback),
694                               (gpointer) game);
695         }
696         gtk_widget_show (hbox);
697
698         hbox = gtk_hbox_new (FALSE, 1);
699         gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
700         {
701             pass_button = gtk_button_new_with_label ("Pass");
702             gtk_box_pack_start (GTK_BOX (hbox), pass_button,
703                                 FALSE, FALSE, 1);
704             gtk_widget_show (pass_button);
705             g_signal_connect (G_OBJECT (pass_button), "clicked",
706                               G_CALLBACK (grr_game_pass_callback),
707                               (gpointer) game);
708
709             zap_button = gtk_button_new_with_label ("Zap timer");
710             gtk_box_pack_start (GTK_BOX (hbox), zap_button,
711                                 FALSE, FALSE, 1);
712             gtk_widget_show (zap_button);
713             g_signal_connect (G_OBJECT (zap_button), "clicked",
714                               G_CALLBACK (grr_game_zap_callback),
715                               (gpointer) game);
716
717             next_button = gtk_button_new_with_label ("Next turn");
718             gtk_box_pack_start (GTK_BOX (hbox), next_button,
719                                 FALSE, FALSE, 1);
720             gtk_widget_show (next_button);
721             g_signal_connect (G_OBJECT (next_button), "clicked",
722                               G_CALLBACK (grr_game_next_callback),
723                               (gpointer) game);
724         }
725         gtk_widget_show (hbox);
726     }
727     gtk_widget_show (vbox);
728
729     g_signal_connect (G_OBJECT (window), "key_press_event",
730                       G_CALLBACK (grr_game_key_press_callback),
731                       (gpointer) game);
732
733     gtk_widget_show (window);
734     
735     gtk_main ();
736     
737     return 0;
738 }