1 /* grrobot - Ricochet Robot using GTK+ and Xr
3 * Copyright © 2003 Carl Worth
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.
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.
24 * Author: Carl Worth <carl@theworths.org>
36 #include <gdk/gdkkeysyms.h>
38 #include "grr_board_view.h"
42 typedef struct grr_game {
47 grr_board_view_t *board_view;
48 GtkWidget *command_entry;
50 GtkTextBuffer *message_buffer;
51 GtkWidget *message_view;
54 rr_robot_t last_move_robot;
58 grr_game_printf (grr_game_t *game, const char *fmt, ...);
61 grr_game_vprintf (grr_game_t *game, const char *fmt, va_list ap);
64 grr_game_print (grr_game_t *game, const char *msg);
67 grr_game_start_gui (grr_game_t *game);
70 grr_game_read_notices (grr_game_t *game);
73 grr_game_notices_source_new (grr_game_t *game);
76 main (int argc, char **argv)
85 gtk_init (&argc, &argv);
87 args_parse (&args, argc, argv);
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);
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);
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);
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);
116 game.board = rr_board_create (16, 16);
118 game.last_move_msg_id = 0;
119 game.last_move_robot = RR_ROBOT_NONE;
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);
126 rr_client_show (game.client, &diagram);
127 rr_board_parse (game.board, diagram);
131 return grr_game_start_gui (&game);
134 typedef struct _GrrNoticeSource {
140 grr_game_notices_prepare (GSource *source,
143 GrrNoticeSource *src = (GrrNoticeSource*)source;
144 grr_game_t *game = src->game;
147 GDK_THREADS_ENTER ();
149 retval = rr_client_notice_pending (game->client);
150 GDK_THREADS_LEAVE ();
156 grr_game_notices_check (GSource *source)
158 GrrNoticeSource *src = (GrrNoticeSource*)source;
159 grr_game_t *game = src->game;
162 GDK_THREADS_ENTER ();
163 retval = rr_client_notice_pending (game->client);
164 GDK_THREADS_LEAVE ();
170 grr_game_notices_dispatch (GSource *source,
171 GSourceFunc callback,
174 GrrNoticeSource *src = (GrrNoticeSource*)source;
175 grr_game_t *game = src->game;
177 grr_game_read_notices (game);
182 static GSourceFuncs game_notices_funcs = {
183 grr_game_notices_prepare,
184 grr_game_notices_check,
185 grr_game_notices_dispatch,
190 grr_game_notices_source_new (grr_game_t *game)
192 GSource *source = g_source_new (&game_notices_funcs, sizeof (GrrNoticeSource));
193 GrrNoticeSource *src = (GrrNoticeSource*)source;
199 grr_game_read_notices (grr_game_t *game)
201 rr_board_t *board = game->board;
205 while (rr_client_notice_pending (game->client)) {
206 status = rr_client_next_notice (game->client, ¬ice);
208 if (status == RR_STATUS_EOF)
209 fprintf (stderr, "The server has disconnected, exiting.\n");
211 fprintf (stderr, "Error during rr_client_next_notice: %s\n",
212 rr_status_str (status));
216 fprintf (stderr, "Missing notice\n");
220 switch (notice->type) {
222 grr_game_printf (game,
223 "\nGLOBAL: %s has connected.", notice->u.string);
226 grr_game_printf (game,
227 "\nGLOBAL: %s has disconnected.", notice->u.string);
230 grr_game_printf (game,
231 "\nGLOBAL: New game available: %s.", notice->u.string);
233 case RR_NOTICE_DISPOSE:
234 grr_game_printf (game,
235 "\nGLOBAL: Game %s has been disposed.", notice->u.string);
237 case RR_NOTICE_MESSAGE:
238 grr_game_printf (game, "\n%s> %s",
239 notice->u.message.username,
240 notice->u.message.text);
242 case RR_NOTICE_BOARD:
243 rr_board_parse (board, notice->u.string);
244 gtk_widget_queue_draw (GTK_WIDGET (game->window));
246 case RR_NOTICE_GAMESTATE:
247 grr_game_printf (game, "\nGame state changed to: %s.",
248 rr_gamestate_str (notice->u.gamestate));
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));
255 case RR_NOTICE_GAMEOVER:
256 grr_game_printf (game, "\nGame over. New game will begin now.");
259 grr_game_printf (game, "\nUser %s has joined the game.",
262 case RR_NOTICE_WATCH:
263 grr_game_printf (game, "\nUser %s has started watching the game.",
267 grr_game_printf (game, "\nUser %s has left the game.",
271 grr_game_printf (game, "\nUser %s bids %d.",
272 notice->u.bid.username,
273 notice->u.bid.number);
275 case RR_NOTICE_REVOKE:
276 grr_game_printf (game, "\nUser %s revokes previous bid.",
279 case RR_NOTICE_ABANDON:
280 grr_game_printf (game, "\nUser %s is willing to abandon this turn.",
283 case RR_NOTICE_NOBID:
284 grr_game_printf (game, "\nUser %s sees no point in bidding further this round",
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));
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;
301 grr_game_print (game, "\nUNDO");
303 case RR_NOTICE_RESET:
304 grr_game_print (game, "\nRESET");
306 case RR_NOTICE_SCORE:
307 grr_game_printf (game, "\nScore for %s: %d.",
308 notice->u.bid.username,
309 notice->u.bid.number);
311 case RR_NOTICE_ACTIVATE:
312 grr_game_printf (game, "\nYour turn.",
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);
320 case RR_NOTICE_TIMER:
321 grr_game_printf (game, "\nTime remaining: %d seconds.",
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));
330 fprintf (stderr, "Unknown notice: %d\n", notice->type);
338 grr_game_key_press_callback (GtkWidget *widget,
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));
353 switch (event->keyval) {
356 grr_board_view_set_active_robot (game->board_view, RR_ROBOT_BLUE);
360 grr_board_view_set_active_robot (game->board_view, RR_ROBOT_GREEN);
364 grr_board_view_set_active_robot (game->board_view, RR_ROBOT_RED);
368 grr_board_view_set_active_robot (game->board_view, RR_ROBOT_YELLOW);
371 grr_board_view_move_active (game->board_view, RR_DIRECTION_NORTH);
374 grr_board_view_move_active (game->board_view, RR_DIRECTION_EAST);
377 grr_board_view_move_active (game->board_view, RR_DIRECTION_SOUTH);
380 grr_board_view_move_active (game->board_view, RR_DIRECTION_WEST);
383 grr_board_view_undo (game->board_view);
387 gtk_widget_grab_focus (GTK_WIDGET (game->command_entry));
388 if (event->keyval == GDK_slash) {
390 gtk_editable_insert_text (GTK_EDITABLE (game->command_entry),
392 gtk_editable_set_position (GTK_EDITABLE (game->command_entry), -1);
397 if (event->state & GDK_CONTROL_MASK) {
407 grr_game_entry_callback (GtkWidget *widget,
411 const gchar *entry_text;
415 entry_text = gtk_entry_get_text (GTK_ENTRY (game->command_entry));
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);
421 grr_game_printf (game, "\nERROR: %s.", rr_status_str (status));
424 grr_game_print (game, "\n");
425 for (i=0; response[i]; i++)
426 grr_game_printf (game, "%s ", response[i]);
430 rr_client_message (game->client, entry_text);
434 gtk_entry_set_text (GTK_ENTRY (game->command_entry), "");
435 gtk_widget_grab_focus (GTK_WIDGET (game->message_view));
437 /* XXX: Huh? Why is this triggering valgrind?
444 grr_game_printf (grr_game_t *game, const char *fmt, ...)
451 msg_id = grr_game_vprintf (game, fmt, ap);
458 grr_game_vprintf (grr_game_t *game, const char *fmt, va_list ap)
463 grr_sprintf_alloc_va (&msg, fmt, ap);
467 msg_id = grr_game_print (game, msg);
475 grr_game_print (grr_game_t *game, const char *msg)
480 /* There might be a lighter way to do this, but this seems to
483 gtk_text_buffer_get_end_iter (game->message_buffer, &iter);
485 mark = gtk_text_buffer_get_mark (game->message_buffer, "end");
487 gtk_text_buffer_move_mark (game->message_buffer, mark, &iter);
489 gtk_text_buffer_insert (game->message_buffer, &iter, msg, -1);
491 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (game->message_view),
492 mark, 0.0, TRUE, 0.0, 1.0);
494 return ++game->msg_id;
498 grr_game_start_gui (grr_game_t *game)
500 GtkWidget *board_frame;
505 GtkWidget *message_view;
506 GtkWidget *command_entry;
508 game->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
509 window = game->window;
511 gtk_window_set_title (GTK_WINDOW (window), "Ricochet Robot");
512 gtk_window_set_default_size (GTK_WINDOW (window), 512, 512);
514 g_signal_connect (G_OBJECT (window), "destroy",
515 G_CALLBACK (exit), NULL);
516 gtk_container_set_border_width (GTK_CONTAINER (window), 0);
518 vbox = gtk_vbox_new (FALSE, 1);
519 gtk_container_add (GTK_CONTAINER (window), vbox);
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,
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 ();
532 vpaned = gtk_vpaned_new ();
533 gtk_container_set_border_width (GTK_CONTAINER (vpaned), 0);
534 gtk_box_pack_start (GTK_BOX (vbox), vpaned,
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);
540 gtk_container_add (GTK_CONTAINER (board_frame), GTK_WIDGET (game->board_view));
541 gtk_widget_show (GTK_WIDGET (game->board_view));
543 gtk_widget_show (board_frame);
545 sw = gtk_scrolled_window_new (NULL, NULL);
546 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
548 GTK_POLICY_AUTOMATIC);
549 gtk_paned_pack2 (GTK_PANED (vpaned), sw, FALSE, TRUE);
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);
557 gtk_widget_show (sw);
559 gtk_widget_show (vpaned);
561 command_entry = game->command_entry;
562 gtk_box_pack_end (GTK_BOX (vbox), command_entry,
564 gtk_widget_show (command_entry);
565 g_signal_connect (G_OBJECT (command_entry), "activate",
566 G_CALLBACK (grr_game_entry_callback),
569 gtk_widget_show (vbox);
571 g_signal_connect (G_OBJECT (window), "key_press_event",
572 G_CALLBACK (grr_game_key_press_callback),
575 gtk_widget_show (window);