]> git.cworth.org Git - grrobot/blobdiff - src/grrobot.c
* src/grrobot.c (grr_game_read_notices): Call new board_view timer
[grrobot] / src / grrobot.c
index 332cca55206501cda7a77790f9ec1ad1cfaf6132..8df5b9815d9da75615c0f9ac690830007e1f2fa6 100644 (file)
@@ -1,4 +1,4 @@
-/* grrobot - Ricochet Robot using GTK+ and Xr
+/* grrobot - Ricochet Robot using GTK+, libxsvg, and Cairo
  *
  * Copyright © 2003 Carl Worth
  *
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
+
 #include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
 
 #include "grr_board_view.h"
+#include "grr_util.h"
 #include "args.h"
 
 typedef struct grr_game {
@@ -40,30 +44,53 @@ typedef struct grr_game {
     rr_board_t *board;
 
     GtkWidget *window;
-    GtkWidget *board_view;
+    grr_board_view_t *board_view;
+    GtkWidget *message_entry;
+    GtkWidget *bid_entry;
+
     GtkTextBuffer *message_buffer;
     GtkWidget *message_view;
-    GtkWidget *command_entry;
-    GtkTextIter message_iter;
+    int msg_id;
+    int last_move_msg_id;
+    rr_robot_t last_move_robot;
+
+    rr_gamestate_t state;
 } grr_game_t;
 
-static int
-grr_game_start_gui (grr_game_t *game);
+static void
+grr_game_pass (grr_game_t *game);
 
 static void
-grr_game_read_callback (gpointer data,
-                       gint fd,
-                       GdkInputCondition condition);
+grr_game_zap (grr_game_t *game);
+
+static void
+grr_game_next (grr_game_t *game);
+
+static int
+grr_game_printf (grr_game_t *game, const char *fmt, ...);
+
+static int
+grr_game_vprintf (grr_game_t *game, const char *fmt, va_list ap);
+
+static int
+grr_game_print (grr_game_t *game, const char *msg);
+
+static int
+grr_game_start_gui (grr_game_t *game, const char *user);
 
 static void
 grr_game_read_notices (grr_game_t *game);
 
+static GSource *
+grr_game_notices_source_new (grr_game_t *game);
+
 int 
 main (int argc, char **argv)
 {
     args_t args;
     rr_status_t status;
     grr_game_t game;
+    GSource *source;
 
     char *diagram;
 
@@ -71,40 +98,120 @@ main (int argc, char **argv)
     
     args_parse (&args, argc, argv);
 
-    game.client = rr_client_create (args.host, args.port, args.user);
-    if (game.client == NULL) {
-       fprintf (stderr, "Failed connecting to %s:%s as %s\n",
-                args.host, args.port, args.user);
-       return 1;
-    }
+    if (args.file) {
+       game.board = rr_board_create_from_file (args.file);
+       if (game.board == NULL) {
+           fprintf (stderr, "Failed to parse board from %s\n", args.file);
+           return 1;
+       }
+       game.client = NULL;
+    } else {
+       GPollFD *poll_fd;
+
+       game.client = rr_client_create (args.host, args.port, args.user);
+       if (game.client == NULL) {
+           fprintf (stderr, "Failed connecting to %s:%s as %s\n",
+                    args.host, args.port, args.user);
+           return 1;
+       }
+
+       if (args.watch) {
+           status = rr_client_watch (game.client, args.game);
+           if (status == RR_STATUS_NO_GAME) {
+               fprintf (stderr, "No game %s to watch\n", args.game);
+           }
+       } else {
+           status = rr_client_join (game.client, args.game);
+           if (status == RR_STATUS_NO_GAME) {
+               status = rr_client_new (game.client, args.game);
+           }
+       }
+
+       game.board = rr_board_create (16, 16);
+       game.msg_id = 0;
+       game.last_move_msg_id = 0;
+       game.last_move_robot = RR_ROBOT_NONE;
+
+       source = grr_game_notices_source_new (&game);
+       poll_fd = g_new (GPollFD, 1);
+       poll_fd->fd = rr_client_fd (game.client);
+       poll_fd->events = G_IO_IN;
+       g_source_add_poll (source, poll_fd);
 
-    status = rr_client_join (game.client, args.game);
-    if (status == RR_STATUS_NO_GAME) {
-       status = rr_client_new (game.client, args.game);
+       g_source_set_priority (source, GDK_PRIORITY_EVENTS);
+       g_source_attach (source, NULL);
+       g_source_unref (source);
+
+       rr_client_show (game.client, &diagram);
+       rr_board_parse (game.board, diagram);
+       free (diagram);
     }
 
-    game.board = rr_board_create (16, 16);
+    return grr_game_start_gui (&game, args.user);
+}
+
+typedef struct _GrrNoticeSource {
+    GSource source;
+    grr_game_t *game;
+} GrrNoticeSource;
 
-    gdk_input_add (rr_client_fd (game.client),
-                  GDK_INPUT_READ,
-                  grr_game_read_callback,
-                  &game);
+static gboolean
+grr_game_notices_prepare (GSource   *source,
+                         gint      *timeout)
+{
+    GrrNoticeSource *src = (GrrNoticeSource*)source;
+    grr_game_t *game = src->game;
+    gboolean retval;
 
-    rr_client_show (game.client, &diagram);
-    rr_board_parse (game.board, diagram);
-    free (diagram);
+    GDK_THREADS_ENTER ();
+    *timeout = -1;
+    retval = rr_client_notice_pending (game->client);
+    GDK_THREADS_LEAVE ();
 
-    return grr_game_start_gui (&game);
+    return retval;
 }
 
-static void
-grr_game_read_callback (gpointer data,
-                     gint fd,
-                     GdkInputCondition condition)
+static gboolean
+grr_game_notices_check (GSource            *source)
 {
-    grr_game_t *game = data;
+    GrrNoticeSource *src = (GrrNoticeSource*)source;
+    grr_game_t *game = src->game;
+    gboolean retval;
+
+    GDK_THREADS_ENTER ();
+    retval = rr_client_notice_pending (game->client);
+    GDK_THREADS_LEAVE ();
 
+    return retval;
+}
+
+static gboolean
+grr_game_notices_dispatch (GSource     *source,
+                          GSourceFunc  callback,
+                          gpointer     user_data)
+{
+    GrrNoticeSource *src = (GrrNoticeSource*)source;
+    grr_game_t *game = src->game;
+    
     grr_game_read_notices (game);
+
+    return TRUE;
+}
+
+static GSourceFuncs game_notices_funcs = {
+  grr_game_notices_prepare,
+  grr_game_notices_check,
+  grr_game_notices_dispatch,
+  NULL
+};
+
+static GSource *
+grr_game_notices_source_new (grr_game_t *game)
+{
+    GSource *source = g_source_new (&game_notices_funcs, sizeof (GrrNoticeSource));
+    GrrNoticeSource *src = (GrrNoticeSource*)source;
+    src->game = game;
+    return source;
 }
 
 static void
@@ -112,85 +219,378 @@ grr_game_read_notices (grr_game_t *game)
 {
     rr_board_t *board = game->board;
     rr_status_t status;
-    char **notice_s;
     rr_notice_t *notice;
-    int i;
 
     while (rr_client_notice_pending (game->client)) {
-       status = rr_client_next_notice (game->client, &notice_s);
+       status = rr_client_next_notice (game->client, &notice);
        if (status) {
-           fprintf (stderr, "Error during rr_client_notice: %s\n",
-                    rr_status_str (status));
-           gtk_exit (1);
-           return;
+           if (status == RR_STATUS_EOF)
+               fprintf (stderr, "The server has disconnected, exiting.\n");
+           else
+               fprintf (stderr, "Error during rr_client_next_notice: %s\n",
+                        rr_status_str (status));
+           gtk_main_quit ();
        }
-       for (i=0; notice_s[i]; i++) {
-           gtk_text_buffer_insert_at_cursor (game->message_buffer, notice_s[i], -1);
-           gtk_text_buffer_insert_at_cursor (game->message_buffer, " ", -1);
+       if (!notice) {
+           fprintf (stderr, "Missing notice\n");
+           continue;
        }
-       gtk_text_buffer_insert_at_cursor (game->message_buffer, "\n", -1);
-       gtk_text_buffer_get_iter_at_offset (game->message_buffer,
-                                           &game->message_iter,
-                                           -1);
-       gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (game->message_view),
-                                     &game->message_iter,
-                                     0.0, FALSE, 0.0, 0.0);
-
-       notice = rr_client_parse_notice (game->client, notice_s);
        switch (notice->type) {
        case RR_NOTICE_USER:
+           grr_game_printf (game,
+                            "\nGLOBAL: %s has connected.", notice->u.string);
+           break;
        case RR_NOTICE_QUIT:
+           grr_game_printf (game,
+                            "\nGLOBAL: %s has disconnected.", notice->u.string);
+           break;
        case RR_NOTICE_GAME:
+           grr_game_printf (game,
+                            "\nGLOBAL: New game available: %s.", notice->u.string);
+           break;
        case RR_NOTICE_DISPOSE:
+           grr_game_printf (game,
+                            "\nGLOBAL: Game %s has been disposed.", notice->u.string);
+           break;
        case RR_NOTICE_MESSAGE:
+           grr_game_printf (game, "\n%s> %s",
+                            notice->u.message.username,
+                            notice->u.message.text);
+           break;
+       case RR_NOTICE_BOARD:
+           rr_board_parse (board, notice->u.string);
+           gtk_widget_queue_draw (GTK_WIDGET (game->window));
+           break;
        case RR_NOTICE_GAMESTATE:
+           game->state = notice->u.gamestate;
+           grr_game_printf (game, "\nGame state changed to: %s.",
+                            rr_gamestate_str (notice->u.gamestate));
+           break;
+       case RR_NOTICE_TURN:
+           /* XXX: Setting game->state here is goofy. Cleaner would
+               be to make the server send a NOTICE GAMESTATE NEW
+               here. */
+           game->state = RR_GAMESTATE_NEW;
+
+           grr_board_view_reset_timer (game->board_view);
+           grr_game_print (game, "\nNew round!");
+           rr_board_set_goal_target (board, notice->u.target);
+           gtk_widget_queue_draw (GTK_WIDGET (game->window));
+           break;
+       case RR_NOTICE_GAMEOVER:
+           grr_game_printf (game, "\nGame over. New game will begin now.");
+           break;
        case RR_NOTICE_JOIN:
