Time for action – functionality of a tic-tac-toe board

We need to implement a function that will be called upon by clicking on any of the nine buttons on the board. It has to change the text of the button that was clicked on—either X or O—based on which player made the move; then, it has to check whether the move resulted in winning the game by the player (or a draw if no more moves are possible), and if the game ended, it should emit an appropriate signal, informing the environment about the event.

When the user clicks on a button, the clicked() signal is emitted. Connecting this signal to a custom slot lets us implement the mentioned functionality, but since the signal doesn't carry any parameters, how do we tell which button caused the slot to be triggered? We could connect each button to a separate slot but that's an ugly solution. Fortunately, there are two ways of working around this problem. When a slot is invoked, a pointer to the object that caused the signal to be sent is accessible through a special method in QObject called sender(). We can use that pointer to find out which of the nine buttons stored in the board list is the one that caused the signal to fire:

void TicTacToeWidget::someSlot() {
 QObject *btn = sender();
  int idx = board.indexOf(btn);
  QPushButton *button = board.at(idx);
  // ...
}

While sender() is a useful call, we should try to avoid it in our own code as it breaks some principles of object-oriented programming. Moreover, there are situations where calling this function is not safe. A better way is to use a dedicated class called QSignalMapper, which lets us achieve a similar result without using sender() directly. Modify the setupBoard() method in TicTacToeWidget as follows:

QGridLayout *gridLayout = new QGridLayout;
QSignalMapper *mapper = new QSignalMapper(this);
for(int row = 0; row < 3; ++row) {
  for(int column = 0; column < 3; ++column) {
    QPushButton *button = new QPushButton;
    button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
    button->setText(" ");
    gridLayout->addWidget(button, row, column);
    board.append(button);
 mapper->setMapping(button, board.count()-1);
 connect(button, SIGNAL(clicked()), mapper, SLOT(map()));
  }
}
connect(mapper, SIGNAL(mapped(int)), this, SLOT(handleButtonClick(int)));
setLayout(gridLayout);

Here, we first created an instance of QSignalMapper and passed a pointer to the board widget as its parent so that the mapper is deleted when the widget is deleted. Then, when we create buttons, we "teach" the mapper that each of the buttons has a number associated with it—the first button will have the number 0, the second one will be bound to the number 1, and so on. By connecting the clicked() signal from the button to mapper's map() slot, we tell the mapper to do its magic upon receiving that signal. What the mapper will do is that it will then find the mapping of the sender of the signal and emit another signal—mapped()—with the mapped number as its parameter. This allows us to connect to that signal with a slot (handleButtonClick) that takes the index of the button in the board list.

Now it is time to implement the slot itself (remember to declare it in the header file!). However, before we do that, let's add a useful enum and a few helper methods to the class:

enum Player {
  Invalid, Player1, Player2, Draw
};

This enum lets us specify information about players in the game. We can use it immediately to mark whose move it is now. To do so, add a private field to the class:

Player m_currentPlayer;

Then, add the two public methods to manipulate the value of this field:

Player currentPlayer() const { return m_currentPlayer; }
void setCurrentPlayer(Player p) {
  if(m_currentPlayer == p) return;
  m_currentPlayer = p;
  emit currentPlayerChanged(p);
}

The last method emits a signal, so we have to add the signal declaration to the class definition along with another signal that we are going to use:

signals:
 void currentPlayerChanged(Player);
 void gameOver(TicTacToeWidget::Player);

Tip

Note that we only emit the currentPlayerChanged signal when the current player really changes. You always have to pay attention that you don't emit a "changed" signal when you set a value to a field to the same value that it had before the function was called. Users of your classes expect that if a signal is called changed, it is emitted when the value really changes. Otherwise, this can lead to an infinite loop in signal emissions if you have two objects that connect their value setters to the other object's changed signal.

Now let's declare the handleButtonClick slot:

public slots:
    void handleButtonClick(int);

And then implement it in the .cpp file:

void TicTacToeWidget::handleButtonClick(int index) {
  if(index < 0 || index >= board.size()) return; // out of bounds check
  QPushButton *button = board.at(index);
  if(button->text() != " ") return; // invalid move
  button->setText(currentPlayer() == Player1 ? "X" : "O");
  Player winner = checkWinCondition(index / 3, index % 3);
  if(winner == Invalid) {
    setCurrentPlayer(currentPlayer() == Player1 ? Player2 : Player1);
    return;
  } else {
    emit gameOver(winner);
  }
}

Here, we first retrieve a pointer to the button based on its index. Then, we check whether the button contains any text—if so, then this means that it doesn't participate in the game anymore, so we return from the method so that the player can pick another field in the board. Next, we set the current player's mark on the button. Then, we check whether the player has won the game, passing it the row (index / 3) and column (index % 3) index of the current move. If the game didn't end, we switch the current player and return. Otherwise, we emit a gameOver() signal, telling our environment who won the game. The checkWinCondition() method returns Player1, Player2, or Draw if the game has ended and Invalid otherwise. We will not show the implementation of this method here as it is quite complex. Try implementing it on your own and if you encounter problems, you can see the solution in the code bundle that accompanies this book.

Properties

Apart from signals and slots, Qt meta-objects also give programmers an ability to use the so-called properties that are essentially named attributes that can be assigned values of a particular type. They are useful to express important features of an object—like text of a button, size of a widget, player names in games, and so on.

Declaring a property

To create a property, we first need to declare it in a private section of a class that inherits QObject using a special Q_PROPERTY macro, which lets Qt know how to use the property. A minimal declaration contains the type of the property, its name, and information about a method name that is used to retrieve a value of the property. For example, the following code declares a property of the type double that is called height and uses a method called height to read the property value:

Q_PROPERTY(double height READ height)

The getter method has to be declared and implemented as usual. Its prototype has to comply with these rules: it has to be a public method that returns a value or constant reference of a type of the property, and it can't take any input parameters and the method itself has to be constant. Typically, a property will manipulate a private member variable of the class:

class Tower : public QObject {
  Q_OBJECT // enable meta-object generation
  Q_PROPERTY(double height READ height) // declare the property
  public:
    Tower(QObject *parent = 0) : QObject(parent) { m_height = 6.28; }
    double height() const { return m_height; } // return property value
  private:
    double m_height; // internal member variable holding the property value
};

Such a property is practically useless because there is no way to change its value. Luckily, we can extend the declaration to include the information about how to write a value to the property:

Q_PROPERTY(double height READ height WRITE setHeight)

Again, we have to declare and implement setHeight so that it behaves as the setter method for the property—it needs to be a public method that takes a value or constant reference of the type of the property and returns void:

void setHeight(double newHeight) { m_height = newHeight; }

Tip

Property setters are good candidates for public slots so that you can easily manipulate property values using signals and slots.

We will learn about some of the other extensions to Q_PROPERTY declarations in the later chapters of this book.

Using a property

There are two ways in which you can access properties. One is of course, to use getter and setter methods that we declared with READ and WRITE keywords in the Q_PROPERTY macro—this will naturally work since they are regular C++ methods.

The other way is to use facilities offered by QObject and the meta-object system. They allow to us access properties by name using two methods that accept property names as strings. A generic property getter (which returns the property value) is a method called property. Its setter counterpart (that takes the value and returns void) is setProperty. Since we can have properties with different data types, what is the data structure that is used by those two methods that hold values for different kinds of properties? Qt has a special class for this called QVariant, which behaves a lot like a C union in the way that it can store values of different types. There are a couple of advantages to using a union though—the three most important are that you can ask the object what type of data it currently holds, you can convert some of the types to other types (for example, a string to an integer), and you can teach it to operate on your own custom types.