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