Qtのジョンコンウェイの「人生」

こんにちは{{username}}!







今日は、QtでJohn Conway によるみんなのお気に入りのLifeゲームを実装する方法を示したいと思います。 ウィジェットに書き込みます。 このプロジェクトの例を使用して、QPainter、コアのいくつかのクラス、レイアウト、および一般的にQt Widgetsのグラフィックスを操作する方法を示します。 このゲームに興味があるか、Qtでグラフィックを操作している人は、読んでください。 一般的に、この記事は初心者を対象としていますが、上級者も読むべきことがあります:)。



怠Toに- ここにプロジェクトのソースがあります。 すぐにビルドできます。コアへの依存関係、gui。





アイデア



Qt GUIのすべてのルールを使用してConwayのGame Of Lifeを実装します。 美しく、スケーラブルでなければなりません。 フィールドのサイズ、世代間の間隔、セルの色の選択を設定できるはずです。 開始、停止、クリアのボタンがまだ必要です。 すべての設定でゲームを保存およびロードできる必要があります。



建築



そうします。 QPaintEventでは、ウィンドウの幅に応じてセルの幅を再計算し、グリッドとセルを描画します。 設計段階で、すべてをレイアウトに美しく保存します-それを把握します。 設定に関しては、すべてを非常にきれいに小さなソケットにまとめます。



設計



一般に、私はこの点にこだわらないことに決めました。 UI Designerで些細なことをする方法は教えません。 建築のみをペイントします。 水平レイアウトマネージャーを作成します。 centralWidgetの下のLayoutim。 次に、そこに(水平方向に)2つの垂直マネージャーを挿入します。 左側には、ゲームのウィンドウがあり、右側には設定があります。 デザイナでは同じサイズに見えますが、コードでストレッチ係数(隣接するレイアウトに対する幅の係数)を設定します。 QWidgetをゲームのレイアウトに挿入します-ちなみに、これをゲームウィジェットに昇格させ、レイアウトの設定-設定))。 長い間話すことができますが、次のように見せることをお勧めします。





考え始める



アルゴリズムから始めましょう。 申し訳ありませんが、考えるのが面倒だったので、最も単純な「Life」シミュレーションアルゴリズムを完全な計算で実装することにしました。 アルゴリズムの本質は非常に簡単です。 各セルについて、前の世代とその隣人に基づいてその運命を考慮します。 アルゴリズムの利点-ミネラルウォーターキャップと同じくらい簡単です。 短所-非常に高価です。 しかし、それは皆で起こるように、怠inessが勝利しました。 すでにいくつかのポイントを思いつきました。 現在と次世代の2つのマトリックス(ユニバース、次)を保存します。 m_masterColor変数は、セルの色、タイマーのタイマー、universeSizeがマトリックスのサイズを格納するのにも役立ちます。 簡単に行います:

  1. startGame()と呼ばれる
  2. タイマーは指定された間隔で回転し始めます
  3. タイムアウト()で、newGeneration()を実行します
  4. ここで、bool isAlive(row、col)に基づいて次のマトリックスに入力します
  5. ユニバースの隣にリダイレクト
  6. 更新によりウィジェットを再描画します()
  7. paintEvent()は、グリッドとセルのレンダリングメソッドを呼び出します
  8. タイマーの実行中は非常に長く退屈です。
  9. そして、タイマーは、universe == nextまたはstopGame()が呼び出されない瞬間まで動作します




このアルゴリズム全体の実装に焦点を当てるのではなく、そのほんの一部であるレンダリングにのみ焦点を当てます。 ちょっとした理論。 Qtでは、グラフィックは主にQPainterの責任であり、グラフィックを操作するためのメソッドが含まれています。 paintEvent()のウィジェット上でのみ描画できます。 ちなみに、次のようになります。

void GameWidget::paintEvent(QPaintEvent *) { QPainter p(this); paintGrid(p); paintUniverse(p); }
      
      







