VgaGames4

VgaGames4 - network man-pages

[.. upper level ..]

# Network functions

VgaGames4 network functions are designed to keep it relatively simple to migrate a game from one-player to multi-player. Networking is implemented using one network server, which coordinates the clients, using UDP-connections. A client sends input-events to the server and gets back input-events from all clients including its own. The number of clients is limited to 8. Additional data can be sent with the input-events, which is resent to all clients. There is a state-counter taking care of keeping clients in sync: - packets must be acknowledged - not acknowledged packets are resent - packets are buffered for delayed clients A delayed client shall skip its game-loop-waiting function vg4->misc->wait_time() until it regained the lost time. Before the game-loop or between the game-loops data may be exchanged from and to all clients with the vg4->nw->xdata_*() functions. In a network-game some different functions must be used: - vg4->nw->update() must be used in the game-loop instead of vg4->input->update() - vg4->nw->key_pressed() must be used for network-keys instead of vg4->input->key_pressed() - vg4->nw->key_newpressed() must be used for network-keys instead of vg4->input->key_newpressed() - vg4->nw->mouse_position() must be used instead of vg4->input->mouse_position() - vg4->nw->mouse_pressed() must be used instead of vg4->input->mouse_pressed() - vg4->nw->mouse_newpressed() must be used instead of vg4->input->mouse_newpressed() - vg4->nw->pause() must be used instead of vg4->misc->pause() - vg4->nw->is_connected() shall be used for each client before acting with it As each client processes its game independently on the others, it is important that each client processes the same steps in each game-loop, that is where vg4->nw->update() is called. Therefore all must be avoided which could disturb the processing of the same steps: - the same code must be executed in the same order on each client - avoid persistant float variables because of the inaccuracy in computing - random numbers must be the same on each client, using vg4->random->get() in the exact same manner - avoid referring to the duration of an audio, (e.g.: while (vg4->audio->is_playing(...) { ... }) as this could take different game-loops on different clients, therefore the sprite's instruction SNDFLAG: WAIT is deactivated on network games

- Enumerations and Structures
- struct VG_NwZdata
- struct VG_NwXdataList
- Managing network-server
- vg4->nw->server()
  Start, stop or restart network-server.
- command vg4_nw_server
  Command-line program
- Connection to network-server
- vg4->nw->set_ports()
  Set UDP-ports for server or client.
- vg4->nw->check_server()
  Check for a running network-server in the LAN.
- vg4->nw->get_ip()
  Return internet-address of network-server.
- vg4->nw->connect()
  Connect to network-server.
- vg4->nw->is_connected()
  Return whether client is still connected.
- vg4->nw->close()
  Close connection to network-server.
- Network-event functions
- vg4->nw->update()
  Retrieve input-events and receive from and send to network-server.
- vg4->nw->key_pressed()
  Check if a network-key for a client is pressed.
- vg4->nw->key_newpressed()
  Check if a network-key for a client is pressed which has not yet been queried.
- vg4->nw->mouse_position()
  Return number of clients (still connected or not).
- vg4->nw->mouse_pressed()
  Return pressed mouse-buttons of a client.
- vg4->nw->mouse_newpressed()
  Return pressed mouse-buttons of a client which have not yet been queried.
- Exchanging data before the game-loop or between the game-loops
- vg4->nw->xdata_send()
  Send exchange-data to network-server.
- vg4->nw->xdata_recv()
  Receive exchange-data from network-server.
- vg4->nw->xdata_retag()
  Set a new tag of exchange-data.
- vg4->nw->xdata_allclients_recv()
  Receive exchange-data from all clients.
- vg4->nw->xdata_allclients_free()
  Free exchange-data list.
- Miscellaneous functions
- vg4->nw->numberofclients()
  Return number of clients (still connected or not).
- vg4->nw->local_clnr()
  Return local client-number.
- vg4->nw->get_clientname()
  Return name of a client.
- vg4->nw->pause()
  Send pause-request to network-server.
- vg4->nw->put_zdata()
  Put additional-data to be sent along with input-events to the network-server.
- vg4->nw->connected_clients()
  Update which clients are connected (outside the game-loop).

## Example

###

    A network-game for up to 4 players: Moving sunnyboys within the window.

    It is a bit extensive to demonstrate the miscellaneous possibilities:
    - connect to network-server with callback-function
    - select a sunnyboy and use exchange-data to inform the other clients,
      receive their selections and take care that the selection is exclusive
    - network-pause
    - use local keys and network-keys
    - use additional-data for sending a string showing onto the own sunnyboy

    The file example.c is the main program: open window, initialize and game-loop.
    The file connect_to_nwserver.c connects to the network-server.
    The file select_sb.c selects the sunnyboy via a canvas,
      sends it to the network-server and receives from it all selections.

    example.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <errno.h>
    #include <vgagames4.h>

    extern VG_BOOL connect_to_nwserver(int, int *, int *);
    extern VG_BOOL select_sb(char *[], int, VG_BOOL);

    /* max. number of clients */
    #define CLMAX 4

    /* keys */
    struct {
      int k_quit;
      int k_pause;
      int k_zdata;
      int k_nw_up;
      int k_nw_down;
      int k_nw_right;
      int k_nw_left;
    } kref;

    /* players (clients) */
    static struct {
      struct VG_Image *img;     /* image */
      struct VG_Rect rect;      /* position */
      struct VG_Image *txtimg;  /* text-image (additional-data) */
      int txtshow;              /* counter for showing text-image */
    } player[CLMAX], *playerp;

    int main(int argc, char **argv) {
      VG_BOOL dowait;
      int winw, winh;
      int clnr, clmax, icl;
      char buf[128];
      struct VG_NwZdata nw_zdata;

      (void)argc; (void)argv;

      /* open window */
      if (!VG_init("test")) { exit(1); }
      if (!vg4->window->open(VG_WINDOW_SIZE_LOW, VG_WINDOW_SCALE_NONE)) { VG_dest(); exit(1); }
      vg4->window->getsize(&winw, &winh);
      vg4->input->mouse_grabbing(VG_FALSE);

      /* connect to network server, get client-number (clnr) and number of clients (clmax) */
      if (!connect_to_nwserver(CLMAX, &clnr, &clmax)) { VG_dest(); exit(1); }

      /* set keys */
      /* quit: Q (local key) */
      if ((kref.k_quit = vg4->input->key_insert("Quit", VG_FALSE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
      vg4->input->key_setkbd(kref.k_quit, VG_INPUT_KBDCODE_Q);
      /* pause: P (local key) */
      if ((kref.k_pause = vg4->input->key_insert("Pause", VG_FALSE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
      vg4->input->key_setkbd(kref.k_pause, VG_INPUT_KBDCODE_P);
      /* send additional-data: space (local key) */
      if ((kref.k_zdata = vg4->input->key_insert("Additional-data", VG_FALSE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
      vg4->input->key_setkbd(kref.k_zdata, VG_INPUT_KBDCODE_SPACE);
      /* move up: upper cursor (network-key) */
      if ((kref.k_nw_up = vg4->input->key_insert("Move Up", VG_FALSE, VG_TRUE)) == 0) { VG_dest(); exit(1); }
      vg4->input->key_setkbd(kref.k_nw_up, VG_INPUT_KBDCODE_UCURS);
      /* move down: lower cursor (network-key) */
      if ((kref.k_nw_down = vg4->input->key_insert("Move Down", VG_FALSE, VG_TRUE)) == 0) { VG_dest(); exit(1); }
      vg4->input->key_setkbd(kref.k_nw_down, VG_INPUT_KBDCODE_DCURS);
      /* move right: right cursor (network-key) */
      if ((kref.k_nw_right = vg4->input->key_insert("Move Right", VG_FALSE, VG_TRUE)) == 0) { VG_dest(); exit(1); }
      vg4->input->key_setkbd(kref.k_nw_right, VG_INPUT_KBDCODE_RCURS);
      /* move left: left cursor (network-key) */
      if ((kref.k_nw_left = vg4->input->key_insert("Move Left", VG_FALSE, VG_TRUE)) == 0) { VG_dest(); exit(1); }
      vg4->input->key_setkbd(kref.k_nw_left, VG_INPUT_KBDCODE_LCURS);

      /* initialize players */
      { char *sbnames[clmax];

        /* select and get selected sunnyboys */
        for (icl = 1; icl <= clmax; icl++) { sbnames[icl - 1] = NULL; }
        if (!select_sb(sbnames, clmax, VG_TRUE)) { VG_dest(); exit(1); }

        /* set players */
        for (icl = 1; icl <= clmax; icl++) {
          if (sbnames[icl - 1] == NULL) { continue; }  /* disconnected */
          playerp = &player[icl - 1];
          /* load image */
          snprintf(buf, sizeof(buf), "images/%s.bmp", sbnames[icl - 1]);
          free(sbnames[icl - 1]);
          playerp->img = vg4->image->load(buf);
          if (playerp->img == NULL) { VG_dest(); exit(1); }
          /* set position and size */
          playerp->rect.x = (winw * icl / clmax) - (winw / clmax / 2);
          playerp->rect.y = winh / 2;
          vg4->image->getsize(playerp->img, NULL, &playerp->rect.w, &playerp->rect.h);
          /* no additional-data */
          playerp->txtimg = NULL;
          playerp->txtshow = 0;
        }
      }

      /* Gameloop */
      for (;;) {
        if (!vg4->nw->update(&dowait, &nw_zdata)) { goto go_end; }
        if (!vg4->nw->is_connected(clnr)) { goto go_end; }  /* i am still connected? */

        /* quit (local key)? */
        if (vg4->input->key_newpressed(kref.k_quit)) { goto go_end; }

        /* pause requested (local key)? */
        if (vg4->input->key_newpressed(kref.k_pause)) { vg4->nw->pause(); }

        /* send additional-data (local key)? */
        if (vg4->input->key_newpressed(kref.k_zdata)) {
          /* create colored string */
          struct VG_NwZdata zdata_put;
          snprintf(zdata_put.data, sizeof(zdata_put.data),
                   "%%{txt[bgcolor=0x888888]: Hi, i am %s %%}", vg4->nw->get_clientname(clnr));
          zdata_put.size = strlen(zdata_put.data) + 1;  /* including null-termination */
          vg4->nw->put_zdata(&zdata_put);
        }

        /* check for received additional-data */
        if (nw_zdata.size > 0) {
          playerp = &player[nw_zdata.clnr - 1];
          /* if still a previous text-image is present, destroy it */
          if (playerp->txtimg != NULL) { vg4->image->destroy(playerp->txtimg); }
          /* create text-image from received null-terminated string */
          playerp->txtimg = vg4->font->totext(nw_zdata.data, NULL, NULL, NULL, NULL);
          playerp->txtshow = 60;  /* number of game-loops to show text-image */
        }

        /* clear window */
        vg4->window->clear();

        /* do actions with each client */
        for (icl = 1; icl <= clmax; icl++) {
          if (!vg4->nw->is_connected(icl)) { continue; }

          playerp = &player[icl - 1];

          /* check for network-keys */
          /* move up */
          if (vg4->nw->key_pressed(icl, kref.k_nw_up)) {
            playerp->rect.y--;
            if (playerp->rect.y < 0) { playerp->rect.y = 0; }
          }
          /* move down */
          if (vg4->nw->key_pressed(icl, kref.k_nw_down)) {
            playerp->rect.y++;
            if (playerp->rect.y > winh - playerp->rect.h) { playerp->rect.y = winh - playerp->rect.h; }
          }
          /* move right */
          if (vg4->nw->key_pressed(icl, kref.k_nw_right)) {
            playerp->rect.x++;
            if (playerp->rect.x > winw - playerp->rect.w) { playerp->rect.x = winw - playerp->rect.w; }
          }
          /* move left */
          if (vg4->nw->key_pressed(icl, kref.k_nw_left)) {
            playerp->rect.x--;
            if (playerp->rect.x < 0) { playerp->rect.x = 0; }
          }

          /* draw client */
          { struct VG_Position posi;
            posi.pos = VG_POS_UPPER_LEFT;
            posi.x = playerp->rect.x;
            posi.y = playerp->rect.y;
            vg4->window->copy(playerp->img, &posi, NULL);
          }

          /* draw client's text-image if not NULL */
          if (playerp->txtimg != NULL) {
            struct VG_Position posi;
            posi.pos = VG_POS_CENTERED;
            posi.x = playerp->rect.x + playerp->rect.w / 2;
            posi.y = playerp->rect.y + playerp->rect.h / 2;
            vg4->window->copy(playerp->txtimg, &posi, NULL);
            if (--playerp->txtshow == 0) {  /* destroy text-image */
              vg4->image->destroy(playerp->txtimg);
              playerp->txtimg = NULL;
            }
          }
        }

        /* flush contents to window, and wait if not delayed */
        vg4->window->flush();
        if (dowait) { vg4->misc->wait_time(50); }
      }

    go_end:
      VG_dest();
      exit(0);
    }

    connect_to_nwserver.c

    /* connect to network-server */

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <errno.h>
    #include <vgagames4.h>

    VG_BOOL connect_to_nwserver(int, int *, int *);

    static void show_progress(const char * const *, int, void *);

    /* callback-function for showing progress at connecting to network-server */
    static void
    show_progress(const char * const *clients, int anz, void *vdata)
    {
      int i1;
      char btxt[512];
      size_t blen;
      struct VG_Image *img;
      char *sdata = (char *)vdata;

      /* create text containing the clients in a list */
      if (sdata != NULL) { snprintf(btxt, sizeof(btxt), "%s:\n", sdata); }
      blen = strlen(btxt);
      for (i1 = 0; i1 < anz; i1++) {
        snprintf(btxt + blen, sizeof(btxt) - blen, " - %d = <%s>\n", i1+1, clients[i1]);
        blen = strlen(btxt);
      }

      /* show text */
      img = vg4->font->totext(btxt, NULL, NULL, NULL, NULL);
      vg4->window->clear();
      vg4->window->copy(img, NULL, NULL);
      vg4->window->flush();
      vg4->image->destroy(img);
    }

    /* connect to network-server
     * @param max_connections  max. connections
     * @param clnr   for returning local client-number
     * @param clmax  for returning number of clients
     * @return  VG_TRUE = OK or VG_FALSE = error
     */
    VG_BOOL
    connect_to_nwserver(int max_connections, int *clnr, int *clmax)
    {
      char remhost[128], *ipsrv;

      if (clnr == NULL || clmax == NULL) { return VG_FALSE; }

      /* get IP of network-server */
      ipsrv = vg4->nw->get_ip(remhost, sizeof(remhost));
      if (ipsrv == NULL) { return VG_FALSE; }

      /* connect to network-server, blocking until all clients connected, showing progress */
      *clnr = vg4->nw->connect(ipsrv, 0, max_connections, NULL, show_progress, "Clients");
      free(ipsrv);
      if (*clnr <= 0) { return VG_FALSE; }

      /* get number of clients */
      *clmax = vg4->nw->numberofclients(NULL);

      return VG_TRUE;
    }

    select_sb.c

    /* select a sunnyboy from a chain and inform the other clients */

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <errno.h>
    #include <vgagames4.h>

    VG_BOOL select_sb(char *[], int, VG_BOOL);

    static VG_BOOL exec_canvas(char *, size_t, char *[], int);
    static void show_info(const char *);

    /* execute canvas to select sunnyboy-file
     * @param sbname   for returning sunnyboy-name
     * @param sbsize   sizeof(sbname)
     * @param sbnames  array of char-pointers with already selected sunnyboys
     * @param clmax    number of array-elements (equal to number of clients)
     * @return VG_TRUE = OK or VG_FALSE = got exit-request
     */
    static VG_BOOL
    exec_canvas(char *sbname, size_t sbsize, char *sbnames[], int clmax)
    {
      const char *chain_name = "sb_chain";
      struct VG_Canvas *cvas;
      const char *selname;
      int icl;

      if (sbname == NULL || sbsize == 0) { return VG_FALSE; }
      if (sbnames == NULL || clmax < 1) { return VG_FALSE; }

      *sbname = '\0';

      /* load canvas */
      cvas = vg4->canvas->load("select_sb.cvas", NULL);
      if (cvas == NULL) { return VG_FALSE; }

      /* disable chain-elements of already set sunnyboy-names */
      for (icl = 1; icl <= clmax; icl++) {
        if (sbnames[icl - 1] != NULL) {
          vg4->canvas->chain_disable(cvas, chain_name, sbnames[icl - 1], VG_TRUE);
        }
      }

      for (;;) {
        /* execute canvas */
        if (!vg4->canvas->exec(cvas, NULL, &selname)) { return VG_FALSE; }
        if (selname == NULL) { break; }  /* cancelled via escape-key or cancel-button */

        /* act according to selection */

        if (*selname == '\0') {  /* no selection, but return-key pressed */
          /* no action, re-execute canvas */
          ;

        } else if (strcmp(selname, chain_name) == 0) {  /* item selected from chain */
          const char *chainkey = vg4->canvas->chain_get_activated(cvas, selname);
          if (chainkey != NULL) {
            snprintf(sbname, sbsize, "%s", chainkey);
            break;
          }
        }
      }

      vg4->canvas->destroy(cvas);

      if (*sbname == '\0') { return VG_FALSE; }
      return VG_TRUE;
    }

    /* show info */
    static void
    show_info(const char *text)
    {
      struct VG_Image *img;

      if (text == NULL || *text == '\0') { return; }

      img = vg4->font->totext(text, NULL, NULL, NULL, NULL);
      vg4->window->clear();
      vg4->window->copy(img, NULL, NULL);
      vg4->window->flush();
      vg4->image->destroy(img);
    }

    /* select sunnyboy, get selected sunnyboys of all clients
     * @param sbnames    array of char-pointers for returning the sunnyboy-names
     * @param clmax      number of array-elements (equal to number of clients)
     * @param exclusive  whether sunnyboy-selection is exclusive (each client must have a different sunnyboy)
     * @return VG_TRUE = OK or VG_FALSE = got exit-request
     *
     * The receiving part of this function is a form of a replacement of vg4->nw->xdata_allclients_recv().
     */
    VG_BOOL
    select_sb(char *sbnames[], int clmax, VG_BOOL exclusive)
    {
      char *ex_data, sbname[32];
      size_t ex_size;
      int ex_clnr, clnr, icl;

      /* sbnames shall be an array of clmax elements */
      if (sbnames == NULL || clmax < 1) { return VG_FALSE; }
      clnr = vg4->nw->local_clnr();
      if (exclusive && clnr == 0) { return VG_FALSE; }

      vg4->nw->xdata_retag();

    reselect:
      /* select sunnyboy */
      if (!exec_canvas(sbname, sizeof(sbname), sbnames, clmax)) { return VG_FALSE; }

      /* show info */
      show_info("Receiving data ...");

      /* send selected sunnyboy-name to network-server */
      if (!vg4->nw->xdata_send(sbname, strlen(sbname) + 1)) { return VG_FALSE; }

      /* receive data from network-server */
      for (;;) {
        if (!vg4->nw->xdata_recv(&ex_data, &ex_size, &ex_clnr)) { return VG_FALSE; }

        if (ex_size > 0) {  /* sunnyboy-name received */
          if (sbnames[ex_clnr - 1] == NULL) {  /* set sunnyboy-name of client-number ex_clnr */
            if (exclusive) {  /* selection is exclusive */
              /* check if any client already owns the received sunnyboy-name */
              for (icl = 1; icl <= clmax; icl++) {
                if (sbnames[icl - 1] != NULL && strcmp(sbnames[icl - 1], ex_data) == 0) { break; }
              }
              if (icl > clmax) { sbnames[ex_clnr - 1] = ex_data; ex_data = NULL; }  /* no, set it */
            } else {  /* not exclusive */
              sbnames[ex_clnr - 1] = ex_data;
              ex_data = NULL;
            }
          }
          if (ex_data != NULL) { free(ex_data); }

        } else {  /* no data received */
          /* check if all connected clients have their sunnyboy-name */
          for (icl = 1; icl <= clmax; icl++) {
            if (sbnames[icl - 1] == NULL && vg4->nw->is_connected(icl)) { break; }
          }
          if (icl > clmax) { break; }  /* yes, we got all information */

          if (exclusive) {  /* selection is exclusive */
            /* check if another client has already my sunnyboy-selection */
            if (sbnames[clnr - 1] == NULL) {  /* my sunnyboy is not yet set */
              for (icl = 1; icl <= clmax; icl++) {
                if (sbnames[icl - 1] != NULL && strcmp(sbnames[icl - 1], sbname) == 0) { break; }
              }
              if (icl <= clmax) {  /* yes, so select another sunnyboy */
                show_info("Selected sunnyboy is no more available");
                sleep(3);
                vg4->window->clear();
                goto reselect;
              }
            }
          }

          /* window-flush and wait */
          vg4->window->flush();
          vg4->misc->wait_time(100);
        }
      }

      return VG_TRUE;
    }

    select_sb.cvas

    # Canvas for selecting a sunnyboy

    [MAIN]
    %{imgfile[
      utag-title=10+130,8+12
      utag-sb_chain=43+64,44+64
      utag-cancel=50+50,116+20
     ]: images/select_sb.bmp%}

    [MOUSE]
    disable: 0

    [FONT]
    sys:low

    # title
    [CV-TEXT]
    name: title
    position-itag: title
    default-text: Select sunnyboy
    text.orientation: center+left
    text.v-orientation: center
    text.fgcolor: 0xffff00
    text.nowrap: 1

    # selection-chain
    [CV-CHAIN]
    name: sb_chain
    position-itag: sb_chain
    element.sb_red: img:images/sb_red.bmp
    element.sb_yellow: img:images/sb_yellow.bmp
    element.sb_blue: img:images/sb_blue.bmp

    # cancel-button
    [CV-BUTTON]
    name: cancel
    position-itag: cancel
    is-cancel: 1
    img-dfl: img:images/button-cancel-up.bmp
    img-sel: img:images/button-cancel-down.bmp