+           grr_game_printf (game, "\nUser %s has joined the game.",
+                            notice->u.string);
+           break;
        case RR_NOTICE_WATCH:
+           grr_game_printf (game, "\nUser %s has started watching the game.",
+                            notice->u.string);
+           break;
        case RR_NOTICE_PART:
+           grr_game_printf (game, "\nUser %s has left the game.",
+                            notice->u.string);
+           break;
        case RR_NOTICE_BID:
+           grr_game_printf (game, "\nUser %s bids %d.",
+                            notice->u.bid.username,
+                            notice->u.bid.number);
+           break;
        case RR_NOTICE_REVOKE:
-       case RR_NOTICE_TIMER:
+           grr_game_printf (game, "\nUser %s revokes previous bid.",
+                            notice->u.string);
+           break;
        case RR_NOTICE_ABANDON:
+           grr_game_printf (game, "\nUser %s is willing to abandon this turn.",
+                            notice->u.string);
+           break;
        case RR_NOTICE_NOBID:
-       case RR_NOTICE_ACTIVE:
+           grr_game_printf (game, "\nUser %s sees no point in bidding further this round",
+                            notice->u.string);
+           break;
        case RR_NOTICE_MOVE:
+           if (game->msg_id == game->last_move_msg_id
+               && game->last_move_robot == notice->u.move.robot) {
+               game->last_move_msg_id = grr_game_printf (game, ",%c",
+                                rr_direction_char (notice->u.move.direction));
+           } else {
+               game->last_move_msg_id = grr_game_printf (game, "\nMove #%d: %s %c",
+                                notice->u.move.count,
+                                rr_robot_str (notice->u.move.robot),
+                                rr_direction_char (notice->u.move.direction));
+               game->last_move_robot = notice->u.move.robot;
+           }
+           break;
        case RR_NOTICE_UNDO:
