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