Jump to content
• ### What is your GameDev Story?

• Advertisement

Public Group

# Bejeweled clone // (was: Reference array [solved])

This topic is 4229 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

## Recommended Posts

struct field {
bool u; // 'Used' boolean
int j; // Type of jewel
};


// Game Functions
//

int anyMatches( field *check[][] ) // Returns the amount of matches found
{
int returnvalue = 0;

for( int i = 0; i < 6; i++ ) {
for( int j = 0; j < 6; j++ ) {
if( ( check[j].j == check[j+1].j ) && ( check[j].j == check[j+2].j ) ) {
check[j].u = check[j+1].u = check[j+2].u = false;
returnvalue++;
}
if( ( check[j].j == check[i+1][j].j ) && ( check[j].j == check[i+2][j].j ) ) {
check[j].u = check[i+1][j].u = check[i+2][j].u = false;
returnvalue++;
}
}
}
}


I'm programming a simple clone of Bejeweled. I want to pass a double array reference of field's and then check it through with a huge amount of if()'s .In my main game loop, I already have a field[][], so what I want to do is pass it on to this function, and then continue with the modified field (without nasty return values). I believe that#s the purpose of references, but I don't know how to use them correctly. Can someone tell me what the correct syntax for the argument in anyMatches() is? Thanks [Edited by - c4c0d3m0n on June 24, 2007 12:44:15 PM]

#### Share this post

##### Share on other sites
Advertisement
You should remove the * because arrays are always passed by pointer. For example, when you write:

int anyMatches( field check[][] ){    check[2][5] = ...;}// Later in another fuctionfield f[10][10];anyMatches(f);

Then check is actually a pointer to the array passed to the function so you can modify it from inside the function.

#### Share this post

##### Share on other sites
Short answer: C++'s built-in arrays don't really work right; use boost::array instead.

Long answer: When you pass the array, it "decays" to a pointer to the beginning element. You'll want to read all of this. Things are actually more complicated in the case of a 2-dimensional array: when passed, it decays to a *single* level of indirection (field*, not field**), because the 2-dimensional array is actually a single chunk of memory (the compiler knows at compile time what the "row length" is, so it can "collapse" each pair of indices into a single index into the chunk).

If you don't mind hard-coding the array size that needs to be passed (and in your case, you seem to be fine with that - given that the array bounds are also hard-coded), you can just do that; but honestly, things will be much, much easier for you if you just wrap up the playing field in a larger struct (which you can then pass by reference normally) - and at that point, you might as well make use of boost::array anyway.

But while I'm looking at your code:

1) Passing a pointer IS NOT passing by reference. C++ has real references; use them.

2) Don't write single-letter variable names and then explain them in a comment. Use a variable name that actually says what is being stored. It really won't kill you to do a little more typing each time, and it saves you from constantly referring back to comments (in who knows whatever other file) when you try to read a line like "if (a.u && b.u) { a.j = (a.j + b.j) % NUM_J; }".

3) Similarly, *think* about your names when they are full. A 'field' normally means a whole playing field, not a single element of it. Those are normally called something like 'cells'. A 'check' is what the function performs, not a proper name for the thing it's performed upon.

4) You might want to actually return your return value.

5) Even if the field size is "hard-coded", it still deserves a constant.

struct cell {    bool used;    int type;};const int fieldsize = 8;typedef boost::array<boost::array<cell, fieldsize>, fieldsize> field;// Mark any matched jewels as unused. Return the number of matched sets.int findMatches(field& f) {    int result = 0;        for (int i = 0; i < fieldsize - 2; ++i) {        for (int j = 0; j < fieldsize - 2; ++j) {            if ((f[j].type == f[j+1].type) &&                 (f[j].type == f[j+2].type)) {                f[j].used = f[j+1].used = f[j+2].used = false;                result++;            }            if ((f[j].type == f[i+1][j].type) &&                 (f[j].type == f[i+2][j].type)) {                f[j].used = f[i+1][j].used = f[i+2][j].used = false;                result++;            }        }    }    return result;}