+           grr_game_print (game, "\nUNDO");
+           break;
        case RR_NOTICE_RESET:
+           grr_game_print (game, "\nRESET");
+           break;
        case RR_NOTICE_SCORE:
+           grr_game_printf (game, "\nScore for %s: %d.",
+                            notice->u.bid.username,
+                            notice->u.bid.number);
+           break;
        case RR_NOTICE_ACTIVATE:
-           /* XXX: Need to actually handle many of these. */
-           fprintf (stderr, "Warning: Ignoring notice of type %d\n", notice->type);
+           grr_game_printf (game, "\nYour turn.",
+                            notice->u.number);
            break;
-       case RR_NOTICE_POSITION:
-           rr_board_position (board, notice->u.position.robot,
-                              notice->u.position.x, notice->u.position.y);
-           gtk_widget_queue_draw (GTK_WIDGET (game->window));
+       case RR_NOTICE_ACTIVE:
+           grr_board_view_stop_timer (game->board_view);
+           grr_game_printf (game, "\nUser %s now active to demonstrate solution in %d moves.",
+                            notice->u.bid.username,
+                            notice->u.bid.number);
            break;
-       case RR_NOTICE_TURN:
-           rr_board_set_goal_target (board, notice->u.target);
-           gtk_widget_queue_draw (GTK_WIDGET (game->window));
+       case RR_NOTICE_TIMER:
+           grr_board_view_start_timer (game->board_view,
+                                       notice->u.number);
+           break;
+       case RR_NOTICE_POSITION:
+       {
+           int x, y;
+           rr_board_find_robot (board, notice->u.position.robot, &x, &y);
+           rr_board_add_robot (board, notice->u.position.robot,
+                               notice->u.position.x, notice->u.position.y);
+           grr_board_view_mark_damage (game->board_view, x, y);
+           grr_board_view_mark_damage (game->board_view,
+                                       notice->u.position.x, notice->u.position.y);
+       }
+       break;
+       default:
+           fprintf (stderr, "Unknown notice: %d\n", notice->type);
            break;
        }
        free (notice);
     }
 }
 
