]> git.cworth.org Git - grrobot/blobdiff - src/grrobot.c
* configure.in:
[grrobot] / src / grrobot.c
index 98f14f2582fad574e77c4317ed16a6fd2a13318b..8c42f26106111b4738b1936e832ad0f549d8b055 100644 (file)
@@ -1,4 +1,4 @@
-/* grrobot - Ricochet Robot using GTK+ and Xr
+/* grrobot - Ricochet Robot using GTK+, libsvg-cairo, and cairo
  *
  * Copyright © 2003 Carl Worth
  *
@@ -33,6 +33,7 @@
 #include <string.h>
 
 #include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
 
 #include "grr_board_view.h"
 #include "grr_util.h"
@@ -43,16 +44,27 @@ typedef struct grr_game {
     rr_board_t *board;
 
     GtkWidget *window;
-    GtkWidget *board_view;
-    GtkWidget *command_entry;
+    grr_board_view_t *board_view;
+    GtkWidget *message_entry;
 
     GtkTextBuffer *message_buffer;
     GtkWidget *message_view;
     int msg_id;
     int last_move_msg_id;
-    rr_robot_t last_move_robot;    
+    rr_robot_t last_move_robot;
+
+    rr_gamestate_t state;
 } grr_game_t;
 
+static void
+grr_game_pass (grr_game_t *game);
+
+static void
+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, ...);
 
@@ -63,7 +75,7 @@ static int
 grr_game_print (grr_game_t *game, const char *msg);
 
 static int
-grr_game_start_gui (grr_game_t *game);
+grr_game_start_gui (grr_game_t *game, const char *user);
 
 static void
 grr_game_read_notices (grr_game_t *game);
@@ -85,33 +97,56 @@ 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;
 
-    status = rr_client_join (game.client, args.game);
-    if (status == RR_STATUS_NO_GAME) {
-       status = rr_client_new (game.client, args.game);
-    }
+       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;
+       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);
-    g_source_set_priority (source, GDK_PRIORITY_EVENTS);
-    g_source_attach (source, NULL);
-    g_source_unref (source);
+       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);
 
-    rr_client_show (game.client, &diagram);
-    rr_board_parse (game.board, diagram);
-    free (diagram);
+       g_source_set_priority (source, GDK_PRIORITY_EVENTS);
+       g_source_attach (source, NULL);
+       g_source_unref (source);
 
-    return grr_game_start_gui (&game);
+       rr_client_show (game.client, &diagram);
+       rr_board_parse (game.board, diagram);
+       free (diagram);
+    }
+
+    return grr_game_start_gui (&game, args.user);
 }
 
 typedef struct _GrrNoticeSource {
@@ -188,12 +223,19 @@ grr_game_read_notices (grr_game_t *game)
     while (rr_client_notice_pending (game->client)) {
        status = rr_client_next_notice (game->client, &notice);
        if (status) {
-           fprintf (stderr, "Error during rr_client_next_notice: %s\n",
-                    rr_status_str (status));
-           gtk_exit (1);
+           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 ();
            return;
        }
-
+       if (!notice) {
+           fprintf (stderr, "Missing notice\n");
+           continue;
+       }
        switch (notice->type) {
        case RR_NOTICE_USER:
            grr_game_printf (game,
@@ -216,26 +258,29 @@ grr_game_read_notices (grr_game_t *game)
                             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_set_timer (game->board_view, 60.0);
            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:
-       {
-           char *diagram;
            grr_game_printf (game, "\nGame over. New game will begin now.");
-           /* XXX: Can drop this when the BOARD NOTICE is added in the server */
-           rr_client_show (game->client, &diagram);
-           rr_board_parse (board, diagram);
-           free (diagram);
-           gtk_widget_queue_draw (GTK_WIDGET (game->window));
-       }
-       break;
+           break;
        case RR_NOTICE_JOIN:
            grr_game_printf (game, "\nUser %s has joined the game.",
                             notice->u.string);
@@ -268,13 +313,13 @@ grr_game_read_notices (grr_game_t *game)
        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, " %s",
-                                rr_direction_str (notice->u.move.direction));
+               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 %s",
+               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_str (notice->u.move.direction));
+                                rr_direction_char (notice->u.move.direction));
                game->last_move_robot = notice->u.move.robot;
            }
            break;
@@ -294,47 +339,212 @@ grr_game_read_notices (grr_game_t *game)
                             notice->u.number);
            break;
        case RR_NOTICE_ACTIVE:
+           grr_board_view_zap_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.username,
                             notice->u.bid.number);
            break;
        case RR_NOTICE_TIMER:
-           grr_game_printf (game, "\nTime remaining: %d seconds.",
-                            notice->u.number);
+           grr_board_view_set_timer (game->board_view,
+                                     notice->u.number);
+           grr_board_view_start_timer (game->board_view);
            break;
        case RR_NOTICE_POSITION:
