@scratch/scratch-vm 11.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/.jsdoc.json +20 -0
  2. package/.nvmrc +1 -0
  3. package/CHANGELOG.md +7004 -0
  4. package/LICENSE +12 -0
  5. package/README.md +120 -0
  6. package/TRADEMARK +1 -0
  7. package/commitlint.config.js +4 -0
  8. package/docs/extensions.md +527 -0
  9. package/package.json +103 -0
  10. package/release.config.js +13 -0
  11. package/src/blocks/scratch3_control.js +206 -0
  12. package/src/blocks/scratch3_core_example.js +69 -0
  13. package/src/blocks/scratch3_data.js +257 -0
  14. package/src/blocks/scratch3_event.js +137 -0
  15. package/src/blocks/scratch3_looks.js +612 -0
  16. package/src/blocks/scratch3_motion.js +288 -0
  17. package/src/blocks/scratch3_operators.js +154 -0
  18. package/src/blocks/scratch3_procedures.js +79 -0
  19. package/src/blocks/scratch3_sensing.js +336 -0
  20. package/src/blocks/scratch3_sound.js +350 -0
  21. package/src/dispatch/central-dispatch.js +143 -0
  22. package/src/dispatch/shared-dispatch.js +235 -0
  23. package/src/dispatch/worker-dispatch.js +110 -0
  24. package/src/engine/adapter.js +176 -0
  25. package/src/engine/block-utility.js +242 -0
  26. package/src/engine/blocks-execute-cache.js +19 -0
  27. package/src/engine/blocks-runtime-cache.js +78 -0
  28. package/src/engine/blocks.js +1299 -0
  29. package/src/engine/comment.js +56 -0
  30. package/src/engine/execute.js +580 -0
  31. package/src/engine/monitor-record.js +23 -0
  32. package/src/engine/mutation-adapter.js +48 -0
  33. package/src/engine/profiler.js +390 -0
  34. package/src/engine/runtime.js +2686 -0
  35. package/src/engine/scratch-blocks-constants.js +27 -0
  36. package/src/engine/sequencer.js +361 -0
  37. package/src/engine/stage-layering.js +29 -0
  38. package/src/engine/target.js +799 -0
  39. package/src/engine/thread.js +404 -0
  40. package/src/engine/variable.js +70 -0
  41. package/src/extension-support/argument-type.js +47 -0
  42. package/src/extension-support/block-type.js +50 -0
  43. package/src/extension-support/define-messages.js +18 -0
  44. package/src/extension-support/extension-manager.js +440 -0
  45. package/src/extension-support/extension-metadata.js +64 -0
  46. package/src/extension-support/extension-worker.js +59 -0
  47. package/src/extension-support/reporter-scope.js +18 -0
  48. package/src/extension-support/target-type.js +17 -0
  49. package/src/extensions/scratch3_boost/index.js +2113 -0
  50. package/src/extensions/scratch3_ev3/index.js +1355 -0
  51. package/src/extensions/scratch3_gdx_for/index.js +981 -0
  52. package/src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js +44 -0
  53. package/src/extensions/scratch3_makeymakey/index.js +396 -0
  54. package/src/extensions/scratch3_microbit/index.js +984 -0
  55. package/src/extensions/scratch3_music/assets/drums/1-snare.mp3 +0 -0
  56. package/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3 +0 -0
  57. package/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3 +0 -0
  58. package/src/extensions/scratch3_music/assets/drums/12-triangle.mp3 +0 -0
  59. package/src/extensions/scratch3_music/assets/drums/13-bongo.mp3 +0 -0
  60. package/src/extensions/scratch3_music/assets/drums/14-conga.mp3 +0 -0
  61. package/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3 +0 -0
  62. package/src/extensions/scratch3_music/assets/drums/16-guiro.mp3 +0 -0
  63. package/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3 +0 -0
  64. package/src/extensions/scratch3_music/assets/drums/18-cuica.mp3 +0 -0
  65. package/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3 +0 -0
  66. package/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3 +0 -0
  67. package/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3 +0 -0
  68. package/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3 +0 -0
  69. package/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3 +0 -0
  70. package/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3 +0 -0
  71. package/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3 +0 -0
  72. package/src/extensions/scratch3_music/assets/drums/9-claves.mp3 +0 -0
  73. package/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3 +0 -0
  74. package/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3 +0 -0
  75. package/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3 +0 -0
  76. package/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3 +0 -0
  77. package/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3 +0 -0
  78. package/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3 +0 -0
  79. package/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3 +0 -0
  80. package/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3 +0 -0
  81. package/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3 +0 -0
  82. package/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3 +0 -0
  83. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3 +0 -0
  84. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3 +0 -0
  85. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3 +0 -0
  86. package/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3 +0 -0
  87. package/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3 +0 -0
  88. package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3 +0 -0
  89. package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3 +0 -0
  90. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3 +0 -0
  91. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3 +0 -0
  92. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3 +0 -0
  93. package/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3 +0 -0
  94. package/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3 +0 -0
  95. package/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3 +0 -0
  96. package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3 +0 -0
  97. package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3 +0 -0
  98. package/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3 +0 -0
  99. package/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3 +0 -0
  100. package/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3 +0 -0
  101. package/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3 +0 -0
  102. package/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3 +0 -0
  103. package/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3 +0 -0
  104. package/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3 +0 -0
  105. package/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3 +0 -0
  106. package/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3 +0 -0
  107. package/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3 +0 -0
  108. package/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3 +0 -0
  109. package/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3 +0 -0
  110. package/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3 +0 -0
  111. package/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3 +0 -0
  112. package/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3 +0 -0
  113. package/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3 +0 -0
  114. package/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3 +0 -0
  115. package/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3 +0 -0
  116. package/src/extensions/scratch3_music/index.js +1333 -0
  117. package/src/extensions/scratch3_music/manifest.js +63 -0
  118. package/src/extensions/scratch3_pen/index.js +770 -0
  119. package/src/extensions/scratch3_speech2text/index.js +700 -0
  120. package/src/extensions/scratch3_text2speech/index.js +766 -0
  121. package/src/extensions/scratch3_translate/index.js +286 -0
  122. package/src/extensions/scratch3_video_sensing/debug.js +13 -0
  123. package/src/extensions/scratch3_video_sensing/index.js +594 -0
  124. package/src/extensions/scratch3_video_sensing/library.js +384 -0
  125. package/src/extensions/scratch3_video_sensing/math.js +76 -0
  126. package/src/extensions/scratch3_video_sensing/view.js +509 -0
  127. package/src/extensions/scratch3_wedo2/index.js +1616 -0
  128. package/src/import/load-costume.js +416 -0
  129. package/src/import/load-sound.js +116 -0
  130. package/src/index.js +10 -0
  131. package/src/io/ble.js +256 -0
  132. package/src/io/bt.js +202 -0
  133. package/src/io/clock.js +39 -0
  134. package/src/io/cloud.js +169 -0
  135. package/src/io/keyboard.js +146 -0
  136. package/src/io/mouse.js +145 -0
  137. package/src/io/mouseWheel.js +28 -0
  138. package/src/io/userData.js +24 -0
  139. package/src/io/video.js +210 -0
  140. package/src/playground/benchmark.css +83 -0
  141. package/src/playground/benchmark.js +753 -0
  142. package/src/playground/index.html +111 -0
  143. package/src/playground/suite.css +78 -0
  144. package/src/playground/suite.html +25 -0
  145. package/src/playground/suite.js +544 -0
  146. package/src/playground/video-sensing.html +18 -0
  147. package/src/playground/video-sensing.js +133 -0
  148. package/src/serialization/deserialize-assets.js +178 -0
  149. package/src/serialization/sb2.js +1295 -0
  150. package/src/serialization/sb2_specmap.js +1818 -0
  151. package/src/serialization/sb3.js +1320 -0
  152. package/src/serialization/serialize-assets.js +60 -0
  153. package/src/sprites/rendered-target.js +1115 -0
  154. package/src/sprites/sprite.js +177 -0
  155. package/src/util/base64-util.js +48 -0
  156. package/src/util/cast.js +218 -0
  157. package/src/util/clone.js +17 -0
  158. package/src/util/color.js +204 -0
  159. package/src/util/fetch-with-timeout.js +53 -0
  160. package/src/util/get-monitor-id.js +33 -0
  161. package/src/util/jsonrpc.js +114 -0
  162. package/src/util/log.js +4 -0
  163. package/src/util/math-util.js +122 -0
  164. package/src/util/maybe-format-message.js +18 -0
  165. package/src/util/new-block-ids.js +33 -0
  166. package/src/util/rateLimiter.js +73 -0
  167. package/src/util/scratch-link-websocket.js +133 -0
  168. package/src/util/string-util.js +94 -0
  169. package/src/util/task-queue.js +203 -0
  170. package/src/util/timer.js +103 -0
  171. package/src/util/uid.js +29 -0
  172. package/src/util/variable-util.js +48 -0
  173. package/src/util/xml-escape.js +33 -0
  174. package/src/virtual-machine.js +1586 -0
  175. package/webpack.config.js +134 -0
