Advertisement

Client/Server Map Sending

Started by December 20, 2005 12:28 PM
9 comments, last by GameDev.net 19 years, 2 months ago
Hello. I've started a little client/server game. I think I have done well so far, clients can connect and move around and see all the other clients moving around. I have become quite stuck though, I am trying to send the map data to all the clients that connect. My map data is stored like this: char Map[2][2] = {1, 1, 1, 1}; The way I send packets is like this: sprintf(buffer, "%d", data); nlWrite(client, buffer, 128); And this is how I use the packets at the other end: sscanf(buffer, "%d", &stuff); Can some one please show me how I can loop through the map data, put it all into a packet. And then sscanf through each part and put it into a two dimensional array. Thanks for any help. Mousie. [Edited by - Mousie_kebabs on December 20, 2005 1:34:04 PM]
Just off the top of my head, but...

The best solution for this is to store your map as a one dimensional aray, not a 2D one. You can represent a 2D array in the form of a 1D array by doing something like [x + y * width] to index into the array, instead of just doing [y][x] to access it. I don't remember if this is the exact formula, but it shouldn't but hard to figure out or look up.

Once you have your map stored as a 1D array, you can easily send it across the network in its pure form.

If you insist on keeping it as a 2D array, your best bet would be to "convert" it to a 1D array in the same fashion you would access it, and then send it over the wire, and then re-convert it back to a 2D array.

If you REALLY want to get ugly, you can keep your 2D array and put deliminators into the feed, saying "go to the next row". That is an ugly solution though.

By the way, what type of game is this? It's probably a bad idea that you are sending the contents of the map to the user when they connect. It is of course perfectly acceptable in a lot of situation, however in a lot of situations it is not. It is also easily avoidable. Consider using a web server instead, and have your client connect to the web server to download the map contents - this will avoid your game from choking when people connect, and also increase scalability on your server in general.
FTA, my 2D futuristic action MMORPG
Advertisement
I just got most of it working before I read this, I've done it the same way you said it though.

It's going to be like an RPG thingy, it's tile based. I only send the map once and thats when a new user logs in.

All I have to do now is convert the 1D array back into a 2D one.

Thanks for your help.

Mousie :)
Please read the Forum FAQ. It tells you that, even though send() takes a char*, it doesn't actually need string data. If you have a Map[], you can send() that entire map as-is (as binary data). As long as it's only plain data, no pointers, it'll come out OK on the other end (as long as the byte-endian is the same).

Thus, you can send a map like so:

  struct MapCmd {    int cmd;    int width;    int height;  };  unsigned char Map[2][2] = { { 1, 2, }, { 3, 4, }, };  MapCmd cmd;  cmd.cmd = CMD_THIS_IS_A_MAP;  cmd.width = 2;  cmd.height = 2;  ::send( sock, (char const *)&cmd, sizeof( cmd ), 0 );  ::send( sock, (char const *)Map, sizeof(Map), 0 );


To receive it, you'd do like so:

  int cmdPart;  MapCmd cmd;  recv_all( sock, &cmdPart, sizeof( cmdPart ) );  switch( cmdPart ) {  case CMD_THIS_IS_A_MAP:    recv_all( sock, &cmd.width, sizeof( cmd )-sizeof( cmdPart ) );    if( cmd.width != 2 || cmd.height != 2 ) bad_data();    recv_all( sock, Map, cmd.width, cmd.height );    break;  }void recv_all( SOCKET sock, void * buf, size_t size ) {  size_t recvd = 0;  char * ptr = (char *)buf;  while( recvd < size ) {    int r = ::recv( sock, ptr, size-recvd, 0 );    if( r < 0 ) {      bad_connection();    }    ptr += r;    recvd += r;  }}

enum Bool { True, False, FileNotFound };
I had no idea you could send multi-dimensional arrays like that.... in fact, I don't even fully understand how that is working. I guess it really has been that long since I've messed with C++ and networking..... sorry for the misinformation.
FTA, my 2D futuristic action MMORPG
OK, this is bothering me... if you don't mind, could you please explain how the "Map" array is layed out into memory? I guess I never really learned about this before.... I know that in C++ a multi-D array is represented as something similar to a pointer to a pointer (and I know we can created a Multi-D array dynamically w/ a pointer-to-a-pointer, but I believe in C++ that is not how it's actually implemented). Let's say that we went with your values (unsigned char Map[2][2] = { { 1, 2, }, { 3, 4, }, };)....

