Qt SCXML Pinball Example | Qt SCXML 5.15.18

- Qt 5.15
- Qt SCXML
- Qt SCXML Examples
- Qt SCXML Pinball Example
- Qt 5.15.18 Reference Documentation

# Qt SCXML Pinball Example

Encapsulates the internal logic of an application in an SCXML file.

Pinball demonstrates a clear separation between the user interface, which may be easily replaced, and the internal logic encapsulated in an SCXML file, which could also be used with another user interface.

#### Running the Example

To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, visit Building and Running an Example.

#### Pinball Features

The Pinball example mimics a pinball game. The targets on the pinball table are substituted by GUI controls, mainly by push buttons. Display elements, including current score, highscore, and targets' lights, are substituted by labels. Usually, the state of the targets' lights changes very often during a game: the lights get turned on or off permanently or they blink at varying speed indicating a game (or a certain target) entered a temporary state. The state of each target light is presented as an enabled or a disabled label. There is no real ball, but clicking a target's button represents hitting a real pinball target with a ball.

Our pinball contains the following features:

- Initially and when the game ends, the pinball table enters `offState`. In that state, all lights on the table blink slowly (at intervals of 1 second).
- After clicking the START button, the pinball table enters `onState`. All lights are turned off and the pinball table is ready to be played.
- When the table is in `onState` and the players click the BALL OUT button, the game ends and enters `offState`. If the players' score is higher than the current highscore, the highscore is updated.
- The goal is to collect the JACKPOT. In order to do that, the players must hit all five CRAZY letters twice. They have unlimited time for hitting them for the first time. However, after they have collected all the letters for the first time, they enter the `hurryState` and must collect them again within 5 seconds. If the time has passed and the letters were not collected again, the players must start collecting the letters from scratch.
- Scores:
  - 1.000 per letter hit when not in `hurryState`.
  - 10.000 per letter hit when in `hurryState`.
  - 100.000 bonus for all 5 letters when not in `hurryState`.
  - 1.000.000 bonus for all 5 letters when in `hurryState` (JACKPOT).
- When not in `hurryState`, the letters already hit should blink at intermediate speed (500ms). Letters not hit yet should stay off.
- When in `hurryState`, the letters already hit should stay on. Letters not hit yet should blink fast (200ms). In addition, the HURRY light should blink at the same speed.
- When the jackpot gets collected, the JACKPOT light should stay on.

#### SCXML Part: Internal Logic Description

The pinball.scxml file describes the internal logic implemented for the pinball game. In this example, we have chosen the ECMAScript data model:

     <scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"
            name="Pinball" datamodel="ecmascript">

The ECMAScript data model enables declaring variables with initial values that can be modified later. We declare the `"highscore"` and `"score"` variables with the initial values of 0:

         <datamodel>
             <data id="highScore" expr="0"/>
             <data id="score" expr="0"/>
         </datamodel>

We define a root parallel state `"global"`, with two child states, `guiControl` and `internalState`, which are also parallel. Because the top `global` state is parallel, all of its direct children are active when it is active. In this example, the role of `global` is to collect the child states and make them both active at a time.

##### Maintaining Light State

The `guiControl` element is responsible for maintaining the current state of each light control that is visible on the pinball table. Each light has a corresponding state.

For example, the light of the letter C corresponds to the `cLight` state. Each light state has two child states indicating whether the light is on or off:

         <parallel id="global">
             <parallel id="guiControl">
                 <state id="cLight">
                     <state id="cLightOn">
                         <transition event="turnOffC" target="cLightOff"/>
                     </state>
                     <state id="cLightOff">
                         <transition event="turnOnC" target="cLightOn"/>
                     </state>
                 </state>