+static gboolean
+grr_game_key_press_callback (GtkWidget *widget,
+                            GdkEventKey *event,
+                            grr_game_t *game)
+{
+    if (GTK_WIDGET_HAS_FOCUS (GTK_WIDGET (game->message_entry))
+       || GTK_WIDGET_HAS_FOCUS (GTK_WIDGET (game->bid_entry))
+       ) {
+       if (event->keyval == GDK_Escape) {
+           gtk_entry_set_text (GTK_ENTRY (game->message_entry), "");
+           gtk_entry_set_text (GTK_ENTRY (game->bid_entry), "");
+           gtk_widget_grab_focus (GTK_WIDGET (game->message_view));
+
+           return TRUE;
+       } else {
+           return FALSE;
+       }
+    }
+
+    switch (event->keyval) {
+    case GDK_N:
+    case GDK_n:
+    case GDK_T:
+    case GDK_t:
+       grr_game_next (game);
+       break;
+    case GDK_P:
+    case GDK_p:
+       grr_game_pass (game);
+       break;
+    case GDK_Z:
+    case GDK_z:
+       grr_game_zap (game);
+       break;
+    case GDK_B:
+    case GDK_b:
+       grr_board_view_set_active_robot (game->board_view, RR_ROBOT_BLUE);
+       break;
+    case GDK_G:
+    case GDK_g:
+       grr_board_view_set_active_robot (game->board_view, RR_ROBOT_GREEN);
+       break;
+    case GDK_R:
+    case GDK_r:
+       grr_board_view_set_active_robot (game->board_view, RR_ROBOT_RED);
+       break;
+    case GDK_Y:
+    case GDK_y:
+       grr_board_view_set_active_robot (game->board_view, RR_ROBOT_YELLOW);
+       break;
+    case GDK_Up:
+       grr_board_view_move_active (game->board_view, RR_DIRECTION_NORTH);
+       break;
+    case GDK_Right:
+       grr_board_view_move_active (game->board_view, RR_DIRECTION_EAST);
+       break;
+    case GDK_Down:
+       grr_board_view_move_active (game->board_view, RR_DIRECTION_SOUTH);
+       break;
+    case GDK_Left:
+       grr_board_view_move_active (game->board_view, RR_DIRECTION_WEST);
+       break;
+    case GDK_BackSpace:
+       grr_board_view_undo (game->board_view);
+       break;
+    case GDK_0:
+    case GDK_1:
+    case GDK_2:
+    case GDK_3:
+    case GDK_4:
+    case GDK_5:
+    case GDK_6:
+    case GDK_7:
+    case GDK_8:
+    case GDK_9:
+        {
+           int pos = -1;
+           const gchar *key = gdk_keyval_name (event->keyval);
+           gtk_widget_grab_focus (GTK_WIDGET (game->bid_entry));
+           gtk_editable_insert_text (GTK_EDITABLE (game->bid_entry),
+                                     key, strlen (key), &pos);
+           gtk_editable_set_position (GTK_EDITABLE (game->bid_entry), -1);
+       }
+       break;
+    case GDK_space:
+    case GDK_slash:
+       gtk_widget_grab_focus (GTK_WIDGET (game->message_entry));
+       if (event->keyval == GDK_slash) {
+           int pos = -1;
+           gtk_editable_insert_text (GTK_EDITABLE (game->message_entry),
+                                     "/", 1, &pos);
+           gtk_editable_set_position (GTK_EDITABLE (game->message_entry), -1);
+       }
+       break;
+    case GDK_Q:
+    case GDK_q:
+       if (event->state & GDK_CONTROL_MASK) {
+           gtk_main_quit ();
+       }
+       break;
+    }
+
+    return TRUE;
+}
+
+/* XXX: Messy, messy. I can't seem to make up my mind whether client
+   functions like such as these should be called here or in
+   grr_board_view. */
 static void