OK, so here we have map[0][0] is 1, map[0][1] is 2, map[1][0] is 3, and map[1][1] is 4, correct?

What values are sitting at the first byte of memory at the position of "Map"? What about the second, third, and fourth bytes? Is it 1-2-3-4?

Thanks for any enlightenment.

PS - sorry for hijacking your thread. I can start a new one if you'd like.

EDIT: another question... if we started out as unsigned char **Map, and created this dynamically... would your method still work?
FTA, my 2D futuristic action MMORPG
Advertisement
OK, I think I figured this out... I busted out the dusty C++ part of VS and whipped this up:

#include <iostream>int main(){	unsigned char Map[2][2] = { { 1, 2, }, { 3, 4, } };	for(int i = 0; i < 2; ++i)	{		for(int j = 0; j < 2; j++)		{			std::cout << (*Map)[j + i * 2] << " <-- (*Map)["<<i<<" + "<<j<<" * 2]"<<std::endl;			std::cout << Map[j] << " <--- Map["<<i<<"]["<<j<<"] "<<std::endl;		}	}	std::cout << (char const *)Map << std::endl;	return 0;}


I was a little surprised at the output. So in actuality a 2D array is really just a 1D array and they are doing the math for you? For some reason I always thought it was represented differently in memory.
FTA, my 2D futuristic action MMORPG
Quote:
Original post by graveyard filla
OK, I think I figured this out... I busted out the dusty C++ part of VS and whipped this up:

*** Source Snippet Removed ***

I was a little surprised at the output. So in actuality a 2D array is really just a 1D array and they are doing the math for you? For some reason I always thought it was represented differently in memory.


It is laid out contiguously in memory, but the pointers that are created are not simply char* or char**, but in the OP's case it would be a char (*pt)[2]. The 2 would represent the 2nd dimension, so that it knows how many bytes to add when doing a ++.

Quote:

EDIT: another question... if we started out as unsigned char **Map, and created this dynamically... would your method still work?

Quote:

As long as it's only plain data, no pointers, it'll come out OK on the other end (as long as the byte-endian is the same).

emphasis added.
Quote:
Original post by nprz
It is laid out contiguously in memory, but the pointers that are created are not simply char* or char**, but in the OP's case it would be a char (*pt)[2]. The 2 would represent the 2nd dimension, so that it knows how many bytes to add when doing a ++.


I see... and what is "char (*pt)[2]"? That's an array of 2 pointers, right?


Quote:

emphasis added.


This confused me...by the "no pointers" comment, I had thought he meant that as long as you don't send actual pointer values across the net.. e.g., don't send 32942342323 (some memory address) because it won't do any good, you'll be fine.

But, if we have unsigned char **Map, wouldn't you be able to do this (::send( sock, (char const *)Map, sizeof(Map), 0 );) still?

Thanks again.
FTA, my 2D futuristic action MMORPG
Quote:
Original post by graveyard filla
I see... and what is "char (*pt)[2]"? That's an array of 2 pointers, right?

This confused me...by the "no pointers" comment, I had thought he meant that as long as you don't send actual pointer values across the net.. e.g., don't send 32942342323 (some memory address) because it won't do any good, you'll be fine.

But, if we have unsigned char **Map, wouldn't you be able to do this (::send( sock, (char const *)Map, sizeof(Map), 0 );) still?

Thanks again.


char (*pt)[2] would be a pointer to an array of size 2 of char. It could be used for a char a[x][2] where x is positive.

If you have a structure that contains a pointer that allocated dynamic memory, then you couldn't send the structure through a socket and have it be usable on the other side.
The send command that you would do would send a pointer (usually 4 bytes) and not the map. It would be sending some memory address because what else would be a char** besides a pointer to a pointer to a char? The compiler wouldn't know the size of the dynamically allocated arrays.

This topic is closed to new replies.

Advertisement