VgaGames4 - tutorial

Tutorial 7 

screenshot.gif

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() and f_childexit() 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>>