-command_callback (GtkWidget *widget,
-                 grr_game_t *game)
+grr_game_pass (grr_game_t *game)
 {
-    const gchar *entry_text;
-    char **response;
+    if (game->client == NULL)
+       return;
 
-    entry_text = gtk_entry_get_text (GTK_ENTRY (game->command_entry));
-    gtk_text_buffer_insert_at_cursor (game->message_buffer, entry_text, -1);
-    gtk_text_buffer_insert_at_cursor (game->message_buffer, "\n", -1);
+    if (game->state == RR_GAMESTATE_SHOW)
+       rr_client_pass (game->client);
+    else
+       rr_client_revoke (game->client);
+}
+
+static void
+grr_game_zap (grr_game_t *game)
+{
+    if (game->client == NULL)
+       return;
 
-    rr_client_request (game->client, entry_text, &response);
+    rr_client_nobid (game->client);
+}
 
-    gtk_entry_set_text (GTK_ENTRY (game->command_entry), "");
+static void
+grr_game_next (grr_game_t *game)
+{
+    if (game->client == NULL)
+       return;
 
-    grr_game_read_notices (game);
+    if (game->state == RR_GAMESTATE_DONE)
+       rr_client_turn (game->client);
+    else
+       rr_client_abandon (game->client);
+}
+
+static void
+grr_game_pass_callback (GtkWidget *widget,
+                       grr_game_t *game)
+{
+    grr_game_pass (game);
+}
+
+static void
+grr_game_zap_callback (GtkWidget *widget,
+                       grr_game_t *game)
+{
+    grr_game_zap (game);
+}
+
+static void
+grr_game_next_callback (GtkWidget *widget,
+                       grr_game_t *game)
+{
+    grr_game_next (game);
+}
+
+static void
+grr_game_bid_entry_callback (GtkWidget *widget,
+                            grr_game_t *game)
+{
+    const gchar *bid_text;
+    char *end;
+    long bid;
+
+    if (game->client == NULL)
+       return;
+
+    bid_text = gtk_entry_get_text (GTK_ENTRY (game->bid_entry));
+
+    if (bid_text && bid_text[0]) {
+       bid = strtol (bid_text, &end, 10);
+       if (*end != '\0' && ! isspace (*end)) {
+           grr_game_printf (game, "\nInvalid bid value: %s", bid_text);
+       } else {
+           if (bid == 0)
+               rr_client_revoke (game->client);
+           else
+               rr_client_bid (game->client, bid);
+       }
+    }
+    
+    gtk_entry_set_text (GTK_ENTRY (game->bid_entry), "");
+    gtk_widget_grab_focus (GTK_WIDGET (game->message_view));
+}
+
+static void
+grr_game_message_entry_callback (GtkWidget *widget,
+                                grr_game_t *game)
+{
+    rr_status_t status;
+    const gchar *entry_text;
+    char **response;
+    int i;
+
+    if (game->client == NULL)
+       return;
+
+    entry_text = gtk_entry_get_text (GTK_ENTRY (game->message_entry));
+
+    if (entry_text && entry_text[0]) {
+       if (entry_text[0] == '/') {
+           status = rr_client_request (game->client, entry_text + 1, &response);
+           if (status) {
+               grr_game_printf (game, "\nERROR: %s.", rr_status_str (status));
+           } else {
+               if (response[0]) {
+                   grr_game_print (game, "\n");
+                   for (i=0; response[i]; i++)
+                       grr_game_printf (game, "%s ", response[i]);
+               }
+           }
+       } else {
+           rr_client_message (game->client, entry_text);
+       }
+    }
+
+    gtk_entry_set_text (GTK_ENTRY (game->message_entry), "");
+    gtk_widget_grab_focus (GTK_WIDGET (game->message_view));
 
 /* XXX: Huh? Why is this triggering valgrind?
     free (response);
@@ -199,15 +599,78 @@ command_callback (GtkWidget *widget,
 }
 
 static int
-grr_game_start_gui (grr_game_t *game)
+grr_game_printf (grr_game_t *game, const char *fmt, ...)
+{
+    int msg_id;
+
+    va_list ap;
+
+    va_start (ap, fmt);
+    msg_id = grr_game_vprintf (game, fmt, ap);
+    va_end (ap);
+
+    return msg_id;
+}
+
+static int
+grr_game_vprintf (grr_game_t *game, const char *fmt, va_list ap)
+{
+    char *msg;
+    int msg_id;
+
+    grr_sprintf_alloc_va (&msg, fmt, ap);
+    if (msg == NULL)
+       return 0;
+
+    msg_id = grr_game_print (game, msg);
+
+    free (msg);
+
+    return msg_id;
+}
+
+static int
+grr_game_print (grr_game_t *game, const char *msg)
+{
+    GtkTextIter iter;
+    GtkTextMark *mark;
+
+    /* There might be a lighter way to do this, but this seems to
+       work. */
+
+    gtk_text_buffer_get_end_iter (game->message_buffer,        &iter);
+
+    mark = gtk_text_buffer_get_mark (game->message_buffer, "end");
+
+    gtk_text_buffer_move_mark (game->message_buffer, mark, &iter);
+
+    gtk_text_buffer_insert (game->message_buffer, &iter, msg, -1);
+
+    gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (game->message_view),
+                                 mark, 0.0, TRUE, 0.0, 1.0);
+
+    return ++game->msg_id;
+}
+
+static int
+grr_game_start_gui (grr_game_t *game, const char *user)
 {
     GtkWidget *board_frame;
     GtkWidget *vpaned;
     GtkWidget *sw;
     GtkWidget *vbox;
+    GtkWidget *hbox;
     GtkWidget *window;
     GtkWidget *message_view;
-    GtkWidget *command_entry;
+    GtkWidget *message_entry;
+    GtkWidget *bid_entry;
+    GtkWidget *bid_button;
+    GtkWidget *pass_button;
+    GtkWidget *zap_button;
+    GtkWidget *next_button;
+    GtkWidget *user_label;
+
+    game->state = RR_GAMESTATE_NEW;
 
     game->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
     window = game->window;
@@ -219,54 +682,123 @@ grr_game_start_gui (grr_game_t *game)
                      G_CALLBACK (exit), NULL);
     gtk_container_set_border_width (GTK_CONTAINER (window), 0);
 
