OOP style question

Started by
7 comments, last by c4c0d3m0n 16 years, 1 month ago
Hello I am programming a game using SDL. I have a very simple question regarding "proper" OOP style. Should I always have my classes send all information that is needed for drawing to the main() function and then draw it there, or is it ok to have a class with a function Draw() that I can call (using SDL_GetScreenSurface() and globals)?
Advertisement
Using globals is not contradictory with the rules of object-oriented design (using objects to represent your problem) but goes against the point of object-oriented programming (writing reusable and easily extensible code).

Here's a quick description: object-oriented programming is about objects sending messages to other objects. For instance, your Character object sends a message to your Renderer object explaining how it should be drawn. Now, what is important is how your character object knows which renderer object should receive the message.

  • The renderer is passed to the character as part of the process which involves sending the message. This is the highest level of reuse potential, since any user of a character object can decide what renderer will be used to draw the character.

  • The renderer is passed to the character when it is constructed. This is a medium level of reuse potential, since any creator of a character object can decide what renderer will be used to draw the character. A simple user of a character object, however, will have no choice but to use the associated renderer.

  • The renderer is hard-coded in the character class when it's implemented. This is a low level of reuse potential, since nobody can decide what renderer will be used to draw the character—that choice was made at implementation time, fixing forever the object that will be used.


You need to choose the level of reuse potential for every class that uses a renderer. If you don't intend to reuse a given class as part of this project or another, then the low level is acceptable. If there is any chance of reusing the class, then the medium level is necessary, and the high level would be even better if you intend to reuse it a lot.

Note that there is nothing wrong with creating a global variable—the only issue is hard-coding a global variable access inside a class, because this reduces the reuse potential of the class. You could of course create a global instance and then pass that global instance to your object using any of the other two reuse modes.
Passing the required information to the classes is better style in this situation. Globals are a sign of bad design and violate the locality prinicple.
Avoid globals when possible, they cause more problems than they solve.
Thanks fo the replies! I was asking because of the structure of my program, it looks so crazy:



main()
> main() creates Player
> > Player creates Tetrominoe
> > > Tetrominoe creates Matrix
> > Player creates Field
> > > Field creates Matrix
> main() calls Player.GetInfo()
> > Player calls Tetrominoe.GetInfo()
> > > Tetrominoe calls Matrix.GetInfo()
< < < Matrix passes info to Tetrominoe
< < Tetrominoe passes info to Player
> > Player calls Field.GetInfo()
> > > Field calls Matrix.GetInfo()
< < < Matrix passes info to Field
< < Field passes info to Player
< Player passes info to main()
> main() draws using info


It's crazy because most of these steps happen many many times, seeing getting the info from the Matrix happens in a few for's.
main is the program entry point. As such, its task should be to collect the initialization parameters from the standard arguments and the environment variables, and then pass them to a function or object representing the actual game.

Then, the actual game should update the state (this process is represented by a function or object), then display the state (this process is represented by another function or object).

However, there is no issue with these two subprocesses delegating their work to the Player and Piece objects they are handling.
"passes"....

Since I doubt you're using message-driven architecture, what exactly are you referring to.

If these are function calls, you have two terms: "call", "returns".

Can you show some code? Especially since, unless I'm mistaken, SDL is not OO, so proper OO isn't possible, you can merely emulate it.
Alright, I didn't know passing was soemthing different. I meant return. Why is it impossible to do OOP with SDL? What does a library need to be OOP-able?
Quote:Original post by Antheus
If these are function calls, you have two terms: "call", "returns".

A function call between objects is equivalent to message passing in proper object oriented parlance.

Quote:...unless I'm mistaken, SDL is not OO, so proper OO isn't possible...

Your ability to write an object oriented program is not limited by the libraries you employ. You simply create an object or objects to manage interface with said libraries.
Right now, my main() function does pretty much everything... I understand now I should make a seperate class for the Game and a seperate class for the Renderer. I tried tackling this issue, but it's over my head, I don't know where to start nor what to do. I have included my current main() function here, can someone please help me by telling me what should go where? Thanks in forward.