As mentioned before, the `guiControl` state is always active, and since it is of parallel type, all its direct children are always active too. Therefore, the `cLight` state is always active. However, only one of its children, `cLightOn` or `cLightOff`, is active at a time. The same applies to the other children of the `guiControl` state. In addition, we define transitions between on and off substates. For example, whenever the active state is `cLightOn` and a `turnOffC` event is received, we change the active substate of `cLight` to `cLightOff`. Whenever the active state is `cLightOff` and we receive a `turnOnC` event, we change the active substate of `cLight` to `cLightOn`.

In our application, we use instances of QLabel class in C++ to represent real lights on the table. When the light transitions into the on or off state, we enable or disable the particular label accordingly. The connection between the state machine and the GUI part of the application will be shown in the C++ code later on. For now, it is enough to realize that changes to active states inside the state machine will serve as the external interface of the state machine that the other parts of the application (such as the GUI part) can listen to.

All of the mentioned events that switch the state of a light will be generated by this state machine inside the `internalState` in reaction to running timers or external triggers.

##### Maintaining Game State

The `internalState` state consists of two main parts: `logicalState` and `workflow`.

The `logicalState` state holds the definitions for the modes that the game is able to go into and for the logical states of collected targets. The `workflow` state implements a generator for light blinking and calculates most of the new states the machine should go into depending on incoming events and on currently active states. As mentioned already, `internalState` is always active, and since it is of a parallel type, `logicalState` and `workflow` are always active too.

##### Maintaining Logical State of Buttons

The `logicalState` state consist of two parts: `letterState` and `modeState`.

As previously mentioned, `logicalState` is always active, and since it is of parallel type, the `letterState` and `modeState` children are always active too. Now let us look at the first part, the `letterState`, which contains one parallel `lettersState`:

                     <state id="letterState">
                         <parallel id="lettersState">
                             <state id="letter.C">
                                 <state id="cLetterOff">
                                     <transition event="cLetterTriggered" cond="In('onState')" target="cLetterOn"/>
                                 </state>
                                 <final id="cLetterOn"/>
                             </state>
                             <state id="letter.R">
                                 ...
                             </state>
                             <state id="letter.A">
                                 ...
                             </state>
                             <state id="letter.Z">
                                 ...
                             </state>
                             <state id="letter.Y">
                                 ...
                             </state>
                             <transition event="resetLetters" target="lettersState"/>
                         </parallel>
                     </state>

The `lettersState` state maintains the logical state of the buttons pretending to be targets that were clicked by the players. The letter state for the letter C holds whether the target for the letter C was hit, while the light state for the letter C holds whether the light for the target for the letter C should be currently on or off. In a real pinball game, these states are usually orthogonal, which means that if you have not hit a target yet, the target is blinking, indicating that it is currently worth hitting. This blinking means that the light state switches between on and off at short intervals, while the target state is continouosly off, because it has not been hit yet. The author of a pinball table can decide that after a target is hit (that is, after the target state switches to on) the target's light is continuously turned off or on or the intervals between lights blinking become shorter or longer.

As mentioned before, `letterState` is always active, which means that its only child `lettersState` should always be active too. However, there is one exception: for a short while the `lettersState` may end up being not active. This happens when the transition for `lettersState` is being performed. This transition is triggered when the `resetLetters` event occurs, and it instructs the state machine to exit `lettersState` and all its descendant states and reenter `lettersState` and set up all its descendant states with their initial states. In short, the `resetLetters` event resets the `lettersState` and all its descendant states to the default configuration.

The `lettersState` contains five direct substates that correspond to five different letters. The content for other letters' states than C is not shown here, but it is analogous to the content for C's state.

The `letter.C` state contains two substates reflecting its off and on states: `cLetterOff` and `cLetterOn`. The `letter.C` state inside its parallel parent `lettersState` is always active (under the condition that `lettersState` is active, as described before). However, only one of its child states is active at a time: `cLetterOff` or `cLetterOn`. The initial substate of the `letter.C` state is `cLetterOff` meaning that whenever the `letter.C` state is being activated (which happens initially and after the `resetLetters` event) its active substate will be set to `cLetterOff`.

