ATpaul
ATpaul

Reputation: 49

Superclass method with subclass data

Good morning. Im learning some concepts about inheritance and consoles manipulation. Im pretty beginner as you could see.

So Im trying to have a single character drawn on a console and I want its position to be updated. Now please note that I know my code is probably very bad in multiple ways and that there are probably hundreds better completely alternative ways to do this, but I want to understand some inheritance concepts first and why it doesn't work the way it is.

So, I draw my player character "X" on the console, then I update its position calling a specific member method to move it. Now, because I made it that Player class expand DrawConsole class, I would like to call drawConsole on the Player instance.

When I do this, I have that playerA instance have its position coordinates actually updated, but the reference to the player instance have now two member called 'position', as you can see on the image. How can I say to choice the playerA one without completely remake the code or use a completely different approach? Or maybe simply I cant and I have actually complete change the approach? Hope I was able to comunicate what my doubt actually is.

Here is the code

#include <ctime>
#include <cstdlib>
#include "windows.h"

#define width 100
#define height 15

class StaticBuffer
{
public:
    StaticBuffer() { srand(time(0)); }

    void loadBackGround(CHAR_INFO *backGround, int swidth, int sheight)
    {

        for (int y = 0; y < sheight; y++)
        {
            int rnd = rand() % 100 + 1;
            for (int x = 0; x < swidth; x++)

                if (y == 0 || y == sheight - 1)
                {
                    backGround[y * swidth + x].Char.AsciiChar = (unsigned char)127;
                    backGround[y * swidth + x].Attributes = (unsigned char)23;
                }
                else if (x > 4 * rnd && x < (4 * rnd) + 5 || x > 4 * rnd / 2 && x < (4 * rnd / 2) + 5)
                {
                    backGround[y * swidth + x].Char.AsciiChar = (unsigned char)178;
                    backGround[y * swidth + x].Attributes = (unsigned char)12;
                }
                else
                {
                    backGround[y * swidth + x].Char.AsciiChar = 32;
                    backGround[y * swidth + x].Attributes = (unsigned char)3;
                }
        }

    }

private:
};

class DrawConsole
{
public:
    DrawConsole()
    {
        wConsole = GetStdHandle(STD_OUTPUT_HANDLE);
        rConsole = GetStdHandle(STD_INPUT_HANDLE);
        windowSizeInit = {0, 0, 30, 10};
        windowSize = {0, 0, bufferSize.X - 1, bufferSize.Y - 1};
        backGround = new CHAR_INFO[bufferSize.X * bufferSize.Y];
        obstacle = new CHAR_INFO[bufferSize.X * bufferSize.Y];
        inputBuffer = new INPUT_RECORD[4];
        drawBackGround.loadBackGround(backGround, bufferSize.X, bufferSize.Y);
        nInputWritten = 0;
        nOutputWritten = 0;
        playerString[0] = L'X';
        charLenght = 1;
        position = {10,13};
    }

    void drawConsole()
    {
        wConsole = GetStdHandle(STD_OUTPUT_HANDLE);
        rConsole = GetStdHandle(STD_INPUT_HANDLE);

        SetConsoleWindowInfo(wConsole, TRUE, &windowSizeInit);
        wConsole = GetStdHandle(STD_OUTPUT_HANDLE);

        SetConsoleScreenBufferSize(wConsole, bufferSize);
        SetConsoleWindowInfo(wConsole, TRUE, &windowSize);

        WriteConsoleOutputA(wConsole, backGround, bufferSize, {0,0}, &windowSize);
        WriteConsoleOutputCharacterW(wConsole, playerString, charLenght, position, &nOutputWritten);
    }

    void drawChar()
    {
        WriteConsoleOutputA(wConsole, backGround, bufferSize, {0,0}, &windowSize);
        WriteConsoleOutputCharacterW(wConsole, playerString, charLenght, position, &nOutputWritten);
    }


protected:
    HANDLE wConsole;
    HANDLE rConsole;

    COORD bufferSize{width, height};
    SMALL_RECT windowSizeInit;
    SMALL_RECT windowSize;

    CHAR_INFO *backGround;
    CHAR_INFO *obstacle;

    INPUT_RECORD *inputBuffer;
    DWORD nInputWritten;
    DWORD nOutputWritten;
    DWORD charLenght;

    StaticBuffer drawBackGround;
    wchar_t playerString[2];


    COORD position;
};

class Player :public DrawConsole
{
public:
    Player()
    {
        position.X = 20;
        position.Y = height - 2;
    }

    void movePlayerRight()
    {
        for (int i = 0; i < 3; i++)
            position.X += 1;
    }
    COORD getPositionC() { return position; }

private:

    COORD position;
};
Player *playerA = new Player;
DrawConsole *myConsole = new DrawConsole;

int main()
{
    myConsole->drawConsole();
    while (true)
    {
        //Sleep(5000);
        playerA->movePlayerRight();
        playerA->drawChar();
    }
} 

image

Upvotes: 0

Views: 69

Answers (1)

Ion Larra&#241;aga
Ion Larra&#241;aga

Reputation: 504

It depends on what you really want. If the idea is that both variables represent the same concept, you shouldn't have to re-define it in the derived class, because it is "protected" in the base class so the derived class is able to access it.

If the variables represent different things, but they happen to have the same name (which, by the way, would be a bad idea), you can qualify it with the class the variable has been defined in. So, for instance, you could do:

DrawConsole::position.X += 1;

To modify the position variable declared in DrawConsole and:

Player::position.X += 1;

To modify the position variable declared in Player

But, as I said before, I would try to avoid having two variables with the same name because it can easily result in errors.


UPDATE:

If you want to maintain the inheritance as is, just remove the attribute position from Player. The reason is as follows:

Currently, when you call drawChar, you are executing code that is in the DrawConsole class (Player itself does not define a drawChar method). This code cannot access Player::position because a method in a parent class cannot access an attribute in a child class (even if you are calling the method from an instance of the child class), so it only sees DrawConsole::position and that is the variable that it is using.

But when you call movePlayerRigth in an instance of Player, the code that is being executed is a method in the Player class. This method tries to access a position attribute and it finds out that there are two possibilities: DrawConsole::position and Player::position. In this case, it chooses Player::position because it is defined in the same class.

So, you have a method that draws the console based on DrawConsole::position and another method that modifies Player::position. This can't work and in fact if you run it, you will see that the X is not moving.

If you remove the position variable from Player, in movePlayerRight when you try to access the variable position, the code will see that Player does not define a position attribute, but it realizes that its parent class (DrawConsole) does indeed define a position attribute, and with protected access. Being protected means that code in child classes can access it directly, and so movePlayerRight will modify DrawConsole::position. In this case, both drawChar and movePlayerRight will access the same variable and it will work as expected.

So, if you want it this way, remove from the Player class the line:

COORD position;

And you will see that the code compiles and works as expected (the X moves right) because now the code in Player and the code in DrawConsole are accessing both the same variable (DrawConsole::position).

Upvotes: 2

Related Questions