2

Visual Studio 2010 と Qt 4.7 (両方のウィンドウ) で c++ の組み合わせを使用してゲームを行っています。ゲームは戦艦のクローンであり、コンソール入力ベースです。Qt デザイナーの Qt 側では、GUI は 10x10 のグリッド レイアウトで構成され、ラベルを使用してゲーム セルのピックスマップを保持します。

ゲームの現在の外観

2 次元配列での位置を表すために、各ラベルに念入りに名前を付けました (つまり、フリート マップ => F_00=> F[0,0]=> F[i],[j])。プロパティ エディターを使用して、表示するピックスマップを手動で選択できますが、動的なものが必要です。

update mapboard クラスを使用して、プレイヤーが発砲した後にゲーム ボードを再描画します。これは char 配列に保存され続けます。一般的な getupdatearray タイプの関数を使用して、それぞれのピックスマップを更新したいと思います。配列をトラバースすると、現在個々のラベルに関連付けられているピックスマップが更新され、配列のいとこに一致します。(F[5][6] = 'X'ヒットの場合、ループが配列内のその位置に到達すると、 F_56 のピックスマップのグリッドが更新され、 hit.png に等しくなり、 empty.pngが置き換えられます。

これを達成するループを作成する方法は考えていますが、各ラベルのピックスマップをランタイム機能と現在のコンパイル時 (静的) 機能の線に沿ったものにする方法がわかりません。QPainter と画像を扱う別の Qt クラスについて読んだことがありますが、まだ苦労しています。

2 次元配列に基づいてこれらのピックスマップを更新するにはどうすればよいですか?

  1. ループ構造 - 私は理解することができます
  2. 条件ステートメント - 私は理解することができます
  3. ラベルを扱うqt固有の構文-初心者なので、atmがわかりません。

これは、私が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

前もって感謝します

アップデート:

ボタンを押してピックスマップを交換できるようになりましたが、もっと動的なものを作成したいと思います。次を使用してxy値を追加することにより、変更したいラベルの名前を配置するQstringを使用したかった:

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

次を使用して呼び出そうとすると:

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

...それは明らかに機能しません。大文字と小文字を区別して入力する方法、または文字列の内容を Qlabel 名として使用する方法はありますか?

4

1 に答える 1

4

200 個のラベル ウィジェットを手動で入力して名前を付けましたか? 誰もあなたを怠け者と呼ばないでください。:)

更新により、 の使用方法がわかるようになりQLabel::setPixmap()ました。あなたが必要だと思うのは、名前から QLabel ポインターを取得することです。これは、次の 2 つの組み合わせになります。

この道をたどると、次のようになります。

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

しかし、注意してください!このアプローチには多くの問題があります。

  • もろい: その名前のウィジェットが存在しない場合 (謎のクラッシュ)? さらに悪いことに、その名前のウィジェットが複数あり、このコードがバグである可能性が高いその奇妙な状態を幸いなことに無知である場合はどうなるでしょうか?

  • OOP が貧弱です: 動的キャスト (または「ダウンキャスト」) を使用する適切なケースがいくつかありますが、通常は設計上の欠陥を示しています。すべての QLabels が QWidgets であることはわかっていますが、すべての QWidgets が QLabels であるとは限りません...そのため、そのqobject_cast呼び出しは NULL を返す可能性があります。もう1つの失敗点です。これを回避できない場合もありますが、実際には、プログラムをそのように構成する必要がある理由はありません。

  • 非常に遅い: ウィジェットを名前で検索するのは、本質的に単純な再帰検索です。グリッドごとに個別のウィジェット フレームを確保し、それのみを検索する場合、Qt は最後の要素を見つけるために 100 回の文字列比較を行う必要があります (平均的なケースでは 50 回)。ループでグリッドをクリアすることを想像してみてください...今度は 100*50 の文字列比較について話しているのです!

これらはすべて回避可能です。ループを使用してコントロール上の画像を名前で設定できるように、ループを使用して最初にウィジェットを作成することができます。基本的に、デザイン ツールでゲーム ボードの領域を空白のままにし、コードを使用して動的にコントロールを作成し、コードを使用してレイアウトにアタッチし、それらへのポインターを 2D 配列に保存します。(その時点では、ラベル名でそれらにアクセスすることはありません。ボードにインデックスを付けるのと同じように、それらにインデックスを付けます。)

GameCellボード セルの情報とそれに関連するメソッドを含む、QLabel から派生した独自のクラス (クラスなど) を作成できます。そうすれば、ボードを表す配列と並行して、ラベル ウィジェットの配列は必要ありません。実装の両方の側面を処理するオブジェクトの配列が 1 つあるだけです。


更新:コメントで詳細を尋ねたので、ここに 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 のように動作し、内部状態を維持するという 2 つの役割を果たします。次に、GameMap ウィジェットはこれらの GameCells の QGridLayout を使用できます。

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 ウィジェットで埋めたいデザイナーのレイアウトにスペースを残すことができます。または、すべてをプログラムで進めて実行することもできます。簡単にするために、2 つのボードを隣り合わせに配置し、ダイアログの表面に垂直セパレーターを配置します。

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 のラベルを取得する方法を示しています。

シンプルな戦艦

私のコードでは、(x,y) 座標から GameCell を取得するのに平均 50 回の文字列比較は必要ありません。2D 配列の形式化された予測可能な性質により、インデックス付けにcells[x][y]は 1 回の乗算操作と 1 回の加算操作のみが必要です。ダウンキャストはなく、単純に次のように記述できます。

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

補遺: グリッド セルごとに QWidget を作成することは、必ずしも最初から行う方法ではありません。それを「重い」と考える人もいるかもしれません。ゲームがタイルの大規模な仮想空間でプレイされている場合、非常に遅くなる可能性があります。QGraphicsGridLayoutを調べると役立つ場合があります。これは、長期的にはより適切なアプローチになる可能性があります。

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

ただし、QWidgets の使用は 10x10 グリッドではそれほど問題にならないため、それを使い続けたい場合は使用できます。そのようにする場合は、少なくともすべてを手動で配置するべきではありません。

于 2011-12-01T01:10:26.717 に答える