Storing multiple levels data

Based on what I’ve learned so far, I figured I could store level data as an array:

const unsigned char MAP_A[10][60] PROGMEM = {
	{ 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,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,2,0,2,0,2,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,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,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 }
};

And then, like before, store a list of these levels:

const unsigned char * const MAP_DATA[] PROGMEM = {
	MAP_A
};

However, the compiler says I am full of shit and should go stand in the corner for a while. So, as far as I can muscle out it clearly hates that MAP_DATA is a single dimension array, and MAP_A is a two dimensional array. But really, when it comes down to it, I just want to store a pointer back to that original array and have this second array contain a list of those pointers. Or at least that’s what I think I want to do.

I figured I could just do MAP_A since MAP_A is supposed to be a pointer, right? But any time I try to do something like:

const byte *  map = MAP_A;

The compiler throws a fit, basically saying the same thing I’m having the trouble with up above. I’m sure this is complex stuff, but I’m also pretty sure whatever I’m doing wrong is pretty obvious to anyone more versed with this stuff. I’m going to keep reading and tinkering, but if someone wanted to lend me their experience I definitely wouldn’t turn you away!

Haha! I’ve learned something! So reading, reading, reading I realize now … to some extent what my mistake was. So, &MAP_A[0][0] points to the first location in memory of a 2D array. The issue was I kept thinking that including that meant I was trying to reach that specific indice rather than the location in memory, well I think I was right but the point of the & is to clarify that. So I broke up what I was trying to accomplish:

So I was able to get the memory address of the ‘line’ I wanted in that array:

const byte *level	= &MAP_A[ _y + Camera.startY ][ 0 ];

And then retrieve the proper value:

byte tile 	= static_cast<byte>( pgm_read_byte( level + _x + Camera.startX ) );

Which means that the whole issue with declaring the constant was that I should have done &MAP_A[0][0]. Which means it SHOULD be trivial to get that location… assuming I understand anything I’ve done. So, I seemed to have at least gotten a start on this, at least gotten it experimentally working, but I’d like to leave the question open if anyone wants to provide some wisdom on the subject.

Okay, so some more tinkering and I’ve sort of come to learn a smidgen more than I knew before. I was able to get this idea to work, and store the addresses to the different level data in an array and then access it. It seems really what I was getting hung up on is the idea of a 2d array in general. It seems that, in these circumstances, that I have to address the array by, essentially, using the original pointer and then doing math:

const byte *level	= &Player.level[ ( _y + Camera.startY ) * Camera.levelWidth ];

This will get me the position in memory that the ‘row’ I should be looking at is in, and then I can get the specific tile:

byte tile 	= static_cast<byte>( pgm_read_byte( level + _x + Camera.startX ) );

So, this does work. But more tests are needed. As a side effect, knowing this, I realized I can include the level data as a sort of header and just skip over it. More experimentation continues.

Pointers and arrays aren’t the same thing in C/C++. A pointer is a variable that holds an address, and operations on it work on the value of the variable. An array is a block of memory at some address, and operations on it work on that address. For 1D arrays, this is close enough that you can often get away with treating them as the same thing. For 2D, not so much. To further confuse matters, the values stored at the address in question have types that has to match.

According to cdecl, the two types you have in your first attempt are:

declare MAP_A as array 10 of array 60 of const unsigned char
declare MAP_DATA as array of const pointer to const unsigned char

So MAP_A is an array of arrays, basically a block of unsigned chars. MAP_DATA is an array of pointers. So your initial problem isn’t that MAP_A isn’t a pointer, it’s that what’s stored in it isn’t pointers.

The second one (const byte * map = MAP_A) is that what’s stored in MAP_A isn’t a byte, but an array of them. You need to declare map as a pointer to an array of const byte instead of a pointer to const byte. cdecl says (well, cdecl doesn’t grok byte, so I sub’ed int and and unsub’ed it here):

const byte (*map)[]

You may need to use MAP_A[0] (or identically, *MAP_A or 0[MAP_A]) instead of just MAP_A.

Declare your pointer of the right type, and the expressions for accessing them will get a lot simpler. If you’re having trouble working out the syntax (not uncommon; C/C++ is the only language I know of with a declaration syntax so confusing an expert felt the need to write a program to deal with it), install and use cdecl.

Level has the right type, but the expression you assign to it is overly complicated. &stuff[0] is the same thing as “stuff” (literally, it desugars to “&(*(stufff+0))”). So you should be able to say:

const byte *level	= MAP_A[ _y + Camera.startY ];

Don’t see that you tried that one.

Not sure what the last post is about - you never showed us Player.level. Also, it helps a lot if you show us the actual error messages, instead of just saying “it didn’t work”.

1 Like

So, most of that I have been able to wrestle out of the sea of documentation out there. I’m still not any good at it, but my test program generally fails when I think it might and it succeeds when I think it should. Generally. Not that I can fully comprehend why, but at least I have some kind of inkling that helps me squirm my way through. I’ll look into cdecl then if it helps with visualizing these expressions, maybe it will help with some other oddities I’ve experienced. It’s hard for me to visualize when something is a pointer, and what expects it, and whether or not it got what I thought I sent. Growing pains.

