Game Programming Using Qt Beginner's Guide
上QQ阅读APP看书,第一时间看更新

Time for action – putting it all together

The visual part of the game is ready and what remains is to complete the logic of the main window and put all the pieces together. Add a public slot to the class and call it startNewGame. In the class constructor, connect the New Game action's triggered signal to this slot and connect the application's quit slot to the other action:

connect(ui->actionNewGame, SIGNAL(triggered()), this, SLOT(startNewGame()));
connect(ui->actionQuit, SIGNAL(triggered()), qApp, SLOT(quit()));

The qApp special macro represents a pointer to the application object instance, so the preceding code will call the quit() slot on the QApplication object created in main(), which in turn will eventually cause the application to end.

Let's implement the startNewGame slot as follows:

void MainWindow::startNewGame() {
  ConfigurationDialog dlg(this);
  if(dlg.exec() == QDialog::Rejected) {
    return; // do nothing if dialog rejected
  }
  ui->player1->setText(dlg.player1Name());
  ui->player2->setText(dlg.player2Name());
  ui->gameBoard->initNewGame();
  ui->gameBoard->setEnabled(true);
}

In this slot, we create the settings dialog and show it to the user, forcing him to enter player names. If the dialog was canceled, we abandon the creation of a new game. Otherwise, we ask the dialog for player names and set them on appropriate labels. Finally, we initialize the board and enable it so that users can interact with it.

While writing a turn-based board game, it is a good idea to always clearly mark whose turn it is now to make a move. We will do this by marking the moving player's name in bold. There is already a signal in the board class that tells us that a valid move was made, which we can react to in order to update the labels. Let's add an appropriate code into the constructor of the main window class:

connect(ui->gameBoard, SIGNAL(currentPlayerChanged(Player)), this, SLOT(updateNameLabels()));

Now for the slot itself; let add a private slot's section to the class and declare the slot there:

private slots:
  void updateNameLabels();

Now, we can implement it:

void MainWindow::updateNameLabels() {
  QFont f = ui->player1->font();
  f.setBold(ui->gameBoard->currentPlayer() == TicTacToeWidget::Player1);
  ui->player1->setFont(f);
  f.setBold(ui->gameBoard->currentPlayer() == TicTacToeWidget::Player2);
  ui->player2->setFont(f);
}

In addition to the slot being called after a signal is emitted, we can also use it to set the initial data for the labels when the game is starting. Since all the slots are also regular methods, we can simply call updateNameLabels() from startNewGame()—go ahead and invoke updateNameLabels() at the end of startNewGame().

The last thing that needs to be done is to handle the situation when the game ends. Connect the gameOver() signal from the board to a new slot in the main window class. Implement the slot as follows:

void MainWindow::handleGameOver(TicTacToeWidget::Player winner) {
  ui->gameBoard->setEnabled(false);
  QString message;
  if(winner == TicTacToeWidget::Draw) {
    message = "Game ended with a draw.";
  } else {
    message = QString("%1 wins").arg(winner == TicTacToeWidget::Player1
    ? ui->player1->text() : ui->player2->text());
  }
  QMessageBox::information(this, "Info", message);
}

What just happened?

Our code does two things. First, it disables the board so that players can no longer interact with it. Second, it checks who won the game, assembles the message (we will learn more about QString in the next chapter), and shows it using a static method QMessageBox::information() that shows a modal dialog containing the message and a button that allows us to close the dialog. The last thing that remains is to update the main() function in order to create an instance of our MainWindow class:

#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
  QApplication a(argc, argv);
  MainWindow w;
  w.show();
  return a.exec();
}

Now, you can run your first Qt game.

Have a go hero – extending the game

As an additional exercise, you can try to modify the code we have written in this chapter to allow playing the game on boards bigger than 3 x 3. Let the user decide about the size of the board (you can modify the game options dialog for that and use QSlider and QSpinBox to allow the user to choose the size of the board) and you can then instruct TicTacToeWidget to build the board based on the size it gets. Remember to adjust the game winning logic! If at any point you run into a dead end and do not know which classes and functions to use, consult the reference manual.

Tip

To quickly find the documentation for a class (or any other page in the docs), switch to the Help pane, choose Index from the drop-down list on top of the sidebar, and type in the search term, such as QAction. Also, the F1 key is very helpful for browsing the manual. Position the mouse pointer or text cursor in the code editor over the name of a class, function, or object and press F1 on your keyboard. By doing this, Qt Creator will happily show you the available help information on the chosen subject.

Pop quiz – using widgets

Q1. A method that returns the preferred size of a widget is called:

  1. preferredSize
  2. sizeHint
  3. defaultSize

Q2. What is the name of a Qt class that can carry values for any property?

  1. QVariant
  2. QUnion
  3. QPropertyValue

Q3. What is the purpose of the QAction object?

  1. It represents a functionality that a user can invoke in the program.
  2. It holds a key sequence to move the focus on a widget.
  3. It is a base class for all forms generated using Qt Designer.