Although we can actually be a little clearer by using nicely named references, and being more clever about the loop bounds:

// Mark any matched jewels as unused. Return the number of matched sets.int findMatches(field& f) {    int result = 0;        for (int i = 1; i < fieldsize - 1; ++i) {        for (int j = 1; j < fieldsize - 1; ++j) {            cell& current = f[j];            cell& above = f[i-1][j];            cell& below = f[i+1][j];            cell& left = f[j-1];            cell& right = f[j+1];            if ((current.type == above.type) &&                 (current.type == below.type)) {                current.used = above.used = below.used = false;                result++;            }            if ((current.type == left.type) &&                 (current.type == right.type)) {                current.used = left.used = right.used = false;                result++;            }        }    }    return result;}

#### Share this post

##### Share on other sites
Quote:
 Original post by ZahlmanShort answer: C++'s built-in arrays don't really work right; use boost::array instead.Long answer: When you pass the array, it "decays" to a pointer to the beginning element. You'll want to read all of this. Things are actually more complicated in the case of a 2-dimensional array: when passed, it decays to a *single* level of indirection (field*, not field**), because the 2-dimensional array is actually a single chunk of memory (the compiler knows at compile time what the "row length" is, so it can "collapse" each pair of indices into a single index into the chunk).

Hmm... I read some of that :) Looks very high level, not sure wether that's for me yet.. What's the difference between boost::array, a normal C++ array or a Vector?

Quote:
 If you don't mind hard-coding the array size that needs to be passed (and in your case, you seem to be fine with that - given that the array bounds are also hard-coded), you can just do that; but honestly, things will be much, much easier for you if you just wrap up the playing field in a larger struct (which you can then pass by reference normally) - and at that point, you might as well make use of boost::array anyway.

I rewrote my code so that the fieldsize is now defined as const int FIELD_SIZE = 8;

Quote:
 But while I'm looking at your code:1) Passing a pointer IS NOT passing by reference. C++ has real references; use them.

What is the difference? How do I do the latter? I have no experience in references nor pointers, I was just experimenting.

Quote:
 2) Don't write single-letter variable names and then explain them in a comment. Use a variable name that actually says what is being stored. It really won't kill you to do a little more typing each time, and it saves you from constantly referring back to comments (in who knows whatever other file) when you try to read a line like "if (a.u && b.u) { a.j = (a.j + b.j) % NUM_J; }".3) Similarly, *think* about your names when they are full. A 'field' normally means a whole playing field, not a single element of it. Those are normally called something like 'cells'. A 'check' is what the function performs, not a proper name for the thing it's performed upon.4) You might want to actually return your return value.

Fixed, fixed and fixed :) Couldn't find a good name, I guess cell is good. I forgot to return the returnvalue, probably because I was still looking at the other compiler errors.

Quote:
 5) Even if the field size is "hard-coded", it still deserves a constant.*** Source Snippet Removed ***Although we can actually be a little clearer by using nicely named references, and being more clever about the loop bounds:*** Source Snippet Removed ***

I gave it a constant as stated. I'll write some references though, it really does look cleaner :) I guess it's better to check 1..7 instead of 0..6 regarding the namegiving.

