Monday, February 20, 2012

Window's Console Game: Variable Sized Object

In the previous post I had talked a little bit about image ordering, and image transparency. The way the image data was held in memory was very quirky! We had to create an entire header file for each image, and each image had to be an entirely different structure definition due to arrays of data being different from image to image. I'd like to show you a method that makes use of some clever macros in order to achieve an interesting variable-sized image structure! The idea was actually brought to me by a friend of mine named Malcolm Smith. Here's the structure design for an object to be dynamically sized during run-time, in our case for images:

typedef struct _Image
{
  int width; 
int height;
unsigned char *chars;
unsigned char *colors;
} Image_;


In the above structure we have two pointers, one to chars and one to colors. These pointers will point to arrays of unsigned characters, which is the datatype that represents both colors and characters for the Window's console. In order to access these arrays, you should recall that the name of an array can be treated the same as a pointer to the first element in the array. So after we allocate space for our arrays we'll need to initialize chars and colors properly by setting them to point to the first element in our arrays.


In order to go about allocating our space properly we need to allocate the space of the Image_ structure, space for the width * the size of our character array * height, and finally space for our color array * width * height. Here's a call to malloc to this in one single swoop:


Image_ *image = (Image_ *)malloc( sizeof( Image_ ) +
                                  sizeof( width * height * sizeof( unsigned char ) +
                                  sizeof( width * height * sizeof( unsigned char ) );

The nice thing about this sort of allocation is that it isn't broken up into separate allocation calls, which speeds up the process since allocation is slow. This also keeps all the parts of memory in the same location in memory all adjacent to one another, as opposed to who knows where in memory when malloc is called multiple times, thus lessening memory fragmentation.


Now we have our problem of getting our chars and colors pointers to point to their proper locations in memory. We aren't going to set up a conventional array of pointers, like proper C would dictate. Instead we'll use a couple macros to help us index the array ourselves, thus saving some references and memory in the process. We'll make use of a few macros to take over the job from the compiler; observe:


#define unsigned char CHAR
#define unsigned char COL

#define PtrAdd( ptr, offset ) \
  (((char *)ptr) + offset)

#define CharAt( image, x, y ) \
  (CHAR *)(PtrAdd( image->chars, ((y) * image->width + (x)) * sizeof( CHAR ) ))

#define ColorAt( image, x, y ) \
  (COL *)(PtrAdd( image->colors, ((y) * image->width + (x)) * sizeof( COL ) ))


The first macro is a nice macro to have as it is generalized. It simply takes a pointer and an offset in bytes, then returns a pointer in memory that is ahead (or behind) the original pointer by the offset bytes. It's a macro for pointer addition/subtraction that works by typecasting the pointer supplied as a char, which is a byte in size, thus forcing the addition of the offset to work in chunks of 8 bits. As such the units of offset is in bytes. This can be used to initialize our chars and colors pointer!


// Initialize chars pointer to directly after Image_ struct
image->chars = PtrAdd( image, sizeof( Image_ ) );

// Initialize colors pointer to directly after chars array
image->colors = PtrAdd( image->chars, width * height * sizeof( CHAR );

Now that we can properly initialize our pointers in our struct, it's time to start making use of our CharAt and ColorAt macros to index our two arrays! First though lets initialize our arrays to zero with a looping function.


void ZeroImage( Image_ *image )
{
  int x, y; // used for indexing

  CHAR *thisChar;
  COL *thisColor;

  for(y = 0; y < image->height; y++)
  {
    for(x = 0; x < image->width; x++)
    {
      thisChar = CharAt( image, x, y );
      *thisChar = 0;
    }
  }

  for(y = 0; y < image->height; y++)
  {
    for(x = 0; x < image->width; x++)
    {
      thisColor = ColorAt( image, x, y );
      *thisColor = 0;
    }
  }
}

A function that loops through all elements of the chars and colors array is very easy to write with our macros. You might be able to guess how we're going to fill out our arrays. You can actually copy/paste the ZeroImage function and change the lines that assign a zero to lines that assign a character value. You can use the x and y iterators to index an array of image data as well! Here's an example function:



void ImageSet( Image_ *image, CHAR *charData, COL *colorData )
{
  int x, y; // used for indexing
  CHAR *thisChar;
  COL *thisColor;

  for(y = 0; y < image->height; y++)
  {
    for(x = 0; x < image->width; x++)
    {
      thisChar = CharAt( image, x, y );
      *thisChar = charData[(y * image->width) + x];
    }
  }

  for(y = 0; y < image->height; y++)
  {
    for(x = 0; x < image->width; x++)
    {
      thisColor = ColorAt( image, x, y );
      *thisColor = colorData[(y * image->width) + x];
    }
  }
}

Now all you'd need in order to implement this variable-sized image format is image data! You can write image data by hand, use some interesting tools, or parse the data from an image (like a BMP) yourself. Later on in another future post I may perhaps write about how to parse a BMP for it's image data. For now however, I suggest getting comfortable with the tools I've linked to.


Series on creating a Windows Console game:

1 comment:

  1. Thanks for doing this! Keep it up ^-^

    ReplyDelete

Note: Only a member of this blog may post a comment.