The `cLetterOff` state defines a transition, which will be triggered by the `cLetterTriggered` event. This transition activates `cLetterOn`, the other child of `letter.C`, only when the machine is in `onState` (that is, when the pinball game is running). The `cLetterTriggered` event is expected to be an event posted into the state machine from outside of the state machine. This event should be generated when the ball hits the letter C target. In our example we mimic it by the clicking the letter C button.

The `cLetterOn` state is defined as a final state, which means that whenever this state is activated the `done.state.letter.C` event will be automatically posted by the state machine. This event will be used later for updating the current score.

Moreover, when all `lettersState` children reach their final state, the state machine will automatically post the `done.state.lettersState` event. This event will be used later, too, for updating the current score and for turning on or off the hurry state.

##### Maintaining Game Modes

The `modeState` state consists of two substates, `offState` and `onState`.

The `offState` state describes what should happen before the pinball game is started and when it is over, while `onState` represents the logic appropriate for the active game.

                         <state id="offState">
                             <onentry>
                                 <if cond="highScore &lt; score">
                                     <assign location="highScore" expr="score"/>
                                 </if>
                                 <raise event="resetLetters"/>
                                 <raise event="update"/>
                             </onentry>
                             <transition event="startTriggered" target="onState"/>
                         </state>

When the pinball application starts or a game ends, the machine goes into `offState`. Entering that state invokes some actions, which are enclosed inside an `<onentry>` element. First, we update the `highScore` variable in case the current `highScore` value is less than current `score` value. This is being checked inside the `"cond"` attribute of the `<if>` element (note that we need to escape the "<" character with "&lt;"). Even in the `off` state, we want to show the last reached score, so we do not clear it here; we will do that when we enter the `on` state. Next, we raise two events: `resetLetters` to logically reset all letters that might have been hit during the last game and `update` to immediately activate the blinking and updating of all lights. When the machine is in `offState`, it is ready to transition into the `onState` if only the `startTriggered` event occurs, which is described by the <transition> element. This event is expected to be generated externally after clicking the START button on the pinball table.

                         <parallel id="onState">
                             <onentry>
                                 <assign location="score" expr="0"/>
                             </onentry>
                             <state id="hurryState">
                                 <state id="hurryStateOff">
                                     <onentry>
                                         <raise event="resetLetters"/>
                                         <raise event="update"/>
                                     </onentry>
                                     <transition event="goToHurryOn" target="hurryStateOn"/>
                                 </state>
                                 <state id="hurryStateOn">
                                     <onentry>
                                         <send event="goToHurryOff" id="hurryId" delay="5s"/>
                                         <raise event="resetLetters"/>
                                         <raise event="update"/>
                                     </onentry>
                                     <transition event="goToHurryOff" target="hurryStateOff"/>
                                     <onexit>
                                         <cancel sendid="hurryId"/>
                                     </onexit>
                                 </state>
                             </state>
                             <state id="jackpotState">
                                 <state id="jackpotStateOff">
                                     <onentry>
                                         <raise event="update"/>
                                     </onentry>
                                     <transition event="goForJackpot" target="jackpotStateOn"/>
                                 </state>
                                 <state id="jackpotStateOn">
                                     <onentry>
                                         <raise event="update"/>
                                     </onentry>
                                 </state>
                             </state>
                             <transition event="ballOutTriggered" target="offState"/>
                         </parallel>

##### Game On

When the state machine enters `onState`, it first clears the current score variable. The `onState` state is of the parallel type and has two direct child states: `hurryState` and `jackpotState`. They are active as long as their parent, `onState`, is active. Both `hurryState` and `jackpotState` contain two substates that reflect their off and on states. Only one substate of `hurryState` and one substate of `jackpotState` can be active at a time. Initially, the off substates are active.