-    vpaned = gtk_vpaned_new ();
-    gtk_container_set_border_width (GTK_CONTAINER (vpaned), 5);
-    gtk_container_add (GTK_CONTAINER (window), vpaned);
+    vbox = gtk_vbox_new (FALSE, 1);
+    gtk_container_add (GTK_CONTAINER (window), vbox);
     {
-       board_frame = gtk_aspect_frame_new (NULL, 0.5, 0.5, 1.0, FALSE);
-       gtk_paned_pack1 (GTK_PANED (vpaned), board_frame, TRUE, TRUE);
+       GtkTextIter iter;
+       game->board_view = grr_board_view_new (game->board);
+       grr_board_view_set_client (GRR_BOARD_VIEW (game->board_view), game->client);
+       game->message_buffer = gtk_text_buffer_new (NULL);
+       gtk_text_buffer_get_iter_at_offset (game->message_buffer,
+                                           &iter, -1);
+       gtk_text_buffer_create_mark (game->message_buffer,
+                                    "end", &iter, FALSE);
+       game->message_view = gtk_text_view_new_with_buffer (game->message_buffer);
+
+       vpaned = gtk_vpaned_new ();
+       gtk_container_set_border_width (GTK_CONTAINER (vpaned), 0);
+       gtk_box_pack_start (GTK_BOX (vbox), vpaned,
+                           TRUE, TRUE, 0);
        {
-           game->board_view = grr_board_view_new (game->board);
+           board_frame = gtk_aspect_frame_new (NULL, 0.5, 0.5, 1.0, FALSE);
+           gtk_paned_pack1 (GTK_PANED (vpaned), board_frame, TRUE, TRUE);
+           {
+               gtk_container_add (GTK_CONTAINER (board_frame), GTK_WIDGET (game->board_view));
+               gtk_widget_show (GTK_WIDGET (game->board_view));
+           }
+           gtk_widget_show (board_frame);
 
-           gtk_container_add (GTK_CONTAINER (board_frame), game->board_view);
-           gtk_widget_show (game->board_view);
-       }
-       gtk_widget_show (board_frame);
-
-       vbox = gtk_vbox_new (FALSE, 1);
-       gtk_paned_pack2 (GTK_PANED (vpaned), vbox, FALSE, TRUE);
-       {
            sw = gtk_scrolled_window_new (NULL, NULL);
            gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
                                            GTK_POLICY_NEVER,
                                            GTK_POLICY_AUTOMATIC);
-           gtk_container_add (GTK_CONTAINER (vbox), sw);
+           gtk_paned_pack2 (GTK_PANED (vpaned), sw, FALSE, TRUE);
+           {
+               message_view = game->message_view;
+               gtk_text_view_set_editable (GTK_TEXT_VIEW (message_view), FALSE);
+               gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (message_view), FALSE);
+               gtk_container_add (GTK_CONTAINER (sw), message_view);
+               gtk_widget_show (message_view);
+           }
            gtk_widget_show (sw);
