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