さらに深くなります。 ここでQPainterのインスタンスを作成し、そのリンクをpaintGrid()およびpaintUniverse()メソッドに渡します。 モデル(ユニバースマトリックス)のレンダリングのみに関与します。 すべてが時計のようなものです。 次にpaintGrid()を検討します。

 void GameWidget::paintGrid(QPainter &p) { QRect borders(0, 0, width()-1, height()-1); // borders of the universe QColor gridColor = m_masterColor; // color of the grid gridColor.setAlpha(10); // must be lighter than main color p.setPen(gridColor); double cellWidth = (double)width()/universeSize; // width of the widget / number of cells at one row for(double k = cellWidth; k <= width(); k += cellWidth) p.drawLine(k, 0, k, height()); double cellHeight = (double)height()/universeSize; // height of the widget / number of cells at one row for(double k = cellHeight; k <= height(); k += cellHeight) p.drawLine(0, k, width(), k); p.drawRect(borders); }
      
      







コメントでは、明確でない可能性のあるすべてのポイントが描かれています。 次に、「宇宙」がどのように描かれるかを見ていきます。

 void GameWidget::paintUniverse(QPainter &p) { double cellWidth = (double)width()/universeSize; double cellHeight = (double)height()/universeSize; for(int k=1; k <= universeSize; k++) { for(int j=1; j <= universeSize; j++) { if(universe[k][j] == true) { // if there is any sense to paint it qreal left = (qreal)(cellWidth*j-cellWidth); // margin from left qreal top = (qreal)(cellHeight*k-cellHeight); // margin from top QRectF r(left, top, (qreal)cellWidth, (qreal)cellHeight); p.fillRect(r, QBrush(m_masterColor)); // fill cell with brush of main color } } } }
      
      







いいね 描くことを学んだと仮定できます。 それでも、私は特にQPainterにこだわることはありません-それはドキュメントで非常によく説明されています、それは3つの象-ペン、ブラシ、シェイプ(QRect、QCircle ...)に基づいていると言うだけです。 ペンは、図形の輪郭、ブラシ-塗りつぶしを描画します。 最後のリストでは、四角形のアウトラインが必要ないため、ハンドルを設定しませんでしたが、ブラシを塗りつぶすように設定しました。



しかし、ユーザーにセルをマークする機能をどのように与えるのでしょうか? 明らかに、keyPressEvent()メソッドを再実装し、その中で何かを行います。 以下にリストを示します。

 void GameWidget::mousePressEvent(QMouseEvent *e) { double cellWidth = (double)width()/universeSize; double cellHeight = (double)height()/universeSize; int k = floor(e->y()/cellHeight)+1; int j = floor(e->x()/cellWidth)+1; universe[k][j] = !universe[k][j]; update(); }
      
      







保存/マップを開く



この機能は、保存/読み込みの2つのボタンで実装されます。 彼らの仕事は、ゲームカードでファイルを開いて保存することです。 ファイルには以下が含まれます。





おおよその形式:

 [size] [dump] [red] [green] [blue] [interval]
      
      







マップサイズはGameWidget :: cellNumber()およびGameWidget :: setCellNumber()によって実装されます

ダンプはGameWidget :: dump()およびGameWidget :: setDump()です。

色-GameWidget :: masterColor()およびGameWidget :: setMasterColor()。

間隔はGameWidget :: interval()およびGameWidget :: setInterval()です。



MainWindowの肩には、正しく読み書きするためだけに残っています。 loadGame()関数をリストします。

 void MainWindow::loadGame() { QString filename = QFileDialog::getOpenFileName(this, tr("Open saved game"), QDir::homePath(), tr("Conway's Game Of Life File (*.life)")); if(filename.length() < 1) return; QFile file(filename); if(!file.open(QIODevice::ReadOnly)) return; QTextStream in(&file); int sv; in >> sv; ui->cellsControl->setValue(sv); game->setCellNumber(sv); QString dump=""; for(int k=0; k != sv; k++) { QString t; in >> t; dump.append(t+"\n"); } game->setDump(dump); int r,g,b; // RGB color in >> r >> g >> b; currentColor = QColor(r,g,b); game->setMasterColor(currentColor); // sets color of the dots QPixmap icon(16, 16); // icon on the button icon.fill(currentColor); // fill with new color ui->colorButton->setIcon( QIcon(icon) ); // set icon for button in >> r; // r will be interval number ui->iterInterval->setValue(r); game->setInterval(r); }
      
      







カラーピッカー



ここではあまり説明しません。QColorDialogとGameWidgetクラスのメソッド(上記)を介して実装されます。 ちなみに、ボタンのテキストの左側には、選択された色で塗りつぶされた小さな正方形があります。 これは、16x16 QPixmap-塗りつぶされたmasterColorを受け取るQIconを介して行われます。



注目したくないもの



タイマーの開始方法(タイマー->開始())またはウィジェットの再描画(更新())の方法はわかりません-これが明確であり、Qtが最終的に、世界最高のドキュメントの1つであると言うことを恐れません。



セルが正方形ではなく長方形であるというコメントを書かないでください。 これは本当に私のカントです-私はそれをすべてQAbstractScrollAreaでラップする必要があります-しかし、それは私がしなかったことが判明しました。 結局、フォークとプールのリクエストは歓迎されます-私がGitHub'e))をホストしているのは何のためでもありません。



写真と例





「マシンガン」グライダーは青い色の50x50のフィールドにいます。





フィールド上の「マシンガン」グライダー100x100





同じ機関銃、オレンジのみ。



読んでくれてありがとう



この記事をお読みいただきありがとうございます。



もう一度GitHubのソースコード。

githubのマシンガンでマップ: gist



私はあなたの成功を祈っています、そして最も重要なこと-成功した乙女、

イリヤ。



All Articles