Qt: обновить растровую сетку со значениями двумерного массива

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

текущий вид игры

Я тщательно назвал каждую метку, чтобы представить ее положение в массиве 2d (например, карта флота => 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, в котором я помещаю имя метки, которую хочу изменить, добавляя значения x y, используя:

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

Когда я пытаюсь вызвать его, используя:

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

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


person Wuts.the.Martyr    schedule 01.12.2011    source источник


Ответы (1)


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

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

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

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

Но ОСТЕРЕГАЙТЕСЬ! В этом подходе много неправильного.

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

  • Это плохое ООП: хотя есть несколько достойных случаев использования динамического приведения (или "понижающего приведения"), обычно это указывает на недостаток в дизайне. Вы знаете, что все QLabels являются QWidgets, но не все QWidgets являются 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 этих ячеек GameCell:

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 меток программным способом:

простой линкор

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

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

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

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

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

person HostileFork says dont trust SE    schedule 01.12.2011
comment
Если у вас есть код, который не делает то, что вы ожидаете, вы должны указать его короткую версию в своем вопросе. Но сначала попробуйте получить простую программу, состоящую всего из одного QLabel, как в ссылке. Если это не сработает, то вопрос может быть сосредоточен на выяснении причин, не беспокоясь о сетках или досках!) - person HostileFork says dont trust SE; 01.12.2011
comment
@ HostileFork, да, я назвал 200 меток на самом деле с двух игровых досок, мне нравится идея создания сетки во время выполнения, но я не знаю, как это сделать, поскольку я новичок в Qt. Насколько я понимаю, если вы используете дизайнер Qt, вы стреляете себе в ногу в таких вещах. Как бы закодировать последний абзац, который вы упоминаете в своем посте? Я еще не нашел подходящий пример? - person Wuts.the.Martyr; 01.12.2011
comment
добавил изображение того, как выглядит текущая сетка, насколько по-другому было бы сделать это вручную с помощью кода? Мне очень нравится ваша идея, потому что именно этого я и добивался. Я пытался использовать имена меток для замены растровых изображений, но ваш метод кажется даже лучше, если честно. Большое вам спасибо, я очень надеюсь, что вы покажете мне, как это сделать, если вам нужен доступ к коду, я тоже могу это сделать, поскольку у меня есть собственный сервер. - person Wuts.the.Martyr; 01.12.2011
comment
@ user1067830 Я немного реорганизовал ваш вопрос и поместил обрезанное изображение в текст. Код, который вы предоставили, компилируется? (Я был бы удивлен, если бы это было так.) Это проект для школы или что-то, что вы делаете для развлечения? - person HostileFork says dont trust SE; 01.12.2011
comment
@HostileFork..Спасибо, пост выглядит намного чище!! Мне это нравится. На map.h, на данный момент это не более или менее псевдо, очевидно, что имя метки + использование [i][j] не будет работать, но я просто хотел показать идею, над которой я работал. Я хотел бы создать упомянутый вами массив с помощью виджетов и протестировать его с помощью двух тестовых кнопок, чтобы переключаться между ними, имитируя getboardupdate. Последний метод, который вы предложили, создает новый массив в каждом цикле? Спасибо за помощь. - person Wuts.the.Martyr; 01.12.2011
comment
Ух ты, ссылка doc.qt.nokia.com/stable/graphicsview-basicgraphicslayouts.html, есть именно то, что мне нужно, теперь осталось только интегрировать это в программу, весело провести время. Я уверен, что вернусь с вопросами о том, как сместить прямоугольные контейнеры, чтобы они находились в правильных местах в моем игровом пространстве. Спасибо - person Wuts.the.Martyr; 01.12.2011
comment
@Wuts.the.Martyr QGraphicsView немного новее, чем многие другие технологии Qt, вы можете найти меньше ресурсов о том, как его использовать, чем QWidget. Но это более подходит для такого рода проблем, чем массивы QLabels... вероятно, стоит вложений. Вы совершенно правы в том, что использование дизайнера Qt действительно ведет по определенному пути, который не подходит для многих вещей. Мой совет: не торопитесь и поработайте с основными примерами Qt, чтобы вы знали всю широту того, что там есть, и как использовать код для создания пользовательского интерфейса вместо инструмента! doc.qt.nokia.com/stable/all-examples.html - person HostileFork says dont trust SE; 01.12.2011
comment
Я бы сделал, если б мог. Я начал использовать SDL для создания своей игры, и у меня было несколько проблем. Итак, моя проектная группа выбрала Qt, в любом случае у нас не было достаточно времени, чтобы изучить и понять все дополнительные библиотеки, движки, IDE для завершения нашего проекта. Это просто недостаток структуры курса. При этом я постараюсь усвоить как можно больше, у меня есть две книги по Qt4 и Интернету, а также отличные сайты, подобные этому. Надеюсь, я смогу вернуться завтра с рабочей версией, чтобы, возможно, настроить ее так, чтобы она работала. Еще раз спасибо за помощь в этом. - person Wuts.the.Martyr; 01.12.2011
comment
Я только что перечитал ваш комментарий, как бы вы тогда сделали это с Qwidget? Я уже выбросил свою растровую сетку, чтобы, надеюсь, с ней было удобнее работать. - person Wuts.the.Martyr; 01.12.2011
comment
@Wuts.the.Martyr Я добавил немного кода, чтобы проиллюстрировать метод. Но вместо того, чтобы продолжать расширять этот вопрос, попробуйте задать вещи (более конкретные) в виде отдельных вопросов. И снова... потратьте некоторое время на примеры Qt! Qt Creator — хороший способ поэкспериментировать с ними, поскольку он автоматически копирует их и создает заново. qt.nokia.com/products/developer-tools - person HostileFork says dont trust SE; 01.12.2011
comment
эй, еще раз спасибо за всю вашу помощь, я дошел до того, что могу менять растровые изображения нажатием кнопок, но я хотел бы создать что-то более динамичное, я хотел использовать Qstring, в котором я помещаю имя метки, которую я хотите изменить использование путем добавления значений xy с помощью TR_position.append(QString::number(xvalue)); когда я пытаюсь вызвать его с помощью ui-›TR_position-›setPixmap(QPixmap(:/images/missspace.png)); это явно не работает, есть ли способ набрать регистр или использовать содержимое строки в качестве имени Qlabel? - person Wuts.the.Martyr; 06.12.2011
comment
Я обновил как ваш вопрос (чтобы включить вашу новую информацию), так и мой ответ (чтобы объяснить, как делать то, что вы хотите, и почему вы не должны делать это таким образом). Но, пожалуйста, переходите к вопросам в новых сообщениях с вопросами и отмечайте этот вопрос как отвеченный в какой-то момент. Подобные длинные ветки комментариев не рекомендуются именно потому, что их трудно разобрать для тех, кто придет и посмотрит на это позже ... и вы не хотите помещать слишком много тем на одну страницу вопросов и ответов. - person HostileFork says dont trust SE; 06.12.2011
comment
спасибо, вы опередили меня прошлой ночью, я надеялся изменить свой комментарий, так как я вернулся к массиву, обновляя сетку при каждом нажатии кнопки отправки координат. Я задал новый вопрос здесь - person Wuts.the.Martyr; 06.12.2011