@@ -0,0 +1,44 @@
1
+ const Base64Util = require('../../util/base64-util');
2
+
3
+ /**
4
+ * Adapter class
5
+ */
6
+ class ScratchLinkDeviceAdapter {
7
+ constructor (socket, {service, commandChar, responseChar}) {
8
+ this.socket = socket;
9
+
10
+ this._service = service;
11
+ this._commandChar = commandChar;
12
+ this._responseChar = responseChar;
13
+ this._onResponse = this._onResponse.bind(this);
14
+ this._deviceOnResponse = null;
15
+ }
16
+
17
+ get godirectAdapter () {
18
+ return true;
19
+ }
20
+
21
+ writeCommand (commandBuffer) {
22
+ const data = Base64Util.uint8ArrayToBase64(commandBuffer);
23
+
24
+ return this.socket
25
+ .write(this._service, this._commandChar, data, 'base64');
26
+ }
27
+
28
+ setup ({onResponse}) {
29
+ this._deviceOnResponse = onResponse;
30
+ return this.socket
31
+ .startNotifications(this._service, this._responseChar, this._onResponse);
32
+
33
+ // TODO:
34
+ // How do we find out from scratch link if communication closes?
35
+ }
36
+
37
+ _onResponse (base64) {
38
+ const array = Base64Util.base64ToUint8Array(base64);
39
+ const response = new DataView(array.buffer);
40
+ return this._deviceOnResponse(response);
41
+ }
42
+ }
43
+
44
+ module.exports = ScratchLinkDeviceAdapter;
@@ -0,0 +1,396 @@
1
+ const formatMessage = require('format-message');
2
+ const ArgumentType = require('../../extension-support/argument-type');
3
+ const BlockType = require('../../extension-support/block-type');
4
+ const Cast = require('../../util/cast');
5
+
6
+ /**
7
+ * Icon svg to be displayed at the left edge of each extension block, encoded as a data URI.
8
+ * @type {string}
9
+ */
10
+ // eslint-disable-next-line max-len
11
+ const blockIconURI = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHN0eWxlPi5zdDJ7ZmlsbDpyZWR9LnN0M3tmaWxsOiNlMGUwZTB9LnN0NHtmaWxsOm5vbmU7c3Ryb2tlOiM2NjY7c3Ryb2tlLXdpZHRoOi41O3N0cm9rZS1taXRlcmxpbWl0OjEwfTwvc3R5bGU+PHBhdGggZD0iTTM1IDI4SDVhMSAxIDAgMCAxLTEtMVYxMmMwLS42LjQtMSAxLTFoMzBjLjUgMCAxIC40IDEgMXYxNWMwIC41LS41IDEtMSAxeiIgZmlsbD0iI2ZmZiIgaWQ9IkxheWVyXzYiLz48ZyBpZD0iTGF5ZXJfNCI+PHBhdGggY2xhc3M9InN0MiIgZD0iTTQgMjVoMzJ2Mi43SDR6TTEzIDI0aC0yLjJhMSAxIDAgMCAxLTEtMXYtOS43YzAtLjYuNC0xIDEtMUgxM2MuNiAwIDEgLjQgMSAxVjIzYzAgLjYtLjUgMS0xIDF6Ii8+PHBhdGggY2xhc3M9InN0MiIgZD0iTTYuMSAxOS4zdi0yLjJjMC0uNS40LTEgMS0xaDkuN2MuNSAwIDEgLjUgMSAxdjIuMmMwIC41LS41IDEtMSAxSDcuMWExIDEgMCAwIDEtMS0xeiIvPjxjaXJjbGUgY2xhc3M9InN0MiIgY3g9IjIyLjgiIGN5PSIxOC4yIiByPSIzLjQiLz48Y2lyY2xlIGNsYXNzPSJzdDIiIGN4PSIzMC42IiBjeT0iMTguMiIgcj0iMy40Ii8+PHBhdGggY2xhc3M9InN0MiIgZD0iTTQuMiAyN2gzMS45di43SDQuMnoiLz48L2c+PGcgaWQ9IkxheWVyXzUiPjxjaXJjbGUgY2xhc3M9InN0MyIgY3g9IjIyLjgiIGN5PSIxOC4yIiByPSIyLjMiLz48Y2lyY2xlIGNsYXNzPSJzdDMiIGN4PSIzMC42IiBjeT0iMTguMiIgcj0iMi4zIi8+PHBhdGggY2xhc3M9InN0MyIgZD0iTTEyLjUgMjIuOWgtMS4yYy0uMyAwLS41LS4yLS41LS41VjE0YzAtLjMuMi0uNS41LS41aDEuMmMuMyAwIC41LjIuNS41djguNGMwIC4zLS4yLjUtLjUuNXoiLz48cGF0aCBjbGFzcz0ic3QzIiBkPSJNNy4yIDE4Ljd2LTEuMmMwLS4zLjItLjUuNS0uNWg4LjRjLjMgMCAuNS4yLjUuNXYxLjJjMCAuMy0uMi41LS41LjVINy43Yy0uMyAwLS41LS4yLS41LS41ek00IDI2aDMydjJINHoiLz48L2c+PGcgaWQ9IkxheWVyXzMiPjxwYXRoIGNsYXNzPSJzdDQiIGQ9Ik0zNS4yIDI3LjlINC44YTEgMSAwIDAgMS0xLTFWMTIuMWMwLS42LjUtMSAxLTFoMzAuNWMuNSAwIDEgLjQgMSAxVjI3YTEgMSAwIDAgMS0xLjEuOXoiLz48cGF0aCBjbGFzcz0ic3Q0IiBkPSJNMzUuMiAyNy45SDQuOGExIDEgMCAwIDEtMS0xVjEyLjFjMC0uNi41LTEgMS0xaDMwLjVjLjUgMCAxIC40IDEgMVYyN2ExIDEgMCAwIDEtMS4xLjl6Ii8+PC9nPjwvc3ZnPg==';
12
+
13
+ /**
14
+ * Length of the buffer to store key presses for the "when keys pressed in order" hat
15
+ * @type {number}
16
+ */
17
+ const KEY_BUFFER_LENGTH = 100;
18
+
19
+ /**
20
+ * Timeout in milliseconds to reset the completed flag for a sequence.
21
+ * @type {number}
22
+ */
23
+ const SEQUENCE_HAT_TIMEOUT = 100;
24
+
25
+ /**
26
+ * An id for the space key on a keyboard.
27
+ */
28
+ const KEY_ID_SPACE = 'SPACE';
29
+
30
+ /**
31
+ * An id for the left arrow key on a keyboard.
32
+ */
33
+ const KEY_ID_LEFT = 'LEFT';
34
+
35
+ /**
36
+ * An id for the right arrow key on a keyboard.
37
+ */
38
+ const KEY_ID_RIGHT = 'RIGHT';
39
+
40
+ /**
41
+ * An id for the up arrow key on a keyboard.
42
+ */
43
+ const KEY_ID_UP = 'UP';
44
+
45
+ /**
46
+ * An id for the down arrow key on a keyboard.
47
+ */
48
+ const KEY_ID_DOWN = 'DOWN';
49
+
50
+ /**
51
+ * Names used by keyboard io for keys used in scratch.
52
+ * @enum {string}
53
+ */
54
+ const SCRATCH_KEY_NAME = {
55
+ [KEY_ID_SPACE]: 'space',
56
+ [KEY_ID_LEFT]: 'left arrow',
57
+ [KEY_ID_UP]: 'up arrow',
58
+ [KEY_ID_RIGHT]: 'right arrow',
59
+ [KEY_ID_DOWN]: 'down arrow'
60
+ };
61
+
62
+ /**
63
+ * Class for the makey makey blocks in Scratch 3.0
64
+ * @constructor
65
+ */
66
+ class Scratch3MakeyMakeyBlocks {
67
+ constructor (runtime) {
68
+ /**
69
+ * The runtime instantiating this block package.
70
+ * @type {Runtime}
71
+ */
72
+ this.runtime = runtime;
73
+
74
+ /**
75
+ * A toggle that alternates true and false each frame, so that an
76
+ * edge-triggered hat can trigger on every other frame.
77
+ * @type {boolean}
78
+ */
79
+ this.frameToggle = false;
80
+
81
+ // Set an interval that toggles the frameToggle every frame.
82
+ setInterval(() => {
83
+ this.frameToggle = !this.frameToggle;
84
+ }, this.runtime.currentStepTime);
85
+
86
+ this.keyPressed = this.keyPressed.bind(this);
87
+ this.runtime.on('KEY_PRESSED', this.keyPressed);
88
+
89
+ this._clearkeyPressBuffer = this._clearkeyPressBuffer.bind(this);
90
+ this.runtime.on('PROJECT_STOP_ALL', this._clearkeyPressBuffer);
91
+
92
+ /*
93
+ * An object containing a set of sequence objects.
94
+ * These are the key sequences currently being detected by the "when
95
+ * keys pressed in order" hat block. Each sequence is keyed by its
96
+ * string representation (the sequence's value in the menu, which is a
97
+ * string of KEY_IDs separated by spaces). Each sequence object
98
+ * has an array property (an array of KEY_IDs) and a boolean
99
+ * completed property that is true when the sequence has just been
100
+ * pressed.
101
+ * @type {object}
102
+ */
103
+ this.sequences = {};
104
+
105
+ /*
106
+ * An array of the key codes of recently pressed keys.
107
+ * @type {array}
108
+ */
109
+ this.keyPressBuffer = [];
110
+ }
111
+
112
+ /*
113
+ * Localized short-form names of the space bar and arrow keys, for use in the
114
+ * displayed menu items of the "when keys pressed in order" block.
115
+ * @type {object}
116
+ */
117
+ get KEY_TEXT_SHORT () {
118
+ return {
119
+ [KEY_ID_SPACE]: formatMessage({
120
+ id: 'makeymakey.spaceKey',
121
+ default: 'space',
122
+ description: 'The space key on a computer keyboard.'
123
+ }),
124
+ [KEY_ID_LEFT]: formatMessage({
125
+ id: 'makeymakey.leftArrowShort',
126
+ default: 'left',
127
+ description: 'Short name for the left arrow key on a computer keyboard.'
128
+ }),
129
+ [KEY_ID_UP]: formatMessage({
130
+ id: 'makeymakey.upArrowShort',
131
+ default: 'up',
132
+ description: 'Short name for the up arrow key on a computer keyboard.'
133
+ }),
134
+ [KEY_ID_RIGHT]: formatMessage({
135
+ id: 'makeymakey.rightArrowShort',
136
+ default: 'right',
137
+ description: 'Short name for the right arrow key on a computer keyboard.'
138
+ }),
139
+ [KEY_ID_DOWN]: formatMessage({
140
+ id: 'makeymakey.downArrowShort',
141
+ default: 'down',
142
+ description: 'Short name for the down arrow key on a computer keyboard.'
143
+ })
144
+ };
145
+ }
146
+
147
+ /*
148
+ * An array of strings of KEY_IDs representing the default set of
149
+ * key sequences for use by the "when keys pressed in order" block.
150
+ * @type {array}
151
+ */
152
+ get DEFAULT_SEQUENCES () {
153
+ return [
154
+ `${KEY_ID_LEFT} ${KEY_ID_UP} ${KEY_ID_RIGHT}`,
155
+ `${KEY_ID_RIGHT} ${KEY_ID_UP} ${KEY_ID_LEFT}`,
156
+ `${KEY_ID_LEFT} ${KEY_ID_RIGHT}`,
157
+ `${KEY_ID_RIGHT} ${KEY_ID_LEFT}`,
158
+ `${KEY_ID_UP} ${KEY_ID_DOWN}`,
159
+ `${KEY_ID_DOWN} ${KEY_ID_UP}`,
160
+ `${KEY_ID_UP} ${KEY_ID_RIGHT} ${KEY_ID_DOWN} ${KEY_ID_LEFT}`,
161
+ `${KEY_ID_UP} ${KEY_ID_LEFT} ${KEY_ID_DOWN} ${KEY_ID_RIGHT}`,
162
+ `${KEY_ID_UP} ${KEY_ID_UP} ${KEY_ID_DOWN} ${KEY_ID_DOWN} ` +
163
+ `${KEY_ID_LEFT} ${KEY_ID_RIGHT} ${KEY_ID_LEFT} ${KEY_ID_RIGHT}`
164
+ ];
165
+ }
166
+
167
+ /**
168
+ * @returns {object} metadata for this extension and its blocks.
169
+ */
170
+ getInfo () {
171
+ return {
172
+ id: 'makeymakey',
173
+ name: 'Makey Makey',
174
+ blockIconURI: blockIconURI,
175
+ blocks: [
176
+ {
177
+ opcode: 'whenMakeyKeyPressed',
178
+ text: formatMessage({
179
+ id: 'makeymakey.whenKeyPressed',
180
+ default: 'when [KEY] key pressed',
181
+ description: 'when a keyboard key is pressed'
182
+ }),
183
+ blockType: BlockType.HAT,
184
+ arguments: {
185
+ KEY: {
186
+ type: ArgumentType.STRING,
187
+ menu: 'KEY',
188
+ defaultValue: KEY_ID_SPACE
189
+ }
190
+ }
191
+ },
192
+ {
193
+ opcode: 'whenCodePressed',
194
+ text: formatMessage({
195
+ id: 'makeymakey.whenKeysPressedInOrder',
196
+ default: 'when [SEQUENCE] pressed in order',
197
+ description: 'when a sequence of keyboard keys is pressed in a specific order'
198
+ }),
199
+ blockType: BlockType.HAT,
200
+ arguments: {
201
+ SEQUENCE: {
202
+ type: ArgumentType.STRING,
203
+ menu: 'SEQUENCE',
204
+ defaultValue: this.DEFAULT_SEQUENCES[0]
205
+ }
206
+ }
207
+ }
208
+ ],
209
+ menus: {
210
+ KEY: {
211
+ acceptReporters: true,
212
+ items: [
213
+ {
214
+ text: formatMessage({
215
+ id: 'makeymakey.spaceKey',
216
+ default: 'space',
217
+ description: 'The space key on a computer keyboard.'
218
+ }),
219
+ value: KEY_ID_SPACE
220
+ },
221
+ {
222
+ text: formatMessage({
223
+ id: 'makeymakey.upArrow',
224
+ default: 'up arrow',
225
+ description: 'The up arrow key on a computer keyboard.'
226
+ }),
227
+ value: KEY_ID_UP
228
+ },
229
+ {
230
+ text: formatMessage({
231
+ id: 'makeymakey.downArrow',
232
+ default: 'down arrow',
233
+ description: 'The down arrow key on a computer keyboard.'
234
+ }),
235
+ value: KEY_ID_DOWN
236
+ },
237
+ {
238
+ text: formatMessage({
239
+ id: 'makeymakey.rightArrow',
240
+ default: 'right arrow',
241
+ description: 'The right arrow key on a computer keyboard.'
242
+ }),
243
+ value: KEY_ID_RIGHT
244
+ },
245
+ {
246
+ text: formatMessage({
247
+ id: 'makeymakey.leftArrow',
248
+ default: 'left arrow',
249
+ description: 'The left arrow key on a computer keyboard.'
250
+ }),
251
+ value: KEY_ID_LEFT
252
+ },
253
+ {text: 'w', value: 'w'},
254
+ {text: 'a', value: 'a'},
255
+ {text: 's', value: 's'},
256
+ {text: 'd', value: 'd'},
257
+ {text: 'f', value: 'f'},
258
+ {text: 'g', value: 'g'}
259
+ ]
260
+ },
261
+ SEQUENCE: {
262
+ acceptReporters: true,
263
+ items: this.buildSequenceMenu(this.DEFAULT_SEQUENCES)
264
+ }
265
+ }
266
+ };
267
+ }
268
+
269
+ /*
270
+ * Build the menu of key sequences.
271
+ * @param {array} sequencesArray an array of strings of KEY_IDs.
272
+ * @returns {array} an array of objects with text and value properties.
273
+ */
274
+ buildSequenceMenu (sequencesArray) {
275
+ return sequencesArray.map(
276
+ str => this.getMenuItemForSequenceString(str)
277
+ );
278
+ }
279
+
280
+ /*
281
+ * Create a menu item for a sequence string.
282
+ * @param {string} sequenceString a string of KEY_IDs.
283
+ * @return {object} an object with text and value properties.
284
+ */
285
+ getMenuItemForSequenceString (sequenceString) {
286
+ let sequenceArray = sequenceString.split(' ');
287
+ sequenceArray = sequenceArray.map(str => this.KEY_TEXT_SHORT[str]);
288
+ return {
289
+ text: sequenceArray.join(' '),
290
+ value: sequenceString
291
+ };
292
+ }
293
+
294
+ /*
295
+ * Check whether a keyboard key is currently pressed.
296
+ * Also, toggle the results of the test on alternate frames, so that the
297
+ * hat block fires repeatedly.
298
+ * @param {object} args - the block arguments.
299
+ * @property {number} KEY - a key code.
300
+ * @param {object} util - utility object provided by the runtime.
301
+ */
302
+ whenMakeyKeyPressed (args, util) {
303
+ let key = args.KEY;
304
+ // Convert the key arg, if it is a KEY_ID, to the key name used by
305
+ // the Keyboard io module.
306
+ if (SCRATCH_KEY_NAME[args.KEY]) {
307
+ key = SCRATCH_KEY_NAME[args.KEY];
308
+ }
309
+ const isDown = util.ioQuery('keyboard', 'getKeyIsDown', [key]);
310
+ return (isDown && this.frameToggle);
311
+ }
312
+
313
+ /*
314
+ * A function called on the KEY_PRESSED event, to update the key press
315
+ * buffer and check if any of the key sequences have been completed.
316
+ * @param {string} key A scratch key name.
317
+ */
318
+ keyPressed (key) {
319
+ // Store only the first word of the Scratch key name, so that e.g. when
320
+ // "left arrow" is pressed, we store "LEFT", which matches KEY_ID_LEFT
321
+ key = key.split(' ')[0];
322
+ key = key.toUpperCase();
323
+ this.keyPressBuffer.push(key);
324
+ // Keep the buffer under the length limit
325
+ if (this.keyPressBuffer.length > KEY_BUFFER_LENGTH) {
326
+ this.keyPressBuffer.shift();
327
+ }
328
+ // Check the buffer for each sequence in use
329
+ for (const str in this.sequences) {
330
+ const arr = this.sequences[str].array;
331
+ // Bail out if we don't have enough presses for this sequence
332
+ if (this.keyPressBuffer.length < arr.length) {
333
+ continue;
334
+ }
335
+ let missFlag = false;
336
+ // Slice the buffer to the length of the sequence we're checking
337
+ const bufferSegment = this.keyPressBuffer.slice(-1 * arr.length);
338
+ for (let i = 0; i < arr.length; i++) {
339
+ if (arr[i] !== bufferSegment[i]) {
340
+ missFlag = true;
341
+ }
342
+ }
343
+ // If the miss flag is false, the sequence matched the buffer
344
+ if (!missFlag) {
345
+ this.sequences[str].completed = true;
346
+ // Clear the completed flag after a timeout. This is necessary because
347
+ // the hat is edge-triggered (not event triggered). Multiple hats
348
+ // may be checking the same sequence, so this timeout gives them enough
349
+ // time to all trigger before resetting the flag.
350
+ setTimeout(() => {
351
+ this.sequences[str].completed = false;
352
+ }, SEQUENCE_HAT_TIMEOUT);
353
+ }
354
+ }
355
+ }
356
+
357
+ /**
358
+ * Clear the key press buffer.
359
+ */
360
+ _clearkeyPressBuffer () {
361
+ this.keyPressBuffer = [];
362
+ }
363
+
364
+ /*
365
+ * Add a key sequence to the set currently being checked on each key press.
366
+ * @param {string} sequenceString a string of space-separated KEY_IDs.
367
+ * @param {array} sequenceArray an array of KEY_IDs.
368
+ */
369
+ addSequence (sequenceString, sequenceArray) {
370
+ // If we already have this sequence string, return.
371
+ if (Object.prototype.hasOwnProperty.call(this.sequences, sequenceString)) {
372
+ return;
373
+ }
374
+ this.sequences[sequenceString] = {
375
+ array: sequenceArray,
376
+ completed: false
377
+ };
378
+ }
379
+
380
+ /*
381
+ * Check whether a key sequence was recently completed.
382
+ * @param {object} args The block arguments.
383
+ * @property {number} SEQUENCE A string of KEY_IDs.
384
+ */
385
+ whenCodePressed (args) {
386
+ const sequenceString = Cast.toString(args.SEQUENCE).toUpperCase();
387
+ const sequenceArray = sequenceString.split(' ');
388
+ if (sequenceArray.length < 2) {
389
+ return;
390
+ }
391
+ this.addSequence(sequenceString, sequenceArray);
392
+
393
+ return this.sequences[sequenceString].completed;
394
+ }
395
+ }
396
+ module.exports = Scratch3MakeyMakeyBlocks;