Qt: обновить макет сетки растрового изображения со значениями двухмерного массива - PullRequest
1 голос
/ 01 декабря 2011

Я занимаюсь игрой, использующей комбинацию c ++ в Visual Studio 2010 и Qt 4.7 (оба окна).Игра представляет собой клон линкора и основана на консольном вводе.Я создал свой графический интерфейс так, как я хочу, чтобы он выглядел, и на стороне Qt в Qt конструкторе мой графический интерфейс состоит из сетки 10х10 с использованием меток для хранения растровых изображений игровых ячеек:

current look of the game

Я тщательно назвал каждую метку для обозначения своей позиции в 2-мерном массиве (т. Е. Карта флота => F_00 => F[0,0] => F[i],[j]).Я могу вручную выбрать то растровое изображение, которое я хотел бы отобразить, используя редактор свойств, но мне бы хотелось что-то динамическое.

Я использую класс update mapboard для перерисовки игрового поля после того, как игрок выстрелил, который хранится надмассив символовЯ хотел бы обновить свои растровые изображения для каждого, используя универсальную функцию типа getupdatearray.Когда мы пересекаем массив, он будет обновлять растровое изображение, связанное в настоящее время с отдельными метками, чтобы соответствовать их кузенам из массива.(скажем, F[5][6] = 'X' для попадания, затем, когда петли дойдут до этой позиции в массиве, он обновит сетку растровых изображений в F_56 до значения hit.png , заменив empty.png .

У меня есть идея, как сделать цикл, который бы достиг этого, но не уверен, как бы я хотел получить растровое изображение для каждой метки, чтобы оно было больше похоже на функцию времени выполнения по сравнению с временем компиляции(статическая) особенность. Я читал о QPainter и другом классе Qt, который работает с изображениями, но все еще испытывает затруднения.

Вопрос к любому из вас, как мне обновить эти растровые изображения на основе 2dмассив?

  1. структура цикла - я могу выяснить
  2. условных операторов - я могу выяснить
  3. qt специфический синтаксис, связанный с метками - новичок, так что понятия не имею.

Вот некоторый псевдокод того, что я пытаюсь сделать с map.h:

#include <QtCore>
#include <QtGui>

// WARNING: PSEUDOCODE, DOES NOT COMPILE
// AT A LOSS ON HOW TO SELECT THE CORRECT LABEL
// MAYBE A CHILD CLASS FOR THAT?

class map {
public:
    char updateboard(char mapname, char b[][10]){
        for(int i=0;i<10;i++){
            for(int j=0;j<10;j++){
                char C = b[i][j]; 
                if (C == 'M'){//miss
                    Qlabel mapname_[i][j](<img src='images/missspace.png'/>);
                    mapname_[i][j].show();
                } 
                else if(C == 'X'){//hit
                    Qlabel mapname_[i][j](<img src='images/hitspace.png'/>);
                    mapname_[i][j].show();
                }
                else if(C == ' '){//undiscovered space
                    Qlabel mapname_[i][j](<img src='image/emptyspace.png'/>);
                    mapname_[i][j].show();
                }
            }
        }
    }
};

Затем в свой mainwindow.cpp я включаю map.h и говорю:

// calls class function update board
// takes updated array values and replaces old pixmap with new

map.updateboard(T,b[][10]); // target map update
map.updateboard(F,v[][10]); // fleet map update

Заранее спасибо

ОБНОВЛЕНИЕ:

Я дошел до того, что я могу поменять местами растровые изображения нажатием кнопок, но я хотел бы создать что-то более динамичное.Я хотел использовать строку Qstring, в которую я помещаю имя метки, которую я хочу изменить, добавляя значения xy с помощью:

TR_position.append(QString::number(xvalue));

Когда я пытаюсь вызвать его с помощью:

ui->TR_position->setPixmap(QPixmap(":/images/missspace.png"));

... это явно не работает.Есть ли способ набрать регистр или использовать содержимое строки в качестве имени Qlabel?

1 Ответ

3 голосов
/ 01 декабря 2011

Вы ввели вручную и назвали 200 виджетов ярлыков ? Пусть никто не называет тебя ленивым. :)

Согласно вашему обновлению, вы знаете, как использовать QLabel::setPixmap(). вы думаете, , что вам нужно, - это получить указатель QLabel из имени, которое будет сочетать две вещи:

Если вы пойдете по этому пути, вы получите нечто вроде:

QWidget* cellWidget = ui->findChild(TR_position);
QLabel* cellLabel = qobject_cast<QLabel*>(cellWidget);
cellLabel->setPixmap(QPixmap(":/images/missspace.png"));

Но ВНИМАНИЕ! В этом подходе много неправильного.

  • Хрупко : Что если не будет никакого виджета с таким именем (таинственный сбой)? Или, что еще хуже, что если есть несколько виджетов с таким именем, и этот код блаженно не замечает этого странного условия, которое, вероятно, является ошибкой?

  • Это плохой ООП : Хотя есть некоторые достойные случаи использования динамического приведения (или «снижения»), это обычно указывает на недостаток в дизайне. Вы знаете, что все QLabels - это QWidgets, но не все QWidget - это QLabels ... так что вызов qobject_cast может вернуть NULL. Это просто еще одна точка отказа. Иногда вы не можете избежать этого, но на самом деле нет причин, по которым ваша программа должна быть структурирована таким образом.

  • Это ужасно медленно : Поиск виджета по его имени по сути является наивным рекурсивным поиском. Если вы отложили отдельный фрейм виджета для каждой сетки и выполняете только поиск, Qt придется выполнить 100 сравнений строк, чтобы найти последний элемент (т.е. 50 в среднем случае). Представьте себе очистку сетки с помощью цикла ... теперь вы говорите о 100 * 50 сравнениях строк!