int main ( int argc, char* argv[] ){    if( !Init( &screen, SCREEN_WIDTH, SCREEN_HEIGHT ) ) {        printf( "Initialization failed, returning...\n" );        return -1;    }    // Seed the randomizer    srand( time(0) );    // Timestep variables    int t = 0;    const int dt = 1;    int current_time = SDL_GetTicks();    int accumulator = 0;    int new_time;    int d_time;    // LOADING STARTS HERE        SDL_Rect backgroundrect;    backgroundrect.x = backgroundrect.y = 0;    backgroundrect.w = SCREEN_WIDTH;    backgroundrect.h = SCREEN_HEIGHT;    // The field where the tetrominoes will end up    SDL_Rect fieldrect;    fieldrect.x = (SCREEN_WIDTH / 2) - (FIELD_WIDTH * BLOCK_SIZE / 2);    fieldrect.y = (SCREEN_HEIGHT / 2) - (FIELD_HEIGHT * BLOCK_SIZE / 2);    fieldrect.w = FIELD_WIDTH * BLOCK_SIZE;    fieldrect.h = FIELD_HEIGHT * BLOCK_SIZE;    // The little window that shows the upcoming tetrominoe    SDL_Rect nxtblockrect;    nxtblockrect.x = fieldrect.x + (BLOCK_SIZE * (FIELD_WIDTH + 1) );    nxtblockrect.y = fieldrect.y;    nxtblockrect.w = nxtblockrect.h = BLOCK_SIZE * 4;    // The player that does everything    Player player( FIELD_WIDTH, FIELD_HEIGHT );        // LOADING ENDS HERE    // MAIN LOOP STARTS HERE    bool quit = false;    while( !quit ) {                // TIMESTEP STARTS HERE        new_time = SDL_GetTicks();        d_time = new_time - current_time;        current_time = new_time;        accumulator += d_time;        while ( accumulator >= dt )        {            // Process here            player.Update( dt );            t += dt;            accumulator -= dt;        }        // TIMESTEP ENDS HERE        while ( SDL_PollEvent(&event) )        {            // If we try to close the program            if( event.type == SDL_QUIT ) {                printf( "Received exit-request, leaving main loop...\n" );                quit = true;            }            if( event.type == SDL_KEYDOWN ) {                // Escape quits                if( event.key.keysym.sym == SDLK_ESCAPE ) {                    printf( "Received exit-request, leaving main loop...\n" );                    quit = true;                }                // The arrow keys move the tetrominoe                if( event.key.keysym.sym == SDLK_LEFT )                    player.MoveLeft();                if( event.key.keysym.sym == SDLK_RIGHT )                    player.MoveRight();                // With A and Z we flip the tetrominoe                if( event.key.keysym.sym == SDLK_a )                    player.FlipCW();                if( event.key.keysym.sym == SDLK_z )                    player.FlipCCW();            }        } // END OF POLLING        // DRAWING STARTS HERE        // Background        SDL_FillRect( SDL_GetVideoSurface(), &backgroundrect,                      SDL_MapColor( screen->format, color[blackc] ) );        // Border around field        for( int i = -1; i < FIELD_HEIGHT + 1; i++ ) {            for( int j = -1; j < FIELD_WIDTH + 1; j++ ) {                block[8].Draw( (j * BLOCK_SIZE) + fieldrect.x,                               (i * BLOCK_SIZE) + fieldrect.y,                               SDL_GetVideoSurface() );            }        }        // Field        for( int i = 0; i < FIELD_HEIGHT; i++ ) {            for( int j = 0; j < FIELD_WIDTH; j++ ) {                block[player.GetFieldXY( j ,i )].Draw( (j * BLOCK_SIZE) + fieldrect.x,                                                       (i * BLOCK_SIZE) + fieldrect.y,                                                       SDL_GetVideoSurface() );            }        }        // Border around next plane        for( int i = -1; i < 5; i++ ) {            for( int j = -1; j < 5; j++ ) {                block[8].Draw( (j * BLOCK_SIZE) + nxtblockrect.x,                               (i * BLOCK_SIZE) + nxtblockrect.y,                               SDL_GetVideoSurface() );            }        }        // Next plane        for( int i = 0; i < 4; i++ ) {            for( int j = 0; j < 4; j++ ) {                block[player.GetNextXY( j ,i )].Draw( (j * BLOCK_SIZE) + nxtblockrect.x,                                                      (i * BLOCK_SIZE) + nxtblockrect.y,                                                      SDL_GetVideoSurface() );            }        }        // Current Tetrominoe        for( int i = 0; i < 4; i++ ) {            for( int j = 0; j < 4; j++ ) {                if( player.GetCurrXY( j, i ) )                    block[player.GetCurrXY( j, i )].Draw( ( j * BLOCK_SIZE ) + fieldrect.x + ( player.GetPosX() * BLOCK_SIZE ),                                                          ( i * BLOCK_SIZE ) + fieldrect.y + ( player.GetPosY() * BLOCK_SIZE ),                                                          SDL_GetVideoSurface() );            }        }        // DRAWING ENDS HERE        SDL_Flip( SDL_GetVideoSurface() );    } // END OF MAIN LOOP    return 0;}


All variables that are in ALL_CAPS are global constants. *screen is a global SDL_Surface. block[] is a global array of Block's. I also included my Block class.

enum typesoftetros { none, I, J, L, O, S, T, Z };class Block{private:    SDL_Rect borderrect;    SDL_Color borderclr;    SDL_Rect lightrect;    SDL_Color lightclr;    SDL_Rect mainrect;    SDL_Color mainclr;public:    Block() {}    Block( int type, int size );    void Draw( int x, int y, SDL_Surface *surface );};// Fancy constructor that actually has a pointBlock::Block( int type, int size ){    borderrect.w = borderrect.h = size;    lightrect.w = lightrect.h = size - 2;    mainrect.w = mainrect.h = size - 4;    // All types of tetrominoes    if( type == I ) {        borderclr = color[redc];        lightclr = color[reda];        mainclr = color[redb];    }    else if( type == J ) {        borderclr = color[whitec];        lightclr = color[whitea];        mainclr = color[whiteb];    }    else if( type == L ) {        borderclr = color[purplec];        lightclr = color[purplea];        mainclr = color[purpleb];    }    else if( type == O ) {        borderclr = color[bluec];        lightclr = color[bluea];        mainclr = color[blueb];    }    else if( type == S ) {        borderclr = color[greenc];        lightclr = color[greena];        mainclr = color[greenb];    }    else if( type == T ) {        borderclr = color[brownc];        lightclr = color[browna];        mainclr = color[brownb];    }    else if( type == Z ) {        borderclr = color[orangec];        lightclr = color[orangea];        mainclr = color[orangeb];    }    // For border blocks of type -1 (Border blocks)    else if( type == -1 ) {        borderclr = color[blackc];        lightclr = color[blackb];        mainclr = color[blackc];    }    // For blocks that don't belong to any tetrominoe    else {        borderclr = color[blackc];        lightclr = color[blacka];        mainclr = color[blackb];    }}void Block::Draw( int x, int y, SDL_Surface *surface ){    borderrect.x = x;    borderrect.y = y;    lightrect.x = x + 1;    lightrect.y = y + 1;    mainrect.x = x + 2;    mainrect.y = y + 2;    SDL_FillRect( surface, &borderrect, SDL_MapColor( surface->format, borderclr ) );    SDL_FillRect( surface, &lightrect, SDL_MapColor( surface->format, lightclr ) );    SDL_FillRect( surface, &mainrect, SDL_MapColor( surface->format, mainclr ) );}


color[] is an array of SDL_Color's. I used enumerations for the names of the colours. Each block consists of 3 rectangles in each other with colour variations to obtain a pretty effect. If you are confused, take a look at the blocks, I used them in a deviation on DeviantArt: [link]

This topic is closed to new replies.

Advertisement