]> git.cworth.org Git - grrobot/blob - src/grrobot.c
8df5b9815d9da75615c0f9ac690830007e1f2fa6
[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     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         GPollFD *poll_fd;
110
111         game.client = rr_client_create (args.host, args.port, args.user);
112         if (game.client == NULL) {
113             fprintf (stderr, "Failed connecting to %s:%s as %s\n",
114                      args.host, args.port, args.user);
115             return 1;
116         }
117
118         if (args.watch) {
119             status = rr_client_watch (game.client, args.game);
120             if (status == RR_STATUS_NO_GAME) {
121                 fprintf (stderr, "No game %s to watch\n", args.game);
122             }
123         } else {
124             status = rr_client_join (game.client, args.game);
125             if (status == RR_STATUS_NO_GAME) {
126                 status = rr_client_new (game.client, args.game);
127             }
128         }
129
130         game.board = rr_board_create (16, 16);
131         game.msg_id = 0;
132         game.last_move_msg_id = 0;
133         game.last_move_robot = RR_ROBOT_NONE;
134
135         source = grr_game_notices_source_new (&game);
136         poll_fd = g_new (GPollFD, 1);
137         poll_fd->fd = rr_client_fd (game.client);
138         poll_fd->events = G_IO_IN;
139         g_source_add_poll (source, poll_fd);
140
141         g_source_set_priority (source, GDK_PRIORITY_EVENTS);
142         g_source_attach (source, NULL);
143         g_source_unref (source);
144
145         rr_client_show (game.client, &diagram);
146         rr_board_parse (game.board, diagram);
147         free (diagram);
148     }
149
150     return grr_game_start_gui (&game, args.user);
151 }
152
153 typedef struct _GrrNoticeSource {
154     GSource source;
155     grr_game_t *game;
156 } GrrNoticeSource;
157
158 static gboolean
159 grr_game_notices_prepare (GSource   *source,
160                           gint      *timeout)
161 {
162     GrrNoticeSource *src = (GrrNoticeSource*)source;
163     grr_game_t *game = src->game;
164     gboolean retval;
165
166     GDK_THREADS_ENTER ();
167     *timeout = -1;
168     retval = rr_client_notice_pending (game->client);
169     GDK_THREADS_LEAVE ();
170
171     return retval;
172 }
173
174 static gboolean
175 grr_game_notices_check (GSource     *source)
176 {
177     GrrNoticeSource *src = (GrrNoticeSource*)source;
178     grr_game_t *game = src->game;
179     gboolean retval;
180
181     GDK_THREADS_ENTER ();
182     retval = rr_client_notice_pending (game->client);
183     GDK_THREADS_LEAVE ();
184
185     return retval;
186 }
187
188 static gboolean
189 grr_game_notices_dispatch (GSource      *source,
190                            GSourceFunc  callback,
191                            gpointer     user_data)
192 {
193     GrrNoticeSource *src = (GrrNoticeSource*)source;
194     grr_game_t *game = src->game;
195     
196     grr_game_read_notices (game);
197
198     return TRUE;
199 }
200
201 static GSourceFuncs game_notices_funcs = {
202   grr_game_notices_prepare,
203   grr_game_notices_check,
204   grr_game_notices_dispatch,
205   NULL
206 };
207
208 static GSource *
209 grr_game_notices_source_new (grr_game_t *game)
210 {
211     GSource *source = g_source_new (&game_notices_funcs, sizeof (GrrNoticeSource));
212     GrrNoticeSource *src = (GrrNoticeSource*)source;
213     src->game = game;
214     return source;
215 }
216
217 static void
218 grr_game_read_notices (grr_game_t *game)
219 {
220     rr_board_t *board = game->board;
221     rr_status_t status;
222     rr_notice_t *notice;
223
224     while (rr_client_notice_pending (game->client)) {
225         status = rr_client_next_notice (game->client, &notice);
226         if (status) {
227             if (status == RR_STATUS_EOF)
228                 fprintf (stderr, "The server has disconnected, exiting.\n");
229             else
230                 fprintf (stderr, "Error during rr_client_next_notice: %s\n",
231                          rr_status_str (status));
232             gtk_main_quit ();
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_reset_timer (game->board_view);
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_stop_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_start_timer (game->board_view,
349                                         notice->u.number);
350             break;
351         case RR_NOTICE_POSITION:
352         {
353             int x, y;
354             rr_board_find_robot (board, notice->u.position.robot, &x, &y);
355             rr_board_add_robot (board, notice->u.position.robot,
356                                 notice->u.position.x, notice->u.position.y);
357             grr_board_view_mark_damage (game->board_view, x, y);
358             grr_board_view_mark_damage (game->board_view,
359                                         notice->u.position.x, notice->u.position.y);
360         }
361         break;
362         default:
363             fprintf (stderr, "Unknown notice: %d\n", notice->type);
364             break;
365         }
366         free (notice);
367     }
368 }
369
370 static gboolean
371 grr_game_key_press_callback (GtkWidget *widget,
372                              GdkEventKey *event,
373                              grr_game_t *game)
374 {
375     if (GTK_WIDGET_HAS_FOCUS (GTK_WIDGET (game->message_entry))
376         || GTK_WIDGET_HAS_FOCUS (GTK_WIDGET (game->bid_entry))
377         ) {
378         if (event->keyval == GDK_Escape) {
379             gtk_entry_set_text (GTK_ENTRY (game->message_entry), "");
380             gtk_entry_set_text (GTK_ENTRY (game->bid_entry), "");
381             gtk_widget_grab_focus (GTK_WIDGET (game->message_view));
382
383             return TRUE;
384         } else {
385             return FALSE;
386         }
387     }
388
389     switch (event->keyval) {
390     case GDK_N:
391     case GDK_n:
392     case GDK_T:
393     case GDK_t:
394         grr_game_next (game);
395         break;
396     case GDK_P:
397     case GDK_p:
398         grr_game_pass (game);
399         break;
400     case GDK_Z:
401     case GDK_z:
402         grr_game_zap (game);
403         break;
404     case GDK_B:
405     case GDK_b:
406         grr_board_view_set_active_robot (game->board_view, RR_ROBOT_BLUE);
407         break;
408     case GDK_G:
409     case GDK_g:
410         grr_board_view_set_active_robot (game->board_view, RR_ROBOT_GREEN);
411         break;
412     case GDK_R:
413     case GDK_r:
414         grr_board_view_set_active_robot (game->board_view, RR_ROBOT_RED);
415         break;
416     case GDK_Y:
417     case GDK_y:
418         grr_board_view_set_active_robot (game->board_view, RR_ROBOT_YELLOW);
419         break;
420     case GDK_Up:
421         grr_board_view_move_active (game->board_view, RR_DIRECTION_NORTH);
422         break;
423     case GDK_Right:
424         grr_board_view_move_active (game->board_view, RR_DIRECTION_EAST);
425         break;
426     case GDK_Down:
427         grr_board_view_move_active (game->board_view, RR_DIRECTION_SOUTH);
428         break;
429     case GDK_Left:
430         grr_board_view_move_active (game->board_view, RR_DIRECTION_WEST);
431         break;
432     case GDK_BackSpace:
433         grr_board_view_undo (game->board_view);
434         break;
435     case GDK_0:
436     case GDK_1:
437     case GDK_2:
438     case GDK_3:
439     case GDK_4:
440     case GDK_5:
441     case GDK_6:
442     case GDK_7:
443     case GDK_8:
444     case GDK_9:
445         {
446             int pos = -1;
447             const gchar *key = gdk_keyval_name (event->keyval);
448             gtk_widget_grab_focus (GTK_WIDGET (game->bid_entry));
449             gtk_editable_insert_text (GTK_EDITABLE (game->bid_entry),
450                                       key, strlen (key), &pos);
451             gtk_editable_set_position (GTK_EDITABLE (game->bid_entry), -1);
452         }
453         break;
454     case GDK_space:
455     case GDK_slash:
456         gtk_widget_grab_focus (GTK_WIDGET (game->message_entry));
457         if (event->keyval == GDK_slash) {
458             int pos = -1;
459             gtk_editable_insert_text (GTK_EDITABLE (game->message_entry),
460                                       "/", 1, &pos);
461             gtk_editable_set_position (GTK_EDITABLE (game->message_entry), -1);
462         }
463         break;
464     case GDK_Q:
465     case GDK_q:
466         if (event->state & GDK_CONTROL_MASK) {
467             gtk_main_quit ();
468         }
469         break;
470     }
471
472     return TRUE;
473 }
474
475 /* XXX: Messy, messy. I can't seem to make up my mind whether client
476    functions like such as these should be called here or in
477    grr_board_view. */
478 static void
479 grr_game_pass (grr_game_t *game)
480 {
481     if (game->client == NULL)
482         return;
483
484     if (game->state == RR_GAMESTATE_SHOW)
485         rr_client_pass (game->client);
486     else
487         rr_client_revoke (game->client);
488 }
489
490 static void
491 grr_game_zap (grr_game_t *game)
492 {
493     if (game->client == NULL)
494         return;
495
496     rr_client_nobid (game->client);
497 }
498
499 static void
500 grr_game_next (grr_game_t *game)
501 {
502     if (game->client == NULL)
503         return;
504
505     if (game->state == RR_GAMESTATE_DONE)
506         rr_client_turn (game->client);
507     else
508         rr_client_abandon (game->client);
509 }
510
511 static void
512 grr_game_pass_callback (GtkWidget *widget,
513                         grr_game_t *game)
514 {
515     grr_game_pass (game);
516 }
517
518 static void
519 grr_game_zap_callback (GtkWidget *widget,
520                         grr_game_t *game)
521 {
522     grr_game_zap (game);
523 }
524
525 static void
526 grr_game_next_callback (GtkWidget *widget,
527                         grr_game_t *game)
528 {
529     grr_game_next (game);
530 }
531
532 static void
533 grr_game_bid_entry_callback (GtkWidget *widget,
534                              grr_game_t *game)
535 {
536     const gchar *bid_text;
537     char *end;
538     long bid;
539
540     if (game->client == NULL)
541         return;
542
543     bid_text = gtk_entry_get_text (GTK_ENTRY (game->bid_entry));
544
545     if (bid_text && bid_text[0]) {
546         bid = strtol (bid_text, &end, 10);
547         if (*end != '\0' && ! isspace (*end)) {
548             grr_game_printf (game, "\nInvalid bid value: %s", bid_text);
549         } else {
550             if (bid == 0)
551                 rr_client_revoke (game->client);
552             else
553                 rr_client_bid (game->client, bid);
554         }
555     }
556     
557     gtk_entry_set_text (GTK_ENTRY (game->bid_entry), "");
558     gtk_widget_grab_focus (GTK_WIDGET (game->message_view));
559 }
560
561 static void
562 grr_game_message_entry_callback (GtkWidget *widget,
563                                  grr_game_t *game)
564 {
565     rr_status_t status;
566     const gchar *entry_text;
567     char **response;
568     int i;
569
570     if (game->client == NULL)
571         return;
572
573     entry_text = gtk_entry_get_text (GTK_ENTRY (game->message_entry));
574
575     if (entry_text && entry_text[0]) {
576         if (entry_text[0] == '/') {
577             status = rr_client_request (game->client, entry_text + 1, &response);
578             if (status) {
579                 grr_game_printf (game, "\nERROR: %s.", rr_status_str (status));
580             } else {
581                 if (response[0]) {
582                     grr_game_print (game, "\n");
583                     for (i=0; response[i]; i++)
584                         grr_game_printf (game, "%s ", response[i]);
585                 }
586             }
587         } else {
588             rr_client_message (game->client, entry_text);
589         }
590     }
591
592     gtk_entry_set_text (GTK_ENTRY (game->message_entry), "");
593     gtk_widget_grab_focus (GTK_WIDGET (game->message_view));
594
595 /* XXX: Huh? Why is this triggering valgrind?
596     free (response);
597 */
598
599 }
600
601 static int
602 grr_game_printf (grr_game_t *game, const char *fmt, ...)
603 {
604     int msg_id;
605
606     va_list ap;
607
608     va_start (ap, fmt);
609     msg_id = grr_game_vprintf (game, fmt, ap);
610     va_end (ap);
611
612     return msg_id;
613 }
614
615 static int
616 grr_game_vprintf (grr_game_t *game, const char *fmt, va_list ap)
617 {
618     char *msg;
619     int msg_id;
620
621     grr_sprintf_alloc_va (&msg, fmt, ap);
622     if (msg == NULL)
623         return 0;
624
625     msg_id = grr_game_print (game, msg);
626
627     free (msg);
628
629     return msg_id;
630 }
631
632 static int
633 grr_game_print (grr_game_t *game, const char *msg)
634 {
635     GtkTextIter iter;
636     GtkTextMark *mark;
637
638     /* There might be a lighter way to do this, but this seems to
639        work. */
640
641     gtk_text_buffer_get_end_iter (game->message_buffer, &iter);
642
643     mark = gtk_text_buffer_get_mark (game->message_buffer, "end");
644
645     gtk_text_buffer_move_mark (game->message_buffer, mark, &iter);
646
647     gtk_text_buffer_insert (game->message_buffer, &iter, msg, -1);
648
649     gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (game->message_view),
650                                   mark, 0.0, TRUE, 0.0, 1.0);
651
652     return ++game->msg_id;
653 }
654
655 static int
656 grr_game_start_gui (grr_game_t *game, const char *user)
657 {
658     GtkWidget *board_frame;
659     GtkWidget *vpaned;
660     GtkWidget *sw;
661     GtkWidget *vbox;
662     GtkWidget *hbox;
663     GtkWidget *window;
664     GtkWidget *message_view;
665     GtkWidget *message_entry;
666     GtkWidget *bid_entry;
667     GtkWidget *bid_button;
668     GtkWidget *pass_button;
669     GtkWidget *zap_button;
670     GtkWidget *next_button;
671     GtkWidget *user_label;
672
673     game->state = RR_GAMESTATE_NEW;
674
675     game->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
676     window = game->window;
677
678     gtk_window_set_title (GTK_WINDOW (window), "Ricochet Robot");
679     gtk_window_set_default_size (GTK_WINDOW (window), 512, 512);
680     
681     g_signal_connect (G_OBJECT (window), "destroy",
682                       G_CALLBACK (exit), NULL);
683     gtk_container_set_border_width (GTK_CONTAINER (window), 0);
684
685     vbox = gtk_vbox_new (FALSE, 1);
686     gtk_container_add (GTK_CONTAINER (window), vbox);
687     {
688         GtkTextIter iter;
689         game->board_view = grr_board_view_new (game->board);
690         grr_board_view_set_client (GRR_BOARD_VIEW (game->board_view), game->client);
691         game->message_buffer = gtk_text_buffer_new (NULL);
692         gtk_text_buffer_get_iter_at_offset (game->message_buffer,
693                                             &iter, -1);
694         gtk_text_buffer_create_mark (game->message_buffer,
695                                      "end", &iter, FALSE);
696         game->message_view = gtk_text_view_new_with_buffer (game->message_buffer);
697
698         vpaned = gtk_vpaned_new ();
699         gtk_container_set_border_width (GTK_CONTAINER (vpaned), 0);
700         gtk_box_pack_start (GTK_BOX (vbox), vpaned,
701                             TRUE, TRUE, 0);
702         {
703             board_frame = gtk_aspect_frame_new (NULL, 0.5, 0.5, 1.0, FALSE);
704             gtk_paned_pack1 (GTK_PANED (vpaned), board_frame, TRUE, TRUE);
705             {
706                 gtk_container_add (GTK_CONTAINER (board_frame), GTK_WIDGET (game->board_view));
707                 gtk_widget_show (GTK_WIDGET (game->board_view));
708             }
709             gtk_widget_show (board_frame);
710
711             sw = gtk_scrolled_window_new (NULL, NULL);
712             gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
713                                             GTK_POLICY_NEVER,
714                                             GTK_POLICY_AUTOMATIC);
715             gtk_paned_pack2 (GTK_PANED (vpaned), sw, FALSE, TRUE);
716             {
717                 message_view = game->message_view;
718                 gtk_text_view_set_editable (GTK_TEXT_VIEW (message_view), FALSE);
719                 gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (message_view), FALSE);
720                 gtk_container_add (GTK_CONTAINER (sw), message_view);
721                 gtk_widget_show (message_view);
722             }
723             gtk_widget_show (sw);
724         }
725         gtk_widget_show (vpaned);
726
727         hbox = gtk_hbox_new (FALSE, 1);
728         gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
729         {
730             user_label = gtk_label_new (user);
731             gtk_box_pack_start (GTK_BOX (hbox), user_label,
732                                 FALSE, FALSE, 1);
733             gtk_widget_show (user_label);
734
735             game->message_entry = gtk_entry_new ();
736             message_entry = game->message_entry;
737             gtk_box_pack_start (GTK_BOX (hbox), message_entry,
738                                 TRUE, TRUE, 1);
739             gtk_widget_show (message_entry);
740             g_signal_connect (G_OBJECT (message_entry), "activate",
741                               G_CALLBACK (grr_game_message_entry_callback),
742                               (gpointer) game);
743         }
744         gtk_widget_show (hbox);
745             
746         hbox = gtk_hbox_new (FALSE, 1);
747         gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
748         {
749             bid_button = gtk_button_new_with_label ("Bid:");
750             gtk_box_pack_start (GTK_BOX (hbox), bid_button,
751                                 FALSE, FALSE, 1);
752             gtk_widget_show (bid_button);
753             g_signal_connect (G_OBJECT (bid_button), "clicked",
754                               G_CALLBACK (grr_game_bid_entry_callback),
755                               (gpointer) game);
756
757             game->bid_entry = gtk_entry_new ();
758             bid_entry = game->bid_entry;
759             gtk_entry_set_max_length (GTK_ENTRY (bid_entry), 3);
760             gtk_entry_set_width_chars (GTK_ENTRY (bid_entry), 4);
761             gtk_box_pack_start (GTK_BOX (hbox), bid_entry,
762                                 FALSE, FALSE, 0);
763             gtk_widget_show (bid_entry);
764             g_signal_connect (G_OBJECT (bid_entry), "activate",
765                               G_CALLBACK (grr_game_bid_entry_callback),
766                               (gpointer) game);
767
768             pass_button = gtk_button_new_with_label ("Pass");
769             gtk_box_pack_start (GTK_BOX (hbox), pass_button,
770                                 FALSE, FALSE, 1);
771             gtk_widget_show (pass_button);
772             g_signal_connect (G_OBJECT (pass_button), "clicked",
773                               G_CALLBACK (grr_game_pass_callback),
774                               (gpointer) game);
775
776             zap_button = gtk_button_new_with_label ("Zap timer");
777             gtk_box_pack_start (GTK_BOX (hbox), zap_button,
778                                 FALSE, FALSE, 1);
779             gtk_widget_show (zap_button);
780             g_signal_connect (G_OBJECT (zap_button), "clicked",
781                               G_CALLBACK (grr_game_zap_callback),
782                               (gpointer) game);
783
784             next_button = gtk_button_new_with_label ("Next turn");
785             gtk_box_pack_start (GTK_BOX (hbox), next_button,
786                                 FALSE, FALSE, 1);
787             gtk_widget_show (next_button);
788             g_signal_connect (G_OBJECT (next_button), "clicked",
789                               G_CALLBACK (grr_game_next_callback),
790                               (gpointer) game);
791         }
792         gtk_widget_show (hbox);
793     }
794     gtk_widget_show (vbox);
795
796     g_signal_connect (G_OBJECT (window), "key_press_event",
797                       G_CALLBACK (grr_game_key_press_callback),
798                       (gpointer) game);
799
800     gtk_widget_show (window);
801     
802     gtk_main ();
803     
804     return 0;
805 }