]> git.cworth.org Git - grrobot/blob - src/grrobot.c
Added keybindings. Can now play a board from a file without any server.
[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 *command_entry;
49
50     GtkTextBuffer *message_buffer;
51     GtkWidget *message_view;
52     int msg_id;
53     int last_move_msg_id;
54     rr_robot_t last_move_robot;    
55 } grr_game_t;
56
57 static int
58 grr_game_printf (grr_game_t *game, const char *fmt, ...);
59
60 static int
61 grr_game_vprintf (grr_game_t *game, const char *fmt, va_list ap);
62
63 static int
64 grr_game_print (grr_game_t *game, const char *msg);
65
66 static int
67 grr_game_start_gui (grr_game_t *game);
68
69 static void
70 grr_game_read_notices (grr_game_t *game);
71
72 static GSource *
73 grr_game_notices_source_new (grr_game_t *game);
74
75 int 
76 main (int argc, char **argv)
77 {
78     args_t args;
79     rr_status_t status;
80     grr_game_t game;
81     GSource *source;
82
83     char *diagram;
84
85     gtk_init (&argc, &argv);
86     
87     args_parse (&args, argc, argv);
88
89     if (args.file) {
90         game.board = rr_board_create_from_file (args.file);
91         if (game.board == NULL) {
92             fprintf (stderr, "Failed to parse board from %s\n", args.file);
93             return 1;
94         }
95         game.client = NULL;
96     } else {
97         game.client = rr_client_create (args.host, args.port, args.user);
98         if (game.client == NULL) {
99             fprintf (stderr, "Failed connecting to %s:%s as %s\n",
100                      args.host, args.port, args.user);
101             return 1;
102         }
103
104         if (args.watch) {
105             status = rr_client_watch (game.client, args.game);
106             if (status == RR_STATUS_NO_GAME) {
107                 fprintf (stderr, "No game %s to watch\n", args.game);
108             }
109         } else {
110             status = rr_client_join (game.client, args.game);
111             if (status == RR_STATUS_NO_GAME) {
112                 status = rr_client_new (game.client, args.game);
113             }
114         }
115
116         game.board = rr_board_create (16, 16);
117         game.msg_id = 0;
118         game.last_move_msg_id = 0;
119         game.last_move_robot = RR_ROBOT_NONE;
120
121         source = grr_game_notices_source_new (&game);
122         g_source_set_priority (source, GDK_PRIORITY_EVENTS);
123         g_source_attach (source, NULL);
124         g_source_unref (source);
125
126         rr_client_show (game.client, &diagram);
127         rr_board_parse (game.board, diagram);
128         free (diagram);
129     }
130
131     return grr_game_start_gui (&game);
132 }
133
134 typedef struct _GrrNoticeSource {
135     GSource source;
136     grr_game_t *game;
137 } GrrNoticeSource;
138
139 static gboolean
140 grr_game_notices_prepare (GSource   *source,
141                           gint      *timeout)
142 {
143     GrrNoticeSource *src = (GrrNoticeSource*)source;
144     grr_game_t *game = src->game;
145     gboolean retval;
146
147     GDK_THREADS_ENTER ();
148     *timeout = -1;
149     retval = rr_client_notice_pending (game->client);
150     GDK_THREADS_LEAVE ();
151
152     return retval;
153 }
154
155 static gboolean
156 grr_game_notices_check (GSource     *source)
157 {
158     GrrNoticeSource *src = (GrrNoticeSource*)source;
159     grr_game_t *game = src->game;
160     gboolean retval;
161
162     GDK_THREADS_ENTER ();
163     retval = rr_client_notice_pending (game->client);
164     GDK_THREADS_LEAVE ();
165
166     return retval;
167 }
168
169 static gboolean
170 grr_game_notices_dispatch (GSource      *source,
171                            GSourceFunc  callback,
172                            gpointer     user_data)
173 {
174     GrrNoticeSource *src = (GrrNoticeSource*)source;
175     grr_game_t *game = src->game;
176     
177     grr_game_read_notices (game);
178
179     return TRUE;
180 }
181
182 static GSourceFuncs game_notices_funcs = {
183   grr_game_notices_prepare,
184   grr_game_notices_check,
185   grr_game_notices_dispatch,
186   NULL
187 };
188
189 static GSource *
190 grr_game_notices_source_new (grr_game_t *game)
191 {
192     GSource *source = g_source_new (&game_notices_funcs, sizeof (GrrNoticeSource));
193     GrrNoticeSource *src = (GrrNoticeSource*)source;
194     src->game = game;
195     return source;
196 }
197
198 static void
199 grr_game_read_notices (grr_game_t *game)
200 {
201     rr_board_t *board = game->board;
202     rr_status_t status;
203     rr_notice_t *notice;
204
205     while (rr_client_notice_pending (game->client)) {
206         status = rr_client_next_notice (game->client, &notice);
207         if (status) {
208             if (status == RR_STATUS_EOF)
209                 fprintf (stderr, "The server has disconnected, exiting.\n");
210             else
211                 fprintf (stderr, "Error during rr_client_next_notice: %s\n",
212                          rr_status_str (status));
213             gtk_exit (1);
214         }
215         if (!notice) {
216             fprintf (stderr, "Missing notice\n");
217             continue;
218         }
219
220         switch (notice->type) {
221         case RR_NOTICE_USER:
222             grr_game_printf (game,
223                              "\nGLOBAL: %s has connected.", notice->u.string);
224             break;
225         case RR_NOTICE_QUIT:
226             grr_game_printf (game,
227                              "\nGLOBAL: %s has disconnected.", notice->u.string);
228             break;
229         case RR_NOTICE_GAME:
230             grr_game_printf (game,
231                              "\nGLOBAL: New game available: %s.", notice->u.string);
232             break;
233         case RR_NOTICE_DISPOSE:
234             grr_game_printf (game,
235                              "\nGLOBAL: Game %s has been disposed.", notice->u.string);
236             break;
237         case RR_NOTICE_MESSAGE:
238             grr_game_printf (game, "\n%s> %s",
239                              notice->u.message.username,
240                              notice->u.message.text);
241             break;
242         case RR_NOTICE_BOARD:
243             rr_board_parse (board, notice->u.string);
244             gtk_widget_queue_draw (GTK_WIDGET (game->window));
245             break;
246         case RR_NOTICE_GAMESTATE:
247             grr_game_printf (game, "\nGame state changed to: %s.",
248                              rr_gamestate_str (notice->u.gamestate));
249             break;
250         case RR_NOTICE_TURN:
251             grr_game_print (game, "\nNew round!");
252             rr_board_set_goal_target (board, notice->u.target);
253             gtk_widget_queue_draw (GTK_WIDGET (game->window));
254             break;
255         case RR_NOTICE_GAMEOVER:
256             grr_game_printf (game, "\nGame over. New game will begin now.");
257             break;
258         case RR_NOTICE_JOIN:
259             grr_game_printf (game, "\nUser %s has joined the game.",
260                              notice->u.string);
261             break;
262         case RR_NOTICE_WATCH:
263             grr_game_printf (game, "\nUser %s has started watching the game.",
264                              notice->u.string);
265             break;
266         case RR_NOTICE_PART:
267             grr_game_printf (game, "\nUser %s has left the game.",
268                              notice->u.string);
269             break;
270         case RR_NOTICE_BID:
271             grr_game_printf (game, "\nUser %s bids %d.",
272                              notice->u.bid.username,
273                              notice->u.bid.number);
274             break;
275         case RR_NOTICE_REVOKE:
276             grr_game_printf (game, "\nUser %s revokes previous bid.",
277                              notice->u.string);
278             break;
279         case RR_NOTICE_ABANDON:
280             grr_game_printf (game, "\nUser %s is willing to abandon this turn.",
281                              notice->u.string);
282             break;
283         case RR_NOTICE_NOBID:
284             grr_game_printf (game, "\nUser %s sees no point in bidding further this round",
285                              notice->u.string);
286             break;
287         case RR_NOTICE_MOVE:
288             if (game->msg_id == game->last_move_msg_id
289                 && game->last_move_robot == notice->u.move.robot) {
290                 game->last_move_msg_id = grr_game_printf (game, " %s",
291                                  rr_direction_str (notice->u.move.direction));
292             } else {
293                 game->last_move_msg_id = grr_game_printf (game, "\nMove #%d: %s %s",
294                                  notice->u.move.count,
295                                  rr_robot_str (notice->u.move.robot),
296                                  rr_direction_str (notice->u.move.direction));
297                 game->last_move_robot = notice->u.move.robot;
298             }
299             break;
300         case RR_NOTICE_UNDO:
301             grr_game_print (game, "\nUNDO");
302             break;
303         case RR_NOTICE_RESET:
304             grr_game_print (game, "\nRESET");
305             break;
306         case RR_NOTICE_SCORE:
307             grr_game_printf (game, "\nScore for %s: %d.",
308                              notice->u.bid.username,
309                              notice->u.bid.number);
310             break;
311         case RR_NOTICE_ACTIVATE:
312             grr_game_printf (game, "\nYour turn.",
313                              notice->u.number);
314             break;
315         case RR_NOTICE_ACTIVE:
316             grr_game_printf (game, "\nUser %s now active to demonstrate solution in %d moves.",
317                                notice->u.bid.username,
318                              notice->u.bid.number);
319             break;
320         case RR_NOTICE_TIMER:
321             grr_game_printf (game, "\nTime remaining: %d seconds.",
322                              notice->u.number);
323             break;
324         case RR_NOTICE_POSITION:
325             rr_board_add_robot (board, notice->u.position.robot,
326                                 notice->u.position.x, notice->u.position.y);
327             gtk_widget_queue_draw (GTK_WIDGET (game->window));
328             break;
329         default:
330             fprintf (stderr, "Unknown notice: %d\n", notice->type);
331             break;
332         }
333         free (notice);
334     }
335 }
336
337 static gboolean
338 grr_game_key_press_callback (GtkWidget *widget,
339                              GdkEventKey *event,
340                              grr_game_t *game)
341 {
342     if (GTK_WIDGET_HAS_FOCUS (GTK_WIDGET (game->command_entry))) {
343         if (event->keyval == GDK_Escape) {
344             gtk_entry_set_text (GTK_ENTRY (game->command_entry), "");
345             gtk_widget_grab_focus (GTK_WIDGET (game->message_view));
346
347             return TRUE;
348         } else {
349             return FALSE;
350         }
351     }
352
353     switch (event->keyval) {
354     case GDK_B:
355     case GDK_b:
356         grr_board_view_set_active_robot (game->board_view, RR_ROBOT_BLUE);
357         break;
358     case GDK_G:
359     case GDK_g:
360         grr_board_view_set_active_robot (game->board_view, RR_ROBOT_GREEN);
361         break;
362     case GDK_R:
363     case GDK_r:
364         grr_board_view_set_active_robot (game->board_view, RR_ROBOT_RED);
365         break;
366     case GDK_Y:
367     case GDK_y:
368         grr_board_view_set_active_robot (game->board_view, RR_ROBOT_YELLOW);
369         break;
370     case GDK_Up:
371         grr_board_view_move_active (game->board_view, RR_DIRECTION_NORTH);
372         break;
373     case GDK_Right:
374         grr_board_view_move_active (game->board_view, RR_DIRECTION_EAST);
375         break;
376     case GDK_Down:
377         grr_board_view_move_active (game->board_view, RR_DIRECTION_SOUTH);
378         break;
379     case GDK_Left:
380         grr_board_view_move_active (game->board_view, RR_DIRECTION_WEST);
381         break;
382     case GDK_BackSpace:
383         grr_board_view_undo (game->board_view);
384         break;
385     case GDK_space:
386     case GDK_slash:
387         gtk_widget_grab_focus (GTK_WIDGET (game->command_entry));
388         if (event->keyval == GDK_slash) {
389             int pos = -1;
390             gtk_editable_insert_text (GTK_EDITABLE (game->command_entry),
391                                       "/", 1, &pos);
392             gtk_editable_set_position (GTK_EDITABLE (game->command_entry), -1);
393         }
394         break;
395     case GDK_Q:
396     case GDK_q:
397         if (event->state & GDK_CONTROL_MASK) {
398             gtk_exit (0);
399         }
400         break;
401     }
402
403     return TRUE;
404 }
405
406 static void
407 grr_game_entry_callback (GtkWidget *widget,
408                          grr_game_t *game)
409 {
410     rr_status_t status;
411     const gchar *entry_text;
412     char **response;
413     int i;
414
415     entry_text = gtk_entry_get_text (GTK_ENTRY (game->command_entry));
416
417     if (entry_text && entry_text[0] && game->client) {
418         if (entry_text[0] == '/') {
419             status = rr_client_request (game->client, entry_text + 1, &response);
420             if (status) {
421                 grr_game_printf (game, "\nERROR: %s.", rr_status_str (status));
422             } else {
423                 if (response[0]) {
424                     grr_game_print (game, "\n");
425                     for (i=0; response[i]; i++)
426                         grr_game_printf (game, "%s ", response[i]);
427                 }
428             }
429         } else {
430             rr_client_message (game->client, entry_text);
431         }
432     }
433
434     gtk_entry_set_text (GTK_ENTRY (game->command_entry), "");
435     gtk_widget_grab_focus (GTK_WIDGET (game->message_view));
436
437 /* XXX: Huh? Why is this triggering valgrind?
438     free (response);
439 */
440
441 }
442
443 static int
444 grr_game_printf (grr_game_t *game, const char *fmt, ...)
445 {
446     int msg_id;
447
448     va_list ap;
449
450     va_start (ap, fmt);
451     msg_id = grr_game_vprintf (game, fmt, ap);
452     va_end (ap);
453
454     return msg_id;
455 }
456
457 static int
458 grr_game_vprintf (grr_game_t *game, const char *fmt, va_list ap)
459 {
460     char *msg;
461     int msg_id;
462
463     grr_sprintf_alloc_va (&msg, fmt, ap);
464     if (msg == NULL)
465         return 0;
466
467     msg_id = grr_game_print (game, msg);
468
469     free (msg);
470
471     return msg_id;
472 }
473
474 static int
475 grr_game_print (grr_game_t *game, const char *msg)
476 {
477     GtkTextIter iter;
478     GtkTextMark *mark;
479
480     /* There might be a lighter way to do this, but this seems to
481        work. */
482
483     gtk_text_buffer_get_end_iter (game->message_buffer, &iter);
484
485     mark = gtk_text_buffer_get_mark (game->message_buffer, "end");
486
487     gtk_text_buffer_move_mark (game->message_buffer, mark, &iter);
488
489     gtk_text_buffer_insert (game->message_buffer, &iter, msg, -1);
490
491     gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (game->message_view),
492                                   mark, 0.0, TRUE, 0.0, 1.0);
493
494     return ++game->msg_id;
495 }
496
497 static int
498 grr_game_start_gui (grr_game_t *game)
499 {
500     GtkWidget *board_frame;
501     GtkWidget *vpaned;
502     GtkWidget *sw;
503     GtkWidget *vbox;
504     GtkWidget *window;
505     GtkWidget *message_view;
506     GtkWidget *command_entry;
507
508     game->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
509     window = game->window;
510
511     gtk_window_set_title (GTK_WINDOW (window), "Ricochet Robot");
512     gtk_window_set_default_size (GTK_WINDOW (window), 512, 512);
513     
514     g_signal_connect (G_OBJECT (window), "destroy",
515                       G_CALLBACK (exit), NULL);
516     gtk_container_set_border_width (GTK_CONTAINER (window), 0);
517
518     vbox = gtk_vbox_new (FALSE, 1);
519     gtk_container_add (GTK_CONTAINER (window), vbox);
520     {
521         GtkTextIter iter;
522         game->board_view = grr_board_view_new (game->board);
523         grr_board_view_set_client (GRR_BOARD_VIEW (game->board_view), game->client);
524         game->message_buffer = gtk_text_buffer_new (NULL);
525         gtk_text_buffer_get_iter_at_offset (game->message_buffer,
526                                             &iter, -1);
527         gtk_text_buffer_create_mark (game->message_buffer,
528                                      "end", &iter, FALSE);
529         game->message_view = gtk_text_view_new_with_buffer (game->message_buffer);
530         game->command_entry = gtk_entry_new ();
531
532         vpaned = gtk_vpaned_new ();
533         gtk_container_set_border_width (GTK_CONTAINER (vpaned), 0);
534         gtk_box_pack_start (GTK_BOX (vbox), vpaned,
535                             TRUE, TRUE, 0);
536         {
537             board_frame = gtk_aspect_frame_new (NULL, 0.5, 0.5, 1.0, FALSE);
538             gtk_paned_pack1 (GTK_PANED (vpaned), board_frame, TRUE, TRUE);
539             {
540                 gtk_container_add (GTK_CONTAINER (board_frame), GTK_WIDGET (game->board_view));
541                 gtk_widget_show (GTK_WIDGET (game->board_view));
542             }
543             gtk_widget_show (board_frame);
544
545             sw = gtk_scrolled_window_new (NULL, NULL);
546             gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
547                                             GTK_POLICY_NEVER,
548                                             GTK_POLICY_AUTOMATIC);
549             gtk_paned_pack2 (GTK_PANED (vpaned), sw, FALSE, TRUE);
550             {
551                 message_view = game->message_view;
552                 gtk_text_view_set_editable (GTK_TEXT_VIEW (message_view), FALSE);
553                 gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (message_view), FALSE);
554                 gtk_container_add (GTK_CONTAINER (sw), message_view);
555                 gtk_widget_show (message_view);
556             }
557             gtk_widget_show (sw);
558         }
559         gtk_widget_show (vpaned);
560
561         command_entry = game->command_entry;
562         gtk_box_pack_end (GTK_BOX (vbox), command_entry,
563                           FALSE, FALSE, 0);
564         gtk_widget_show (command_entry);
565         g_signal_connect (G_OBJECT (command_entry), "activate",
566                           G_CALLBACK (grr_game_entry_callback),
567                           (gpointer) game);
568     }
569     gtk_widget_show (vbox);
570
571     g_signal_connect (G_OBJECT (window), "key_press_event",
572                       G_CALLBACK (grr_game_key_press_callback),
573                       (gpointer) game);
574
575     gtk_widget_show (window);
576     
577     gtk_main ();
578     
579     return 0;
580 }