+       }
+       gtk_widget_show (vpaned);
+
+       hbox = gtk_hbox_new (FALSE, 1);
+       gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+       {
+           user_label = gtk_label_new (user);
+           gtk_box_pack_start (GTK_BOX (hbox), user_label,
+                               FALSE, FALSE, 1);
+           gtk_widget_show (user_label);
+
+           game->message_entry = gtk_entry_new ();
+           message_entry = game->message_entry;
+           gtk_box_pack_start (GTK_BOX (hbox), message_entry,
+                               TRUE, TRUE, 1);
+           gtk_widget_show (message_entry);
+           g_signal_connect (G_OBJECT (message_entry), "activate",
+                             G_CALLBACK (grr_game_message_entry_callback),
+                             (gpointer) game);
+       }
+       gtk_widget_show (hbox);
+           
+       hbox = gtk_hbox_new (FALSE, 1);
+       gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+       {
+           bid_button = gtk_button_new_with_label ("Bid:");
+           gtk_box_pack_start (GTK_BOX (hbox), bid_button,
+                               FALSE, FALSE, 1);
+           gtk_widget_show (bid_button);
+           g_signal_connect (G_OBJECT (bid_button), "clicked",
+                             G_CALLBACK (grr_game_bid_entry_callback),
+                             (gpointer) game);
+
+           game->bid_entry = gtk_entry_new ();
+           bid_entry = game->bid_entry;
+           gtk_entry_set_max_length (GTK_ENTRY (bid_entry), 3);
+           gtk_entry_set_width_chars (GTK_ENTRY (bid_entry), 4);
+           gtk_box_pack_start (GTK_BOX (hbox), bid_entry,
+                               FALSE, FALSE, 0);
+           gtk_widget_show (bid_entry);
+           g_signal_connect (G_OBJECT (bid_entry), "activate",
+                             G_CALLBACK (grr_game_bid_entry_callback),
+                             (gpointer) game);
+
+           pass_button = gtk_button_new_with_label ("Pass");
+           gtk_box_pack_start (GTK_BOX (hbox), pass_button,
+                               FALSE, FALSE, 1);
+           gtk_widget_show (pass_button);
+           g_signal_connect (G_OBJECT (pass_button), "clicked",
+                             G_CALLBACK (grr_game_pass_callback),
+                             (gpointer) game);
 
-           game->message_buffer = gtk_text_buffer_new (NULL);
-           game->message_view = gtk_text_view_new_with_buffer (game->message_buffer);
-           message_view = game->message_view;
-           gtk_text_view_set_editable (GTK_TEXT_VIEW (message_view), FALSE);
-           gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (message_view), FALSE);
-           gtk_container_add (GTK_CONTAINER (sw), message_view);
-           gtk_widget_show (message_view);
-
-           game->command_entry = gtk_entry_new ();
-           command_entry = game->command_entry;
-           gtk_container_add (GTK_CONTAINER (vbox), command_entry);
-           gtk_widget_show (command_entry);
-           g_signal_connect (G_OBJECT (command_entry), "activate",
-                             G_CALLBACK (command_callback),
+           zap_button = gtk_button_new_with_label ("Zap timer");
+           gtk_box_pack_start (GTK_BOX (hbox), zap_button,
+                               FALSE, FALSE, 1);
+           gtk_widget_show (zap_button);
+           g_signal_connect (G_OBJECT (zap_button), "clicked",
+                             G_CALLBACK (grr_game_zap_callback),
+                             (gpointer) game);
+
+           next_button = gtk_button_new_with_label ("Next turn");
+           gtk_box_pack_start (GTK_BOX (hbox), next_button,
+                               FALSE, FALSE, 1);
+           gtk_widget_show (next_button);
+           g_signal_connect (G_OBJECT (next_button), "clicked",
+                             G_CALLBACK (grr_game_next_callback),
                              (gpointer) game);
        }
-       gtk_widget_show (vbox);
+       gtk_widget_show (hbox);
     }
-    gtk_widget_show (vpaned);
+    gtk_widget_show (vbox);
+
+    g_signal_connect (G_OBJECT (window), "key_press_event",
+                     G_CALLBACK (grr_game_key_press_callback),
+                     (gpointer) game);
 
     gtk_widget_show (window);
     
-    grr_game_read_notices (game);
-
     gtk_main ();
     
     return 0;