--------------------
My source codes:
// Bejeweled [main.cpp]// Rob Spoel//#include <cstdlib>#include "windows.h"#include "SDL/SDL.h" // Simple Directmedia Layer#include "SDL/SDL_image.h"#include "SDL/SDL_ttf.h"#include "SDL/SDL_mixer.h"#include <string>#include <sstream>#include <fstream>const int SCREEN_WIDTH = 480;const int SCREEN_HEIGHT = 480;const int SCREEN_BPP = 32;const int SQUARE_SIZE = 48; // The field exists of 8*8 SQUARE_SIZEd squares (px)const int FIELD_SIZE = 8;const int FRAMES_PER_SEC = 30;SDL_Surface *screen = NULL; // Screen surfaceSDL_Surface *bluej = NULL, *greenj = NULL, *orangej = NULL, *purplej = NULL, *redj = NULL, *silverj = NULL, *yellowj = NULL;SDL_Event event;#include "beclassed.h" // Classes#include "befunctioned.h" // Functionsint main( int argc, char* argv[] ){    if( !init() ) {        return -1;    }    if ( !loadFiles() ) {        return -1;    }    srand( time( 0 ) ); // Seed randomizer    bool quit = false; // Used to quit main loop    //Timer fps; // Used to regulate framerate    cell field[FIELD_SIZE][FIELD_SIZE]; // Playfield [y][x]    for( int i = 0; i < 8; i++ ) {        for( int j = 0; j < 8; j++ ) {            field[j].used = true;            field[j].jewel = rand() %7; // Random jewel (0..6)        }    }    while( !quit ) {        //fps.start(); // Start timer        SDL_FillRect( screen, &screen->clip_rect, SDL_MapRGB( screen->format, 0x63, 0x42, 0x18 ) ); // Fill the screen brown        for( int i = 0; i < 8; i++ ) {            for( int j = 0; j < 8; j++ ) {                SDL_Rect offset;                offset.x = ( SCREEN_WIDTH/2 - (FIELD_SIZE/2)*SQUARE_SIZE ) + j*SQUARE_SIZE; // Center                offset.y = ( SCREEN_HEIGHT/2 - (FIELD_SIZE/2)*SQUARE_SIZE ) + i*SQUARE_SIZE; // Center                offset.w = offset.h = SQUARE_SIZE;                if( (i+j)%2 == 1 ) { // Have each square get a different colour                    SDL_FillRect( screen, &offset, SDL_MapRGB( screen->format, 0xE7, 0xCE, 0xA5 ) ); // Dark                }                else {                    SDL_FillRect( screen, &offset, SDL_MapRGB( screen->format, 0x9C, 0x84, 0x5A ) ); // Light                }                switch( field[j].jewel * field[j].used ) {                    case 0 :                        if( field[j].used ) {                            applySurface( offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, bluej, screen );                        }                        break;                    case 1 :                        applySurface( offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, greenj, screen );                        break;                    case 2 :                        applySurface( offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, orangej, screen );                        break;                    case 3 :                        applySurface( offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, purplej, screen );                        break;                    case 4 :                        applySurface( offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, redj, screen );                        break;                    case 5 :                        applySurface( offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, silverj, screen );                        break;                    case 6 :                        applySurface( offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, yellowj, screen );                        break;                }            }        }        while( iMatches( field ) != 0 ) {}        while( SDL_PollEvent( &event ) ) { // While there are events to handle            if( event.type == SDL_QUIT ) { // If user Xed out of the window, switch quit flag				quit = true;			}        }        SDL_Flip( screen ); // Update screen        /*while( fps.get_ticks() < 1000/FRAMES_PER_SEC ) { // Resource-saver            Sleep( 1 );        }*/        Sleep( 1 );    } // End of main loop    return 0;} // End of main()
// Bejeweled [beclassed.h]// Classes for Bejeweled//enum jewel { BLUE, GREEN, ORANGE, PURPLE, RED, SILVER, YELLOW };struct cell {    bool used; // 'Used' boolean    int jewel; // Type of jewel};typedef boost::array<boost::array<cell, FIELD_SIZE>, FIELD_SIZE> field;
// Bejeweled [befunctioned.h]// Functions for Bejeweled//bool init(){	if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 ) { // Initialize all SDL subsystems		return false;	}	    //SDL_WM_SetIcon( SDL_LoadBMP( "data\\icon.bmp" ), NULL );    screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE ); // Set up the screen    if( screen == NULL ) {        return false;    }    if( TTF_Init() == -1 ) { // Initialize SDL_ttf        return false;    }    if( Mix_OpenAudio( 22050, MIX_DEFAULT_FORMAT, 2, 4096 ) == -1 ) { // Initialize SDL_mixer        return false;    }    SDL_WM_SetCaption( "Bejeweled v0.0", NULL ); // Set the window caption    return true;}SDL_Surface *loadImage( std::string filename ) // Function to load+optimize images{	SDL_Surface* loaded_image = NULL; // The image that's loaded	SDL_Surface* optimized_image = NULL; // The optimized image that will be used	loaded_image = IMG_Load( filename.c_str() ); // Load the image using SDL_image	if( loaded_image != NULL ) // If the image loaded	{		optimized_image = SDL_DisplayFormat( loaded_image ); // Create an optimized image		SDL_FreeSurface( loaded_image ); // Free the old image		if( optimized_image != NULL ) // If the image was optimized just fine		{			Uint32 colorkey = SDL_MapRGB( optimized_image->format, 0, 0xFF, 0xFF ); // Map the color key #00FFFF			SDL_SetColorKey( optimized_image, SDL_RLEACCEL | SDL_SRCCOLORKEY, colorkey ); // Set all pixels of color R 0, G 0xFF, B 0xFF to be transparent		}	}	return optimized_image;}void applySurface( int x, int y, SDL_Surface* source, SDL_Surface* destination ){	SDL_Rect offset; // Make a temporary rectangle to hold the offsets	offset.x = x; // Give the offsets to the rectangle	offset.y = y;	SDL_BlitSurface( source, NULL, destination, &offset ); // Blit the surface}void applySurface( int x, int y, int w, int h, SDL_Surface* source, SDL_Surface* destination ){	SDL_Rect offset; // Make a temporary rectangle to hold the offsets	offset.x = x; // Give the offsets to the rectangle	offset.y = y;	offset.w = w;	offset.h = h;	SDL_BlitSurface( source, NULL, destination, &offset ); // Blit the surface}bool loadFiles(){    bluej = loadImage( "data\\blue.bmp" );    greenj = loadImage( "data\\green.bmp" );    orangej = loadImage( "data\\orange.bmp" );    purplej = loadImage( "data\\purple.bmp" );    redj = loadImage( "data\\red.bmp" );    silverj = loadImage( "data\\silver.bmp" );    yellowj = loadImage( "data\\yellow.bmp" );}// Game Functions//int iMatches( cell& f ) // Returns the amount of matches found and marks matched jewels as !used{    int result = 0;        for( int i = 1; i < FIELD_SIZE-1; i++ ) {        for( int j = 1; j < FIELD_SIZE-1; j++ ) {            cell& current = f[j];            cell& above = f[i-1][j];            cell& below = f[i+1][j];            cell& left = f[j-1];            cell& right = f[j+1];                        if( ( current.jewel == above.jewel ) &&                ( current.jewel == below.jewel ) ) {                current.used = above.used = below.used = false;                result++;            }            if( ( current.jewel == left.jewel ) &&                ( current.jewel == right.jewel ) ) {                current.used = left.used = right.used = false;                result++;            }        }    }        return result;}
Don't mind the unused includes, I have them there since I am planning on using them in a later stage. The fps capping is commented out, since I was unable to use Timer.