Whenever we enter `hurryStateOff` or `hurryStateOn`, we generate the same two events we generate when entering the `onState` state: `resetLetters` and `update`. In addition, when we enter the `hurryStateOn` state, we send a delayed event, `goToHurryOff`, with a delay of five seconds, marked with `hurryId`. This means that after five seconds we just switch the state back to `hurryStateOff` without granting the bonus points. In this way, we implement the five-second hurry feature of the pinball table. We also define transitions from `hurryStateOff` to `hurryStateOn` when the `goToHurryOn` event occurs and from `hurryStateOn` to `hurryStateOff` when the `goToHurryOff` event occurs. When we exit the `hurryStateOn` state, we cancel the possibly pending delayed event that was marked with `hurryId`. This is important in case the five secons have not elapsed yet, but players have collected all the five letters in the hurry state. We then collect the jackpot and want the pending timer to finish.

The substates of `jackpotState` generate the request to update the state of lights. The `jackpotStateOff` state defines the transition to `jackpotStateOn` when the `goForJackpot` event occurs. The opposite transition is not needed, because when the jackpot gets collected, the corresponding light remains lit until the end of game. When a new game starts, the `jackpotState` is entered again which causes its initial active substate to be `jackpotStateOff`.

In addition, the `onState` state defines one transition in reaction to the `ballOutTriggered` event which instructs the machine to go into the `offState`. The `ballOutTriggered` event is expected to be an event posted into the state machine from outside of the state machine. This event should be generated when the ball gets out of playing area of the table. In our example we mimic it by the clicking BALL OUT button. Posting the event from outside of state machine will be shown in the C++ code later on.

##### Generating Blinking Lights

The `workflow` state is responsible for generating the blinking lights. The generator is defined in its `lightImpulseGenerator` substate. In addition, it is responsible for reacting to events that have been posted so far from the other parts of the state machine.

                 <state id="workflow">
                     <state id="lightImpulseGenerator">
                         <state id="lightImpulseOn"/>
                         <state id="lightImpulseOff"/>

                         <onentry>
                             <raise event="update"/>
                         </onentry>

                         <transition event="scheduleNewImpulse">
                             <cancel sendid="lightId"/>
                             <if cond="In('offState')">
                                 <send event="lightImpulse" id="lightId" delay="1s"/>
                             <elseif cond="In('hurryStateOff')"/>
                                 <send event="lightImpulse" id="lightId" delay="500ms"/>
                             <else/>
                                 <send event="lightImpulse" id="lightId" delay="200ms"/>
                             </if>
                         </transition>

                         <transition event="update">
                             <raise event="scheduleNewImpulse"/>
                             <raise event="updateLights"/>
                         </transition>

                         <transition event="lightImpulse" cond="In('lightImpulseOn')" target="lightImpulseOff"/>
                         <transition event="lightImpulse" cond="In('lightImpulseOff')" target="lightImpulseOn"/>
                     </state>

                     <transition event="done.state.letter.*">
                         ...
                     </transition>

                     <transition event="done.state.lettersState">
                         ...
                     </transition>

                     <transition event="updateLights">
                         ...
                     </transition>

                     <transition event="updateLightsAccordingToLettersState">
                         ...
                     </transition>

                     <transition event="turnOnLights">
                         ...
                     </transition>

                     <transition event="turnOffLights">
                         ...
                     </transition>
                 </state>

The `lightImpulseGenerator` contains two child states: `lightImpulseOn` and `lightImpulseOff`, with only one active at a time.

