Pregunta

Estoy emprendiendo un juego usando una combinación de C++ en Visual Studios 2010 y Qt 4.7 (ambas ventanas).El juego es un clon de Battleship y se basa en entradas de consola.He creado mi interfaz gráfica de usuario como quiero que se vea, y en el lado de Qt en Qt Designer, mi interfaz gráfica de usuario consta de un diseño de cuadrícula de 10x10, que usa etiquetas para contener mapas de píxeles de las celdas del juego:

current look of the game

He nombrado minuciosamente cada etiqueta para representar su posición en la matriz 2d (es decir,mapa de flota => F_00 => F[0,0] => F[i],[j]).Puedo elegir manualmente qué mapa de píxeles me gustaría mostrar usando el editor de propiedades, pero me gustaría algo dinámico.

Utilizo una clase de mapa de actualización para volver a dibujar el tablero de juego después de que un jugador dispara, que se sigue almacenando en una matriz de caracteres.Me gustaría actualizar mis mapas de píxeles para cada uno, usando una función genérica de tipo getupdatearray.A medida que recorremos la matriz, se actualizará el mapa de píxeles asociado actualmente con las etiquetas individuales para que coincida con sus primos de la matriz.(decir F[5][6] = 'X' para hit, luego, cuando los bucles llegaran a esa posición en la matriz, actualizaría la cuadrícula de mapas de píxeles en F_56 para igualar hit.png, Reemplazo de la vacío.png.

Tengo una idea de cómo hacer el bucle que lograría esto, pero no estoy seguro de cómo haría para que el mapa de píxeles de cada etiqueta se parezca más a una función de tiempo de ejecución en comparación con la función de tiempo de compilación (estática).He leído sobre QPainter y otra clase de Qt que trata con imágenes, pero todavía me resulta difícil.

Una pregunta para cualquiera de ustedes: ¿cómo actualizo estos mapas de píxeles en función de una matriz 2D?

  1. estructura de bucle: puedo descubrirlo
  2. declaraciones de condición - puedo entender
  3. Sintaxis específica de qt que trata con etiquetas: novato, así que no tengo idea de cajero automático.

Aquí hay un pseudocódigo del tipo de cosas que estoy tratando de hacer con 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();
                }
            }
        }
    }
};

Luego, en mi ventana principal.cpp, incluyo map.h y digo:

// 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

Gracias de antemano

ACTUALIZAR:

Llegué al punto en el que puedo intercambiar mapas de píxeles presionando botones, pero me gustaría crear algo más dinámico.Quería usar un Qstring en el que coloco el nombre de la etiqueta que quiero cambiar agregando valores x y usando:

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

Cuando intento llamarlo usando:

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

...obviamente no funciona.¿Hay alguna forma de escribirlo en mayúsculas y minúsculas o utilizar el contenido de la cadena como nombre de Qlabel?

¿Fue útil?

Solución

Usted ingresó y nombró manualmente 200 widgets de etiquetas?Que nadie te llame flojo.:)

Según tu actualización, ahora saber como usar QLabel::setPixmap().Lo que tu pensar lo que necesita es obtener un puntero QLabel a partir de un nombre, que sería una combinación de dos cosas:

Si sigues este camino, terminarás con algo como:

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

¡Pero cuidado!Hay muchas cosas malas en este enfoque.

  • es quebradizo:¿Qué pasa si no hay ningún widget con ese nombre (caída misteriosa)?O peor aún, ¿y si hay múltiple ¿Los widgets con ese nombre y este código avanzan felizmente ignorantes de esa extraña condición que probablemente sea un error?

  • Es pobre programación orientada a objetos:Si bien hay algunos casos decentes para utilizar la conversión dinámica (o "downcasting"), generalmente indica una falla en un diseño.Usted sabe que todos los QLabels son QWidgets, pero no todos los QWidgets son QLabels... así que qobject_cast la llamada podría devolver NULL.Es sólo un punto más de fracaso.A veces no puedes evitar esto, pero en realidad no hay ninguna razón para que tu programa deba estructurarse de esa manera.

  • es terriblemente lento:Buscar un widget por su nombre es esencialmente una búsqueda recursiva ingenua.Si ha reservado un marco de widget separado para cada cuadrícula y solo busca eso, Qt tendrá que hacer 100 comparaciones de cadenas para encontrar el último elemento (es decir, 50 en el caso promedio).Imagínate limpiar la cuadrícula con un bucle... ¡ahora estás hablando de comparaciones de cadenas de 100*50!

Todas estas cosas son evitables.Así como es posible usar bucles para configurar las imágenes en los controles por nombre, es posible usar bucles para crear los widgets en primer lugar.Básicamente, dejaría el área del tablero de juego en blanco en la herramienta de diseño y luego crearía dinámicamente los controles con código... los adjuntaría al diseño con código... y guardaría los punteros a ellos en una matriz 2D.(En ese momento no accedería a ellos por el nombre de la etiqueta, los indexaría tal como está indexando su tablero).

Podrías crear tu propia clase derivada de QLabel (como un GameCell class) que contenía la información de la celda de su tablero y los métodos relacionados con ella.Entonces no necesitarías una serie de widgets de etiquetas en paralelo a una matriz que represente tu tablero.Simplemente tendrías una matriz de objetos que se encargaría de ambos aspectos de la implementación.


ACTUALIZAR:Como preguntaste detalles en los comentarios, aquí tienes una clase de 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;
};

Esto cumple una doble función al comportarse como un QWidget y al mismo tiempo mantener una parte del estado interno.Entonces un widget GameMap puede usar un QGridLayout de estas 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;
};

Si quisieras, podrías dejar espacios en tu diseño en el diseñador que deseas completar con el widget GameMap.O puede seguir adelante y hacer todo mediante programación.Para simplificar, simplemente colocaré dos tableros uno al lado del otro con un separador vertical en la superficie de un diálogo:

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;
};

No voy a escribir un juego completo aquí ni voy a hacer que parezca sofisticado.Eso es sólo lo esencial, y muestra cómo crear 200 etiquetas de forma programática:

simple battleship

Con mi código, obtener una GameCell a partir de una coordenada (x,y) no requiere un promedio de 50 comparaciones de cadenas.Debido a la naturaleza formalizada y predecible de las matrices 2D, la indexación en cells[x][y] solo requiere una única operación de multiplicación y una única operación de suma.No hay que abatirse y simplemente puedes escribir:

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

APÉNDICE:Crear un QWidget para cada celda de la cuadrícula no es necesariamente el camino a seguir en primer lugar.Algunos podrían considerarlo "peso pesado".Si tu juego se jugara en un gran espacio virtual de mosaicos, entonces podría ser demasiado lento.Quizás le resulte útil investigar QGraphicsGridLayout, que podría ser un enfoque más apropiado a largo plazo:

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

Sin embargo, usar QWidgets no será un gran problema con una cuadrícula de 10x10, así que si quieres seguir con eso, puedes hacerlo.Si vas a hacerlo de esa manera, ¡al menos no deberías colocarlos todos a mano!

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top