1 /* grrobot - Ricochet Robot using GTK+, libxsvg, and Cairo
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 *message_entry;
51 GtkTextBuffer *message_buffer;
52 GtkWidget *message_view;
55 rr_robot_t last_move_robot;
61 grr_game_pass (grr_game_t *game);
64 grr_game_zap (grr_game_t *game);
67 grr_game_next (grr_game_t *game);
70 grr_game_printf (grr_game_t *game, const char *fmt, ...);
73 grr_game_vprintf (grr_game_t *game, const char *fmt, va_list ap);
76 grr_game_print (grr_game_t *game, const char *msg);
79 grr_game_start_gui (grr_game_t *game, const char *user);
82 grr_game_read_notices (grr_game_t *game);
85 grr_game_notices_source_new (grr_game_t *game);
88 main (int argc, char **argv)
97 gtk_init (&argc, &argv);
99 args_parse (&args, argc, argv);
102 game.board = rr_board_create_from_file (args.file);
103 if (game.board == NULL) {
104 fprintf (stderr, "Failed to parse board from %s\n", args.file);
111 game.client = rr_client_create (args.host, args.port, args.user);
112 if (game.client == NULL) {
113 fprintf (stderr, "Failed connecting to %s:%s as %s\n",
114 args.host, args.port, args.user);
119 status = rr_client_watch (game.client, args.game);
120 if (status == RR_STATUS_NO_GAME) {
121 fprintf (stderr, "No game %s to watch\n", args.game);
124 status = rr_client_join (game.client, args.game);
125 if (status == RR_STATUS_NO_GAME) {
126 status = rr_client_new (game.client, args.game);
130 game.board = rr_board_create (16, 16);
132 game.last_move_msg_id = 0;
133 game.last_move_robot = RR_ROBOT_NONE;
135 source = grr_game_notices_source_new (&game);
136 poll_fd = g_new (GPollFD, 1);
137 poll_fd->fd = rr_client_fd (game.client);
138 poll_fd->events = G_IO_IN;
139 g_source_add_poll (source, poll_fd);
141 g_source_set_priority (source, GDK_PRIORITY_EVENTS);
142 g_source_attach (source, NULL);
143 g_source_unref (source);
145 rr_client_show (game.client, &diagram);
146 rr_board_parse (game.board, diagram);
150 return grr_game_start_gui (&game, args.user);
153 typedef struct _GrrNoticeSource {
159 grr_game_notices_prepare (GSource *source,
162 GrrNoticeSource *src = (GrrNoticeSource*)source;
163 grr_game_t *game = src->game;
166 GDK_THREADS_ENTER ();
168 retval = rr_client_notice_pending (game->client);
169 GDK_THREADS_LEAVE ();
175 grr_game_notices_check (GSource *source)
177 GrrNoticeSource *src = (GrrNoticeSource*)source;
178 grr_game_t *game = src->game;
181 GDK_THREADS_ENTER ();
182 retval = rr_client_notice_pending (game->client);
183 GDK_THREADS_LEAVE ();
189 grr_game_notices_dispatch (GSource *source,
190 GSourceFunc callback,
193 GrrNoticeSource *src = (GrrNoticeSource*)source;
194 grr_game_t *game = src->game;
196 grr_game_read_notices (game);
201 static GSourceFuncs game_notices_funcs = {
202 grr_game_notices_prepare,
203 grr_game_notices_check,
204 grr_game_notices_dispatch,
209 grr_game_notices_source_new (grr_game_t *game)
211 GSource *source = g_source_new (&game_notices_funcs, sizeof (GrrNoticeSource));
212 GrrNoticeSource *src = (GrrNoticeSource*)source;
218 grr_game_read_notices (grr_game_t *game)
220 rr_board_t *board = game->board;
224 while (rr_client_notice_pending (game->client)) {
225 status = rr_client_next_notice (game->client, ¬ice);
227 if (status == RR_STATUS_EOF)
228 fprintf (stderr, "The server has disconnected, exiting.\n");
230 fprintf (stderr, "Error during rr_client_next_notice: %s\n",
231 rr_status_str (status));
235 fprintf (stderr, "Missing notice\n");
239 switch (notice->type) {
241 grr_game_printf (game,
242 "\nGLOBAL: %s has connected.", notice->u.string);
245 grr_game_printf (game,
246 "\nGLOBAL: %s has disconnected.", notice->u.string);
249 grr_game_printf (game,
250 "\nGLOBAL: New game available: %s.", notice->u.string);
252 case RR_NOTICE_DISPOSE:
253 grr_game_printf (game,
254 "\nGLOBAL: Game %s has been disposed.", notice->u.string);
256 case RR_NOTICE_MESSAGE:
257 grr_game_printf (game, "\n%s> %s",
258 notice->u.message.username,
259 notice->u.message.text);
261 case RR_NOTICE_BOARD:
262 rr_board_parse (board, notice->u.string);
263 gtk_widget_queue_draw (GTK_WIDGET (game->window));
265 case RR_NOTICE_GAMESTATE:
266 game->state = notice->u.gamestate;
267 grr_game_printf (game, "\nGame state changed to: %s.",
268 rr_gamestate_str (notice->u.gamestate));
271 /* XXX: Setting game->state here is goofy. Cleaner would
272 be to make the server send a NOTICE GAMESTATE NEW
274 game->state = RR_GAMESTATE_NEW;
276 grr_board_view_reset_timer (game->board_view);
277 grr_game_print (game, "\nNew round!");
278 rr_board_set_goal_target (board, notice->u.target);
279 gtk_widget_queue_draw (GTK_WIDGET (game->window));
281 case RR_NOTICE_GAMEOVER:
282 grr_game_printf (game, "\nGame over. New game will begin now.");
285 grr_game_printf (game, "\nUser %s has joined the game.",
288 case RR_NOTICE_WATCH:
289 grr_game_printf (game, "\nUser %s has started watching the game.",
293 grr_game_printf (game, "\nUser %s has left the game.",
297 grr_game_printf (game, "\nUser %s bids %d.",
298 notice->u.bid.username,
299 notice->u.bid.number);
301 case RR_NOTICE_REVOKE:
302 grr_game_printf (game, "\nUser %s revokes previous bid.",
305 case RR_NOTICE_ABANDON:
306 grr_game_printf (game, "\nUser %s is willing to abandon this turn.",
309 case RR_NOTICE_NOBID:
310 grr_game_printf (game, "\nUser %s sees no point in bidding further this round",
314 if (game->msg_id == game->last_move_msg_id
315 && game->last_move_robot == notice->u.move.robot) {
316 game->last_move_msg_id = grr_game_printf (game, ",%c",
317 rr_direction_char (notice->u.move.direction));
319 game->last_move_msg_id = grr_game_printf (game, "\nMove #%d: %s %c",
320 notice->u.move.count,
321 rr_robot_str (notice->u.move.robot),
322 rr_direction_char (notice->u.move.direction));
323 game->last_move_robot = notice->u.move.robot;
327 grr_game_print (game, "\nUNDO");
329 case RR_NOTICE_RESET:
330 grr_game_print (game, "\nRESET");
332 case RR_NOTICE_SCORE:
333 grr_game_printf (game, "\nScore for %s: %d.",
334 notice->u.bid.username,
335 notice->u.bid.number);
337 case RR_NOTICE_ACTIVATE:
338 grr_game_printf (game, "\nYour turn.",
341 case RR_NOTICE_ACTIVE:
342 grr_board_view_stop_timer (game->board_view);
343 grr_game_printf (game, "\nUser %s now active to demonstrate solution in %d moves.",
344 notice->u.bid.username,
345 notice->u.bid.number);
347 case RR_NOTICE_TIMER:
348 grr_board_view_start_timer (game->board_view,
351 case RR_NOTICE_POSITION:
354 rr_board_find_robot (board, notice->u.position.robot, &x, &y);
355 rr_board_add_robot (board, notice->u.position.robot,
356 notice->u.position.x, notice->u.position.y);
357 grr_board_view_mark_damage (game->board_view, x, y);
358 grr_board_view_mark_damage (game->board_view,
359 notice->u.position.x, notice->u.position.y);
363 fprintf (stderr, "Unknown notice: %d\n", notice->type);
371 grr_game_key_press_callback (GtkWidget *widget,
375 if (GTK_WIDGET_HAS_FOCUS (GTK_WIDGET (game->message_entry))
376 || GTK_WIDGET_HAS_FOCUS (GTK_WIDGET (game->bid_entry))
378 if (event->keyval == GDK_Escape) {
379 gtk_entry_set_text (GTK_ENTRY (game->message_entry), "");
380 gtk_entry_set_text (GTK_ENTRY (game->bid_entry), "");
381 gtk_widget_grab_focus (GTK_WIDGET (game->message_view));
389 switch (event->keyval) {
394 grr_game_next (game);
398 grr_game_pass (game);
406 grr_board_view_set_active_robot (game->board_view, RR_ROBOT_BLUE);
410 grr_board_view_set_active_robot (game->board_view, RR_ROBOT_GREEN);
414 grr_board_view_set_active_robot (game->board_view, RR_ROBOT_RED);
418 grr_board_view_set_active_robot (game->board_view, RR_ROBOT_YELLOW);
421 grr_board_view_move_active (game->board_view, RR_DIRECTION_NORTH);
424 grr_board_view_move_active (game->board_view, RR_DIRECTION_EAST);
427 grr_board_view_move_active (game->board_view, RR_DIRECTION_SOUTH);
430 grr_board_view_move_active (game->board_view, RR_DIRECTION_WEST);
433 grr_board_view_undo (game->board_view);
447 const gchar *key = gdk_keyval_name (event->keyval);
448 gtk_widget_grab_focus (GTK_WIDGET (game->bid_entry));
449 gtk_editable_insert_text (GTK_EDITABLE (game->bid_entry),
450 key, strlen (key), &pos);
451 gtk_editable_set_position (GTK_EDITABLE (game->bid_entry), -1);
456 gtk_widget_grab_focus (GTK_WIDGET (game->message_entry));
457 if (event->keyval == GDK_slash) {
459 gtk_editable_insert_text (GTK_EDITABLE (game->message_entry),
461 gtk_editable_set_position (GTK_EDITABLE (game->message_entry), -1);
466 if (event->state & GDK_CONTROL_MASK) {
475 /* XXX: Messy, messy. I can't seem to make up my mind whether client
476 functions like such as these should be called here or in
479 grr_game_pass (grr_game_t *game)
481 if (game->client == NULL)
484 if (game->state == RR_GAMESTATE_SHOW)
485 rr_client_pass (game->client);
487 rr_client_revoke (game->client);
491 grr_game_zap (grr_game_t *game)
493 if (game->client == NULL)
496 rr_client_nobid (game->client);
500 grr_game_next (grr_game_t *game)
502 if (game->client == NULL)
505 if (game->state == RR_GAMESTATE_DONE)
506 rr_client_turn (game->client);
508 rr_client_abandon (game->client);
512 grr_game_pass_callback (GtkWidget *widget,
515 grr_game_pass (game);
519 grr_game_zap_callback (GtkWidget *widget,
526 grr_game_next_callback (GtkWidget *widget,
529 grr_game_next (game);
533 grr_game_bid_entry_callback (GtkWidget *widget,
536 const gchar *bid_text;
540 if (game->client == NULL)
543 bid_text = gtk_entry_get_text (GTK_ENTRY (game->bid_entry));
545 if (bid_text && bid_text[0]) {
546 bid = strtol (bid_text, &end, 10);
547 if (*end != '\0' && ! isspace (*end)) {
548 grr_game_printf (game, "\nInvalid bid value: %s", bid_text);
551 rr_client_revoke (game->client);
553 rr_client_bid (game->client, bid);
557 gtk_entry_set_text (GTK_ENTRY (game->bid_entry), "");
558 gtk_widget_grab_focus (GTK_WIDGET (game->message_view));
562 grr_game_message_entry_callback (GtkWidget *widget,
566 const gchar *entry_text;
570 if (game->client == NULL)
573 entry_text = gtk_entry_get_text (GTK_ENTRY (game->message_entry));
575 if (entry_text && entry_text[0]) {
576 if (entry_text[0] == '/') {
577 status = rr_client_request (game->client, entry_text + 1, &response);
579 grr_game_printf (game, "\nERROR: %s.", rr_status_str (status));
582 grr_game_print (game, "\n");
583 for (i=0; response[i]; i++)
584 grr_game_printf (game, "%s ", response[i]);
588 rr_client_message (game->client, entry_text);
592 gtk_entry_set_text (GTK_ENTRY (game->message_entry), "");
593 gtk_widget_grab_focus (GTK_WIDGET (game->message_view));
595 /* XXX: Huh? Why is this triggering valgrind?
602 grr_game_printf (grr_game_t *game, const char *fmt, ...)
609 msg_id = grr_game_vprintf (game, fmt, ap);
616 grr_game_vprintf (grr_game_t *game, const char *fmt, va_list ap)
621 grr_sprintf_alloc_va (&msg, fmt, ap);
625 msg_id = grr_game_print (game, msg);
633 grr_game_print (grr_game_t *game, const char *msg)
638 /* There might be a lighter way to do this, but this seems to
641 gtk_text_buffer_get_end_iter (game->message_buffer, &iter);
643 mark = gtk_text_buffer_get_mark (game->message_buffer, "end");
645 gtk_text_buffer_move_mark (game->message_buffer, mark, &iter);
647 gtk_text_buffer_insert (game->message_buffer, &iter, msg, -1);
649 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (game->message_view),
650 mark, 0.0, TRUE, 0.0, 1.0);
652 return ++game->msg_id;
656 grr_game_start_gui (grr_game_t *game, const char *user)
658 GtkWidget *board_frame;
664 GtkWidget *message_view;
665 GtkWidget *message_entry;
666 GtkWidget *bid_entry;
667 GtkWidget *bid_button;
668 GtkWidget *pass_button;
669 GtkWidget *zap_button;
670 GtkWidget *next_button;
671 GtkWidget *user_label;
673 game->state = RR_GAMESTATE_NEW;
675 game->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
676 window = game->window;
678 gtk_window_set_title (GTK_WINDOW (window), "Ricochet Robot");
679 gtk_window_set_default_size (GTK_WINDOW (window), 512, 512);
681 g_signal_connect (G_OBJECT (window), "destroy",
682 G_CALLBACK (exit), NULL);
683 gtk_container_set_border_width (GTK_CONTAINER (window), 0);
685 vbox = gtk_vbox_new (FALSE, 1);
686 gtk_container_add (GTK_CONTAINER (window), vbox);
689 game->board_view = grr_board_view_new (game->board);
690 grr_board_view_set_client (GRR_BOARD_VIEW (game->board_view), game->client);
691 game->message_buffer = gtk_text_buffer_new (NULL);
692 gtk_text_buffer_get_iter_at_offset (game->message_buffer,
694 gtk_text_buffer_create_mark (game->message_buffer,
695 "end", &iter, FALSE);
696 game->message_view = gtk_text_view_new_with_buffer (game->message_buffer);
698 vpaned = gtk_vpaned_new ();
699 gtk_container_set_border_width (GTK_CONTAINER (vpaned), 0);
700 gtk_box_pack_start (GTK_BOX (vbox), vpaned,
703 board_frame = gtk_aspect_frame_new (NULL, 0.5, 0.5, 1.0, FALSE);
704 gtk_paned_pack1 (GTK_PANED (vpaned), board_frame, TRUE, TRUE);
706 gtk_container_add (GTK_CONTAINER (board_frame), GTK_WIDGET (game->board_view));
707 gtk_widget_show (GTK_WIDGET (game->board_view));
709 gtk_widget_show (board_frame);
711 sw = gtk_scrolled_window_new (NULL, NULL);
712 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
714 GTK_POLICY_AUTOMATIC);
715 gtk_paned_pack2 (GTK_PANED (vpaned), sw, FALSE, TRUE);
717 message_view = game->message_view;
718 gtk_text_view_set_editable (GTK_TEXT_VIEW (message_view), FALSE);
719 gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (message_view), FALSE);
720 gtk_container_add (GTK_CONTAINER (sw), message_view);
721 gtk_widget_show (message_view);
723 gtk_widget_show (sw);
725 gtk_widget_show (vpaned);
727 hbox = gtk_hbox_new (FALSE, 1);
728 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
730 user_label = gtk_label_new (user);
731 gtk_box_pack_start (GTK_BOX (hbox), user_label,
733 gtk_widget_show (user_label);
735 game->message_entry = gtk_entry_new ();
736 message_entry = game->message_entry;
737 gtk_box_pack_start (GTK_BOX (hbox), message_entry,
739 gtk_widget_show (message_entry);
740 g_signal_connect (G_OBJECT (message_entry), "activate",
741 G_CALLBACK (grr_game_message_entry_callback),
744 gtk_widget_show (hbox);
746 hbox = gtk_hbox_new (FALSE, 1);
747 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
749 bid_button = gtk_button_new_with_label ("Bid:");
750 gtk_box_pack_start (GTK_BOX (hbox), bid_button,
752 gtk_widget_show (bid_button);
753 g_signal_connect (G_OBJECT (bid_button), "clicked",
754 G_CALLBACK (grr_game_bid_entry_callback),
757 game->bid_entry = gtk_entry_new ();
758 bid_entry = game->bid_entry;
759 gtk_entry_set_max_length (GTK_ENTRY (bid_entry), 3);
760 gtk_entry_set_width_chars (GTK_ENTRY (bid_entry), 4);
761 gtk_box_pack_start (GTK_BOX (hbox), bid_entry,
763 gtk_widget_show (bid_entry);
764 g_signal_connect (G_OBJECT (bid_entry), "activate",
765 G_CALLBACK (grr_game_bid_entry_callback),
768 pass_button = gtk_button_new_with_label ("Pass");
769 gtk_box_pack_start (GTK_BOX (hbox), pass_button,
771 gtk_widget_show (pass_button);
772 g_signal_connect (G_OBJECT (pass_button), "clicked",
773 G_CALLBACK (grr_game_pass_callback),
776 zap_button = gtk_button_new_with_label ("Zap timer");
777 gtk_box_pack_start (GTK_BOX (hbox), zap_button,
779 gtk_widget_show (zap_button);
780 g_signal_connect (G_OBJECT (zap_button), "clicked",
781 G_CALLBACK (grr_game_zap_callback),
784 next_button = gtk_button_new_with_label ("Next turn");
785 gtk_box_pack_start (GTK_BOX (hbox), next_button,
787 gtk_widget_show (next_button);
788 g_signal_connect (G_OBJECT (next_button), "clicked",
789 G_CALLBACK (grr_game_next_callback),
792 gtk_widget_show (hbox);
794 gtk_widget_show (vbox);
796 g_signal_connect (G_OBJECT (window), "key_press_event",
797 G_CALLBACK (grr_game_key_press_callback),
800 gtk_widget_show (window);