summaryrefslogtreecommitdiffstats
path: root/examples/scxml/pinball/doc/src/pinball.qdoc
blob: f67b66d2784a4409f06404cb250851fd5fd5fcbb (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
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
/****************************************************************************
**
** 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 Demonstrates how the internal
    logic of the application can be encapsulated in a scxml file.

    This example demonstrates a clear separation
    between chosen user interface, which may be easily replaced,
    and internal logic encapsulated in the scxml file,
    which may stay the same for different user interface.

    \section1 Pinball Features

    \image pinball.png Screenshot of the Pinball example

    \e {Pinball Example} mimics a pinball game.
    The targets on the pinball table are substituted here by GUI controls,
    mainly by push buttons. Display elements, including current
    score, highscore and targets' lights are substituted by labels.
    Usually targets' lights state changes very often during game:
    they get turned on or off, they blink slowly, fast or
    with any intermediate speed indicating a game (or a certain target)
    entered some temporary state. The state of each target light
    is presented here as an enabled or a disabled label.
    There is no real ball here, but one can easily imagine that
    pressing a target's button can mimic 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
        goes into \c offState. In that state all lights on the table
        blink slowly (with 1s interval).
    \li After pressing the \uicontrol START button the pinball table
        goes into \c onState. All lights get turned off and the
        pinball table is ready to be played.
    \li Whenever the table is in \c onState and the inattentive player
        presses the \uicontrol {BALL OUT} button, the game ends
        and enters into the \c offState. If the player's 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 player must hit all five \uicontrol CRAZY letters twice.
        He has unlimited time for collecting it for the first time.
        However, after he has collected all the letters for the first time,
        he enters the \c hurryState and he must quickly collect
        all the letters again within 5s. If the time passed and
        the letters were not collected again, the player 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
        with 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 with the same speed.
    \li When jackpot gets collected the \uicontrol JACKPOT light should stay on.
    \endlist

    \section1 SCXML Part: Internal Logic Description

    The pinball.scxml file describes the internal logic implemented for
    pinball game.

    \quotefromfile pinball/pinball.scxml
    \skipto scxml
    \printuntil guiControl
    \dots 12
    \skipto /^\ {8}<\//
    \printuntil internalState
    \dots 12
    \skipto /^\ {8}<\//
    \printuntil scxml

    In this example we've chosen ecmascript data model
    ("datamodel" attribute of <scxml> element).
    This data model enables declaring variables with
    initial values, which can be modified later.
    We declare two variables in our model: "highscore" and "score",
    with initial values of 0 (two <data> elements inside <datamodel> element).
    We also define a root parallel state "global",
    with two child states: \c guiControl and \c internalState,
    which are also parallel. Since the top \c global state is
    parallel, it means that whenever a \c global state is active
    all of its direct children are also active. In fact,
    the top level \c global is always active, meaning
    all of its children are always active, too.
    The \c global state in our example doesn't play any other role apart from
    collecting its two children and making them both active at a time.

    \quotefromfile pinball/pinball.scxml
    \skipto guiControl
    \printuntil rLight
    \dots 16
    \skipto /^\ {12}<\//
    \printuntil aLight
    \dots 16
    \skipto /^\ {12}<\//
    \printuntil zLight
    \dots 16
    \skipto /^\ {12}<\//
    \printuntil yLight
    \dots 16
    \skipto /^\ {12}<\//
    \printuntil hurryLight
    \dots 16
    \skipto /^\ {12}<\//
    \printuntil jackpotLight
    \dots 16
    \skipto /^\ {12}<\//
    \printuntil gameOverLight
    \dots 16
    \skipto /^\ {12}<\//
    \printuntil /^\ {8}<\//

    The \c guiControl part is responsible for maintaining the current
    state of every light control, which is visible on the pinball table.
    We have eight different lights, five for letters: C, R, A, Z and Y,
    and three for whole texts: \uicontrol HURRY!, \uicontrol JACKPOT
    and \uicontrol {GAME OVER}. Every light
    corresponds to its state, e.g. for light of letter \uicontrol C we have
    a \c cLight state and so on. Every light state has two child states
    indicating whether the light is on or off, e.g. \c cLight state
    contains \c cLightOn and \c cLightOff states. The content
    for other light states, which is omitted here, is analogous.
    As mentioned before, \c guiControl state is always active, and since
    it is of parallel type, all its direct children are always active too.
    So, \c cLight state is always active, however,
    only one of \c cLightOn or \c cLightOff it active at a time.
    The same applies to other children of \c guiControl state.
    In addition, we define transitions between on and off substates, e.g.
    whenever the active state is \c cLightOn and someone posts \c turnOffC event,
    we change the active substate of \c cLight into \c cLightOff, and vice versa:
    whenever the active state is \c cLightOff and we receive \c turnOnC event,
    we change the active substate of \c cLight into \c cLightOn.

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

    All of the mentioned events which switch the state of every light
    will be generated by this state machine inside \c internalState
    in reaction to running timers or to external triggers.

    \quotefromfile pinball/pinball.scxml
    \skipto internalState
    \printuntil logicalState
    \dots 16
    \skipto /^\ {12}<\//
    \printuntil workflow
    \dots 16
    \skipto /^\ {12}<\//
    \printuntil /^\ {8}<\//

    The \c internalState consists of two main parts: \c logicalState and \c workflow.
    The \c logicalState holds the definitions for modes the game is able
    to go into and logical states of collected targets, while \c workflow state
    implements generator for light blinking and calculates most of new states
    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 children are always active too.

    \quotefromfile pinball/pinball.scxml
    \skipto logicalState
    \printuntil letterState
    \dots 20
    \skipto /^\ {16}<\//
    \printuntil modeState
    \dots 20
    \skipto /^\ {16}<\//
    \printuntil /^\ {12}<\//

    The \c logicalState consist of two parts: \c letterState and \c modeState.
    As previously mentioned, \c logicalState is always active, and since
    it is of parallel type, \c letterState and \c modeState children are always active too.
    Now let's look at the first part of it, the \c letterState:

    \quotefromfile pinball/pinball.scxml
    \skipto letterState
    \printuntil lettersState
    \printuntil rLetter
    \dots 28
    \skipto /^\ {24}<\//
    \printuntil aLetter
    \dots 28
    \skipto /^\ {24}<\//
    \printuntil zLetter
    \dots 28
    \skipto /^\ {24}<\//
    \printuntil yLetter
    \dots 28
    \skipto /^\ {24}<\//
    \printuntil /^\ {16}<\//

    It contains one parallel \c lettersState.
    The \c lettersState maintains the logical state of
    buttons pretending targets which were pressed by the user.
    Please notice the difference between lettersStates and lightStates
    which were mentioned earlier. E.g. the letter state for \uicontrol C letter
    holds the info if the target for \uicontrol C letter was hit or not,
    while the light state for \uicontrol C letter holds the info if the
    light for the target for \uicontrol C letter should be currently on or off.
    In the real pinball game these states are usually orthogonal,
    e.g. if you didn't hit any target yet, the target is blinking,
    indicating that it's currently worth to hit it. This blinking
    means the light state switches between on or off in some short time interval,
    while the target state is continouosly off, since it was not hit yet.
    The author of a pinball table can decide e.g. that
    after hitting the target (so after the target state goes to on)
    he turns target's light continouosly off or on or just increase
    of decrease the interval of blinking.

    As mentioned before, \c letterState is always active, so it means
    that its only child \c lettersState should always be active too. However,
    there is an exception here: 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 will appear 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 which
    correspond to five different letters. The content
    for other letters states than C, which is omitted here, is analogous.

    The \c cLetter contains two substates reflecting its off and on states:
    \c cLetterOff and \c cLetterOn. The "cLetter" state inside its parallel
    parent \c lettersState is always active (under condition that
    \c lettersState is active, too, what was described before), however,
    only one of its child states is active at a time: \c cLetterOff or \c cLetterOn.
    The initial substate of \c cLetter state is \c cLetterOff meaning
    that whenever \c cLetter state is being activated (what happens
    initially and after \c resetLetters event) its active
    substate will be set to \c cLetterOff.

    The \c cLetterOff defines a transition, which will be triggered by
    the \c {cLetterTriggered} event. This transition activates the \c cLetterOn,
    the other child of \c cLetter, only when the machine is in \c onState
    (which will be defined later, but in short: 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 \uicontrol C letter target. In our example we mimic
    it by the pressing \uicontrol C letter button.

    The \c cLetterOn state defines additional action. The action is defined
    inside \c onentry element of the state, which means it will be executed
    when the state machine enters this state. The action will generate
    the event \c letterOn, which is common for all other letter states
    defined in \c lettersState. This event will be used in further part
    for updating the current score. So, whenever any of letters will switch
    its state from off to on (but not vice versa), the \c letterOn
    event will be posted.

    The \c cLetterOn state is defined as a final state, which means that
    whenever this state is activated the \c {done.state.cLetter} event
    will be automatically posted by the state machine. Moreover,
    when all \c lettersState children reach their final state,
    the state machine will automatically post \c {done.state.lettersState} event.
    Later, we will make use of it.

    \quotefromfile pinball/pinball.scxml
    \skipto modeState
    \printuntil offState
    \dots 24
    \skipto /^\ {20}<\//
    \printuntil onState
    \dots 24
    \skipto /^\ {20}<\//
    \printuntil /^\ {16}<\//

    The \c modeState consists of two substates, \c offState and \c onState.
    The \c offState describes what should happen when the pinball game
    is not yet started or when it is over,
    while \c onState represents the logic appropriate for the active game.

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

    Whenever the pinball application starts or when the game ends the machine goes into the
    \c offState. Entering that state invokes some actions, which are
    enclosed inside <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 <if> element
    (please note that we need to escape the "<" character with "&lt;").
    When being in off state, we still want to show the last reached score,
    so we don't clear it here - we will do that when we enter the on state.
    Next, we raise two events: \c resetLetters for logical reset of
    all letter which were possibly hit during last game and \c update
    for immediate generation of light blink and update of all lights.
    When the machine is in \c offState it is ready to go into the
    \c onState if only the \c startTriggered event occurs, which is described
    by <transition> element. This event is expected to be generated externally
    after pressing the \uicontrol START button on the pinball table.

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

    The fist thing which is being done by the state machine after in enters
    the \c onState is clearing the current score variable. The \c onState
    is of parallel type and consists of two direct child states:
    \c hurryState and \c jackpotState, which both are always active as long as
    their parent "onState" is active too. Both \c hurryState and \c jackpotState
    contain two substates which reflects the off and the on state of each of them.
    Only one substate of \c hurryState and one substate of \c jackpotState
    can be acitve at a time and initially the off substates are active.

    Whenever we enter \c hurryStateOff or \c hurryStateOn we generate two events:
    \c resetLetters and \c update (the same as we generate when entering the \c onState).
    In addition when we enter the \c hurryStateOn we send a delayed event
    \c goToHurryOff with a delay of 5s, marked with \c hurryId. It means, that after 5s we just
    switch the state back to \c hurryStateOff. In this way we implement
    the five seconds hurry feature of the pinball table.
    We also define transitions: \c hurryStateOff -> \c hurryStateOn
    when \c goToHurryOn event occurs and the opposite,
    \c hurryStateOn -> \c hurryStateOff when \c goToHurryOff event occurs.
    When we exit the \c hurryStateOn we cancel possibly pending delayed
    event which was marked with \c hurryId. This is important in case
    the 5s time hasn't elapsed yet, but we've 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 for update the state
    of lights. The \c jackpotStateOff defines the transition to \c jackpotStateOn
    when the \c goForJackpot event occurs. The opposite transition isn't
    needed, since when the jackpot gets collected the corresponding light
    is left lit until the end of game. When the new game starts, the \c jackpotState
    is entered again which causes that its initial active substate will be
    \c jackpotStateOff.

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

    \quotefromfile pinball/pinball.scxml
    \skipto workflow
    \printuntil letterOn
    \dots 20
    \skipto /^\ {16}<\//
    \printuntil lettersState
    \dots 20
    \skipto /^\ {16}<\//
    \printuntil updateLights
    \dots 20
    \skipto /^\ {16}<\//
    \printuntil /^\ {12}<\//

    The \c workflow state is responsible for generator for light blinking,
    which is defined in its \c lightImpulseGenerator substate, and
    for reacting for events which were posted so far from other parts
    of the state machine.

    The \c lightImpulseGenerator contains two child states:
    \c lightImpulseOn and \c lightImpulseOff, with only one active at a time.
    Whenever the delayed \c lightImpulse event is being delivered, it immediately
    cause 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 cause generation of the \c update event.
    The \c update event triggers the targetless transition and posts two other
    events: \c scheduleNewImpulse and \c updateLights. The first one,
    \c scheduleNewImpulse returns back to the \c lightImpulseGenerator and
    it posts delayed \c lightImpulse event. After the desired delay,
    the \c lightImpulse gets delivered to \c lightImpulseGenerator back
    and it causes to toggle its substate again. In this way the machine
    gets into the cycle. The current delay of the \c lightImpulse
    event depends on the state in which the machine was in time of posting that delayed event.
    Please also notice, that the next \c scheduleNewImpulse
    event may occur on demand, before the next delayed \c lightImpulse
    event gets delivered, so for that case we cancel any possible pending event.

    \quotefromfile pinball/pinball.scxml
    \skipto workflow
    \skipto letterOn
    \printuntil /^\ {16}<\//
    \printuntil /^\ {16}<\//

    Whenever we receive the \c letterOn event we update the current score.
    The transition for the \c letterOn event is targetless, since we just
    listen for that event 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 if we currently are
    in \c hurryStateOff or \c hurryStateOn.
    After the score is updated we generate the \c updateLights event
    in order to update immediately letters' lights accordingly.
    We don't generate "update" here, since we don't want to toggle
    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 were hit.
    Depending on which state we are currently in, we grant
    a small bonus of 100.000 or a big one of 1.000.000 (jackpot).
    In addition we toggle \c hurryState substate by
    sending \c goToHurryOn or \c goToHurryOff event.
    When all letters were collected being in \c hurryStateOn
    we also raise the \c goForJackpot event which instructs
    the machine to activate the \c jackpotStateOn.

    \skipto updateLights
    \printuntil rLetterOn
    \dots 32
    \skipto /^\ {28}<\//
    \printuntil aLetterOn
    \dots 32
    \skipto /^\ {28}<\//
    \printuntil zLetterOn
    \dots 32
    \skipto /^\ {28}<\//
    \printuntil yLetterOn
    \dots 32
    \skipto /^\ {28}<\//
    \printuntil rLetterOn
    \dots 32
    \skipto /^\ {28}<\//
    \printuntil aLetterOn
    \dots 32
    \skipto /^\ {28}<\//
    \printuntil zLetterOn
    \dots 32
    \skipto /^\ {28}<\//
    \printuntil yLetterOn
    \dots 32
    \skipto /^\ {28}<\//
    \printuntil /^\ {16}<\//

    When we receive \c updateLights event, we first want to send a
    \c updateScore signal outside of the state machine. We pass
    to the signal current value of highscore and score variables.
    This signal is received by the cpp part.

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

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

    \section1 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.

    \target cpp
    \section1 CPP Part: Glue GUI with SCXML

    The CPP 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.

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

    The 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 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 with 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 enablement 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 lables.

    We also connect to the \c updateScore propagated
    by the state machine and 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 corresponding event into the state machine.

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

    Finally, there is main.cpp file with the main() function
    inside which 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.
*/