Whenever the delayed `lightImpulse` event is being delivered, it immediately causes the transition from `lightImpluseOn` into `lightImpulseOff` or vice versa, depending on the state the machine was in. In effect, the `lightImpulseGenerator` toggles between its on and off state. These transitions are defined inside `lightImpulseGenerator`, so it means that during this toggling the machine also exits `lightImpulseGenerator` and reenters it immediately afterwards. Entering `lightImpulseGenerator` causes the generation of the `update` event. The `update` event triggers a targetless transition and posts two other events: `scheduleNewImpulse` and `updateLights`. The first one, `scheduleNewImpulse`, returns back to the `lightImpulseGenerator`, which posts a delayed `lightImpulse` event. After the delay, the `lightImpulse` event gets delivered back to `lightImpulseGenerator`, which causes it to toggle its substate again. In this way, the machine enters into a cycle. The current delay of the `lightImpulse` event depends on the state in which the machine was in the time of posting the delayed event. If a `scheduleNewImpulse` event occurs on demand, before the next delayed `lightImpulse` event gets delivered, we cancel any possible pending events.

                     <transition event="done.state.letter.*">
                         <if cond="In('hurryStateOff')">
                             <assign location="score" expr="score + 1000"/>
                         <elseif cond="In('hurryStateOn')"/>
                             <assign location="score" expr="score + 10000"/>
                         </if>
                         <raise event="updateLights"/>
                     </transition>

                     <transition event="done.state.lettersState">
                         <if cond="In('hurryStateOff')">
                             <assign location="score" expr="score + 100000"/>
                             <raise event="goToHurryOn"/>
                         <elseif cond="In('hurryStateOn')"/>
                             <assign location="score" expr="score + 1000000"/>
                             <raise event="goToHurryOff"/>
                             <raise event="goForJackpot"/>
                         </if>
                     </transition>

Whenever we receive the event the name of which matches the `done.state.letter.*`, we update the current score. When the machine enters the final substate of the `letter.C`, it emits the `done.state.letter.C` event. The same happens for all other letters we have previously defined. We capture the events for all letters, that is why we have used an asterisk after a dot in the event name. The transition above is targetless, since we just listen for matching events and update the internal data accordingly without changing any active state. The new score is being increased by 1.000 or 10.000 points, depending on whether we currently are in `hurryStateOff` or `hurryStateOn`. After the score is updated, we generate the `updateLights` event in order to immediately update the letters' lights accordingly. We do not generate the `update` event here, since we do not want to toggle the light impulse now, but just update the lights according to the current impulse state.

We also intercept the `done.state.lettersState` event, which is being generated when all the letters have been hit. Depending on which state we are currently in, we grant the players either a small bonus of 100.000 or a big one of 1.000.000 (jackpot). In addition, we toggle the `hurryState` substate by sending the `goToHurryOn` or `goToHurryOff` event. When all letters have been collected while in `hurryStateOn`, we also raise the `goForJackpot` event which instructs the machine to activate the `jackpotStateOn`.

                     <transition event="updateLights">
                         <send event="updateScore">
                             <param name="highScore" expr="highScore"/>
                             <param name="score" expr="score"/>
                         </send>
                         <if cond="In('jackpotStateOn')">
                             <raise event="turnOnJackpot"/>
                         <else/>
                             <raise event="turnOffJackpot"/>
                         </if>

                         <if cond="In('lightImpulseOn')">
                             <if cond="In('offState')">
                                 <raise event="turnOnLights"/>
                                 <raise event="turnOnHurry"/>
                                 <raise event="turnOnJackpot"/>
                                 <raise event="turnOnGameOver"/>
                             <elseif cond="In('hurryStateOff')"/>
                                 <raise event="updateLightsAccordingToLettersState"/>
                                 <raise event="turnOffHurry"/>
                                 <raise event="turnOffGameOver"/>
                             <else/>
                                 <raise event="turnOnLights"/>
                                 <raise event="turnOnHurry"/>
                                 <raise event="turnOffGameOver"/>
                             </if>
                         <else/>
                             <if cond="In('offState')">
                                 <raise event="turnOffLights"/>
                                 <raise event="turnOffHurry"/>
                                 <raise event="turnOffJackpot"/>
                             <elseif cond="In('hurryStateOff')"/>
                                 <raise event="turnOffLights"/>
                             <else/>
                                 <raise event="updateLightsAccordingToLettersState"/>
                             </if>
                             <raise event="turnOffHurry"/>
                             <raise event="turnOffGameOver"/>
                         </if>
                     </transition>

                     <transition event="updateLightsAccordingToLettersState">
                         <if cond="In('cLetterOn')">
                             <raise event="turnOnC"/>
                         <else/>
                             <raise event="turnOffC"/>
                         </if>
                         <if cond="In('rLetterOn')">
                             <raise event="turnOnR"/>
                         <else/>
                             <raise event="turnOffR"/>
                         </if>
                         <if cond="In('aLetterOn')">
                             <raise event="turnOnA"/>
                         <else/>
                             <raise event="turnOffA"/>
                         </if>
                         <if cond="In('zLetterOn')">
                             <raise event="turnOnZ"/>
                         <else/>
                             <raise event="turnOffZ"/>
                         </if>
                         <if cond="In('yLetterOn')">
                             <raise event="turnOnY"/>
                         <else/>
                             <raise event="turnOffY"/>
                         </if>
                     </transition>

                     <transition event="turnOnLights">
                         <raise event="turnOnC"/>
                         <raise event="turnOnR"/>
                         <raise event="turnOnA"/>
                         <raise event="turnOnZ"/>
                         <raise event="turnOnY"/>
                     </transition>

                     <transition event="turnOffLights">
                         <raise event="turnOffC"/>
                         <raise event="turnOffR"/>
                         <raise event="turnOffA"/>
                         <raise event="turnOffZ"/>
                         <raise event="turnOffY"/>
                     </transition>