Все эти вещи можно избежать. Так же, как можно использовать циклы для установки изображений на элементах управления по имени, можно использовать циклы для создания виджетов в первую очередь. Вы обычно оставляете область для игровой доски пустой в инструменте дизайна, а затем динамически создаете элементы управления с кодом ... присоединяете их к макету с кодом ... и сохраняете указатели на них в 2D-массиве. (Вы не сможете получить к ним доступ по имени ярлыка, вы будете индексировать их так же, как индексируете свою доску.)

Вы можете создать свой собственный класс, производный от QLabel (например, класс GameCell), который будет содержать информацию для ячейки вашей доски и связанные с ней методы. Тогда вам не понадобится массив виджетов с ярлыками параллельно массиву, представляющему вашу доску. У вас просто будет один массив объектов, который позаботится об обоих аспектах реализации.


ОБНОВЛЕНИЕ : Поскольку вы задали вопрос в комментариях, вот класс GameCell:

class GameCell : public QLabel
{
    Q_OBJECT    

public:
    enum State { Undiscovered, Hit, Miss };

    GameCell (QWidget *parent = 0) : QLabel (parent),
        currentState (Undiscovered)
    {
        syncBitmap();
    }

    State getState() const { return currentState; }

    void setState(State newState) {
        if (currentState != newState) {
            currentState = newState;
            syncBitmap();
        }
    }

private:
    void syncBitmap() { // you'd use setPixmap instead of setText
        switch (currentState) {
        case Undiscovered: setText("U"); break;
        case Hit: setText("H"); break;
        case Miss: setText("M"); break;
        }
    }

    State currentState;
};

Это делает двойную обязанность, ведя себя как QWidget, а также поддерживая часть внутреннего состояния. Затем виджет GameMap может использовать QGridLayout из следующих GameCells:

class GameMap : public QWidget {
    Q_OBJECT

public:
    static const int Rows = 10;
    static const int Columns = 10;

    GameMap (QWidget* parent = 0) :
        QWidget (parent)
    {
        layout = new QGridLayout (this);
        for (int column = 0; column < Columns; column++) {
            for (int row = 0; row < Rows; row++) {
                GameCell* cell = new GameCell (this);
                cells[column][row] = cell;
                layout->addWidget(cell, row, column);
            }
        }
    }

private:
    GameCell* cells[Columns][Rows];
    QGridLayout* layout;
};

Если вы хотите, вы можете просто оставить пробелы в макете в конструкторе, который вы хотите заполнить виджетом GameMap. Или вы можете нажать и сделать все это программно. Для простоты я просто положу две доски рядом с вертикальным разделителем на поверхности диалога:

class Game : public QDialog
{
    Q_OBJECT

public:
    Game (QWidget *parent = 0)
        : QDialog(parent)
    {
        targetMap = new GameMap (this);
        fleetMap = new GameMap (this);

        verticalSeparator = new QFrame (this);
        verticalSeparator->setFrameShape(QFrame::VLine);
        verticalSeparator->setFrameShadow(QFrame::Sunken);

        layout = new QHBoxLayout (this);
        layout->addWidget(targetMap);
        layout->addWidget(verticalSeparator);
        layout->addWidget(fleetMap);
        setLayout(layout);

        setWindowTitle(tr("Battleship"));
    }

private:
    GameMap* targetMap;
    QFrame* verticalSeparator;
    GameMap* fleetMap;
    QHBoxLayout* layout;
};

Я не собираюсь писать здесь целую игру или делать ее причудливой. Вот только суть, показывающая, как получить 200 ярлыков программным способом:

simple battleship

С моим кодом, получение GameCell по координате (x, y) не требует в среднем 50 сравнений строк. Из-за формализованного и предсказуемого характера двумерных массивов индексирование в cells[x][y] требует только одной операции умножения и одной операции сложения. Там нет уныния, и вы можете просто написать:

cells[x][y].setState(GameCell::Miss);

ADDENDUM : Создание QWidget для каждой ячейки сетки не обязательно является первым шагом. Некоторые могут считать это «тяжеловесом». Если ваша игра разыгрывается на большом виртуальном пространстве тайлов, то она может быть слишком медленной. Возможно, вам будет полезно взглянуть на QGraphicsGridLayout , который может быть более подходящим подходом в долгосрочной перспективе:

http://doc.qt.io/qt-5/qtwidgets-graphicsview-basicgraphicslayouts-example.html

Использование QWidgets не будет большой проблемой для сетки 10х10, поэтому, если вы хотите просто придерживаться этого, то можете. Если вы собираетесь делать это таким образом, то, по крайней мере, вам не следует размещать их все вручную!

...