VgaGames4

VgaGames4 - tutorial

# Tutorial 7

The single-player game is finished now. So it is time to expand it to a dual-player game via network. We remove the right border and substitute it with the second player-racket.

At first an overview about modifications in the file pingpong.c.

    pingpong.c: Overview about modifications

    /* global declarations */
    [CODEBOX]

    /* main function */
    int main(int argc, char **argv) {
      /* variable declaration */
      [CODEBOX]

      /* initializing */
      [...]      /* opening, collision functions and tag, loading audio files */
      [CODEBOX]  /* set keys */
      [...]      /* show help */
      [CODEBOX]  /* connect to network-server */
      [CODEBOX]  /* create object-instances */
      [...]      /* start background music */

      /* game loop */
      [CODEBOX]  /* receive input-events and check the local key-strokes */
      [CODEBOX]  /* call f_run() of all object-instances */
      [...]      /* clear window, draw background, call f_draw() of all object-instances */
      [CODEBOX]  /* flush and wait */

      /* end game */
      [...]      /* destroy and exit */
    }

In the file pingpong.h we add two variables regarding the local and remote player into the private structure of the game.

    pingpong.h

    #ifndef PINGPONG_H_
    #define PINGPONG_H_

    #include <vgagames4.h>

    /* include export-functions to create new object-instances */
    #include "objnew.h"

    /* private structure of the game */
    enum { PLY_NOBODY = 0, PLY_LEFT, PLY_RIGHT };
    struct s_game {
      int winw, winh;  /* window's width and height */
      unsigned int coll_tag;  /* collision-tag */
      int ply_local;          /* location of local player: PLY_LEFT or PLY_RIGHT */
      int winner;             /* who is the winner: one of PLY_* */

      struct {  /* keys */
        int k_quit_lalt;  /* part of quit: Left-ALT */
        int k_quit_q;     /* part of quit: Q */
        int k_pause;      /* pause */
        int k_up;         /* move up */
        int k_down;       /* move down */
      } kref;
    };

    #endif /* PINGPONG_H_ */

The global declarations of the file pingpong.c, we add the function for connecting to the network-server.
[To Position]

    pingpong.c: global declarations

    /* object-collision functions */
    extern void objcoll(void);

    /* connect to nw-server */
    extern int connect_to_nwserver(int, int);

The main function of the file pingpong.c
[To Position]

    pingpong.c: variable declaration

    struct s_game sgame;  /* private structure of the game */
    int audc_gameover, audc_bgmusic;  /* audio descriptors */
    int clnr, clmax, cli;  /* local client-number, number of clients, control variable */
    VG_BOOL dowait;  /* whether vg4->misc->wait_time() shall be called */

