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