When we receive the `updateLights` event, we first want to send a `updateScore` event outside of the state machine. We pass the current values of the `highScore` and `score` variables to the event. This event is received by the C++ part.

Next, depending on whether we are in `jackpotStateOn` or `jackpotStateOff`, we send the `turnOnJackpot` or the `turnOffJackpot` event, which instructs the `guiControl` state to transition to `jackpotLightOn` or `jackpotLightOff`, respectively.

When the machine is in idle state, (that is, in the off state) or when the game is on, but no interaction occurs, the `updateLights` event is delivered periodically during the game, each time with the `lightImpulseOn` or `lightImpulseOff` state toggled. Depending on the current state of the light impulse and on the active state (`offState`, `hurryStateOff` or `hurryStateOn`), we turn on or off all the lights according to the description of the pinball table.

#### GUI Part: User Interface Description

The GUI part of the application consists of a mainwindow.ui file which describes the static user interface of the game.

#### C++ Part: Glue GUI with SCXML

The C++ part of the application consists of a `MainWindow` class which glues the GUI part with the SCXML part. The class is declared in mainwindow.h.

     class MainWindow;
     }
     class QScxmlStateMachine;
     QT_END_NAMESPACE

     class MainWindow : public QWidget
     {
         Q_OBJECT

     public:
         explicit MainWindow(QScxmlStateMachine *machine, QWidget *parent = nullptr);
         ~MainWindow();

     private:
         void initAndConnect(const QString &state, QWidget *widget);
         QT_PREPEND_NAMESPACE(Ui::MainWindow) *m_ui;
         QScxmlStateMachine *m_machine;
     };