-           rr_board_position_robot (board, notice->u.position.robot,
-                                    notice->u.position.x, notice->u.position.y);
-           gtk_widget_queue_draw (GTK_WIDGET (game->window));
+       {
+           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))) {
+       if (event->keyval == GDK_Escape) {
+           gtk_entry_set_text (GTK_ENTRY (game->message_entry), "");
+           gtk_widget_grab_focus (GTK_WIDGET (game->message_view));
+           return TRUE;
+       } else {
+           if ((event->state & GDK_CONTROL_MASK) == 0)
+               return FALSE;
+       }
+    }
+
+    if (event->state & GDK_CONTROL_MASK) {
+       switch (event->keyval) {
+       case GDK_N:
+       case GDK_n:
+       case GDK_T:
+       case GDK_t:
+           grr_game_next (game);
+           return TRUE;
+       case GDK_P:
+       case GDK_p:
+           grr_game_pass (game);
+           return TRUE;
+       case GDK_Z:
+       case GDK_z:
+           grr_game_zap (game);
+           return TRUE;
+       case GDK_B:
+       case GDK_b:
+           grr_board_view_set_active_robot (game->board_view, RR_ROBOT_BLUE);
+           return TRUE;
+       case GDK_G:
+       case GDK_g:
+           grr_board_view_set_active_robot (game->board_view, RR_ROBOT_GREEN);
+           return TRUE;
+       case GDK_R:
+       case GDK_r:
+           grr_board_view_set_active_robot (game->board_view, RR_ROBOT_RED);
+           return TRUE;
+       case GDK_Y:
+       case GDK_y:
+           grr_board_view_set_active_robot (game->board_view, RR_ROBOT_YELLOW);
+           return TRUE;
+       case GDK_Q:
+       case GDK_q:
+           if (event->state & GDK_CONTROL_MASK) {
+               gtk_main_quit ();
+           }
+           return TRUE;
+       }
+    }
+
+    switch (event->keyval) {
+    case GDK_BackSpace:
+       grr_board_view_undo (game->board_view);
+       return TRUE;
+    case GDK_Up:
+       grr_board_view_move_active (game->board_view, RR_DIRECTION_NORTH);
+       return TRUE;
+    case GDK_Right:
+       grr_board_view_move_active (game->board_view, RR_DIRECTION_EAST);
+       return TRUE;
+    case GDK_Down:
+       grr_board_view_move_active (game->board_view, RR_DIRECTION_SOUTH);
+       return TRUE;
+    case GDK_Left:
+       grr_board_view_move_active (game->board_view, RR_DIRECTION_WEST);
+       return TRUE;
+    case GDK_Control_L:
+    case GDK_Control_R:
+       return TRUE;
+    }
+
+    gtk_widget_grab_focus (GTK_WIDGET (game->message_entry));
+    return FALSE;
+}
+
+/* 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
+grr_game_pass (grr_game_t *game)
+{
+    if (game->client == NULL)
+       return;
+
+    if (game->state == RR_GAMESTATE_SHOW)
+       rr_client_pass (game->client);
+    else
+       rr_client_abandon (game->client);
+}
+
+static void
+grr_game_zap (grr_game_t *game)
+{
+    if (game->client == NULL)
+       return;
+
+    rr_client_nobid (game->client);
+}
+
+static void
+grr_game_next (grr_game_t *game)
+{
+    if (game->client == NULL)
+       return;
+
+    rr_client_turn (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_entry_callback (GtkWidget *widget,
-                        grr_game_t *game)
+grr_game_next_callback (GtkWidget *widget,
+                       grr_game_t *game)
+{
+    grr_game_next (game);
+}
+
+static void
+grr_game_message_entry_callback (GtkWidget *widget,
+                                grr_game_t *game)
 {
     rr_status_t status;
     const gchar *entry_text;
     char **response;
+    char *end;
+    long bid;
     int i;
 
-    entry_text = gtk_entry_get_text (GTK_ENTRY (game->command_entry));
-
-    status = rr_client_request (game->client, entry_text, &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]);
+    if (game->client == NULL)
+       return;
+
+    entry_text = gtk_entry_get_text (GTK_ENTRY (game->message_entry));
+
+    if (entry_text && entry_text[0]) {
+       bid = strtol (entry_text, &end, 10);
+       if (*end == '\0' || isspace (*end)) {
+           if (bid == 0)
+               rr_client_revoke (game->client);
+           else
+               rr_client_bid (game->client, bid);
+       } else  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->command_entry), "");
+    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);
@@ -397,15 +607,22 @@ grr_game_print (grr_game_t *game, const char *msg)
 }
 
 static int
-grr_game_start_gui (grr_game_t *game)
+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 *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;
@@ -429,7 +646,6 @@ grr_game_start_gui (grr_game_t *game)
        gtk_text_buffer_create_mark (game->message_buffer,
                                     "end", &iter, FALSE);
        game->message_view = gtk_text_view_new_with_buffer (game->message_buffer);
-       game->command_entry = gtk_entry_new ();
 
        vpaned = gtk_vpaned_new ();
        gtk_container_set_border_width (GTK_CONTAINER (vpaned), 0);
@@ -439,8 +655,8 @@ grr_game_start_gui (grr_game_t *game)
            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), game->board_view);
-               gtk_widget_show (game->board_view);
+               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);
 
@@ -460,16 +676,60 @@ grr_game_start_gui (grr_game_t *game)
        }
        gtk_widget_show (vpaned);
 
-       command_entry = game->command_entry;
-       gtk_box_pack_end (GTK_BOX (vbox), command_entry,
-                         FALSE, FALSE, 0);
-       gtk_widget_show (command_entry);
-       g_signal_connect (G_OBJECT (command_entry), "activate",
-                         G_CALLBACK (grr_game_entry_callback),
-                         (gpointer) game);
+       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);
+       {
+           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);
+
+           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 (hbox);
     }
     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);
     
     gtk_main ();