marcus74
marcus74

Reputation: 31

QGraphicsScene crashes after even the tiniest update

I'm working currently on simple project in Qt. It is based on popular game "Ludo". But I've got annoying problem with making any changes on screen. Board drawing works nice, but it's enough to uncomment line with play() function and disaster guaranteed. Simply I'm not able to setPixmap of field in Player::turn() to show pawns' movement. Scene becomes plain and every drawn field disappears. On the other hand, game runs logically and correctly due to output. Unfortunately, it completely doesn't cooperate with GUI.

I attached images "before-after" and necessary code.

main.cpp

QApplication a(argc, argv);

Board *scene = new Board();

QGraphicsView *view = new QGraphicsView(scene);
view->resize(1200, 1000);
view->show();

scene->draw();
// scene->play();

return a.exec();

Board::play()

dice = new Dice();
unsigned int i=0;
while(true) {

    bool playMore = players.at(i)->turn(dice, players, fieldsToPlay);
    if(playMore) {

        i++;
        if(i == 4)
            i=0;
    }
    else break;
}

Player::turn()

int result;
std::cout << "Player " << colour << " ";

if(hasPawnOnField) {

    // TODO: check if field has pawn
    result = dice->roll();
    std::cout << result << '\n';

    while(result) {

        pawns.at(0)->currentField->set_Pixmap(QPixmap(":/img/border.png"));
        pawns.at(0)->currentField->setPawn(nullptr);
        bool finito = pawns.at(0)->move(fieldsToPlay, baseAndFinish);
        pawns.at(0)->currentField->setPawn(pawns.at(0));


        char col = pawns.at(0)->getColour();
        switch (col) {

            case 'b':
                pawns.at(0)->currentField->set_Pixmap(QPixmap(":/img/bluepawn.png"));
                break;
            case 'r':
                pawns.at(0)->currentField->set_Pixmap(QPixmap(":/img/redpawn.png"));
                break;
            case 'y':
                pawns.at(0)->currentField->set_Pixmap(QPixmap(":/img/yellowpawn.png"));
                break;
            case 'g':
                pawns.at(0)->currentField->set_Pixmap(QPixmap(":/img/greenpawn.png"));
                break;
        }

        if(finito) {

            finishedPawns++;
            delete pawns.at(0);
            pawns.erase(pawns.begin());
            hasPawnOnField = false;
            break;
        }

        result--;
    }
    if(finishedPawns == 4) // condition which ends the game
        return 0;
}
else {

   int attempts = 3;

   while(attempts) {

       result = dice->roll();
       std::cout << result << '\n';
       if(result == 6) {

           hasPawnOnField = true;
           if(start->getPawn()) {

              char col = start->getPawn()->getColour();
              switch (col) {

                case 'b':
                  players.at(0)->pawns.at(0)->setField(fieldsToPlay.at(0));
                  players.at(0)->pawns.at(0)->zero();
                  fieldsToPlay.at(0)->setPawn(players.at(0)->pawns.at(0));
                  fieldsToPlay.at(0)->set_Pixmap(QPixmap(":/img/bluepawn.png"));
                  break;
                case 'r':
                  players.at(1)->pawns.at(0)->setField(fieldsToPlay.at(12));
                  players.at(1)->pawns.at(0)->zero();
                  fieldsToPlay.at(12)->setPawn(players.at(1)->pawns.at(0));
                  fieldsToPlay.at(12)->set_Pixmap(QPixmap(":/img/redpawn.png"));
                  break;
                case 'y':
                  players.at(2)->pawns.at(0)->setField(fieldsToPlay.at(24));
                  players.at(2)->pawns.at(0)->zero();
                  fieldsToPlay.at(24)->setPawn(players.at(2)->pawns.at(0));
                  fieldsToPlay.at(24)->set_Pixmap(QPixmap(":/img/yellowpawn.png"));
                  break;
                case 'g':
                  players.at(3)->pawns.at(0)->setField(fieldsToPlay.at(36));
                  players.at(3)->pawns.at(0)->zero();
                  fieldsToPlay.at(36)->setPawn(players.at(3)->pawns.at(0));
                  fieldsToPlay.at(36)->set_Pixmap(QPixmap(":/img/greenpawn.png"));
                  break;
              }
           }
           pawns.at(0)->setField(start);
           start->setPawn(pawns.at(0));
           start->set_Pixmap(baseAndFinish.at(finishedPawns)->getPixmap());

           baseAndFinish.at(finishedPawns)->set_Pixmap(QPixmap(":/img/border.png"));
           break;
       }

       attempts--;
   }
}

Pawn::move()

passedFields++;

if(passedFields >= 48) {

    currentField = basFin.at(4+(passedFields-48));

    if(passedFields == FINISH) {
        FINISH--;
        delete basFin.at(basFin.size()-1);
        basFin.pop_back();
        return true;
    }

}
else
    currentField = fieldsToPlay.at( (startID+passedFields)%48);

return false;

Board.h

class Board : public QGraphicsScene {

Q_OBJECT

public:

Board();
virtual ~Board();
void draw();
void drawField(std::vector<Field *> &vec, Vector &current, Vector dir, QString image);
void play();

static const int rect_size = 70;

Vector directions[4] = {Vector (0, -rect_size),   //up
                        Vector (rect_size, 0),    //right
                        Vector (0, rect_size),    //down
                        Vector (-rect_size, 0) }; //left

private:
std::vector<Field *> fieldsToPlay;
std::vector<Player *> players;

std::vector<Field *> fieldsBlue;    //fields 0-3 basement, fields 4-7 finish
std::vector<Field *> fieldsRed;
std::vector<Field *> fieldsYellow;
std::vector<Field *> fieldsGreen;

Dice *dice;
QTimer *timer;
};

Scene before updating Pixmap

Scene after

What am I doing wrong? How to update my scene in runtime without crash?

Upvotes: 2

Views: 75

Answers (1)

G.M.
G.M.

Reputation: 12879

Rather than use an infinite loop you could make use of a QTimer (I see Board already has a QTimer * member so perhaps that was the intention all along). Rather than use an explicit timer you might want to use QTimer::singleShot to `nudge' the game one step forward at a time.

Firstly, make the player counter/index i a member of Board and initialise it and dice in the constructor...

Board::Board ()
    : dice(new Dice)
    , i(0)
{
     ...
}

Now change Board::play so that it makes a single call to Player::turn for the current player and rearms a timer if required...

void Board::play ()
{

    /*
     * Give the current player their turn.
     */
    bool playMore = players.at(i)->turn(dice, players, fieldsToPlay);
    if (playMore) {

        /*
         * The game hasn't yet finished so update the player index
         * and rearm the timer with a lambda to make another call
         * to play() in 1 second.
         */
        i++;
        if (i == 4)
            i=0;
        QTimer::singleShot(1000,
                           [this]()
                           {
                               play();
                           });
    }
}

I haven't tested the above but it at least shows the basic outline of what you should aim for.

Upvotes: 1

Related Questions