When I compile, I get an error that boost hasn't been declared. All the following errors have to do with the iMatches() function.

#### Share this post

##### Share on other sites
Quote:
 Original post by c4c0d3m0nWhat's the difference between boost::array, a normal C++ array or a Vector?

Normal C++ arrays are, well, the horrible beasts you have been reading about.

boost::array is a class that provides the same basic interface as an array (i.e. you can get and set elements by using [index]es), but the convenience of working with an actual object (because you actually have one). That is, you can treat the type name (boost::array<elementtype, count>) the same way you treat 'int' or 'double', so specifying a reference to one is much more straightforward (it looks the same as specifying a reference to an int or double), and you never have to worry about weird things like pointer decay.

It's actually very easy to get the same effect by writing it yourself: basically, all they do is make a class which has an array of the appropriate type and size as a data member (this is where templating comes in - so that the one template can be instantiated for any kind of array you may need), and give it overloads for 'operator[]' which delegate to the array.

std::vector, although it's in the standard library rather than the third-party Boost libraries, provides a rather more complex behaviour. It's designed to behave like a *resizable* array. It does this by keeping a dynamic array-allocation on the heap somewhere, and remembering the size of its allocation (the 'capacity'), as well as the amount of that allocation that is actually in use (the 'size'). You can explicitly resize to a larger amount (causing the remaining slots to be filled in with default-constructed values) or implicitly by appending things to the end (with '.push_back()'), and if the size is about to exceed the capacity, the vector will automatically allocate a bigger chunk, copy all the elements over, put in the requested new elements, and then clean up the old chunk. It also cleans up its current memory chunk in the destructor, and does similar things in the copy constructor and assignment operator. The net effect is that you can use the vector as if the array elements were all just on the stack (rather than being in a dynamic allocation); the vector does all the "memory management" for you.

