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


Example

screenshot1.gif
screenshot2.gif
screenshot3.gif

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