ANSI C - Custom 2D level array

Started by
6 comments, last by Vortez 9 years, 10 months ago

The last thread I made had no bites, probably because of how confusing it was. No problem, I wrote my own level editor in lua. At the moment I can parse levels but do nothing with them. Take this test level for example:


-- name of the level
name = "testlevel"
-- dimensions of the level
width = 20
height = 13
-- tile placement in the level (please keep proportional to the dimensions)
level = 
{
	{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
	{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
}

This lua script is the level, the numbers inside my level array represent tiles. I would have it parsed like so in my game:


int parse_level(const char* filename) {
/* initialize Lua */
	L = lua_open();
/* load Lua base libraries */
	luaL_openlibs(L);
/* load the Lua file */
	if(luaL_loadfile( L, filename) || lua_pcall(L, 0, 0, 0)) {
		printf("ERROR: something is wrong with the file!\n");
	}
/* parse level */
	open_level( "level.lua" );
	/* parse information */
		lua_getglobal(L, "name");
		const char *name = lua_tostring(L, -1);
		lua_getglobal(L, "width");
		int width = lua_tonumber(L, -1);
		lua_getglobal(L, "height");
		int height = lua_tonumber(L, -1);
	/* print information */
		printf("LEVEL INFORMATION\n");
		printf("name:	%s\n",name);
		printf("width:	%d\n",width);
		printf("height:	%d\n",height);
	/* parse the level */
		lua_getglobal(L, "level"); 
		int y, x;
		int level[height][width];
		printf("level:	\n");
		for (y = 1; y <= height; y++) 
		{ 
			lua_rawgeti(L, -1, y); 
			for (x = 1; x <= width; x++) { 
				lua_rawgeti(L, -1, x);
				level[y-1][x-1]=lua_tonumber(L, -1);
				/* print the level to console */
				if( x < width )
					printf("%d,",level[y-1][x-1]);
				else
					printf("%d\n",level[y-1][x-1]);
				lua_pop(L, 1); 
			} 
			lua_pop(L, 1); 
		}
/* close Lua */
	lua_close(L);
}

My level parses just fine, console will tell me the name, width, height, and display the numbers properly for level. However my issue now is getting these variables outside of my parse_level() function. It's easy to do that with the width and height, but I have an issue with the name because if I make it a char, then I get errors when I try to make it equal the const char * created in my function. The biggest issue though, is moving my two-dimensional level array outside of the function, I am gonna need help on that one.

Any ideas?

Thanks

Advertisement

Nevermind the fact I would probably use json (and jansson or cjson api to parse) to handle creating the level and reading it, for returning the level, I would suggest making life a little easier for you, and return the level as a int * pointer, and determine the location of level[y][x] as level[y*height + x], like this:


int* loadlevel()
{
int* level = NULL;
...
// get width and height
level = malloc(width * height);
// fill the level
for (y...) {
  for (x...) {
    level[y*height + x] = lua_data_stuff();
  }
}
 
return level;
}

I typically use a flat array for these kinds of things, IMO, it's easier to keep straight, but that's just my opinion.

Oh, and if you want to use char's, why not just cast from int to char before you store in the array?

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)


int open_level(const char* filename) {
/* initialize Lua */
	L = lua_open();
/* load Lua base libraries */
	luaL_openlibs(L);
/* load the Lua file */
	if(luaL_loadfile( L, filename) || lua_pcall(L, 0, 0, 0)) {
		printf("ERROR: something is wrong with the file!\n");
	}
}

int* level = NULL;

int run_level() { 
/* parse level */
	if( load_level == false ) { 
		open_level( "level.lua" ); 
		/* parse information */
			lua_getglobal(L, "name"); 
			const char *name = lua_tostring(L, -1); 
			lua_getglobal(L, "width"); 
			int width = lua_tonumber(L, -1); 
			lua_getglobal(L, "height"); 
			int height = lua_tonumber(L, -1); 
		/* print information */
			printf("LEVEL INFORMATION\n"); 
			printf("name:	%s\n",name); 
			printf("width:	%d\n",width); 
			printf("height:	%d\n",height); 
		/* parse the level */
			lua_getglobal(L, "level"); 
			int y, x; 
			/*int level[height][width]; */
			level = malloc(width * height);
			printf("level:	\n"); 
			for (y = 1; y <= height; y++) 
			{ 
				lua_rawgeti(L, -1, y); 
				for (x = 1; x <= width; x++) { 
					lua_rawgeti(L, -1, x); 
					level[y*height + x]=lua_tonumber(L, -1); 
					/*level[y-1][x-1]=lua_tonumber(L, -1); 
					/* print the level to console */
					/*if( x < width ) 
						printf("%d,",level[y-1][x-1]); 
					else 
						printf("%d\n",level[y-1][x-1]); */
					lua_pop(L, 1); 
				} 
				lua_pop(L, 1); 
			} 
		/* close Lua */
			lua_close(L); 
		load_level = true; 
	} 
/* operate level */
	
} 

right now I have things everywhere lol, I commented out the old level, and brought in the new level you mentioned. The game crashes upon starting up. Any ideas?

EDIT:

I saw that y*height + x gives me numbers all over the place. I instead made an integer, z, started it at 0, and progressed from there. Still crashed. width * height is 260, so shouldn't it be level[260]? I experimented by having only one specific level[] given a value, the highest I could go was level[79], after that the game crashed. Any ideas?

EDIT2:

fixed with malloc(sizeof(int) * width * height)

EDIT3:

Okay, it's all good, for now. Now I am at the point of placing them onscreen. When my levels were hard-coded at 2D arrays, I could easily execute the level like so:


/* put tiles onscreen */
	for( i = 0; i < 13; i++ )
		for( ii = 0; ii < 20; ii++ ) 
			switch( level[i][ii] )
			{
				case 1: 
					tile( ii*16, i*16 );
				break;
			}

However when I tried to do similar things for my new method, it either looks all messed out, or the tiles cover the entire screen. So I gotta think of something. Though if you got an idea that would be great.

You could probably get away with several things here. One being that you only really need the width of the level. The height could be calculated by dividing the total number of tiles by the width. With that said, if some tiles are missing from your map you could just fill them in with your default tile (or maybe a tile saying 'missing' or 'error'). I also suggest you store your maps as a 1d array as its cleaner, and often much faster to process in scripting languages. You can use the formula 'y * width + x' to access elements in the map much like you would in a 2d array. Also to display your map from top to bottom you'll want to do this:

for (unsigned int y = 0; y < mapHeight; y++) {
for (unsigned int x = 0; x < mapWidth; x++) {
DrawTile(y * mapWidth + x);
}
}

The game crashes every time I try the system y * mapWidth + x instead of z. Isn't what I have already a 1D array?


level = malloc(width * height);

Keep in mind that you are allocating ints, not byte, so you have to adjust for that:

int NumCells = width * height;

int *level = malloc(NumCells * sizeof(int));

for (y = 1; y <= height; y++) {
for (x = 1; x <= width; x++) {

this is bad too, array are zero based in c++, so you're off by one here, so, that's another bug. Try this instead:


for (y = 0; y < height; y++) {
    for (x = 0; x < width; x++) {
        int Indx = (y * width) + x;
        level[Indx]=lua_tonumber(L, -1);
    }
}

The reason that it crash is

a) The buffer is too small

b) you forgot to substract 1 from x and y in the loop

If you want to move the array outside the function, i suggest making a class, for example CLevelManager then store everything level related there. It would make a lot more sense that way and it would be easier to remember to free the allocated memory you new'ed in the destructor,

Ex:


class CLevelManager {
public:
    CLevelManager();
    ~CLevelManager();
private:
    int *Cells;
    
    int Width;
    int Height;

    void AllocateCells(int w, int h);
    void FreeCells();

    int  CalcOffset(int x, int y);
    bool IsCellValid(int x, int y);
public:
    void LoadLevel(char *fname);
    void FreeLevel();
    
    int  GetCell(int x, int y);
    void SetCell(int x, int y, int value);

    int GetWidth(){return Width;}
    int GetHeight(){return Height;}
};

CLevelManager::CLevelManager()
{
    Cells = NULL;
    Width = Height = 0;
}

CLevelManager::~CLevelManager()
{
    FreeLevel();
}

void CLevelManager::AllocateCells(int w, int h)
{
    // Free previous cells, if any
    FreeCells();

    // Calculate the buffer size
    int BufSize = w * h * sizeof(int);
    
    // Allocate the cells
    Cells = malloc(BufSize);
    
    // Initialize them
    ZeroMemory(Cells, BufSize)
}

void CLevelManager::FreeCells()
{
    if(Cells){
        free(Cells);
        Cells = NULL;
    }
}

void CLevelManager::LoadLevel(char *fname)
{
    // put your loading routine here
    // Width  = ;
    // Height = ;

    // AllocateCells(Width, Height);

    // set the cells here (loop)
}

void CLevelManager::FreeLevel()
{
    FreeCells();
}

bool CLevelManager::IsCellValid(int x, int y)
{
    if(!Cells || x >= Width || y >= Height)
        return false;

    return true;
}

int CLevelManager::CalcOffset(int x, int y)
{
    return (y * Width) + x);
}