My last post was what I did to get it to work. Player.level is simply a pointer to whatever array of data should be used, and it does a bit of math to find the start of the row. Then when it goes to draw everything else, it works from there, I came to recognize what you were talking about: arrays are just a series of memory addresses of the type specified, so rather than thinking about it as being the indice itself, I am simply finding a pointer to that address. Which I’ll admit, I still can’t fully wrap my head around but I get the general idea. The “final” code looks like this:

void WORLD::drawWorld() {
	for ( byte _y = 0; _y < 9; _y++ ) {
		char y	= _y * 8 - Camera.trimY;
		const byte *line	= &level[ ( _y + Camera.startY ) * columns + MAP_OFFSET ];
		
		if ( _y < 0 || _y >= rows ) { continue; }
		
		for ( byte _x = 0; _x < 16; _x++ ) {
			byte tile 	= static_cast<byte>( pgm_read_byte( line + _x + Camera.startX ) );
			byte x		= _x * 8 - Camera.trimX;
			
			if ( tile > 0 ) { arduboy.drawBitmap( x, y, static_cast<byte>( pgm_read_byte( &( BLOCKS[ tile ] ) ) ), 8, 8, WHITE ); }
			
		}
		
	}
	
}

As for posting the error, well. The thing is, it’s two lines of code. It fails because I didn’t understand what I was doing wrong. Or to put it another way: the error message isn’t the problem. I was hoping someone, like yourself, would come along and post some insight. Solving the error is useless, understanding what’s causing it is what I wanted to learn about. Your experience is what I was looking for here and I appreciate you providing it.

Sometimes I like to write a ‘grid’ class to solve this sort of problem.
Like this:

template< int Width, int Height >
class PgmGrid
{
private:
	const uint8_t * grid;
	
public:
	PgmGrid(const uint8_t & grid)
	{
		this->grid = &grid;
	}

	uint8_t get(uint8_t x, uint8_t y) const
	{
		return pgm_read_byte(&this->grid[(y * Width) + x]);
	}
};

Or if you don’t have a bloody clue what the template bit is, you can do it like this:

class PgmGrid
{
private:
	const uint8_t * grid;
	const uint8_t width;
	const uint8_t height;
	
public:
	PgmGrid(const uint8_t & grid, uint8_t width, uint8_t height)
	{
		this->grid = &grid;
		this->width = width;
		this->height = height;
	}

	uint8_t get(uint8_t x, uint8_t y) const
	{
		return pgm_read_byte(&this->grid[(y * this->width) + x]);
	}
};

Strictly speaking height isn’t needed so you can remove that if you want, but it can be helpful for making things flexible, for example if you modified the code to be this:

class PgmGrid
{
private:
	const uint8_t * grid;
	const uint8_t width;
	const uint8_t height;
	
public:
	PgmGrid(const uint8_t & grid, uint8_t width, uint8_t height)
	{
		this->grid = &grid;
		this->width = width;
		this->height = height;
	}

	uint8_t get(uint8_t x, uint8_t y) const
	{
		return pgm_read_byte(&this->grid[(y * this->width) + x]);
	}
	
	uint8_t getWidth(void) const
	{
		return this->width;
	}
	
	uint8_t getHeight(void) const
	{
		return this->height;
	}
};

Then you can write flexible code like this:

for(uint8_t y = 0; y < grid.getHeight(); ++y)
{
	for(uint8_t x = 0; x < grid.getWidth(); ++x)
	{
		uint8_t data = grid.get(x, y);
		// work on data
	}
}

There’s a performance trade off for that flexibility, but it depends how big your game is going to be. Personally I like to avoid trading flexibility for size/speed until I’m close to the memory limit or things are measurably lagging.

Either way I think you should wrap some of your index calculations in functions so you don’t have to worry about it as much.

1 Like

Hmm, I ended up doing some of this, in that I wrote myself a function quite like what you wrote, but clearly I’m shite at it:

byte WORLD::getTile( byte x, byte y ) {
	return static_cast<byte>( pgm_read_byte( &level[ ( y * columns ) + x + MAP_OFFSET ] ) );
	
};

EDIT: Hmm, after reading over your stuff I realize now the const pointer is quite confusing. So basically, under your implementation grid is saying that it’s a POINTER to a const uint8_t array, not that it, itself, is read-only. C++ has some really, really confusing aspects. Some of it I can understand pretty easily, but there are other parts that really, really will need some time to digest.

Either way, I really appreciate your comments and examples. Taking them apart gives me things to look up from new directions.

EDIT9000- So my question then becomes about template. If templates are a way to generate … generalized classes then what’s the point of declaring it as specific types? This is my theory, so correct me if I understand your implementation wrong, but basically you would create the level doing something like

PgmGrid blocks<40,40>(grid);

Which, then, I assume when the level changed you would call delete on this one and create a new one? I guess that’s the part that confuses me the most, other than being a semi-generic data type, what’s the value of specifying it that way?

I think I’ll just send you a PM rather than hogging the forums to answer all your questions :P