We modify the moving keys from local-keys to network-keys, so that all clients receive them. We could have this done earlier as this parameter is ignored in a single-player game.
[To Position]

    pingpong.c: set keys, modify some of them to network-keys

    /* set keys */

    /* quit with ALT+Q, not changeable, local key */
    if ((sgame.kref.k_quit_lalt = vg4->input->key_insert("Quit-LALT", VG_FALSE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
    vg4->input->key_setkbd(sgame.kref.k_quit_lalt, VG_INPUT_KBDCODE_LALT);
    if ((sgame.kref.k_quit_q = vg4->input->key_insert("Quit-Q", VG_FALSE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
    vg4->input->key_setkbd(sgame.kref.k_quit_q, VG_INPUT_KBDCODE_Q);

    /* system-menu with ESC, not changeable, local key */
    if ((sgame.kref.k_sysmenu = vg4->input->key_insert("System-menu", VG_FALSE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
    vg4->input->key_setkbd(sgame.kref.k_sysmenu, VG_INPUT_KBDCODE_ESCAPE);

    /* pause with P, changeable, local key */
    if ((sgame.kref.k_pause = vg4->input->key_insert("Pause", VG_TRUE, VG_FALSE)) == 0) { VG_dest(); exit(1); }
    vg4->input->key_setkbd(sgame.kref.k_pause, VG_INPUT_KBDCODE_P);

    /* move-up with cursor-key UP, changeable, network key */
    if ((sgame.kref.k_up = vg4->input->key_insert("Move up", VG_TRUE, VG_TRUE)) == 0) { VG_dest(); exit(1); }
    vg4->input->key_setkbd(sgame.kref.k_up, VG_INPUT_KBDCODE_UCURS);
    vg4->input->key_setgc(sgame.kref.k_up, 0, VG_INPUT_GCAXIS_RIGHTY_UP);

    /* move-down with cursor-key DOWN, changeable, network key */
    if ((sgame.kref.k_down = vg4->input->key_insert("Move down", VG_TRUE, VG_TRUE)) == 0) { VG_dest(); exit(1); }
    vg4->input->key_setkbd(sgame.kref.k_down, VG_INPUT_KBDCODE_DCURS);
    vg4->input->key_setgc(sgame.kref.k_down, 0, VG_INPUT_GCAXIS_RIGHTY_DOWN);

Now we connect to the network-server, which of course must run already.
[To Position]

    pingpong.c: connect to network-server

    /* connect to network-server */
    if ((clnr = connect_to_nwserver(2, 2)) == 0) { VG_dest(); exit(1); }
    clmax = vg4->nw->numberofclients(NULL);  /* get number of clients */
    if (clmax != 2) { fprintf(stderr, "There must be exact 2 players.\n"); VG_dest(); exit(1); }

We create two players and additionally pass the client-number. The right border is removed.
[To Position]

    pingpong.c: create object-instances, we now have two players

    /* create object-instances */
    for (cli = 1; cli <= clmax; cli++) {  /* a player for each client */
      if (objnew_PLAYER(&sgame, cli) == 0) { VG_dest(); exit(1); }
    }
    if (objnew_BALL(&sgame) == 0) { VG_dest(); exit(1); }
    if (objnew_BORDER(&sgame, "top") == 0) { VG_dest(); exit(1); }
    if (objnew_BORDER(&sgame, "bottom") == 0) { VG_dest(); exit(1); }

We retrieve the input-events from the network-server, check if our local client is still connected and change the called pause-function.
[To Position]

    pingpong.c: entering game-loop: receive input-events and check the local key-strokes

    /* game loop */
    for (;;) {
      /* retrieve input-events from network-server */
      if (!vg4->nw->update(&dowait, NULL)) { goto endgame; }

      /* check if own client is still connected */
      if (!vg4->nw->is_connected(clnr)) { goto endgame; }

      /* quit? */
      if (vg4->input->key_newpressed(sgame.kref.k_quit_q) && vg4->input->key_pressed(sgame.kref.k_quit_lalt)) { goto endgame; }

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

      /* system-menu? */
      if (vg4->input->key_newpressed(sgame.kref.k_sysmenu)) {
        struct VG_Hash *hvar = vg4->hash->create();
        vg4->nw->pause();  /* send pause-request to suspend the game */
        vg4->hash->setstr(hvar, "top:title", "System-menu");
        vg4->hash->setstr(hvar, "volume:top:title", "Set audio volumes");
        vg4->hash->setstr(hvar, "keydef:top:title", "Key Redefinition");
        vg4->hash->setstr(hvar, "keydef:press:title", "Press key");
        vg4->audio->suspend(VG_TRUE);
        if (!vg4->dialog->sysmenu("files/canvas", NULL, hvar, &sgame, NULL, NULL, NULL)) { goto endgame; }
        vg4->audio->suspend(VG_FALSE);
        vg4->hash->destroy(hvar);
      }

When the game is over we check for the winner and looser and show an appropriate message.
[To Position]

    pingpong.c: in game-loop: call f_run() of all object-instances

      /* call f_run() of all object-instances */

      if (!vg4->object->call_run(&sgame)) {
        /* see obj-ball.c: player missed ball */

        if (sgame.winner != PLY_NOBODY) {  /* there is a winner */
          /* show message, play audio and wait for its end */
          struct VG_Image *imgp;
          if (sgame.winner == sgame.ply_local) {  /* local player wins */
            imgp = vg4->font->totext("You win", "[fgcolor=0x00ff00]", NULL, NULL, NULL);
          } else {  /* local player looses */
            imgp = vg4->font->totext("You loose", "[fgcolor=0xff0000]", NULL, NULL, NULL);
          }
          vg4->window->copy(imgp, NULL, NULL);
          vg4->image->destroy(imgp);
          vg4->audio->stop(audc_bgmusic, VG_FALSE);
          vg4->audio->play(audc_gameover, VG_FALSE, VG_FALSE);
          while (vg4->audio->is_playing(audc_gameover, NULL)) {
            vg4->window->flush();
            vg4->misc->wait_time(40);
          }
        }
        goto endgame;
      }

The function vg4->window->flush() is called according to the value in dowait.
[To Position]

    pingpong.c: in game-loop: flush and wait

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

The object-file for the player-racket is changed as the client-number is passed and the left player must be distinguished from the right player.

    obj-player.h

    #ifndef OBJ_PLAYER_H_
    #define OBJ_PLAYER_H_

    #include "pingpong.h"

    /* private structure for object PLAYER */
    struct sobj_player {
      struct VG_Image *imgp;  /* image */
      struct VG_Rect rect;    /* rectangle position */
      int clnr;               /* client-number of player-instance */
    };

    #endif /* OBJ_PLAYER_H_ */

    obj-player.c: export function for creating object-instances

    /* export-function to create a new object-instance of "PLAYER" */
    unsigned int
    objnew_PLAYER(void *vgame, int clnr)
    {
      const int coll_percent = 90;
      struct s_game *sgame = (struct s_game *)vgame;
      struct VG_Object *objp;
      struct sobj_player *objvars;

      if (sgame == NULL) { return 0; }
      if (clnr < 1) { return 0; }

      /* allocate private struct */
      objvars = calloc(1, sizeof(*objvars));
      if (objvars == NULL) { return 0; }

      /* set private struct */
      objvars->imgp = vg4->image->load("files/player.bmp");
      if (objvars->imgp == NULL) { return 0; }
      vg4->image->getsize(objvars->imgp, NULL, &objvars->rect.w, &objvars->rect.h);
      /* first connected player shall be the left player, the second the right player */
      if (clnr == 1) {
        objvars->rect.x = 0;
        objvars->rect.y = (sgame->winh - objvars->rect.h) / 2;
        if (clnr == vg4->nw->local_clnr()) { sgame->ply_local = PLY_LEFT; }
      } else {
        objvars->rect.x = sgame->winw - objvars->rect.w;
        objvars->rect.y = (sgame->winh - objvars->rect.h) / 2;
        if (clnr == vg4->nw->local_clnr()) { sgame->ply_local = PLY_RIGHT; }
      }
      objvars->clnr = clnr;

      /* create object-instance */
      objp = vg4->object->create(OBJID, 0, 0, 2, objvars);

      /* set functions */
      objp->f_free = f_free;
      objp->f_run = f_run;
      objp->f_draw = f_draw;
      /* f_data(), f_childexit() and f_signal() are not needed here */

      /* insert object-instance into collision-tag */
      vg4->collision->insert(sgame->coll_tag, objp->instanceid, &objvars->rect, coll_percent);

      return objp->instanceid;
    }

In the run-function we add the check whether the client is still connected and substitute the key-checking functions.

    obj-player.c: run-function for the object-instances

    /* move object-instance, called from vg4->object->call_run() */
    static VG_BOOL
    f_run(void *vgame, struct VG_Object *objp)
    {
      struct s_game *sgame = (struct s_game *)vgame;
      struct sobj_player *objvars = (struct sobj_player *)objp->opriv;
      int y_old;

      if (sgame == NULL) { return VG_TRUE; }

      /* if any player is no longer connected, stop game */
      if (!vg4->nw->is_connected(objvars->clnr)) {
        fprintf(stderr, "Player %d is no longer connected. Stop game.\n", objvars->clnr);
        return VG_FALSE;
      }

      /* +++ check for key-strokes +++ */

      y_old = objvars->rect.y;

      /* moving up */
      if (vg4->nw->key_pressed(objvars->clnr, sgame->kref.k_up)) {
        objvars->rect.y -= 2;
      }

      /* moving down */
      if (vg4->nw->key_pressed(objvars->clnr, sgame->kref.k_down)) {
        objvars->rect.y += 2;
      }

      /* +++ check for collisions if moved +++ */

      if (y_old != objvars->rect.y) {  /* moved */
        int canz, cret;
        struct VG_Coll *collp;

        /* update object-instance in collision-tag and check for collisions */
        canz = vg4->collision->setpos(sgame->coll_tag, objp->instanceid, &objvars->rect, &collp);

        /* call collision-functions and free collision array */
        cret = vg4->object->collision_call(sgame, objp->instanceid, collp, canz);
        if (collp != NULL) { free(collp); }

        /* check return-value */
        if (cret == VG_COLL_RETURN_STOP) {
          /* stopped, go back and update object-instance in collision-tag */
          objvars->rect.y = y_old;
          vg4->collision->setpos(sgame->coll_tag, objp->instanceid, &objvars->rect, NULL);
        }
        /* no other return-values are set in any collision-function here */
      }

      return VG_TRUE;
    }

The object-file for the ball. In the run-function we modify the check if the player missed the ball into a check for both players.

    obj-ball.c

    /* move object-instance, called from vg4->object->call_run() */
    static VG_BOOL
    f_run(void *vgame, struct VG_Object *objp)
    {
      struct s_game *sgame = (struct s_game *)vgame;
      struct sobj_ball *objvars = (struct sobj_ball *)objp->opriv;
      struct VG_RectCent *rectcp;
      int rectc_nr;

      if (sgame == NULL) { return VG_TRUE; }

      /* increase ball-factor for 1/1000 pixel */
      objvars->factor++;

      /* get moving steps */
      rectc_nr = vg4->misc->moving_one_step(&objvars->rectc, objvars->angle, objvars->factor / 100, &rectcp);

      /* check for collisions for each rectangle */
      if (!vg4->misc->move_and_check_collisions(vgame, objp->instanceid, &objvars->rectc, sgame->coll_tag, rectcp, rectc_nr)) {
        /* object-instance is dead (shouldn't happen!) */
        if (rectcp != NULL) { free(rectcp); }
        vg4->object->destroy(sgame, objp->instanceid);
        return VG_TRUE;
      }

      /* free moving steps */
      if (rectcp != NULL) { free(rectcp); }

      /* check if a player missed ball */
      if (objvars->rectc.rect.x + objvars->rectc.rect.w <= 0) {
        sgame->winner = PLY_RIGHT;  /* right player wins */
        return VG_FALSE;
      }
      if (objvars->rectc.rect.x >= sgame->winw) {
        sgame->winner = PLY_LEFT;  /* left player wins */
        return VG_FALSE;
      }

      return VG_TRUE;
    }

In the object-collision file objcoll-player-ball.c we add the checking for collision at the right respectively at the left.

    objcoll-player-ball.c: collision of player-racket and ball

    int
    objcoll_PLAYER_BALL(void *vgame, unsigned int instanceid, struct VG_Coll *collb)
    {
      struct VG_Object *objp;
      struct sobj_ball *objvars_ball;
      int xdelta, ydelta;
      int retw = VG_COLL_RETURN_CONTINUE;

      (void)vgame;

      /* only entering into collision is interesting here */
      if (collb->type != VG_COLL_TYPE_ENTRY) { return retw; }

      /* act according to moving object-instance */

      objp = vg4->object->instance_getobj(instanceid);

      if (strcmp(objp->objid, "PLAYER") == 0) {
        /* PLAYER is moving, hitting BALL */
        objp = vg4->object->instance_getobj(collb->instanceid);
        objvars_ball = (struct sobj_ball *)objp->opriv;

        /* check collision sides of hit object-instance (ball) */
        if (collb->side & (VG_COLL_SIDE_LEFT | VG_COLL_SIDE_RIGHT)) {
          /* set new moving-angle of ball */
          vg4->misc->xy_from_angle(objvars_ball->angle, &xdelta, &ydelta);
          xdelta = -xdelta;
          objvars_ball->angle = vg4->misc->angle_from_xy(xdelta, ydelta, NULL);
          retw = VG_COLL_RETURN_STOP;  /* stop moving object-instance (player) */
        }
        if (collb->side & (VG_COLL_SIDE_TOP | VG_COLL_SIDE_BOTTOM)) {
          /* set new moving-angle of ball */
          vg4->misc->xy_from_angle(objvars_ball->angle, &xdelta, &ydelta);
          ydelta = -ydelta;
          objvars_ball->angle = vg4->misc->angle_from_xy(xdelta, ydelta, NULL);
          retw = VG_COLL_RETURN_STOP;  /* stop moving object-instance (player) */
        }

      } else {
        /* BALL is moving, hitting PLAYER */
        objvars_ball = (struct sobj_ball *)objp->opriv;

        /* check collision sides of hit object-instance (player) */
        if (collb->side & (VG_COLL_SIDE_LEFT | VG_COLL_SIDE_RIGHT)) {
          /* set new moving-angle of ball */
          vg4->misc->xy_from_angle(objvars_ball->angle, &xdelta, &ydelta);
          xdelta = -xdelta;
          objvars_ball->angle = vg4->misc->angle_from_xy(xdelta, ydelta, NULL);
          retw = VG_COLL_RETURN_STOP;  /* stop moving object-instance (ball) */
        }
        if (collb->side & (VG_COLL_SIDE_TOP | VG_COLL_SIDE_BOTTOM)) {
          /* set new moving-angle of ball */
          vg4->misc->xy_from_angle(objvars_ball->angle, &xdelta, &ydelta);
          ydelta = -ydelta;
          objvars_ball->angle = vg4->misc->angle_from_xy(xdelta, ydelta, NULL);
          retw = VG_COLL_RETURN_STOP;  /* stop moving object-instance (ball) */
        }
      }

      /* play hit-sound if collision */
      if (retw == VG_COLL_RETURN_STOP) { vg4->audio->play(objvars_ball->audc_hit, VG_FALSE, VG_FALSE); }

      return retw;
    }

At last the new file nw_connect.c with the function connect_to_nwserver() to connect to the network-server.

    nw_connect.c: connect to the network-server

    /* 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;  /* vdata is here just a string */

      /* 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, " - <%s>\n", 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 min_connections  min. connections
     * @param max_connections  max. connections
     * @return  local client-number or 0 = connection failed
     */
    int
    connect_to_nwserver(int min_connections, int max_connections)
    {
      char remhost[128], *ipsrv;
      int clnr;

      /* get IP of network-server */
      ipsrv = vg4->nw->get_ip(remhost, sizeof(remhost));
      if (ipsrv == NULL) { return 0; }
      printf("Found network-server \"%s\" [%s]\n", remhost, ipsrv);

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

      return clnr;
    }

<<Previous | Download | Next>>