As you can imagine, implementing this one properly is considerably more involved. There's also no reason to, since the standard library already provides it.

Quote:
Quote:
 Passing a pointer IS NOT passing by reference. C++ has real references; use them.

What is the difference? How do I do the latter? I have no experience in references nor pointers, I was just experimenting.

References.

Out of curiousity, though, if you *don't* know any of this, then where did you find the term "reference" at all?

Quote:
 When I compile, I get an error that boost hasn't been declared.

Well, of course. The Boost libraries are third-party; they don't come with your compiler - you need to download some headers, and in some cases (not for boost::array though), do some installation work (i.e. compile some implementation files).

(Although, as mentioned before, it's really not that hard to do yourself:

template <typename T, size_t size>class array {  T storage[size];  public:  const T& operator[](size_t pos) const { return storage[pos]; }  T& operator[](size_t pos) { return storage[pos]; }};

If any of that doesn't make sense, see here.

And IIRC, boost::array *does* do a few other things besides that, but they're probably not things you'll desperately need.)

Quote:
 All the following errors have to do with the iMatches() function.

Don't pass a single cell to it; pass a field.

Anyway, just wanted to comment on this monstrousity:

                switch( field[j].jewel * field[j].used ) {                    case 0 :                        if( field[j].used ) {                            applySurface( offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, bluej, screen );                        }                        break;                    case 1 :                        applySurface( offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, greenj, screen );                        break;                    case 2 :                        applySurface( offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, orangej, screen );                        break;                    case 3 :                        applySurface( offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, purplej, screen );                        break;                    case 4 :                        applySurface( offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, redj, screen );                        break;                    case 5 :                        applySurface( offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, silverj, screen );                        break;                    case 6 :                        applySurface( offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, yellowj, screen );                        break;                }

We're using tricky arithmetic (multiplying by a boolean? o_O) that actually doesn't help us out (because we *still* have to handle a special case if the result is 0), and duplicating code (the only thing that changes is which jewel to use).

The natural way of letting a number specify which jewel image to use would be to put the jewel images into an array, and let the number index into the array:

// And again, a reference can help us...cell& c = field[j];if (c.used) {  applySurface(offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, jewels[c.jewel], screen);}

#### Share this post

##### Share on other sites
I came accross the term reference while creating my first ever classes, Card and Deck. I had some trouble building the deck and its functions, but passing a Card reference from the Deck array was the solution to everything. I thought I understood the principle, but I am proven wrong :)

I decided to use the hand built template, since I'm not in the mood for installing more stuff to my computer, it's getting a total fresh system soon anyway.

Everything seems to work great now, I'll continue developping my game :)

#### Share this post

##### Share on other sites

• Advertisement
• Advertisement
• ### What is your GameDev Story?

In 2019 we are celebrating 20 years of GameDev.net! Share your GameDev Story with us.

(You must login to your GameDev.net account.)

• ### Popular Now

• 9
• 31
• 16
• 11
• 10
• Advertisement
• ### Forum Statistics

• Total Topics
634122
• Total Posts
3015621
×

## Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!