The `MainWindow` class holds the pointer to the `QScxmlStateMachine *m_machine` which is the state machine class automatically generated by Qt out of SCMXL file and the pointer to the `Ui::MainWindow *m_ui` which describes the GUI part. It also declares two helper methods.

     #include "mainwindow.h"
     #include "ui_mainwindow.h"

     #include <QScxmlStateMachine>
     #include <QStringListModel>

     QT_USE_NAMESPACE

     MainWindow::MainWindow(QScxmlStateMachine *machine, QWidget *parent) :
         QWidget(parent),
         m_ui(new Ui::MainWindow),
         m_machine(machine)
     {
         m_ui->setupUi(this);

         // lights
         initAndConnect(QLatin1String("cLightOn"), m_ui->cLabel);
         initAndConnect(QLatin1String("rLightOn"), m_ui->rLabel);
         initAndConnect(QLatin1String("aLightOn"), m_ui->aLabel);
         initAndConnect(QLatin1String("zLightOn"), m_ui->zLabel);
         initAndConnect(QLatin1String("yLightOn"), m_ui->yLabel);
         initAndConnect(QLatin1String("hurryLightOn"), m_ui->hurryLabel);
         initAndConnect(QLatin1String("jackpotLightOn"), m_ui->jackpotLabel);
         initAndConnect(QLatin1String("gameOverLightOn"), m_ui->gameOverLabel);

         // help labels
         initAndConnect(QLatin1String("offState"), m_ui->offStateLabel);
         initAndConnect(QLatin1String("hurryStateOff"), m_ui->normalStateLabel);
         initAndConnect(QLatin1String("hurryStateOn"), m_ui->hurryStateLabel);
         initAndConnect(QLatin1String("jackpotStateOn"), m_ui->jackpotStateLabel);

         // context enablement
         initAndConnect(QLatin1String("offState"), m_ui->startButton);
         initAndConnect(QLatin1String("onState"), m_ui->cButton);
         initAndConnect(QLatin1String("onState"), m_ui->rButton);
         initAndConnect(QLatin1String("onState"), m_ui->aButton);
         initAndConnect(QLatin1String("onState"), m_ui->zButton);
         initAndConnect(QLatin1String("onState"), m_ui->yButton);
         initAndConnect(QLatin1String("onState"), m_ui->ballOutButton);

         // datamodel update
         m_machine->connectToEvent("updateScore", [this] (const QScxmlEvent &event) {
             const QVariant data = event.data();
             const QString highScore = data.toMap().value("highScore").toString();
             m_ui->highScoreLabel->setText(highScore);
             const QString score = data.toMap().value("score").toString();
             m_ui->scoreLabel->setText(score);
         });

         // gui interaction
         connect(m_ui->cButton, &QAbstractButton::clicked,
                 [this] { m_machine->submitEvent("cLetterTriggered");
                 });
         connect(m_ui->rButton, &QAbstractButton::clicked,
                 [this] { m_machine->submitEvent("rLetterTriggered");
                 });
         connect(m_ui->aButton, &QAbstractButton::clicked,
                 [this] { m_machine->submitEvent("aLetterTriggered");
                 });
         connect(m_ui->zButton, &QAbstractButton::clicked,
                 [this] { m_machine->submitEvent("zLetterTriggered");
                 });
         connect(m_ui->yButton, &QAbstractButton::clicked,
                 [this] { m_machine->submitEvent("yLetterTriggered");
                 });
         connect(m_ui->startButton, &QAbstractButton::clicked,
                 [this] { m_machine->submitEvent("startTriggered");
                 });
         connect(m_ui->ballOutButton, &QAbstractButton::clicked,
                 [this] { m_machine->submitEvent("ballOutTriggered");
                 });
     }

The constructor of the `MainWindow` class instantiates the GUI part of the application and stores the pointer to the passed `QScxmlStateMachine`. It also initializes the GUI part and glues the GUI part to the state machine by connecting their communication interfaces together.

The `initAndConnect()` method connects the state with the corresponding GUI widget by binding its activity with the enabling of the widget, so that whenever the state is active, its corresponding widget is enabled and whenever the state is inactive, the widget is disabled. We do that for all lights, targets, and description labels.

We also intercept the `updateScore` event sent by the state machine, in order to update the score displays with the values passed with the event.

The info about hitting any GUI target needs to be passed to the state machine and we do that by connecting all target buttons' `clicked` signals to the lambda expressions which submit the corresponding event into the state machine.

     #include "mainwindow.h"
     #include "pinball.h"

     #include <QApplication>

     int main(int argc, char **argv)
     {
         QApplication app(argc, argv);

         Pinball machine;
         MainWindow mainWindow(&machine);

         machine.start();
         mainWindow.show();
         return app.exec();
     }

In the `main()` function in the main.cpp file, we instantiate the `app` application object, `Pinball` state machine, and `MainWindow` GUI class. We initialize and start the state machine, show the main window, and execute the application.

Example project @ code.qt.io