summaryrefslogtreecommitdiffstats
path: root/examples/scxml/pinball/doc/src/pinball.qdoc
blob: 81a35aa377a59c6ad364470315b2f309e96d1063 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the documentation of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:FDL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Free Documentation License Usage
** Alternatively, this file may be used under the terms of the GNU Free
** Documentation License version 1.3 as published by the Free Software
** Foundation and appearing in the file included in the packaging of
** this file. Please review the following information to ensure
** the GNU Free Documentation License version 1.3 requirements
** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
** $QT_END_LICENSE$
**
****************************************************************************/

/*!
    \example pinball
    \title Qt SCXML Pinball Example
    \ingroup examples-qtscxml
    \brief Encapsulates the internal logic of an application in an SCXML file.

    \e {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.

    \include examples-run.qdocinc

    \section1 Pinball Features

    \image pinball.png Screenshot of the Pinball example

    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:
    \list
    \li Initially and when the game ends, the pinball table
        enters \c offState. In that state, all lights on the table
        blink slowly (at intervals of 1 second).
    \li After clicking the \uicontrol START button, the pinball table
        enters \c onState. All lights are turned off and the
        pinball table is ready to be played.
    \li When the table is in \c onState and the players
        click the \uicontrol {BALL OUT} button, the game ends
        and enters \c offState. If the players' score is
        higher than the current highscore, the highscore is updated.
    \li The goal is to collect the \uicontrol JACKPOT. In order to do that,
        the players must hit all five \uicontrol 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 \c 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.
    \li Scores:
        \list
        \li 1.000 per letter hit when not in \c hurryState.
        \li 10.000 per letter hit when in \c hurryState.
        \li 100.000 bonus for all 5 letters when not in \c hurryState.
        \li 1.000.000 bonus for all 5 letters when in \c hurryState
            (\uicontrol JACKPOT).
        \endlist
    \li When not in \c hurryState, the letters already hit should blink
        at intermediate speed (500ms). Letters not hit yet should stay off.
    \li When in \c hurryState, the letters already hit should
        stay on. Letters not hit yet should blink fast (200ms).
        In addition, the \uicontrol HURRY light should blink at the same speed.
    \li When the jackpot gets collected, the \uicontrol JACKPOT light should
        stay on.
    \endlist

    \section1 SCXML Part: Internal Logic Description

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

    \quotefromfile pinball/pinball.scxml
    \skipto scxml
    \printuntil ecmascript

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

    \printuntil </datamodel>

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

    \image pinball-statechart-global.png

    \section2 Maintaining Light State

    The \c 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.

    \image pinball-statechart-guicontrol.png

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

    \printuntil target="cLightOn"
    \printuntil /^\ {12}<\//

    As mentioned before, the \c guiControl state is always active, and since
    it is of parallel type, all its direct children are always active too.
    Therefore, the \c cLight state is always active. However,
    only one of its children, \c cLightOn or \c cLightOff, is active at a time.
    The same applies to the other children of the \c guiControl state.
    In addition, we define transitions between on and off substates. For example,
    whenever the active state is \c cLightOn and a \c turnOffC event is received,
    we change the active substate of \c cLight to \c cLightOff.
    Whenever the active state is \c cLightOff and we receive a \c turnOnC event,
    we change the active substate of \c cLight to \c 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 \e on or \e 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 \l {cpp}{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 \c internalState
    in reaction to running timers or external triggers.

    \section2 Maintaining Game State

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

    \image pinball-statechart-internalstate.png

    The \c 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 \c 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, \c internalState is always active, and since
    it is of a parallel type, \c logicalState and \c workflow are always active too.

    \section2 Maintaining Logical State of Buttons

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

    \image pinball-statechart-logicalstate.png

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

    \quotefromfile pinball/pinball.scxml
    \skipto letterState
    \printuntil lettersState
    \printuntil letter.R
    \dots 28
    \skipto /^\ {24}<\//
    \printuntil letter.A
    \dots 28
    \skipto /^\ {24}<\//
    \printuntil letter.Z
    \dots 28
    \skipto /^\ {24}<\//
    \printuntil letter.Y
    \dots 28
    \skipto /^\ {24}<\//
    \printuntil /^\ {16}<\//

    The \c 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
    \uicontrol C holds whether the target for the letter \uicontrol C was hit,
    while the light state for the letter \uicontrol C holds whether the light
    for the target for the letter \uicontrol 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, \c letterState is always active, which means
    that its only child \c lettersState should always be active too. However,
    there is one exception: for a short while the \c lettersState may
    end up being \e {not active}. This happens when the transition for
    \c lettersState is being performed. This transition is triggered when
    the \c resetLetters event occurs, and it instructs the state machine
    to exit \c lettersState and all its descendant states and reenter
    \c lettersState and set up all its descendant states with their initial states.
    In short, the \c resetLetters event resets the \c lettersState and all its
    descendant states to the default configuration.

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

    The \c cLetterOff state defines a transition, which will be triggered by
    the \c {cLetterTriggered} event. This transition activates \c cLetterOn,
    the other child of \c {letter.C}, only when the machine is in \c onState
    (that is, when the pinball game is running).
    The \c {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 \uicontrol C target. In our example we mimic
    it by the clicking the letter \uicontrol C button.

    The \c cLetterOn state is defined as a final state, which means that
    whenever this state is activated the \c {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 \c lettersState children reach their final state,
    the state machine will automatically post the \c {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.

    \section2 Maintaining Game Modes

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

    \image pinball-statechart-modestate.png

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

    \quotefromfile pinball/pinball.scxml
    \skipto offState
    \printuntil /^\ {20}<\//

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

    \skipto onState
    \printuntil /^\ {20}<\//

    \section2 Game On

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

    \image pinball-statechart-onstate.png

    Whenever we enter \c hurryStateOff or \c hurryStateOn, we generate the same
    two events we generate when entering the \c onState state: \c resetLetters and
    \c update. In addition, when we enter the \c hurryStateOn state, we send a delayed
    event, \c goToHurryOff, with a delay of five seconds, marked with \c hurryId.
    This means that after five seconds we just
    switch the state back to \c 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 \c hurryStateOff to \c hurryStateOn when the
    \c goToHurryOn event occurs and from \c hurryStateOn to \c hurryStateOff
    when the \c goToHurryOff event occurs. When we exit the \c hurryStateOn
    state, we cancel the possibly pending delayed event that was marked with
    \c 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 \c jackpotState generate the request to update the state
    of lights. The \c jackpotStateOff state defines the transition to \c jackpotStateOn
    when the \c 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 \c jackpotState
    is entered again which causes its initial active substate to be
    \c jackpotStateOff.

    In addition, the \c onState state defines one transition in reaction to the
    \c ballOutTriggered event which instructs the machine to go into the \c offState.
    The \c 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 \uicontrol {BALL OUT} button. Posting the event from outside of state
    machine will be shown in the \l{cpp}{C++ code} later on.

    \section2 Generating Blinking Lights

    The \c workflow state is responsible for generating the blinking lights. The
    generator is defined in its \c 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.

    \quotefromfile pinball/pinball.scxml
    \skipto workflow
    \printuntil done.state.letter.*
    \dots 20
    \skipto /^\ {16}<\//
    \printuntil done.state.lettersState
    \dots 20
    \skipto /^\ {16}<\//
    \printuntil updateLights
    \dots 20
    \skipto /^\ {16}<\//
    \printuntil updateLightsAccordingToLettersState
    \dots 20
    \skipto /^\ {16}<\//
    \printuntil turnOnLights
    \dots 20
    \skipto /^\ {16}<\//
    \printuntil turnOffLights
    \dots 20
    \skipto /^\ {16}<\//
    \printuntil /^\ {12}<\//

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

    \image pinball-statechart-workflow.png

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

    \quotefromfile pinball/pinball.scxml
    \skipto workflow
    \skipto done.state.letter.*
    \printuntil /^\ {16}<\//
    \printuntil /^\ {16}<\//

    Whenever we receive the event the name of which matches the
    \c {done.state.letter.*}, we update the current score.
    When the machine enters the final substate of the \c {letter.C},
    it emits the \c {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 \c hurryStateOff or \c hurryStateOn.
    After the score is updated, we generate the \c updateLights event
    in order to immediately update the letters' lights accordingly.
    We do not generate the \c 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 \c {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 \c hurryState substate by
    sending the \c goToHurryOn or \c goToHurryOff event.
    When all letters have been collected while in \c hurryStateOn,
    we also raise the \c goForJackpot event which instructs
    the machine to activate the \c jackpotStateOn.

    \skipto updateLights
    \printuntil /^\ {16}<\//
    \printuntil updateLightsAccordingToLettersState
    \printuntil /^\ {16}<\//
    \printuntil turnOnLights
    \printuntil /^\ {16}<\//
    \printuntil turnOffLights
    \printuntil /^\ {16}<\//

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

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

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

    \section1 GUI Part: User Interface Description

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

    \target cpp
    \section1 C++ Part: Glue GUI with SCXML

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

    \quotefromfile pinball/mainwindow.h
    \skipto MainWindow
    \printuntil };

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

    \quotefromfile pinball/mainwindow.cpp
    \skipto #include
    \printuntil /\}$/

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

    The \c 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 \c 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' \c clicked signals to the lambda expressions
    which submit the corresponding event into the state machine.

    \quotefromfile pinball/main.cpp
    \skipto #include
    \printuntil /\}$/

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