int CLevelManager::GetCell(int x, int y)
{
    if(!IsCellValid(x, y))
        return -1;
 
    return Cells[CalcOffset(x, y)];
}

void CLevelManager::SetCell(int x, int y, int value)
{
    if(!IsCellValid(x, y))
        return;

    Cells[CalcOffset(x, y)] = value;
}

So, i coded this in 5 mins so please, don't yell at me if there's any bugs smile.png(it's just a draft), It should give you an idea about how you could wrap this up in it's own class.

EDIT: just added a few more lines, that should be enough to get you started.


int NumCells = width * height;

int *level = malloc(NumCells * sizeof(int));

brings the same results as:


level = malloc(sizeof(int) * width * height);

that number should actually be 1040 I think (at least that's what I get when I do printf("%d\n",sizeof(int) * width * height);), when I tried your system, the game still crashed after storing variables with (y * width) + x instead of the z thing I had, however what is more odd is they still printed to screen before crashing. Your class code looks cool, however C is not capable of using classes, though this code is still useful. Thanks

Once upon a time I had a game engine in C++ going that used a vector class named tile, and read .xml files for level placement. The best part about that engine was making levels, the worst part was everything else lol, so that's why I moved to C.

EDIT:

noticed that it may actually be 260 (0 - 259). disregard what I said lol. if I do int Indx = ((y * width) + x) - 21; then it works for now, that 21 may change based on the level's size but I have no idea right now.


The best part about that engine was making levels, the worst part was everything else lol, so that's why I moved to C.

I don't get it. You can still do almost everything in c++ as you are doing in c, but at least c++ give you classes, new, delete, ect

Most peoples refer to this as "C with classes".

Your life will be a lot easier if you learn oop properly.


int NumCells = width * height;
int *level = malloc(NumCells * sizeof(int));
brings the same results as:
level = malloc(sizeof(int) * width * height);

I know, i added it for readability, and in case you might need it later in the code for example.

And, for your little array problem, you have a map of 20x13 tiles, that make 260 tiles. Now, each tiles take 4 bytes (1 int) to represent so that's 260x4 = 1040 Bytes.

As long as you pass a value between 0 and 259 inclusive in the level[] bracket, you're fine. If not, your code have a bug because it read into junk memory.

If you want to represent the first tile with 1 instead of zero, you have to subtract one from the x and y in the (y*h)+x formula.

Ex:

for (y = 1; y <= height; y++){

for (y = 1; y <= height; y++){

level[((y-1)*height) + (x-1)]=lua_tonumber(L, -1);

}

}

C++ array are zero based, lua might start at one so that's why you're having bugs, so, for everything lua, you use 1 to x, in c/c++ use 0 to x-1.

My class might look cool but that's not it's purpose, it's purpose is to keep your code safe and organized.


CLevelManager LevelManager;
LevelManager.Load("MapName.txt");

...

// get a cell value
int val = LevelManager.GetCell(x-1, y-1);

// Set a cell value (this should not have to be called here, but in the LevelManager.Load() function instead)
LevelManager.SetCell(x-1, y-1, lua_tonumber(L, -1));

That's much more readable than a big function imo. The getter and setter prevent you from reading/writting to garbage memory, and if you forgot to release the memory allocated, the destructor have your back.

Same thing for the lua code, encapsulate that so it's easier to work with, that's what i did.

This topic is closed to new replies.

Advertisement