/* -*- mode:C; indent-tabs-mode:nil; tab-width:8; c-basic-offset:2; -*- */ /* Originally was a lame CORBA-based implementation that was no use on the Internet. This is a simple line-oriented text protocol that you almost "can't get wrong". To be improved: Make it more obvious what is going on (instead of just using status bar) and indicate which color you are playing as. All UI stuff. -- Elliot */ #include #include #include #include #include #include #include #include #include #include #include #include "othello.h" #include "gnothello.h" #include "network.h" /* (('g'<<8)+'n' */ #define GAME_PORT "26478" /* Shared with gnothello.c */ char *game_server = "gnothello.gnome.org"; typedef struct { char inbuf[1024]; int inpos; int fd; GIOChannel *fd_gioc; guint read_tag, write_tag; int mycolor; int sent_newgame; enum { CONNECTING, CONNECTED, DISCONNECTED } status; GString *outbuf; } NetworkGame; static NetworkGame *netgame = NULL; typedef enum { CALLER_READ_CB, CALLER_WRITE_CB, CALLER_OTHER } CallerType; static gboolean network_handle_read (GIOChannel *source, GIOCondition cond, gpointer data); static gboolean network_handle_write (GIOChannel *source, GIOCondition cond, gpointer data); static void network_set_status (NetworkGame *ng, int status, const char *message); static void network_handle_input (NetworkGame *ng, char *buf); static gboolean network_io_setup (NetworkGame *ng, CallerType caller); void network_start (void) { NetworkGame *nng; if (netgame) return; netgame = nng = g_new0 (NetworkGame, 1); nng->fd = -1; nng->outbuf = g_string_new (""); network_set_status (nng, DISCONNECTED, _("Network initialization complete.")); } void network_stop (void) { if (!netgame) return; network_set_status (netgame, DISCONNECTED, _("Network shutdown in progress.")); network_io_setup (netgame, CALLER_OTHER); g_string_free (netgame->outbuf, TRUE); g_free (netgame); netgame = NULL; } static void network_set_status (NetworkGame *ng, int status, const char *message) { if (status != CONNECTED) game_in_progress = 0; if (status == DISCONNECTED) { if (ng->fd >= 0) { close (ng->fd); ng->fd = -1; } if (ng->fd_gioc) { g_io_channel_unref (ng->fd_gioc); ng->fd_gioc = NULL; } g_string_truncate (netgame->outbuf, 0); } ng->status = status; ng->mycolor = 0; gui_message ((char *)message); } static gboolean network_io_setup (NetworkGame *ng, CallerType caller) { gboolean need_read = FALSE, need_write = FALSE, retval = TRUE; if (ng->status == CONNECTING) { need_write = TRUE; need_read = FALSE; } else if (ng->status == CONNECTED) { need_read = TRUE; need_write = ng->outbuf->len?TRUE:FALSE; } if (need_read && !ng->read_tag) { ng->read_tag = g_io_add_watch (ng->fd_gioc, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, network_handle_read, ng); } else if (!need_read && ng->read_tag) { if (caller == CALLER_READ_CB) retval = FALSE; else g_source_remove (ng->read_tag); ng->read_tag = 0; } if (need_write && !ng->write_tag) { ng->write_tag = g_io_add_watch (ng->fd_gioc, G_IO_OUT, network_handle_write, ng); } else if (!need_write && ng->write_tag) { if (caller == CALLER_WRITE_CB) retval = FALSE; else g_source_remove (ng->write_tag); ng->write_tag = 0; } return retval; } static gboolean network_handle_read (GIOChannel *source, GIOCondition cond, gpointer data) { NetworkGame *ng = data; int maxread, n; char *ctmp; maxread = sizeof (ng->inbuf) - ng->inpos - 2; if (!(cond & G_IO_IN) || !maxread) goto errout; n = read (ng->fd, ng->inbuf + ng->inpos, maxread); if (n <= 0) goto errout; ng->inpos += n; ng->inbuf[ng->inpos] = '\0'; while ((ctmp = strchr (ng->inbuf, '\n'))) { int itmp; *(ctmp++) = '\0'; network_handle_input (ng, ng->inbuf); itmp = ng->inpos - (ctmp - ng->inbuf); memmove (ng->inbuf, ctmp, itmp); ng->inpos -= ctmp - ng->inbuf; ng->inbuf[ng->inpos] = '\0'; } return network_io_setup (ng, CALLER_READ_CB); errout: /* Shut down the connection, either it was broken or someone is messing with us */ network_set_status (ng, DISCONNECTED, _("The remote player disconnected")); return network_io_setup (ng, CALLER_READ_CB); } static gboolean network_handle_write (GIOChannel *source, GIOCondition cond, gpointer data) { NetworkGame *ng = data; int n; if (ng->status == CONNECTING) { int errval; socklen_t optlen = sizeof (errval); if (getsockopt (ng->fd, SOL_SOCKET, SO_ERROR, &errval, &optlen)) g_error ("getsockopt failed!"); if (errval) network_set_status (ng, DISCONNECTED, _("Error occurred during connect attempt.")); else network_set_status (ng, CONNECTED, _("Connection succeeded, waiting for opponent")); return network_io_setup (ng, CALLER_WRITE_CB); } g_assert (ng->outbuf->len); g_assert (ng->status == CONNECTED); n = write (ng->fd, ng->outbuf->str, ng->outbuf->len); if (n <= 0) { network_set_status (ng, DISCONNECTED, _("Error occurred during write.")); } else g_string_erase (ng->outbuf, 0, n); return network_io_setup (ng, CALLER_WRITE_CB); } static void network_handle_input (NetworkGame *ng, char *buf) { char *args; args = strchr (buf, ' '); if (args) { *args = '\0'; args++; } if (!strcmp (buf, "set_peer")) { int me; if (ng->mycolor) { network_set_status (ng, DISCONNECTED, _("Invalid move attempted")); return; } if (!args || sscanf (args, "%d", &me) != 1 || (me != WHITE_TURN && me != BLACK_TURN)) { network_set_status (ng, DISCONNECTED, _("Invalid game data (set_peer)")); return; } white_level_cb (NULL, "0"); black_level_cb (NULL, "0"); ng->mycolor = me; gui_message (_("Peer introduction complete")); } else if (! strcmp (buf, "move")) { int x, y, me; if (!args || sscanf(args, "%d %d %d", &x, &y, &me) != 3 || !me || me != (32-ng->mycolor) || x >= 8 || y >= 8) { network_set_status (ng, DISCONNECTED, _("Invalid game data (move)")); return; } move (x, y, me); } else if (!strcmp(buf, "new_game")) { gui_message (_("New game started")); if (!ng->sent_newgame) g_string_append_printf (netgame->outbuf, "new_game\n"); ng->sent_newgame = 0; whose_turn = BLACK_TURN; init_new_game (); } } int game_move (guint x, guint y, guint me) { NetworkGame *ng = netgame; gnome_triggers_do ("", NULL, "gnothello", "flip-piece", NULL); if (ng) { if (me != ng->mycolor) g_error ("Impossible!"); if (ng->status == CONNECTED) { g_string_append_printf (ng->outbuf, "move %u %u %u\n", x, y, me); network_io_setup (ng, CALLER_OTHER); } } return move (x, y, me); } int network_allow (void) { if (netgame && netgame->mycolor) return whose_turn == netgame->mycolor; return !netgame; } static void network_connect (void) { int x; struct addrinfo *res = NULL, hints; g_string_truncate (netgame->outbuf, 0); netgame->inpos = 0; memset (&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; x = getaddrinfo (game_server, GAME_PORT, &hints, &res); if (x) { network_set_status (netgame, DISCONNECTED, gai_strerror(x)); return; } if (netgame->status != DISCONNECTED) { network_set_status (netgame, DISCONNECTED, _("Cleaning up connection")); network_io_setup (netgame, CALLER_OTHER); } netgame->fd = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP); g_assert (netgame->fd >= 0); fcntl (netgame->fd, F_SETFL, O_NONBLOCK); netgame->fd_gioc = g_io_channel_unix_new (netgame->fd); x = connect (netgame->fd, res->ai_addr, res->ai_addrlen); if (x) { if (errno == EINPROGRESS) network_set_status (netgame, CONNECTING, _("Connection in progress...")); else { perror("gnothello"); network_set_status (netgame, DISCONNECTED, _("Connection failed")); } } else network_set_status (netgame, CONNECTED, _("Connection succeeded, waiting for opponent")); network_io_setup (netgame, CALLER_OTHER); freeaddrinfo (res); } void network_new (void) { network_start (); if (! game_server) { network_set_status (netgame, DISCONNECTED, _("No game server defined")); return; } if (netgame->status != CONNECTED) network_connect (); clear_board (); g_string_append_printf (netgame->outbuf, "new_game\n"); netgame->sent_newgame = 1; network_io_setup (netgame, CALLER_OTHER); }