├── .gitmodules ├── CNAME ├── LICENSE ├── README.md ├── buildhtml.cmd ├── buildhtml.py ├── html ├── about.html ├── block-xml │ ├── divisibility.xml │ ├── function-limit.xml │ ├── prime.xml │ ├── sequence-limit-q1-correct.xml │ ├── sequence-limit-q1-pieces.xml │ └── sequence-limit.xml ├── builder.html ├── images │ ├── ICME poster.pdf │ ├── logo-large.png │ ├── logo-transparent.png │ ├── logo.png │ ├── logo2.png │ └── video-poster.png ├── limit-exercise.html ├── logic-exercise.html ├── logo.html ├── main-html.css ├── math-blockly-old.css ├── math-blockly.css ├── mathjax-test.html ├── templates │ ├── header-index.html │ └── header.html ├── test.html ├── translation-exercise.html ├── trig-functions.html ├── vector-exercise.html ├── vectors.html └── videos │ ├── addfades.cmd │ ├── blocks1.mp4 │ ├── blocks2.mp4 │ ├── combined.mp4 │ ├── concatlist.txt │ ├── ffmpeg notes.txt │ ├── getframecount.cmd │ ├── gif2mp4.cmd │ ├── latex1.mp4 │ ├── vectors1.mp4 │ ├── vectors2.mp4 │ └── video-test.html ├── index.html └── js ├── exercise-utils.js ├── expression-generator.js ├── field_mathjax.js ├── field_mathvariable.js ├── field_mathvariable_old.js ├── latex-generator.js ├── math-blocks-limit-exercise.js ├── math-blocks-logic-exercise.js ├── math-blocks-old.js ├── math-blocks-translation-exercise.js ├── math-blocks-trig.js ├── math-blocks-vectors.js ├── math-blocks.js ├── shaped-connectors.js ├── utils.js └── video-utils.js /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "closure-library"] 2 | path = closure-library 3 | url = https://github.com/google/closure-library.git 4 | [submodule "blockly"] 5 | path = blockly 6 | url = https://github.com/awmorp/blockly.git 7 | [submodule "blockly-type-indicator"] 8 | path = blockly-type-indicator 9 | url = https://github.com/awmorp/blockly-type-indicator.git 10 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | mathsblocks.com -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A visual mathematical expression builder using Blockly. -------------------------------------------------------------------------------- /buildhtml.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | echo Building index.html 5 | python buildhtml.py index.html 6 | 7 | cd html 8 | for /d %%i in ( builder.html logic-exercise.html limit-exercise.html translation-exercise.html vectors.html ) do echo Building %%i & python ..\buildhtml.py %%i 9 | 10 | -------------------------------------------------------------------------------- /buildhtml.py: -------------------------------------------------------------------------------- 1 | # Simple template replacement engine 2 | # 3 | # Usage: buildhtml inputfile.html 4 | # 5 | # Replaces any text in inputfile.html between tags with the content of file.html 6 | 7 | import sys # for sys.argv 8 | import shutil # for shutil.move 9 | 10 | # regexp module 11 | import re 12 | 13 | beginRE = r'' # matches 14 | endRE = r'' # matches or 15 | 16 | filename = sys.argv[1] 17 | 18 | inputFile = open( filename, "r" ) 19 | outputFile = open( filename + ".tmp", "w" ) 20 | 21 | def output(str): 22 | outputFile.write(str) 23 | 24 | STATE_NORMAL = 0 25 | STATE_INCLUDING = 1 26 | state = STATE_NORMAL 27 | 28 | for line in inputFile: 29 | if state == STATE_NORMAL: 30 | matchResult = re.search( beginRE, line ) 31 | if matchResult: 32 | output( line[0:matchResult.end()] + "\n" ) # Output to end of 33 | includeFileName = matchResult.group( "fname" ) 34 | includeFile = open( includeFileName, "r" ) 35 | for line2 in includeFile: 36 | output(line2) 37 | # print "MATCH: file '" + includeFile + "'" 38 | includeFile.close() 39 | state = STATE_INCLUDING 40 | else: 41 | output( line ) 42 | elif state == STATE_INCLUDING: 43 | matchResult = re.search( endRE, line ) 44 | if matchResult: 45 | output( "\n" + line[matchResult.start():] ) # Output from start of 46 | state = STATE_NORMAL 47 | 48 | inputFile.close() 49 | outputFile.close() 50 | shutil.move( filename, filename + ".bak" ) 51 | shutil.move( filename + ".tmp", filename ) 52 | -------------------------------------------------------------------------------- /html/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Maths Blocks - About the project 6 | 7 | 8 | 9 |
10 |
11 | Math Blocks 12 |
Visual blocks for mathematical syntax
13 |
14 | 33 |
34 | 35 |

About the project

36 |
37 |

38 | Maths Blocks is a system for constructing and manipulating mathematical expressions, using visual blocks which represent syntactical elements. 39 |

40 |

41 | Project lead, design and programming: 42 |

43 |
44 |

45 | Anthony Morphett 46 |

47 |

48 | a.morphett@unimelb.edu.au 49 |

50 |
51 |

52 | User testing: 53 |

54 |
55 |

56 | Anthony Morphett and David Wakeham 57 |

58 |
59 |

60 | School of Mathematics & Statistics, The University of Melbourne 61 |

62 |

63 | Publications: 64 |

67 | 68 |

69 |

70 | Maths Blocks is based on the Blockly project. 71 | Source is available at Github. Gratefully using code and inspiration from Polymorphic Blocks, App Inventor and HenrikD's Blockly plugins. Some code from Maths Blocks is also used in CodeWorld Blocks. 72 |

73 | 74 |
75 | 76 | -------------------------------------------------------------------------------- /html/block-xml/divisibility.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/block-xml/divisibility.xml -------------------------------------------------------------------------------- /html/block-xml/function-limit.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/block-xml/function-limit.xml -------------------------------------------------------------------------------- /html/block-xml/prime.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/block-xml/prime.xml -------------------------------------------------------------------------------- /html/block-xml/sequence-limit-q1-correct.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | epsilon 4 | > 5 | 0 6 | 7 | 8 | M 9 | NATURALS 10 | 11 | 12 | n 13 | > 14 | M 15 | 16 | 17 | < 18 | 19 | 20 | 21 | 22 | MINUS 23 | 24 | 25 | 26 | 27 | 28 | L 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | epsilon 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /html/block-xml/sequence-limit-q1-pieces.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | BLANK 4 | > 5 | BLANK 6 | 7 | 8 | BLANK 9 | > 10 | BLANK 11 | 12 | 13 | BLANK 14 | REAL 15 | 16 | 17 | < 18 | 19 | 20 | ADD 21 | 22 | 23 | 24 | 25 | BLANK 26 | 27 | -------------------------------------------------------------------------------- /html/block-xml/sequence-limit.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/block-xml/sequence-limit.xml -------------------------------------------------------------------------------- /html/builder.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Maths Blocks - mathematical expression builder 6 | 7 | 8 | 9 | 10 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 70 | 71 | 72 | 73 |
74 |
75 | Maths Blocks 76 |
Visual blocks for mathematical syntax
77 |
78 | 97 |
98 | 99 |

Visual Mathematical Expression Builder

100 | 101 |

Use the blocks to build mathematical expressions.

102 |

Click the categories 'Logic', 'Number' etc to show the different kinds of blocks. Drag a block from the toolbox into the main workspace to use the block. You can drag a block into a free space in another block to connect them - but only if the shapes match!

103 |
104 |
Load a pre-built example: 105 | 112 | 113 | Replace existing blocks 114 | 115 |
116 | 120 |
121 | 122 |
123 | 124 |
125 | (dump XML) (show Latex source) 126 |
127 | 128 | 129 | 130 | 131 | 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 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | = 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /html/images/ICME poster.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/images/ICME poster.pdf -------------------------------------------------------------------------------- /html/images/logo-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/images/logo-large.png -------------------------------------------------------------------------------- /html/images/logo-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/images/logo-transparent.png -------------------------------------------------------------------------------- /html/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/images/logo.png -------------------------------------------------------------------------------- /html/images/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/images/logo2.png -------------------------------------------------------------------------------- /html/images/video-poster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/images/video-poster.png -------------------------------------------------------------------------------- /html/limit-exercise.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Maths Blocks - Limit definitions exercise 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 31 | 32 | 45 | 46 | 47 | 48 |
49 |
50 | Maths Blocks 51 |
Visual blocks for mathematical syntax
52 |
53 | 72 |
73 | 74 |

Limit definition exercises

75 | 76 |
Q1. Use the blocks provided to give the definition of \(\displaystyle \lim_{n\to\infty} f(n) = L \).
77 | 78 | 79 |
80 | 81 |
82 | 83 | 92 |
93 | 94 |
95 | (cheat) (dump XML) 96 |
97 | 98 | 112 | 145 | 146 | 147 | 148 | FORALL 149 | EPSILON 150 | > 151 | 0 152 | 153 | 154 | EXISTS 155 | M 156 | NATURALS 157 | 158 | 159 | FORALL 160 | n 161 | > 162 | M 163 | 164 | 165 | < 166 | 167 | 168 | 169 | 170 | MINUS 171 | 172 | 173 | 174 | 175 | 176 | L 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | EPSILON 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | EXISTS 199 | BLANK 200 | BLANK 201 | 202 | 203 | 204 | 205 | EXISTS 206 | L 207 | REAL 208 | 209 | 210 | FORALL 211 | EPSILON 212 | > 213 | 0 214 | 215 | 216 | EXISTS 217 | M 218 | NATURALS 219 | 220 | 221 | FORALL 222 | n 223 | > 224 | M 225 | 226 | 227 | < 228 | 229 | 230 | 231 | 232 | MINUS 233 | 234 | 235 | 236 | 237 | 238 | L 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | EPSILON 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | FORALL 263 | L 264 | REAL 265 | 266 | 267 | EXISTS 268 | EPSILON 269 | > 270 | 0 271 | 272 | 273 | FORALL 274 | M 275 | NATURALS 276 | 277 | 278 | EXISTS 279 | n 280 | > 281 | M 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | MINUS 290 | 291 | 292 | 293 | 294 | 295 | L 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | EPSILON 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 330 | 331 | 332 | -------------------------------------------------------------------------------- /html/logo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Maths Blocks logo 6 | 7 | 8 | 9 | 26 | 27 | 28 | 39 | 40 | 41 | 42 | 43 | 44 | 95 | 96 | 97 |
98 |
99 | (dump XML) 100 |
101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /html/main-html.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | margin-top: 0; 4 | } 5 | 6 | li { 7 | margin: 6px 6px; 8 | } 9 | 10 | a { 11 | text-decoration: none; 12 | /* border-bottom: 1px solid; */ 13 | } 14 | 15 | a:hover { 16 | text-decoration: underline; 17 | } 18 | 19 | .textcontent { 20 | margin-left: 20px; 21 | margin-right: 20px; 22 | } 23 | 24 | .medspace p { 25 | margin-top: 0.3em; 26 | margin-bottom: 0.3em; 27 | } 28 | 29 | h4 { 30 | margin-top: 2em; 31 | margin-bottom: 0.5em; 32 | } 33 | 34 | #headerbar { 35 | padding: 5px; 36 | background: #eeeeee; 37 | /* border: 1px solid red; */ 38 | position: relative; 39 | } 40 | 41 | #tagline { 42 | display: inline; 43 | /* margin-left: 30px; */ 44 | font-style: italic; 45 | } 46 | 47 | #tagline.largeheader { 48 | display: block; 49 | margin-left: 30px; 50 | font-style: italic; 51 | font-size: large; 52 | } 53 | 54 | #titlebar { 55 | display: inline-block; 56 | } 57 | 58 | #navbar { 59 | /* float: right; */ 60 | /* border: 1px solid green; */ 61 | } 62 | 63 | #menulist { 64 | text-align: justify; 65 | padding-top: 6px; 66 | vertical-align: top; 67 | position: absolute; 68 | right: 14px; 69 | top: 0; 70 | } 71 | 72 | .menuitem { 73 | display: inline-block; 74 | /* border: 1px solid blue; */ 75 | padding: 6px; 76 | /* width: 30%;*/ 77 | white-space: nowrap; 78 | border-radius: 4px; 79 | border: 1px solid grey; 80 | background: #d2d2d2; 81 | vertical-align: top; 82 | } 83 | .menuitem:hover { 84 | background: #ebdff8; 85 | } 86 | 87 | .submenulist { 88 | display: block; 89 | height: 0; 90 | } 91 | 92 | .submenulist .menuitem { 93 | display: block; 94 | visibility: collapse; 95 | margin-top: 0; 96 | } 97 | 98 | .menuitem:hover + .submenulist, .menuitem + .submenulist:hover, 99 | .menuitem:hover > .submenulist, .menuitem > .submenulist:hover { 100 | height: 100%; 101 | } 102 | 103 | .menuitem:hover + .submenulist .menuitem, .menuitem + .submenulist:hover .menuitem, 104 | .menuitem:hover > .submenulist .menuitem, .menuitem > .submenulist:hover .menuitem { 105 | visibility: visible; 106 | margin-top: 5px; 107 | // position: relative; 108 | } 109 | 110 | .submenulist .menuitem:hover { 111 | background: #c8bdd2; 112 | } -------------------------------------------------------------------------------- /html/math-blockly-old.css: -------------------------------------------------------------------------------- 1 | /* Obsolete rules from older versions */ 2 | .modal-dialog-bg { 3 | position: absolute; 4 | top: 0px; 5 | left: 0px; 6 | background-color: #FFF; 7 | } 8 | 9 | .modal-dialog { 10 | position: absolute; 11 | top: 0px; 12 | left: 0px; 13 | width: 300px; 14 | background-color: #eee; 15 | border: 2px solid #999; 16 | } 17 | 18 | .modal-dialog-title { 19 | position:relative; 20 | /* background: #C3D9FF; */ 21 | padding: 4px; 22 | font: bold 11px verdana; 23 | cursor: default; 24 | } 25 | 26 | .modal-dialog-content { 27 | /* background: #E8EEF7; */ 28 | padding: 12px 18px 12px 18px; 29 | font: normal 12px verdana; 30 | } 31 | 32 | .modal-dialog-userInput { 33 | font: normal 12px verdana; 34 | width: 90%; 35 | } 36 | 37 | .modal-dialog-buttons { 38 | /* background: #E8EEF7; */ 39 | padding: 4px; 40 | font: normal 12px verdana; 41 | text-align: right; 42 | } 43 | 44 | .modal-dialog-buttons button { 45 | margin: 5px; 46 | } 47 | -------------------------------------------------------------------------------- /html/math-blockly.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #fff; 3 | font-family: sans-serif; 4 | } 5 | 6 | h1 { 7 | font-weight: normal; 8 | font-size: 140%; 9 | } 10 | 11 | /* Hide block highlight SVG elements while hacking */ 12 | /*.blocklyPathLight { 13 | display: none; 14 | } 15 | */ 16 | 17 | #blocklyDiv, .blocklyWorkspace { 18 | margin-top: 1em; 19 | margin-bottom: 1em; 20 | } 21 | 22 | .question { 23 | display: none; 24 | } 25 | 26 | /* One question in multi-question page eg logic-exercise.html */ 27 | .q { 28 | margin-bottom: 1em; 29 | padding-top: 1em; 30 | } 31 | 32 | .red { 33 | color: red; 34 | } 35 | 36 | xml { 37 | display: none; 38 | } 39 | 40 | .debug { 41 | display: none; 42 | float: right; 43 | font-size: small; 44 | } 45 | 46 | /* The following are rules for SVG so use different properties than HTML, eg 'fill' rather than 'background' */ 47 | .blocklyFlydown { 48 | fill: rgb(191,191,191); 49 | fill-opacity: 0.8; 50 | } 51 | 52 | /* Force blue background colour for number variable in quantifier block */ 53 | .blocklyEditableText.blocklyQuantifierVarField > rect { 54 | fill: rgb(189, 194, 219); 55 | fill-opacity: 1; 56 | /* Give the field a darker blue border */ 57 | stroke-width: 1.5; 58 | stroke: rgb(100, 113, 177); /* Number block colour is rgb(91, 103, 160) */ 59 | } 60 | 61 | /* Red background for clashing variables */ 62 | .blocklyMathVariableError > rect { 63 | fill: rgb(255, 192, 192) !important; 64 | fill-opacity: 1 !important; 65 | } -------------------------------------------------------------------------------- /html/mathjax-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Blockly testing 6 | 7 | 8 | 9 | 26 | 27 | 28 | 39 | 40 | 41 | 42 | 43 | 44 | 93 | 94 | 95 |
96 |
97 | (dump XML) 98 |
99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | \lim_{n \to \infty} ( 1 + x/n )^n = e^x 108 | 109 | 110 | 111 | 112 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /html/templates/header-index.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Maths Blocks 4 |
Visual blocks for mathematical syntax
5 |
6 | 25 |
-------------------------------------------------------------------------------- /html/templates/header.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Maths Blocks 4 |
Visual blocks for mathematical syntax
5 |
6 | 25 |
-------------------------------------------------------------------------------- /html/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Blockly testing 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 127 | 128 | 129 |
130 | 134 | 135 | 144 | 145 | 148 | 149 | 150 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /html/translation-exercise.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Maths Blocks - Translation from English to mathematics 6 | 7 | 8 | 9 | 10 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 49 | 50 | 51 | 52 |
53 |
54 | Maths Blocks 55 |
Visual blocks for mathematical syntax
56 |
57 | 76 |
77 | 78 |

Translation from English to mathematics

79 | 80 |
Q1. Use the blocks provided to express the statement `Every multiple of 6 is also a multiple of 3'.
81 |
Q2. Use the blocks provided to express the statement `Every multiple of 6 is also a multiple of 3', but using only mathematical notation.
82 |
Q3. Use the blocks provided to express the statement `Some multiple of 3 is also a multiple of 6'.
83 |
84 | 85 | 86 | 87 |
88 |
89 | 90 | 99 |
100 | 101 |
102 | (dump XML) (cheat) 103 |
104 | 105 | 119 | 136 | 137 | 138 | 139 | FORALL 140 | a 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | a 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | a 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | FORALL 169 | b 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | b 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | b 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | FORALL 198 | c 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | c 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | c 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | BLANK 227 | BLANK 228 | 229 | 230 | BLANK 231 | BLANK 232 | 233 | 234 | = 235 | 236 | 237 | = 238 | 239 | 240 | 241 | 242 | 243 | 244 | BLANK 245 | 246 | 247 | BLANK 248 | 249 | 250 | 6 251 | 252 | 253 | BLANK 254 | 255 | 256 | BLANK 257 | 258 | 259 | 3 260 | 261 | 262 | 263 | 264 | 265 | FORALL 266 | a 267 | 268 | 269 | 270 | 271 | 272 | EXISTS 273 | b 274 | 275 | 276 | = 277 | 278 | 279 | a 280 | 281 | 282 | 283 | 284 | 285 | 286 | 6 287 | 288 | 289 | 290 | 291 | b 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | EXISTS 303 | c 304 | 305 | 306 | = 307 | 308 | 309 | a 310 | 311 | 312 | 313 | 314 | 315 | 316 | 3 317 | 318 | 319 | 320 | 321 | c 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | EXISTS 338 | a 339 | 340 | 341 | 342 | 343 | 344 | EXISTS 345 | b 346 | 347 | 348 | = 349 | 350 | 351 | a 352 | 353 | 354 | 355 | 356 | 357 | 358 | 6 359 | 360 | 361 | 362 | 363 | b 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | EXISTS 375 | c 376 | 377 | 378 | = 379 | 380 | 381 | a 382 | 383 | 384 | 385 | 386 | 387 | 388 | 3 389 | 390 | 391 | 392 | 393 | c 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 422 | 423 | 424 | 425 | -------------------------------------------------------------------------------- /html/trig-functions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Maths Blocks - Trigonometric functions and inverses demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 34 | 35 | 39 | 40 | 41 | 42 |
43 |
44 | Maths Blocks 45 |
Visual blocks for mathematical syntax
46 |
47 | 66 |
67 | 68 |

Demo of trigonometric functions and inverses

69 | 70 |
71 |
72 | (dump XML) 73 |
74 | 75 | 83 | 84 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /html/vector-exercise.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Maths Blocks - Vector and scalar syntax exercises 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 27 | 28 | 32 | 33 | 34 | Generate a random expression  debug 35 |
36 |

37 |     

38 |   
39 | 


--------------------------------------------------------------------------------
/html/vectors.html:
--------------------------------------------------------------------------------
  1 | 
  2 | 
  3 | 
  4 |   
  5 |   Maths Blocks - Vectors and scalars demo
  6 | 
  7 |   
  8 |   
  9 | 
 10 |   
 11 |   
 12 |   
 13 |   
 14 |    
 15 |     
 16 |   
 17 |     
 18 |     
 19 | 
 20 |    
 21 |   
 34 | 
 35 |   
 39 | 
 40 | 
 41 | 
 42 |   
43 |
44 | Maths Blocks 45 |
Visual blocks for mathematical syntax
46 |
47 | 66 |
67 | 68 |

Demo of vector and scalar arithmetic

69 | 70 | 71 | 72 |
73 |
74 | (dump XML) 75 |
76 | 77 | 116 | 117 | 128 | 129 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /html/videos/addfades.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Add a fadein/fadeout at start/end of video 3 | setlocal enabledelayedexpansion 4 | 5 | rem Length of fade (# of frames) 6 | set FADELENGTH=15 7 | 8 | for %%I in (%*) do ( 9 | setlocal enabledelayedexpansion 10 | title Adding fadein/fadeout %%~nI 11 | for /f %%J in ('getframecount %%I') do set ABC=%%J 12 | echo Frames: !ABC! 13 | set /a STARTFADE=!ABC!-15 14 | rem echo Start fadeout: !STARTFADE! 15 | 16 | color 2f 17 | echo ffmpeg -i %%I -filter:v "fade=in:0:!FADELENGTH!:color=white,fade=out:!STARTFADE!:!FADELENGTH!:color=white" "%%~nI-fades%%~xI" 18 | ffmpeg -i %%I -filter:v "fade=in:0:!FADELENGTH!:color=white,fade=out:!STARTFADE!:!FADELENGTH!:color=white" "%%~nI-fades-temp%%~xI" 19 | ffmpeg -i "%%~nI-fades-temp%%~xI" -movflags faststart -pix_fmt yuv420p "%%~nI-fades%%~xI" 20 | del "%%~nI-fades-temp%%~xI" 21 | ) 22 | title Command prompt 23 | color 24 | -------------------------------------------------------------------------------- /html/videos/blocks1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/videos/blocks1.mp4 -------------------------------------------------------------------------------- /html/videos/blocks2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/videos/blocks2.mp4 -------------------------------------------------------------------------------- /html/videos/combined.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/videos/combined.mp4 -------------------------------------------------------------------------------- /html/videos/concatlist.txt: -------------------------------------------------------------------------------- 1 | file 'blocks1-fades.mp4' 2 | file 'vectors1-fades.mp4' 3 | file 'latex1-fades.mp4' 4 | -------------------------------------------------------------------------------- /html/videos/ffmpeg notes.txt: -------------------------------------------------------------------------------- 1 | These animation videos were created by: 2 | 1. Recording video using LICEcap (http://www.cockos.com/licecap/). Dimensions: 600x280 3 | 2. Converted to mp4 using ffmpeg: ffmpeg -i input.gif -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" -r 25 output.mp4 4 | 5 | Fade-in/out to white: 6 | 1. Convert .gif to .mp4 as above: 7 | > ffmpeg -i input.gif -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" -r 25 output.mp4 8 | 2. Determine total # frames: 9 | > ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 output.mp4 10 | [Source: https://stackoverflow.com/questions/2017843/fetch-frame-count-with-ffmpeg] 11 | 3. Add fade-in/out: 12 | > ffmpeg -i output.mp4 -filter:v "fade=in:0:15:color=white,fade=out:160:15:color=white" output2.mp4 13 | NOTE: replace 160 with 15 frames before end of video 14 | [Source: https://ffmpeg.org/ffmpeg-filters.html#Filtering-Introduction , https://superuser.com/questions/386065/is-there-a-way-to-add-a-fade-to-black-effect-to-a-video-from-the-command-line ] 15 | 4. Recode to make file readable by Windows Media Player: 16 | > ffmpeg -i output2.mp4 -movflags faststart -pix_fmt yuv420p output3.mp4 17 | 18 | 19 | 20 | To concatenate videos: 21 | 1. list files to be concatenated in concatlist.txt 22 | 2. > ffmpeg -f concat -safe 0 -i concatlist.txt -c copy combined.mp4 23 | 24 | 25 | 26 | To repeat the last frame for XXX seconds: 27 | > ffmpeg -f lavfi -i nullsrc=s=600x280:d=ZZZZZ:r=25 -i input.mp4 -filter_complex "[0:v][1:v]overlay[video]" -map "[video]" -shortest output.mp4 28 | where ZZZZZ is the desired new length of video. Note: Update resolution and frame rate accordingly. 29 | 30 | To extract the last frame of a video: 31 | > ffmpeg -i input.mp4 -ss TIME lastframe.jpg 32 | where TIME is the length of the video (as reported by ffprobe), minus 1/25 = 0.04 seconds (duration of one frame) 33 | 34 | To repeat a still frame for XXXX seconds: 35 | > ffmpeg -f lavfi -i aevalsrc=0 -i lastframe.jpg -loop 1 -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" -r 25 -t 0:0:XXXX lastframe.mp4 36 | 37 | 38 | 39 | To crop the first XX seconds of a video: 40 | > ffmpeg -i input.mp4 -ss XX blocks-demo-1-cut.mp4 41 | where XX is in the form h:mm:ss.ss 42 | 43 | 44 | To add text to a video: 45 | > ffmpeg -i input.mp4 -vf drawtext="text='Text here':fontcolor=blue:fontsize=20:x=(w-text_w)/2:y=(h-2.5*text_h):fontfile=/Windows/Fonts/arial.ttf" output.mp4 -------------------------------------------------------------------------------- /html/videos/getframecount.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 %1 -------------------------------------------------------------------------------- /html/videos/gif2mp4.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Encode an animated GIF into HTML5-compatible mp4 3 | setlocal 4 | 5 | for %%I in (%*) do ( 6 | title Encoding %%~nI 7 | color 2f 8 | ffmpeg -i "%%~dpnI%%~xI" -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" -r 25 "%%~dpnI.mp4" 9 | ) 10 | title Command prompt 11 | color 12 | -------------------------------------------------------------------------------- /html/videos/latex1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/videos/latex1.mp4 -------------------------------------------------------------------------------- /html/videos/vectors1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/videos/vectors1.mp4 -------------------------------------------------------------------------------- /html/videos/vectors2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/html/videos/vectors2.mp4 -------------------------------------------------------------------------------- /html/videos/video-test.html: -------------------------------------------------------------------------------- 1 | 2 | HTML5 video test 3 | 4 |
5 | 9 |
10 |
11 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Maths Blocks - Home 6 | 16 | 17 | 18 | 19 |
20 |
21 | Maths Blocks 22 |
Visual blocks for mathematical syntax
23 |
24 | 43 |
44 | 45 |

Maths Blocks: A visual mathematical expression builder

46 |
47 |

48 | Maths Blocks is a system for constructing and manipulating mathematical expressions from visual blocks representing syntactical elements. 49 |

50 |

51 | Try it out: 52 |

53 | 61 |
62 | 65 |
66 |
67 | Maths Blocks is based on the Blockly project. 68 | Source is available at Github. Gratefully using code and inspiration from Polymorphic Blocks, App Inventor and HenrikD's Blockly plugins. 69 |
70 |
71 | 72 | -------------------------------------------------------------------------------- /js/exercise-utils.js: -------------------------------------------------------------------------------- 1 | /* Javascript for English to mathematics translation exercise */ 2 | 3 | /* Animation functions */ 4 | function pause( t ) { 5 | return( function(continuation) { 6 | // console.log( "Pausing for " + t ); 7 | setTimeout( continuation, t ); 8 | } ); 9 | } 10 | 11 | function animateBlock( block, x, y, time ) { 12 | return( function(continuation) { 13 | // console.log( "Animating block " + block.id ); 14 | block.select(); 15 | var startPos = block.getRelativeToSurfaceXY(); 16 | var dX = (x - startPos.x)/50; 17 | var dY = (y - startPos.y)/50; 18 | var i = 0; 19 | var timer; 20 | timer = window.setInterval( function() { 21 | block.moveBy( dX, dY ); 22 | if( i++ == 50 ) { 23 | window.clearInterval( timer ); 24 | continuation(); 25 | } 26 | }, time/50); 27 | }); 28 | } 29 | 30 | function fixBlock( block ) { 31 | return( function(continuation) { 32 | // console.log( "Fixing block " + block.id ); 33 | block.setMovable( false ); 34 | block.setEditable( false ); 35 | block.setDisabled( true ); 36 | // block.updateDisabled(); 37 | 38 | continuation(); 39 | }); 40 | } 41 | 42 | function deleteBlock( block ) { 43 | return( function(continuation) { 44 | // console.log( "Deleting block " + block.id ); 45 | block.dispose(false, true); // Delete block with animation 46 | continuation(); 47 | }); 48 | } 49 | 50 | 51 | function unplug( block ) { 52 | return( function(continuation) { 53 | // console.log( "Unplugging block " + block.id ); 54 | block.unplug(); 55 | continuation(); 56 | }); 57 | } 58 | 59 | function loadXml( nodeId ) { 60 | return( function(continuation) { 61 | // console.log( "loadXml( " + nodeId + " )" ); 62 | Blockly.Xml.domToWorkspace( workspace, document.getElementById( nodeId ) ); 63 | continuation(); 64 | }); 65 | } 66 | 67 | /* Run an animation sequence */ 68 | function runSequence( queue ) { 69 | if( queue ) { 70 | var next = queue.shift(); 71 | if( goog.isFunction( next ) ) next( function() { runSequence( queue ) } ); 72 | else if( goog.isNumber( next ) ) setTimeout( next, function() { runSequence( queue ) } ); 73 | } 74 | } 75 | 76 | /* Load question 2 - run some animations, manipulate some blocks */ 77 | function loadQ2() { 78 | var rootBlock = workspace.getBlockById("6/8T!gQ*,D?*RUNRynp3"); 79 | var impBlock = workspace.getBlockById(":]LON,k-!D|CGQb$A3FB"); 80 | var mult3Block = workspace.getBlockById("fX(^g7xz/h`iw/Q!X8W="); 81 | var mult6Block = workspace.getBlockById("$O{41OtB{O`4$8%M:2E}"); 82 | var v1Block = workspace.getBlockById("mtgBEc]uuk$k=.EoQh,h"); 83 | var v2Block = workspace.getBlockById("Jr{b*jBHteQRO#N;X3,6"); 84 | 85 | workspace.getAllBlocks().forEach( function(x) { x.setEditable( false ); } ); 86 | 87 | Blockly.Events.disable(); 88 | var queue = [animateBlock( rootBlock, workspace.getWidth()/2 - rootBlock.width/2 + 150, 20, 300 ), pause( 500 ), 89 | unplug( mult6Block ), animateBlock( mult6Block, workspace.getWidth()/3 - mult6Block.width/2, 100, 600 ), 90 | unplug( mult3Block ), animateBlock( mult3Block, 2*workspace.getWidth()/3 - mult6Block.width/2, 100, 600 ), 91 | // fixBlock( rootBlock ), fixBlock( impBlock ), 92 | deleteBlock( mult3Block ), deleteBlock( mult6Block ), pause( 250 ), 93 | loadXml( "q2addition" ), 94 | function(cont) { 95 | workspace.getAllBlocks().forEach( function(x) { 96 | x.setDisabled( false ); 97 | x.setMovable( true ); 98 | x.setEditable( true ); 99 | x.updateDisabled(); 100 | } ); 101 | Blockly.Events.enable(); 102 | cont(); 103 | } 104 | ]; 105 | 106 | // Start sequence. 107 | runSequence( queue ); 108 | } 109 | 110 | /* Compare two XML workspace representations, ignoring block id's and positions */ 111 | /* a, b should be XML text */ 112 | function compareXML(a,b) { 113 | /* Strip out x="..", y="..", id=".." attributes and whitespace */ 114 | a = a.replace(/[xy]="[0-9]+"/g,'').replace(/id="[^"]+"/g,'').replace(/[ \n]/g,''); 115 | b = b.replace(/[xy]="[0-9]+"/g,'').replace(/id="[^"]+"/g,'').replace(/[ \n]/g,''); 116 | return( a == b); 117 | } 118 | 119 | function checkAnswer() { 120 | // var startTime1 = Date.now(); 121 | 122 | var correct = false; 123 | var topBlocks = workspace.getTopBlocks(); 124 | if( topBlocks.length == 1 ) { 125 | Blockly.Events.disable(); 126 | correct = questionConfig[currentQuestion-1].solnsBlock.some( function(x) { /* Does user's response match one of the allowable solutions? */ 127 | // var startTime = Date.now(); 128 | var result = compareBlocks( topBlocks[0], x, !questionConfig[currentQuestion-1].strictVars ); 129 | // console.log( "compareBlocks took " + (Date.now() - startTime) + "ms" ); 130 | return( result ); 131 | } ); 132 | Blockly.Events.enable(); 133 | } 134 | 135 | if( correct ) { 136 | goog.dom.getElement( "resultCorrect" ).style.display = "inline"; 137 | goog.dom.getElement( "resultIncorrect" ).style.display = "none"; 138 | if( currentQuestion == questionConfig.length ) { /* Last question finished? */ 139 | goog.dom.getElement( "finished" ).style.display = "inline"; 140 | } else { 141 | goog.dom.getElement( "nextButton" ).style.display = "inline"; 142 | } 143 | } else { 144 | goog.dom.getElement( "resultCorrect" ).style.display = "none"; 145 | goog.dom.getElement( "resultIncorrect" ).style.display = "inline"; 146 | goog.dom.getElement( "nextButton" ).style.display = "none"; 147 | goog.dom.getElement( "finished" ).style.display = "none"; 148 | } 149 | // console.log( "checkAnswer took " + (Date.now() - startTime1) + "ms" ); 150 | } 151 | 152 | var currentQuestion = 0; 153 | 154 | /* Load next question */ 155 | function nextQuestion() { 156 | if( currentQuestion > 0 ) goog.dom.getElement( "q" + currentQuestion ).style.display = "none"; 157 | goog.dom.getElement( "q" + (currentQuestion + 1) ).style.display = "inline"; 158 | currentQuestion++; 159 | if( currentQuestion <= questionConfig.length ) { /* Note: questions numbered from 1, array indexing from 0 */ 160 | /* Not up to last question yet. Load next question */ 161 | Blockly.Events.disable(); 162 | var loadData = questionConfig[currentQuestion-1].loadData; /* Questions numbered from 1, array indexing from 0 */ 163 | if( goog.typeOf( loadData ) == "string" ) { 164 | // console.log( loadData, document.getElementById( loadData ) ); 165 | Blockly.Xml.domToWorkspace( workspace, document.getElementById( loadData ) ); 166 | } else if( goog.typeOf( loadData ) == "function" ) { 167 | loadData(); 168 | } 169 | Blockly.Events.enable(); 170 | } 171 | goog.dom.getElement( "resultCorrect" ).style.display = "none"; 172 | goog.dom.getElement( "resultIncorrect" ).style.display = "inline"; 173 | goog.dom.getElement( "nextButton" ).style.display = "none"; 174 | goog.dom.getElement( "finished" ).style.display = "none"; 175 | } 176 | 177 | var answerWorkspace; 178 | function setupAnswers() { 179 | // var time = Date.now(); 180 | if( answerWorkspace ) answerWorkspace.dispose(); 181 | /* For efficiency, we pre-load all answer blocks into a headless workspace */ 182 | answerWorkspace = new Blockly.Workspace(); 183 | for( i in questionConfig ) { 184 | questionConfig[i].solnsBlock = []; 185 | for( j in questionConfig[i].solns ) { 186 | var child = goog.dom.getFirstElementChild( goog.dom.getElement( questionConfig[i].solns[j] ) ); 187 | var block = Blockly.Xml.domToBlockHeadless_( child, answerWorkspace ); /* Assuming that answer is stored as an XML node with a single block child */ 188 | questionConfig[i].solnsBlock[j] = block; 189 | } 190 | } 191 | // console.log( "setupAnswers() took " + (Date.now() - time) + "ms" ); 192 | } 193 | 194 | function cheat() { 195 | Blockly.Events.disable(); 196 | workspace.clear(); 197 | var xmlNode = goog.dom.getElement( questionConfig[currentQuestion-1].solns[0] ); /* Questions numbered from 1, array indexing from 0 */ 198 | // console.log( "Cheat: ", xmlNode ) 199 | Blockly.Xml.domToWorkspace(workspace,xmlNode); 200 | Blockly.Events.enable(); 201 | checkAnswer(); 202 | } 203 | 204 | /* Compare two blocks for logical equivalence. */ 205 | function compareBlocks( b1, b2, doSubst ) { 206 | var varCount = 0; 207 | var compareBlocksR = function( b1, b2, subst1, subst2 ) /* Recursive helper function */ 208 | { 209 | if( !b1 && !b2 ) return( true ); /* Both blocks are null */ 210 | else if( !b1 || !b2 ) return( false ); /* One but not both are null */ 211 | 212 | /* Compare the top block */ 213 | if( b1.type != b2.type ) return( false ); /* Different type blocks can't match */ 214 | switch( b1.type ) { 215 | case "logic_quantifier_condition_restricted": 216 | if( (b1.getFieldValue( "COMPARISON_OPERATOR" ) != b2.getFieldValue( "COMPARISON_OPERATOR" )) || (b1.getFieldValue( "BOUND" ) != b2.getFieldValue( "BOUND" )) ) return( false ); 217 | /* Fall through to next case */ 218 | case "logic_quantifier_set_restricted": 219 | case "logic_quantifier_set_restricted_1": 220 | case "logic_quantifier_set_restricted_2": 221 | case "logic_quantifier_condition_restricted": 222 | if( b1.getField("QUANTIFIER").getValue() != b2.getField("QUANTIFIER").getValue() ) return( false ); /* Different quantifiers */ 223 | var p1 = b1.getInputTargetBlock( "PREDICATE" ); 224 | var p2 = b2.getInputTargetBlock( "PREDICATE" ); 225 | var newsubst1 = subst1; /* Must make copies as direct modifications to substX will propagate back up call chain due to Javascript passing arguments by reference */ 226 | var newsubst2 = subst2; 227 | if( doSubst ) { 228 | newsubst1[b1.getFieldValue("VAR")] = newsubst2[b2.getFieldValue("VAR")] = "_v"+varCount; /* Substitute variables with unique identifier */ 229 | varCount++; 230 | } else { 231 | if( b1.getFieldValue("VAR") != b2.getFieldValue("VAR") ) return( false ); 232 | } 233 | // console.log( "Comparing quantifiers. varCount = " + varCount, newsubst1, newsubst2 ); 234 | return( compareBlocksR( p1, p2, newsubst1, newsubst2 ) ); 235 | 236 | case "number_comparison": 237 | var op1 = b1.getFieldValue( "COMPARISON_OPERATOR" ); 238 | var op2 = b2.getFieldValue( "COMPARISON_OPERATOR" ); 239 | var l1 = b1.getInputTargetBlock( "LEFTINPUT" ); 240 | var l2 = b2.getInputTargetBlock( "LEFTINPUT" ); 241 | var r1 = b1.getInputTargetBlock( "RIGHTINPUT" ); 242 | var r2 = b2.getInputTargetBlock( "RIGHTINPUT" ); 243 | var reverseOp = {"=":"=", "≠":"≠","<":">", ">":"<", "≤":"≥", "≥":"≤"}; /* Mapping of operator to reversed operator */ 244 | return( 245 | (op2 == op1 && (compareBlocksR( l1, l2, subst1, subst2 ) && compareBlocksR( r1, r2, subst1, subst2 ))) || 246 | (op2 == reverseOp[op1] && (compareBlocksR( l1, r2, subst1, subst2 ) && compareBlocksR( r1, l2, subst1, subst2 ))) 247 | ); 248 | 249 | case "math_arithmetic": 250 | var op1 = b1.getFieldValue( "OP" ); 251 | var op2 = b2.getFieldValue( "OP" ); 252 | var l1 = b1.getInputTargetBlock( "A" ); 253 | var l2 = b2.getInputTargetBlock( "A" ); 254 | var r1 = b1.getInputTargetBlock( "B" ); 255 | var r2 = b2.getInputTargetBlock( "B" ); 256 | if( op1 != op2 ) return( false ); 257 | if( op1 == "MULTIPLY" || op1 == "ADD" ) { 258 | /* Transitive ops */ 259 | return( (compareBlocksR( l1, l2, subst1, subst2 ) && compareBlocksR( r1, r2, subst1, subst2 )) || (compareBlocksR( l1, r2, subst1, subst2 ) && compareBlocksR( r1, l2, subst1, subst2 )) ); 260 | } else { 261 | return( compareBlocksR( l1, l2, subst1, subst2 ) && compareBlocksR( r1, r2, subst1, subst2 ) ); 262 | } 263 | 264 | case "number_multiplication": 265 | var l1 = b1.getInputTargetBlock( "A" ); 266 | var l2 = b2.getInputTargetBlock( "A" ); 267 | var r1 = b1.getInputTargetBlock( "B" ); 268 | var r2 = b2.getInputTargetBlock( "B" ); 269 | return( (compareBlocksR( l1, l2, subst1, subst2 ) && compareBlocksR( r1, r2, subst1, subst2 )) || (compareBlocksR( l1, r2, subst1, subst2 ) && compareBlocksR( r1, l2, subst1, subst2 )) ); 270 | 271 | case "number_variable_restricted": 272 | var v1 = b1.getFieldValue( "VAR" ); 273 | var v2 = b2.getFieldValue( "VAR" ); 274 | // console.log( "Comparing variables v1 = " + v1 + " v2 = " + v2, subst1, subst2 ); 275 | if( subst1[v1] ) v1 = subst1[v1]; 276 | if( subst2[v2] ) v2 = subst2[v2]; 277 | // console.log( " after subst: v1 = " + v1 + " v2 = " + v2 ); 278 | return( v1 == v2 ); 279 | 280 | case "math_number": 281 | return( b1.getField("NUM").getValue() == b2.getField("NUM").getValue() ); 282 | 283 | case "number_abs": 284 | return( compareBlocksR( b1.getInputTargetBlock( "NUM" ), b1.getInputTargetBlock( "NUM" ), subst1, subst2 ) ); 285 | 286 | case "logic_connective": 287 | var op1 = b1.getFieldValue( "CONNECTIVE" ); 288 | var op2 = b2.getFieldValue( "CONNECTIVE" ); 289 | var l1 = b1.getInputTargetBlock( "LEFTINPUT" ); 290 | var l2 = b2.getInputTargetBlock( "LEFTINPUT" ); 291 | var r1 = b1.getInputTargetBlock( "RIGHTINPUT" ); 292 | var r2 = b2.getInputTargetBlock( "RIGHTINPUT" ); 293 | if( op1 != op2 ) return( false ); 294 | if( op1 == "⇒" ) { /* Intransitive */ 295 | return( compareBlocksR( l1, l2, subst1, subst2 ) && compareBlocksR( r1, r2, subst1, subst2 ) ); 296 | } else { /* Transitive */ 297 | return( (compareBlocksR( l1, l2, subst1, subst2 ) && compareBlocksR( r1, r2, subst1, subst2 )) || (compareBlocksR( l1, r2, subst1, subst2 ) && compareBlocksR( r1, l2, subst1, subst2 )) ); 298 | } 299 | /* Note: No attempt to take into account other logical equivalences, eg ~(A and B) == ~A or ~B */ 300 | 301 | case "predicate_multiple_of_3": 302 | case "predicate_multiple_of_6": 303 | return( compareBlocksR( b1.getInputTargetBlock("NUM"), b2.getInputTargetBlock("NUM"), subst1, subst2 ) ); 304 | 305 | case "set_dropdown": 306 | return( b1.getFieldValue( "SET" ) == b2.getFieldValue( "SET" ) ); 307 | 308 | case "function_fn": 309 | return( true ); 310 | 311 | default: 312 | console.warn( "Trying to compare unsupported block type '" + b1.type + "'" ); 313 | /* TODO: use generic fieldwise comparison */ 314 | return( false ); 315 | } 316 | } 317 | return( compareBlocksR( b1, b2, {}, {} ) ); 318 | } 319 | -------------------------------------------------------------------------------- /js/expression-generator.js: -------------------------------------------------------------------------------- 1 | /* Routines for generating random mathematical expressions */ 2 | 3 | /***************************************/ 4 | /* Abstract binary tree data structure */ 5 | 6 | function Node(data, left, right) { 7 | this.data = data; 8 | this.left = left; 9 | this.right = right; 10 | this.isLeaf = (left == null && right == null); 11 | } 12 | 13 | Node.prototype.toString = function() { 14 | if( this.isLeaf ) { 15 | return( "Leaf" + (this.data?"("+this.data+")":"") ); 16 | } else { 17 | return( "Node" + (this.data?"("+this.data+")":"") + "[" + this.left.toString() + ", " + this.right.toString() + "]" ); 18 | } 19 | }; 20 | 21 | function fullBinaryTree(depth) { 22 | if( depth == 0 ) { 23 | return new Node(null,null,null); // leaf 24 | } else { 25 | return new Node(null, fullBinaryTree(depth - 1), fullBinaryTree(depth - 1)); 26 | } 27 | } 28 | 29 | function getAllNodes(tree) { 30 | if( tree == null ) { 31 | return( [] ); 32 | } else { 33 | return( [tree].concat(getAllNodes(tree.left), getAllNodes(tree.right)) ); 34 | } 35 | } 36 | 37 | function getLeaves(tree) { 38 | return( getAllNodes(tree).filter(function(x) {return x.isLeaf;}) ); 39 | } 40 | 41 | function getNonLeaves(tree) { 42 | return( getAllNodes(tree).filter(function(x) {return !x.isLeaf;}) ); 43 | } 44 | 45 | function countLeaves(tree) { 46 | return( getLeaves(tree).length ); 47 | } 48 | 49 | function pruneChildren(tree) { 50 | //Note: this modifies the original object! 51 | tree.left = null; 52 | tree.right = null; 53 | tree.isLeaf = true; 54 | return( tree ); 55 | } 56 | 57 | /*** 58 | * randomPrune: generate a random tree by pruning branches from a given tree until a desired level of complexity is reached. 59 | * arguments: 60 | * tree: a tree to be pruned. (Suggestion: start with a complete binary tree of suitable depth.) 61 | * leaftarget: the desired number of leaf nodes in the pruned tree 62 | * 63 | * Algorithm: 64 | * Choose a random non-leaf node. Count the number of leaves below it. 65 | * If we can prune those leaves without going below the desired number of leaves (leaftarget), then do so. 66 | * Otherwise, try another node. 67 | */ 68 | 69 | function randomPrune(tree, leaftarget) { 70 | var currentLeafCount = countLeaves(tree); 71 | // console.log( "randomPrune( " + tree + " (" + currentLeafCount + "), " + leaftarget + ")" ); 72 | 73 | if( currentLeafCount <= leaftarget ) return( tree ); // Already reached target. Nothing to do. 74 | 75 | // Otherwise, choose a random non-leaf node to prune (maybe). 76 | var nodes = getNonLeaves(tree); 77 | var i = Math.floor(Math.random()*nodes.length); 78 | var leafChange = countLeaves( nodes[i] ) - 1; // Net change in number of leaves that would result from pruning this node 79 | if( currentLeafCount - leafChange >= leaftarget ) { 80 | // Found an acceptable node. Prune it. 81 | pruneChildren( nodes[i] ); 82 | if( currentLeafCount - leafChange == leaftarget ) { 83 | return( tree ); // Finished pruning. 84 | } else { 85 | return( randomPrune( tree, leaftarget ) ); // Continue pruning. 86 | } 87 | } else { 88 | // Pruning this node would result in too few leaves remaining. Try again. 89 | return( randomPrune( tree, leaftarget ) ); 90 | } 91 | } 92 | /* Note: The distribution of trees produced by this algorithm is not uniform. Balanced trees occur half as frequently. 93 | Run this line in the browser console to demonstrate: 94 | counts={}; "a".repeat(1000).split("").map(function(x) {return randomPrune(fullBinaryTree(3),4).toString();}).sort().forEach(function(x) { counts[x] = (counts[x] || 0)+1; }); counts 95 | */ 96 | 97 | 98 | function addTally( tallies, key ) { 99 | tallies[key] = (tallies[key] || 0) + 1; 100 | } 101 | 102 | 103 | /* Order of operations constants */ 104 | //**** TODO: Re-use latex generator enums. 105 | ORDER_ATOMIC = 0; 106 | ORDER_MULTIPLICATION = 5; 107 | ORDER_DOTPRODUCT = 7; 108 | ORDER_ADDITION = 10; 109 | 110 | 111 | /* Functions to assign syntax elements to a given tree */ 112 | function OpRule( name, outputType, valid, render, order, leftOp, rightOp, leftAtom, rightAtom ) { 113 | this.name = name; // human-readable name for debugging 114 | this.outputType = outputType; // output type eg "vector" 115 | this.valid = valid; // Does this construct produce valid or invalid syntax? 116 | this.render = render; // Latex code for rendering this op's symbol 117 | this.order = order; // Order of operations priority, for bracketing 118 | this.leftOp = leftOp; // List of allowable operations for LHS of expression 119 | this.rightOp = rightOp; 120 | this.leftAtom = leftAtom; // List of allowable atoms for LHS of expression 121 | this.rightAtom = rightAtom; 122 | } 123 | OpRule.prototype.toString = function() { 124 | return( this.name ); 125 | } 126 | 127 | function Atom( outputType, render ) { 128 | this.name = render; 129 | this.outputType = outputType; 130 | this.render = render; 131 | this.order = ORDER_ATOMIC; 132 | this.valid = true; 133 | } 134 | Atom.prototype.toString = function() { 135 | return( this.name ); 136 | } 137 | 138 | var syntaxConfig = { 139 | opScalarPlus: new OpRule( "Scalar +", "scalar", true, "+", ORDER_ADDITION, ["scalarOps"], ["scalarOps"], ["scalarVars","scalarConsts"],["scalarVars","scalarConsts"]), 140 | 141 | opVectorPlus: new OpRule( "Vector +", "vector",true,"+", ORDER_ADDITION, ["vectorOps"],["vectorOps"],["vectorTerms"],["vectorTerms"]), 142 | 143 | opVectorDot: new OpRule( "Vector dot", "scalar",true,"\\cdot", ORDER_DOTPRODUCT,["vectorOps"],["vectorOps"],["vectorTerms"],["vectorTerms"]), 144 | 145 | opScalarMult: new OpRule( "Scalar × ", "scalar",true,"",ORDER_MULTIPLICATION,["scalarOps"],["scalarOps"],["scalarVars","scalarConsts"],["scalarVars","scalarConsts"]), 146 | 147 | opScalarVectorMult: new OpRule( "Scalar × vector", "vector",true,"",ORDER_MULTIPLICATION,["scalarOps"],["vectorOps"],["scalarVars","scalarConsts"],["vectorTerms"]), 148 | 149 | opVectorPlusScalar: new OpRule( "Vector + scalar", "invalid",false,"+",ORDER_ADDITION,["vectorOps"],["scalarOps"],["vectorTerms"],["scalarVars","scalarConsts"]), 150 | 151 | scalarVars: {outputType: "scalar", atoms:["x","y","z","a","b","α","λ"]}, 152 | scalarConsts: {outputType: "scalar", atoms:["1","2","3","4","5","12","0.5","1.2","0","\\sqrt{2}","\\sqrt{5}","\\frac{1}{2}","\\frac{\\sqrt{2}}{2}"]}, 153 | vectorTerms: {outputType: "vector", atoms: ["\\mathbf{u}","\\mathbf{v}","\\mathbf{w}","\\overrightarrow{AB}","\\overrightarrow{OA}","\\overrightarrow{PQ}","\\mathbf{0}"]}, 154 | vectorOps: ["opVectorPlus", "opScalarVectorMult"], 155 | scalarOps: ["opScalarPlus","opVectorDot", "opScalarMult"], 156 | 157 | }; 158 | 159 | function allocateNode( node, types, tallies ) { 160 | // debugger; 161 | console.log( "allocateNode( " + node.toString() + ", " + types + " )" ); 162 | 163 | if( !types ) { 164 | // No types specified (must be root node). Choose one at random. 165 | if( node.isLeaf ) { 166 | types = ["scalarVars", "scalarConsts", "vectorTerms"]; 167 | } else { 168 | types = ["vectorOps", "scalarOps"]; 169 | } 170 | } 171 | 172 | if( !tallies ) tallies = {}; 173 | 174 | // Choose a type at random from the list of allowed types 175 | var type = types[Math.floor(Math.random()*types.length)]; 176 | 177 | if( node.isLeaf ) { 178 | // type should be the name of a list of atoms. 179 | var atomsList = syntaxConfig[type]; 180 | var atom = atomsList.atoms[Math.floor(Math.random()*atomsList.atoms.length)]; 181 | if( typeof atom === "function" ) atom = atom(); // TODO: use goog.isFunction XXXXXXXXXXXXXXXXXXXXXXXXXX 182 | node.data = new Atom( atomsList.outputType, atom ); 183 | console.log( " Allocated atom " + node.data.toString() ); 184 | addTally( tallies, atomsList.outputType ); 185 | addTally( tallies, node.data.valid?"valid":"invalid" ); 186 | return( node.data ); 187 | } else { 188 | // type should be the name of a list of operations. 189 | var ops = syntaxConfig[type]; 190 | var op = syntaxConfig[ops[Math.floor(Math.random()*ops.length)]]; 191 | node.data = op; 192 | console.log( " Allocated op " + node.data.toString() ); 193 | addTally( tallies, node.data.outputType ); 194 | addTally( tallies, node.data.valid?"valid":"invalid" ); 195 | if( node.left.isLeaf ) { 196 | allocateNode( node.left, op.leftAtom, tallies ); 197 | } else { 198 | allocateNode( node.left, op.leftOp, tallies ); 199 | } 200 | if( node.right.isLeaf ) { 201 | allocateNode( node.right, op.rightAtom, tallies ); 202 | } else { 203 | allocateNode( node.right, op.rightOp, tallies ); 204 | } 205 | return( node.data ); 206 | } 207 | } 208 | 209 | function renderNode( node ) { 210 | console.log( "renderNode( " + node.toString() + " )" ); 211 | if( node.isLeaf ) { 212 | return( [node.data.render, node.data.order] ); 213 | } else { 214 | var leftRender = renderNode( node.left ); 215 | var rightRender = renderNode( node.right ); 216 | var leftOutput = leftRender[0]; 217 | if( leftRender[1] > node.data.order ) { 218 | leftOutput = "\\left( " + leftOutput + " \\right)"; 219 | } 220 | var rightOutput = rightRender[0]; 221 | if( rightRender[1] > node.data.order ) { 222 | rightOutput = "\\left( " + rightOutput + " \\right)"; 223 | } 224 | return( [leftOutput + " " + node.data.render + " " + rightOutput, node.data.order] ); 225 | } 226 | } 227 | 228 | 229 | var tree; 230 | function go() { 231 | tree = randomPrune( fullBinaryTree( targetComplexity ), targetComplexity ); 232 | var tally = {}; 233 | allocateNode( tree, null, tally ); 234 | setContentById( "debug-output", "tally: " + JSON.stringify( tally ) + "\n" + tree.toString() ); 235 | var renderResult = renderNode(tree) 236 | var output = "\\[ " + renderResult[0] + " \\]"; 237 | renderLatex( output ); 238 | } 239 | 240 | function debug1() { 241 | debugger; 242 | renderNode( tree ); 243 | } 244 | 245 | 246 | 247 | /* The desired complexity of the randomly generated expression, measured as the # of atomic terms in the expression. */ 248 | var targetComplexity = 4; 249 | 250 | 251 | 252 | /* For debugging convenience */ 253 | var t2 = fullBinaryTree(2); 254 | var t3 = fullBinaryTree(3); 255 | var rt = randomPrune(fullBinaryTree(3),4); 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | /* 264 | Notes: 265 | * global 'complexity level' variable - determining size of tree (# of leaves) 266 | 267 | Two possible approaches for generating valid/invalid expressions: 268 | 1. Decide initially whether expression should be valid or invalid. Decide at which leaf the error should lie. Pass this to the generator. 269 | Advantage: can produce any desired distribution of valid/invalid, or where the error is. 270 | 2. At each generation step, randomly decide whether to make it valid or invalid. Once one invalid leaf is produced, all others should be valid. Tune the probability so that we get a desired overall valid/invalid rate (binomial distribution). 271 | 272 | Approach 1 probably better? 273 | 274 | 275 | 276 | 277 | 278 | */ -------------------------------------------------------------------------------- /js/field_mathjax.js: -------------------------------------------------------------------------------- 1 | /** Based on Blockly field_image.js **/ 2 | 3 | /* This does not work in Internet Explorer, due to lack of support for SVG foreignObject element. */ 4 | 5 | 'use strict'; 6 | 7 | goog.provide('Blockly.FieldMathJax'); 8 | 9 | goog.require('Blockly.Field'); 10 | goog.require('goog.dom'); 11 | goog.require('goog.math.Size'); 12 | goog.require('goog.userAgent'); 13 | 14 | 15 | /** 16 | * Class for displaying a mathematical expression rendered by MathJax. 17 | * @param {string} src A mathematical expression, in latex, asciimath or any other format supported by MathJax 18 | * @param {string=} opt_alt Optional alt text for when block is collapsed. 19 | * @param {bool=} opt_initialsource If true, show the alt text during initial rendering. 20 | * @extends {Blockly.Field} 21 | * @constructor 22 | */ 23 | Blockly.FieldMathJax = function(src, opt_alt, opt_initial) { 24 | this.sourceBlock_ = null; 25 | this.text_ = opt_alt || ''; 26 | this.src_ = src; 27 | this.size_ = new goog.math.Size(0, 0); /* Size cannot be determined until after rendering */ 28 | this.initialSource_ = (opt_initial == true); 29 | }; 30 | goog.inherits(Blockly.FieldMathJax, Blockly.Field); 31 | 32 | Blockly.FieldMathJax.svgCache_ = {}; 33 | 34 | /* TODO: cache clean-up? */ 35 | 36 | Blockly.FieldMathJax.addToCache = function( src, node ) { 37 | Blockly.FieldMathJax.svgCache_[src] = {node: node.cloneNode(true), width: node.offsetWidth, height: node.offsetHeight}; 38 | } 39 | 40 | /** 41 | * Rectangular mask used by Firefox. 42 | * @type {Element} 43 | * @private 44 | */ 45 | Blockly.FieldMathJax.prototype.rectElement_ = null; 46 | 47 | /** 48 | * Editable fields are saved by the XML renderer, non-editable fields are not. 49 | */ 50 | Blockly.FieldMathJax.prototype.EDITABLE = false; 51 | 52 | /** 53 | * Install this field on a block. 54 | * @param {!Blockly.Block} block The block containing this text. 55 | */ 56 | Blockly.FieldMathJax.prototype.init = function() { 57 | if (this.foreignObject_) { 58 | // Field has already been initialized once. 59 | return; 60 | } 61 | // Build the DOM. 62 | this.fieldGroup_ = Blockly.createSvgElement('g', {}, null); 63 | if (!this.visible_) { 64 | this.fieldGroup_.style.display = 'none'; 65 | } 66 | 67 | this.foreignElement_ = Blockly.createSvgElement('foreignObject', 68 | {}, this.fieldGroup_); 69 | this.setValue(this.src_); 70 | if (goog.userAgent.GECKO) { 71 | // Due to a Firefox bug which eats mouse events on image elements, 72 | // a transparent rectangle needs to be placed on top of the image. 73 | // TODO: Check if this bug holds for foreignelement also. 74 | this.rectElement_ = Blockly.createSvgElement('rect', 75 | {'fill-opacity': 0}, this.fieldGroup_); 76 | } 77 | this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_); 78 | 79 | // Configure the field to be transparent with respect to tooltips. 80 | var topElement = this.rectElement_ || this.foreignElement_; 81 | topElement.tooltip = this.sourceBlock_; 82 | Blockly.Tooltip.bindMouseEvents(topElement); 83 | }; 84 | 85 | Blockly.FieldMathJax.prototype.setSize_ = function(width, height) { 86 | this.width_ = width; 87 | this.height_ = height; 88 | this.size_ = new goog.math.Size(this.width_, 89 | this.height_ + 2 * Blockly.BlockSvg.INLINE_PADDING_Y); 90 | 91 | this.foreignElement_.setAttribute("width", width + "px"); 92 | this.foreignElement_.setAttribute("height", height + "px"); 93 | 94 | if( this.rectElement_ ) { 95 | this.rectElement_.setAttribute("width", width + "px"); 96 | this.rectElement_.setAttribute("height", height + "px"); 97 | } 98 | }; 99 | 100 | /** 101 | * Set the latex/asciimath source of the expression. 102 | * @param {?string} src New source. 103 | * @override 104 | */ 105 | Blockly.FieldMathJax.prototype.setValue = function(src) { 106 | if (src === null) { 107 | // No change if null. 108 | return; 109 | } 110 | this.src_ = src; 111 | if( !this.foreignElement_ ) { 112 | /* Block hasn't been initialised yet. Store string for later. */ 113 | return; 114 | } 115 | 116 | /* If this src has been rendered before, re-use svg from cache */ 117 | if( Blockly.FieldMathJax.svgCache_[src] ) { 118 | goog.dom.removeNode( this.mathDiv_ ); 119 | var cacheData = Blockly.FieldMathJax.svgCache_[src]; 120 | var node = cacheData.node.cloneNode(true); 121 | this.mathDiv_ = node; 122 | this.setSize_( cacheData.width, cacheData.height ); 123 | 124 | this.foreignElement_.appendChild( node ); 125 | node.style.position = "relative"; 126 | node.style.visibility = "visible"; 127 | /* Workaround for a Chrome bug - see http://stackoverflow.com/questions/8185845/svg-foreignobject-behaves-as-though-absolutely-positioned-in-webkit-browsers */ 128 | if( goog.userAgent.WEBKIT ) { 129 | node.style.position = "fixed"; 130 | } 131 | return; 132 | } 133 | 134 | /* Otherwise, render the math using MathJax */ 135 | 136 | if( !this.mathDiv_ && this.initialSource_ ) { 137 | /* Temporarily display latex source */ 138 | this.mathDiv_ = document.createElement("div"); 139 | this.mathDiv_.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); 140 | this.mathDiv_.innerHTML = this.text_; 141 | this.mathDiv_.style.visibility = "hidden"; 142 | this.mathDiv_.style.position = "absolute"; 143 | this.mathDiv_.style.margin = "0"; 144 | this.mathDiv_.style.fontWeight = "bold"; 145 | document.body.appendChild( this.mathDiv_ ); 146 | this.setSize_( this.mathDiv_.offsetWidth, this.mathDiv_.offsetHeight ); 147 | this.foreignElement_.appendChild( this.mathDiv_ ); 148 | this.mathDiv_.style.visibility = "visible"; 149 | this.mathDiv_.style.position = "relative"; 150 | 151 | /* Workaround for a Chrome bug - see http://stackoverflow.com/questions/8185845/svg-foreignobject-behaves-as-though-absolutely-positioned-in-webkit-browsers */ 152 | if( goog.userAgent.WEBKIT ) { 153 | this.mathDiv_.style.position = "fixed"; 154 | } 155 | } 156 | 157 | var newDiv = document.createElement("div"); 158 | newDiv.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); 159 | newDiv.innerHTML = "\\(\\displaystyle " + src + " \\)"; /* Allow Latex only */ 160 | // newDiv.innerHTML = "\\/( " + src + " \\/)"; /* Allow Latex and asciimath */ 161 | 162 | // Temporarily add div to document so that we can get its size. 163 | // Set visibility to hidden so it will not display. 164 | newDiv.style.visibility = "hidden"; 165 | newDiv.style.position = "absolute"; 166 | document.body.appendChild( newDiv ); 167 | 168 | var t = this; 169 | var callback = function() { 170 | /* Called when MathJax has finished rendering math expression */ 171 | t.setSize_( newDiv.offsetWidth, newDiv.offsetHeight ); 172 | 173 | goog.dom.removeNode( t.mathDiv_ ); 174 | t.foreignElement_.appendChild( newDiv ); 175 | newDiv.style.position = "relative"; 176 | newDiv.style.visibility = "visible"; 177 | /* Workaround for a Chrome bug - see http://stackoverflow.com/questions/8185845/svg-foreignobject-behaves-as-though-absolutely-positioned-in-webkit-browsers */ 178 | if( goog.userAgent.WEBKIT ) { 179 | newDiv.style.position = "fixed"; 180 | } 181 | 182 | t.mathDiv_ = newDiv; 183 | 184 | /* Re-render block in case size has changed */ 185 | t.sourceBlock_.render(); 186 | 187 | /* If block is in a flyout, re-flow the flyout due to block size change */ 188 | if( t.sourceBlock_.workspace.isFlyout ) { 189 | t.sourceBlock_.workspace.targetWorkspace.flyout_.reflow(); // A bit hackish 190 | } 191 | 192 | /* Add rendered svg to cache */ 193 | Blockly.FieldMathJax.addToCache( src, newDiv ); 194 | 195 | }; 196 | MathJax.Hub.Queue(["Typeset", MathJax.Hub, newDiv, callback]); 197 | }; 198 | 199 | /** 200 | * Get the latex/asciimath source of this expression. 201 | * @return {string} Current text. 202 | * @override 203 | */ 204 | Blockly.FieldMathJax.prototype.getValue = function() { 205 | return this.src_; 206 | }; 207 | 208 | /** 209 | * Dispose of all DOM objects belonging to this text. 210 | */ 211 | Blockly.FieldMathJax.prototype.dispose = function() { 212 | goog.dom.removeNode(this.fieldGroup_); 213 | this.fieldGroup_ = null; 214 | this.foreignElement_ = null; 215 | this.mathDiv_ = null; 216 | this.rectElement_ = null; 217 | }; 218 | 219 | /** 220 | * Change the tooltip text for this field. 221 | * @param {string|!Element} newTip Text for tooltip or a parent element to 222 | * link to for its tooltip. 223 | */ 224 | Blockly.FieldMathJax.prototype.setTooltip = function(newTip) { 225 | var topElement = this.rectElement_ || this.foreignElement_; 226 | topElement.tooltip = newTip; 227 | }; 228 | 229 | /** 230 | * Set the alt text of this field. 231 | * @param {?string} alt New alt text. 232 | * @override 233 | */ 234 | Blockly.FieldMathJax.prototype.setText = function(alt) { 235 | if (alt === null) { 236 | // No change if null. 237 | return; 238 | } 239 | this.text_ = alt; 240 | }; 241 | 242 | /** 243 | * Field is fixed width, no need to render. 244 | * @private 245 | */ 246 | // TODO: Implement this? 247 | Blockly.FieldMathJax.prototype.render_ = function() { 248 | // NOP 249 | }; 250 | -------------------------------------------------------------------------------- /js/field_mathvariable.js: -------------------------------------------------------------------------------- 1 | /**** Code for custom variable dropdowns ****/ 2 | 'use strict'; 3 | 4 | /* Set up inheritance */ 5 | goog.provide('Blockly.FieldMathVariable'); 6 | 7 | goog.require('Blockly.FieldFlydown'); 8 | goog.require('Blockly.Msg'); 9 | goog.require('Blockly.Variables'); 10 | goog.require('goog.string'); 11 | 12 | /** 13 | * A text input for a math variable. Notable characteristics: 14 | * - variable names are restricted to one alphabetic character 15 | * - variables are typed 16 | * - hovering over the field will (optionally) produce a flydown with variable block for easy access 17 | * Based on FieldFlydown from App Inventor. 18 | * @param {?bool} opt_flydown If true, on mouseover a flyout will appear with a block for this variable, for easy access. 19 | * @param {?bool} opt_strict If true, field highlights out-of-scope variables which are not quantified or predefined global variables. 20 | */ 21 | Blockly.FieldMathVariable = function(varname, opt_type, opt_validator, opt_flydown, opt_strict) { 22 | Blockly.FieldMathVariable.superClass_.constructor.call(this, varname, 23 | true, /* isEditable */ 24 | Blockly.FieldFlydown.DISPLAY_BELOW, /* displayLocation */ 25 | (opt_validator != null ? opt_validator : Blockly.FieldMathVariable.validator_), 26 | 1 /* opt_maxlength - maximum allowed input length */ 27 | ); 28 | this.enforceScope_ = !!opt_strict; 29 | this.useFlydown_ = !!opt_flydown; 30 | this.type_ = opt_type; 31 | this.onchange = function() {console.log("onchange", this.getValue()); }; 32 | }; 33 | goog.inherits(Blockly.FieldMathVariable, Blockly.FieldFlydown); 34 | 35 | Blockly.FieldMathVariable.prototype.isVariable_ = true; 36 | 37 | /* CSS class for the flydown */ 38 | Blockly.FieldMathVariable.prototype.flyoutCSSClassName = 'blocklyFieldMathVariableFlydown'; 39 | 40 | // Called when field is installed on a block. 41 | Blockly.FieldMathVariable.prototype.init = function() { 42 | if( this.useFlydown_ ) { 43 | Blockly.FieldMathVariable.superClass_.init.call( this ); 44 | } else { 45 | /* Skip FieldFlydown init and go straight to FieldTextInput init */ 46 | Blockly.FieldFlydown.superClass_.init.call( this ); 47 | } 48 | var workspace = this.sourceBlock_.workspace; 49 | if( !workspace.isFlyout && !workspace.fieldMathVariableEventHandler_ ) { 50 | /* Install onchange handler to detect variable conflicts on block addition/removal */ 51 | workspace.fieldMathVariableEventHandler_ = function(event) { 52 | if( event.type == Blockly.Events.CREATE || event.type == Blockly.Events.DELETE ) 53 | { 54 | Blockly.FieldMathVariable.checkVars_( workspace ); 55 | } 56 | } 57 | workspace.addChangeListener( workspace.fieldMathVariableEventHandler_ ); 58 | } 59 | }; 60 | 61 | 62 | /* Restrict input to one character alphabetic */ 63 | Blockly.FieldMathVariable.validator_ = function(text) { 64 | return( (text.length == 1 && text.search(/^[a-zA-Zα-ωΑ-Ωϵ]$/) == 0) ? text : null ); // Regexp: string consists of a single alphabetic Latin or Greek character. 65 | } 66 | 67 | Blockly.FieldMathVariable.prototype.setValue = function(text) { 68 | var oldvar = this.getValue(); 69 | Blockly.FieldMathVariable.superClass_.setValue.call(this, text); 70 | if( this.sourceBlock_ && !this.sourceBlock_.isInFlyout ) { 71 | /* Check for variable collisions */ 72 | Blockly.FieldMathVariable.checkVars_( this.sourceBlock_.workspace ); 73 | } 74 | } 75 | 76 | Blockly.FieldMathVariable.checkVars_ = function(workspace) { 77 | // debugger; 78 | var varDB = {}; 79 | /* Iterate over all variables from the workspace, and check if their types match */ 80 | var blocks = workspace.getAllBlocks(); 81 | for( var i in blocks ) { 82 | var b = blocks[i]; 83 | for( var j in b.inputList ) { 84 | var input = b.inputList[j]; 85 | for( var k in input.fieldRow ) { 86 | var field = input.fieldRow[k]; 87 | if( field instanceof Blockly.FieldMathVariable ) { 88 | var vname = field.getValue(); 89 | if( varDB[vname] ) { 90 | varDB[vname][2].push(field); 91 | if( varDB[vname][0] != field.type_ ) varDB[vname][1] = true; 92 | } else { 93 | varDB[vname] = [field.type_, false, [field]]; /* type, conflicting, list of fields */ 94 | } 95 | } 96 | } 97 | } 98 | } 99 | // console.log( "checkVars_", varDB ); 100 | for( var i in varDB ) { 101 | if( varDB[i][1] ) { 102 | /* Highlight fields in red */ 103 | varDB[i][2].map( function(f) {f.addCSSClass("blocklyMathVariableError");} ); 104 | } else { 105 | /* Remove red highlight */ 106 | varDB[i][2].map( function(f) {f.removeCSSClass("blocklyMathVariableError");} ); 107 | } 108 | } 109 | } 110 | 111 | Blockly.FieldMathVariable.prototype.flydownBlocksXML_ = function() { 112 | /* Mapping from type to corresponding variable block name */ 113 | var typemap = { 114 | "Number": "number_variable", 115 | "Set": "set_variable", 116 | "Boolean": "logic_prop_variable", 117 | "Function": "function_variable", 118 | "Abstract": "abstract_variable" 119 | }; 120 | 121 | var blocktype = typemap[this.type_]; 122 | 123 | var getterSetterXML = 124 | '' + 125 | '' + 126 | '' + 127 | this.getText() + 128 | '' + 129 | '' + 130 | ''; 131 | return getterSetterXML; 132 | } 133 | -------------------------------------------------------------------------------- /js/field_mathvariable_old.js: -------------------------------------------------------------------------------- 1 | /**** Code for custom variable dropdowns ****/ 2 | /* This version uses a dropdown menu based on Blockly's FieldVariable. */ 3 | 'use strict'; 4 | 5 | /* Set up inheritance */ 6 | goog.provide('Blockly.FieldMathVariable'); 7 | 8 | goog.require('Blockly.FieldDropdown'); 9 | goog.require('Blockly.Flydown'); 10 | goog.require('Blockly.Msg'); 11 | goog.require('Blockly.Variables'); 12 | goog.require('goog.string'); 13 | 14 | /** 15 | * A dropdown menu for a math variable. Differences to standard FieldVariable: 16 | * - variable names are restricted to one alphabetic character 17 | * - variables are typed 18 | * @param {?bool} opt_strict If true, dropdown shows only variables which are either quantified, or predefined global variables. 19 | * @param {?bool} opt_flydown If true, on mouseover a flyout will appear with a block for this variable, for easy access. 20 | */ 21 | Blockly.FieldMathVariable = function(varname, opt_type, opt_validator, opt_flydown, opt_strict) { 22 | Blockly.FieldMathVariable.superClass_.constructor.call(this, 23 | varname, opt_validator, opt_type); 24 | this.menuGenerator_ = Blockly.FieldMathVariable.dropdownCreate; 25 | this.enforceScope_ = !!opt_strict; 26 | this.useFlydown_ = !!opt_flydown; 27 | }; 28 | goog.inherits(Blockly.FieldMathVariable, Blockly.FieldVariable); 29 | 30 | // Called when field is installed on a block. 31 | Blockly.FieldMathVariable.prototype.init = function(block) { 32 | Blockly.FieldMathVariable.superClass_.init.call( this, block ); 33 | 34 | if( this.useFlydown_ ) { 35 | Blockly.Flydown.workspaceInit( block.workspace ); // Set up Flydown for this workspace 36 | this.mouseOverWrapper_ = 37 | Blockly.bindEvent_(this.fieldGroup_, 'mouseover', this, this.onMouseOver_); 38 | this.mouseOutWrapper_ = 39 | Blockly.bindEvent_(this.fieldGroup_, 'mouseout', this, this.onMouseOut_); 40 | } 41 | }; 42 | 43 | /** 44 | * Milliseconds to wait before showing flydown after mouseover event on flydown field. 45 | */ 46 | Blockly.FieldMathVariable.flydownTimeout = 750; 47 | 48 | /** 49 | * Process ID for timer event to show flydown (scheduled by mouseover event) 50 | * @type {number} 51 | */ 52 | Blockly.FieldMathVariable.showPid_ = 0; 53 | 54 | // Note: To be correct, the next two should be per-workspace. App Inventor code assumes only one (non-flyout) Blockly workspace is present. 55 | /** 56 | * The flydown which is currently active (if any) 57 | */ 58 | Blockly.FieldMathVariable.activeFlydown_ = null; 59 | 60 | /** 61 | * Which instance of FieldMathVariable (or a subclass) is an open flydown attached to? 62 | * @type {Blockly.FieldMathVariable (or subclass)} 63 | * @private 64 | */ 65 | Blockly.FieldMathVariable.flydownOwner_ = null; 66 | 67 | Blockly.FieldMathVariable.prototype.onMouseOver_ = function(e) { 68 | console.log( "FieldMathVariable onmouseover" ); 69 | if( !this.sourceBlock_.isInFlyout ) { // [lyn, 10/22/13] No flydowns in a flyout! 70 | var field = this; 71 | var callback = function() { 72 | Blockly.FieldMathVariable.showPid_ = 0; 73 | field.showFlydown_(); 74 | }; 75 | if( Blockly.FieldMathVariable.showPid_ ) window.clearTimeout( Blockly.FieldMathVariable.showPid_ ); 76 | Blockly.FieldMathVariable.showPid_ = window.setTimeout( callback, Blockly.FieldMathVariable.flydownTimeout ); 77 | // This event has been handled. No need to bubble up to the document. 78 | } 79 | e.stopPropagation(); 80 | }; 81 | 82 | Blockly.FieldMathVariable.prototype.onMouseOut_ = function(e) { 83 | console.log( "FieldMathVariable onmouseout" ); 84 | // Clear any pending timer event to show flydown 85 | window.clearTimeout(Blockly.FieldMathVariable.showPid_); 86 | Blockly.FieldMathVariable.showPid_ = 0; 87 | e.stopPropagation(); 88 | }; 89 | 90 | Blockly.FieldMathVariable.prototype.showEditor_ = function() { 91 | console.log( "FieldMathVariable showEditor_" ); 92 | Blockly.FieldMathVariable.hideFlydown(); 93 | Blockly.FieldMathVariable.superClass_.showEditor_.call( this ); 94 | } 95 | 96 | /** 97 | * Creates a Flydown containing a block for the current variable. 98 | */ 99 | Blockly.FieldMathVariable.prototype.showFlydown_ = function() { 100 | console.log("FieldMathVariable show Flydown"); 101 | if( !this.getValue() || this.getValue() == "" ) return; // No flydown if no variable currently selected 102 | 103 | Blockly.hideChaff(); // Hide open context menus, dropDowns, flyouts, and other flydowns 104 | Blockly.FieldMathVariable.flydownOwner_ = this; // Remember field to which flydown is attached 105 | var flydown = this.sourceBlock_.workspace.flydown_; 106 | Blockly.FieldMathVariable.activeFlydown_ = flydown; 107 | var blocksXMLText = '' + this.getValue() + ''; 108 | var blocksDom = Blockly.Xml.textToDom(blocksXMLText); 109 | var blocksXMLList = goog.dom.getChildren(blocksDom); // List of blocks for flydown 110 | var xy = Blockly.getSvgXY_(this.borderRect_, this.sourceBlock_.workspace); 111 | var borderBBox = this.borderRect_.getBBox(); 112 | var x = xy.x; 113 | var y = xy.y; 114 | y = y + borderBBox.height; 115 | flydown.showAt(blocksXMLList, x, y); 116 | }; 117 | 118 | /** 119 | * Hide the flydown menu and squash any timer-scheduled flyout creation 120 | */ 121 | Blockly.FieldMathVariable.hideFlydown = function() { 122 | console.log( "hideFlydown_: ", Blockly.FieldMathVariable.showPid_, Blockly.FieldMathVariable.activeFlydown_ ); 123 | // Clear any pending timer event to show flydown 124 | if( Blockly.FieldMathVariable.showPid_ != 0 ) window.clearTimeout(Blockly.FieldMathVariable.showPid_); 125 | // Clear any displayed flydown 126 | if( Blockly.FieldMathVariable.activeFlydown_ ) Blockly.FieldMathVariable.activeFlydown_.hide(); 127 | if( Blockly.FieldMathVariable.flydownOwner_ ) Blockly.FieldMathVariable.flydownOwner_ = null; 128 | }; 129 | 130 | /** 131 | * Close the flydown and dispose of all UI. 132 | */ 133 | Blockly.FieldMathVariable.prototype.dispose = function() { 134 | if (Blockly.FieldMathVariable.flydownOwner_ == this) { 135 | Blockly.FieldMathVariable.hideFlydown(); 136 | } 137 | // Call parent's destructor. 138 | Blockly.FieldMathVariable.superClass_.dispose.call( this ); 139 | }; 140 | 141 | 142 | /** 143 | * Return a sorted list of variable names for variable dropdown menus. 144 | * Include a special option at the end for creating a new variable name. 145 | * @return {!Array.} Array of variable names. 146 | */ 147 | Blockly.FieldMathVariable.dropdownCreate = function() { 148 | var variables = []; 149 | if (this.sourceBlock_ && this.sourceBlock_.workspace) { 150 | if( this.enforceScope_ ) { 151 | /* Recursively collect variables from parents */ 152 | var parent = this.sourceBlock_; 153 | while( parent = parent.parentBlock_ ) { 154 | if( parent.getVars && 155 | !(parent.isQuantifier && 156 | parent.getInput( "SCOPE" ) && parent.getInput( "SCOPE" ).connection.targetBlock() && 157 | parent.getInput( "SCOPE" ).connection.targetBlock().isParentOf( this.sourceBlock_ )) ) 158 | { 159 | // Variable defined by a quantifier is not available to blocks in the 'SCOPE' input 160 | // ie, you can't have "Forall x > x" or "Exists x in { y: y != x}" 161 | variables = variables.concat( parent.getVars() ); 162 | } 163 | } 164 | } else { 165 | var blocks = this.sourceBlock_.workspace.getAllBlocks(); 166 | for (var x = 0; x < blocks.length; x++) { 167 | if (blocks[x].getVars) { 168 | variables = variables.concat( blocks[x].getVars() ); 169 | } 170 | } 171 | } 172 | // Add predefined global variables 173 | var workspace = this.sourceBlock_.workspace; 174 | if( workspace.globalVariables ) { 175 | variables = variables.concat( workspace.globalVariables ); 176 | } 177 | } else { 178 | variables = []; 179 | } 180 | 181 | var variableList = []; 182 | for (var y = 0; y < variables.length; y++) { 183 | var v = variables[y]; 184 | if( goog.isString(v) ) { 185 | /* Variable is untyped */ 186 | if( !this.type_ ) { 187 | var varName = v; 188 | // Variable name may be null if the block is only half-built. 189 | if (varName && variableList.indexOf(varName) == -1) { 190 | variableList.push( varName ); 191 | } 192 | } 193 | } else if( goog.isArray(v) ) { 194 | /* Variable is typed - v is an array [name, type] */ 195 | var varName = v[0]; 196 | var varType = v[1]; 197 | // Variable name may be null if the block is only half-built. 198 | if (varName && (!this.type_ || (this.type_ == varType)) && variableList.indexOf(varName) == -1) { 199 | variableList.push( varName ); 200 | } 201 | } 202 | } 203 | 204 | if( !this.enforceScope_ ) { 205 | // Ensure that the currently selected variable is an option. 206 | var name = this.getText(); 207 | if (name && variableList.indexOf(name) == -1) { 208 | variableList.push(name); 209 | } 210 | } 211 | variableList.sort(goog.string.caseInsensitiveCompare); 212 | if( !this.enforceScope_ ) { 213 | // variableList.push(Blockly.Msg.RENAME_VARIABLE); 214 | variableList.push(Blockly.Msg.NEW_VARIABLE); 215 | } 216 | // Variables are not language-specific, use the name as both the user-facing 217 | // text and the internal representation. 218 | var options = []; 219 | for (var x = 0; x < variableList.length; x++) { 220 | options[x] = [variableList[x], variableList[x]]; 221 | } 222 | return options; 223 | }; 224 | 225 | /* We want to use our own 'new variable' dialog but the normal FieldVariable dropdownChange is a static member function so we have to hook it in the hard way. */ 226 | Blockly.FieldMathVariable.prototype.setValidator = function(handler) { 227 | var wrappedHandler; 228 | if (handler) { 229 | // Wrap the user's change handler together with the variable rename handler. 230 | wrappedHandler = function(value) { 231 | var v1 = handler.call(this, value); 232 | if (v1 === null) { 233 | var v2 = v1; 234 | } else { 235 | if (v1 === undefined) { 236 | v1 = value; 237 | } 238 | var v2 = Blockly.FieldMathVariable.dropdownChange.call(this, v1); 239 | if (v2 === undefined) { 240 | v2 = v1; 241 | } 242 | } 243 | return v2 === value ? undefined : v2; 244 | }; 245 | } else { 246 | wrappedHandler = Blockly.FieldMathVariable.dropdownChange; 247 | } 248 | Blockly.FieldVariable.superClass_.setValidator.call(this, wrappedHandler); // Skipping FieldMathVariable setValidator as it will use default dropdownChange instead 249 | }; 250 | 251 | Blockly.FieldMathVariable.promptCallback_ = function(text) { 252 | if( text ) Blockly.FieldMathVariable.promptOwner_.setValue( text ); 253 | }; 254 | Blockly.FieldMathVariable.promptValidator_ = function(text) { 255 | return( text.length == 1 && text.search(/^[a-zA-Z]$/) == 0 ); // Regexp: string consists of a single alphabetic character. TODO: support Greek letters. 256 | }; 257 | Blockly.FieldMathVariable.promptOwner_ = null; 258 | Blockly.FieldMathVariable.prompt_ = new goog.ui.Prompt(Blockly.Msg.NEW_VARIABLE_TITLE, "", Blockly.FieldMathVariable.promptCallback_); 259 | Blockly.FieldMathVariable.prompt_.setValidationFunction( Blockly.FieldMathVariable.promptValidator_ ); 260 | 261 | Blockly.FieldMathVariable.dropdownChange = function(text) { 262 | if( text == Blockly.Msg.NEW_VARIABLE ) { 263 | Blockly.FieldMathVariable.promptOwner_ = this; 264 | var t = this; 265 | Blockly.hideChaff(); 266 | Blockly.FieldMathVariable.prompt_.setDefaultValue( this.getValue() ); 267 | Blockly.FieldMathVariable.prompt_.setVisible(true); 268 | Blockly.FieldMathVariable.prompt_.getInputElement().setAttribute( "maxlength", 1 ); 269 | return( null ); 270 | } else { 271 | return( Blockly.FieldVariable.dropdownChange.call(this, text) ); 272 | } 273 | } -------------------------------------------------------------------------------- /js/latex-generator.js: -------------------------------------------------------------------------------- 1 | /* Code generator to produce latex from math blocks */ 2 | /* Adapted from blockly/generators/javascript.js */ 3 | 4 | Blockly.Latex = new Blockly.Generator('Latex'); 5 | 6 | 7 | /** 8 | * Order of operation ENUMs. 9 | */ 10 | Blockly.Latex.ORDER_ATOMIC = 0; // Literals, variables 11 | 12 | /* Logic/boolean operations */ 13 | Blockly.Latex.ORDER_FORALL = 1; // forall X in __ ... 14 | Blockly.Latex.ORDER_EXISTS = 1; // exists X in __ ... 15 | Blockly.Latex.ORDER_QUANTIFIER_SET = 1; // exists __ in XXX ... 16 | Blockly.Latex.ORDER_QUANTIFIER_NUM = 1; // exists __ > XXX ... 17 | Blockly.Latex.ORDER_QUANTIFIER_SCOPE = 1; // exists __ _ XXX ... 18 | Blockly.Latex.ORDER_NOT = 2; // NOT a 19 | Blockly.Latex.ORDER_IFF = 4; // a IFF b 20 | Blockly.Latex.ORDER_IMPLIES = 4; // a IMPLIES b 21 | Blockly.Latex.ORDER_AND = 3; // a AND b 22 | Blockly.Latex.ORDER_OR = 4; // a OR b 23 | 24 | /* Comparisons - all have equal precedence */ 25 | Blockly.Latex.ORDER_EQUAL = 10; // a = b 26 | Blockly.Latex.ORDER_NOTEQUAL = 10; // a = b 27 | Blockly.Latex.ORDER_SUBSET = 10; // A subset B 28 | Blockly.Latex.ORDER_SUBSETEQ = 10; // A subseteq B 29 | Blockly.Latex.ORDER_LESS = 10; // A < B 30 | Blockly.Latex.ORDER_LESSEQ = 10; // A <= B 31 | Blockly.Latex.ORDER_GREATER = 10; // A > B 32 | Blockly.Latex.ORDER_GREATEREQ = 10; // A >= B 33 | Blockly.Latex.ORDER_COMPARISON = 10; // covers all comparisons: =, <, subseteq, etc 34 | 35 | 36 | /* Set operations */ 37 | Blockly.Latex.ORDER_SET_COMPLEMENT = 2; // set complement A^c 38 | Blockly.Latex.ORDER_SET_EXCLUSION = 3; // set exclusion A \ B 39 | Blockly.Latex.ORDER_INTERSECTION = 4; // set intersection 40 | Blockly.Latex.ORDER_UNION = 5; // set union 41 | Blockly.Latex.ORDER_SET_ELEMENT = 20; // X element of __ 42 | Blockly.Latex.ORDER_SET_SET = 20; // __ element of X 43 | Blockly.Latex.ORDER_SET_MEMBERSHIP = 21;// (XX element of YY) 44 | 45 | /* Set bounding operations - all have equal precedence */ 46 | Blockly.Latex.ORDER_SUP = 1; 47 | Blockly.Latex.ORDER_INF = 1; 48 | Blockly.Latex.ORDER_MIN = 1; 49 | Blockly.Latex.ORDER_MAX = 1; 50 | 51 | /* Number operations */ 52 | Blockly.Latex.ORDER_FUNCTION = 1; // Function application eg sin(...) 53 | Blockly.Latex.ORDER_MULT_INVERSE = 2; 54 | Blockly.Latex.ORDER_ADD_INVERSE = 3; 55 | Blockly.Latex.ORDER_POWER = 4; 56 | Blockly.Latex.ORDER_MULTIPLICATION = 5; 57 | Blockly.Latex.ORDER_DIVISION = 5; 58 | Blockly.Latex.ORDER_ADDITION = 6; 59 | Blockly.Latex.ORDER_SUBTRACTION = 6; 60 | 61 | Blockly.Latex.ORDER_NONE = 99; // (...) 62 | 63 | /* Text to appear as a placeholder for any empty slots */ 64 | Blockly.Latex.blank = "\\text{[blank]}"; 65 | 66 | /* Code for left & right parentheses */ 67 | Blockly.Latex.leftParen = "\\left("; 68 | Blockly.Latex.rightParen = "\\right)"; 69 | 70 | /* Latex uses \\ for line break */ 71 | Blockly.Latex.lineSeparator = "\\\\\n"; 72 | 73 | /* Adds comments or other annotations to code. Not needed in this case. */ 74 | Blockly.Latex.scrub_ = function(block, code) { 75 | return( code ); 76 | } 77 | 78 | /* Adds semicolons, linebreaks etc at end of line. Not needed in this case. */ 79 | Blockly.Latex.scrubNakedValue = function(line) { 80 | return( line ); 81 | } 82 | 83 | /* Add any prelude, eg variable definitions. Not needed in this case. */ 84 | Blockly.Latex.finish = function(code) { 85 | return( code ); 86 | } 87 | 88 | /* Mapping from field names (Unicode symbols or text labels) to latex command and precedence value */ 89 | Blockly.Latex.symbolToLatex = { 90 | '∧': ["\\wedge",Blockly.Latex.ORDER_AND], 91 | '∨': ["\\vee",Blockly.Latex.ORDER_OR], 92 | '⇒': ["\\implies",Blockly.Latex.ORDER_IMPLIES], 93 | '⇔': ["\\iff",Blockly.Latex.ORDER_IFF], 94 | '∨': ["\\vee",Blockly.Latex.ORDER_OR], 95 | '∨': ["\\vee",Blockly.Latex.ORDER_OR], 96 | '∪': ["\\cup", Blockly.Latex.ORDER_UNION], 97 | '∩': ["\\cap", Blockly.Latex.ORDER_INTERSECTION], 98 | '\\': ["\\setminus", Blockly.Latex.ORDER_SET_EXCLUSION], 99 | 'SUBSET': ["\\subset", Blockly.Latex.ORDER_SUBSET], 100 | 'SUBSETEQ': ["\\subseteq", Blockly.Latex.ORDER_SUBSETEQ], 101 | '=': ["=", Blockly.Latex.ORDER_EQUAL], 102 | '≠': ["\\not=", Blockly.Latex.ORDER_NOTEQUAL], 103 | '<': ["<", Blockly.Latex.ORDER_LESS], 104 | '>': [">", Blockly.Latex.ORDER_GREATER], 105 | '≤': ["\\leq", Blockly.Latex.ORDER_LESSEQ], 106 | '≥': ["\\geq", Blockly.Latex.ORDER_GREATEREQ], 107 | 'SUPREMUM': ["\\sup", Blockly.Latex.ORDER_SUP], 108 | 'INFIMUM': ["\\inf", Blockly.Latex.ORDER_INF], 109 | 'MINIMUM': ["\\min", Blockly.Latex.ORDER_MIN], 110 | 'MAXIMUM': ["\\max", Blockly.Latex.ORDER_MAX], 111 | 'ADD': ["+", Blockly.Latex.ORDER_ADDITION], 112 | 'MINUS': ["-", Blockly.Latex.ORDER_SUBTRACTION], 113 | 'MULTIPLY': ["\\times", Blockly.Latex.ORDER_MULTIPLICATION], 114 | 'DIVIDE': ["/", Blockly.Latex.ORDER_DIVISION], 115 | 'POWER': ["^", Blockly.Latex.ORDER_POWER], 116 | '∀': ["\\forall", Blockly.Latex.ORDER_FORALL], 117 | '∃': ["\\exists", Blockly.Latex.ORDER_EXISTS], 118 | 'FORALL': ["\\forall", Blockly.Latex.ORDER_FORALL], 119 | 'EXISTS': ["\\exists", Blockly.Latex.ORDER_EXISTS], 120 | '∈': ["\\in", Blockly.Latex.ORDER_SET_MEMBERSHIP], 121 | 'BLANK': [Blockly.Latex.blank, Blockly.Latex.ORDER_ATOMIC], // For dropdowns with nothing selection 122 | }; 123 | 124 | 125 | Blockly.Latex.init = function(workspace) { 126 | }; 127 | 128 | /* Quantifiers: forall x in set P(x), forall x > n P(x) etc */ 129 | Blockly.Latex['logic_quantifier'] = function(block) { 130 | var order = Blockly.Latex.symbolToLatex[block.getFieldValue( "QUANTIFIER" )][1] 131 | var str = Blockly.Latex.symbolToLatex[block.getFieldValue( "QUANTIFIER" )][0] + " "; 132 | for( var i = 1; i <= block.varCount_; i++ ) { 133 | if( i > 1 ) str += ", "; 134 | str += (block.getFieldValue( "VAR"+i ) || Blockly.Latex.blank); 135 | } 136 | str += " " + Blockly.Latex.symbolToLatex[block.getFieldValue( "OPERATOR" )][0] + " " + 137 | (Blockly.Latex.valueToCode(block, "SCOPE", Blockly.Latex.ORDER_QUANTIFIER_SCOPE) || Blockly.Latex.blank ) + " " + 138 | // (block.getInput( "STLABEL" ).isVisible() ? "\\text{ s.t. }" : "\\; " ) + 139 | (Blockly.Latex.valueToCode( block, "PREDICATE", Blockly.Latex.ORDER_EXISTS ) || Blockly.Latex.blank ); 140 | return [str, order]; 141 | }; 142 | 143 | Blockly.Latex['logic_forall'] = function(block) { 144 | var str = "\\forall " + (block.getFieldValue( "VAR" ) || Blockly.Latex.blank) + " \\in " + 145 | (Blockly.Latex.valueToCode(block, "SCOPE", Blockly.Latex.ORDER_QUANTIFIER_SET) || Blockly.Latex.blank ) + 146 | " \\; " + (Blockly.Latex.valueToCode( block, "PREDICATE", Blockly.Latex.ORDER_EXISTS ) || Blockly.Latex.blank ); 147 | return [str, Blockly.Latex.ORDER_FORALL]; 148 | }; 149 | 150 | Blockly.Latex['logic_forall_condition'] = function(block) { 151 | var str = "\\forall " + (block.getFieldValue( "VAR" ) || Blockly.Latex.blank) + " " + block.getFieldValue( "COMPARISON_OPERATOR" ) + " " + 152 | (Blockly.Latex.valueToCode(block, "SCOPE", Blockly.Latex.ORDER_QUANTIFIER_NUM) || Blockly.Latex.blank ) + 153 | " \\; " + (Blockly.Latex.valueToCode( block, "PREDICATE", Blockly.Latex.ORDER_FORALL ) || Blockly.Latex.blank ); 154 | return [str, Blockly.Latex.ORDER_FORALL]; 155 | }; 156 | 157 | Blockly.Latex['logic_exists'] = function(block) { 158 | var str = "\\exists " + (block.getFieldValue( "VAR" ) || Blockly.Latex.blank) + " \\in " + 159 | (Blockly.Latex.valueToCode(block, "SCOPE", Blockly.Latex.ORDER_QUANTIFIER_SET) || Blockly.Latex.blank) + 160 | " \\text{ s.t. } " + (Blockly.Latex.valueToCode( block, "PREDICATE", Blockly.Latex.ORDER_EXISTS ) || Blockly.Latex.blank ); 161 | return [str, Blockly.Latex.ORDER_EXISTS]; 162 | }; 163 | 164 | Blockly.Latex['logic_exists_condition'] = function(block) { 165 | var str = "\\exists " + (block.getFieldValue( "VAR" ) || Blockly.Latex.blank) + " " + block.getFieldValue( "COMPARISON_OPERATOR" ) + " " + 166 | (Blockly.Latex.valueToCode(block, "SCOPE", Blockly.Latex.ORDER_QUANTIFIER_NUM) || Blockly.Latex.blank ) + 167 | " \\text{ s.t. } " + (Blockly.Latex.valueToCode( block, "PREDICATE", Blockly.Latex.ORDER_FORALL ) || Blockly.Latex.blank ); 168 | return [str, Blockly.Latex.ORDER_EXISTS]; 169 | }; 170 | 171 | Blockly.Latex['logic_connective'] = function(block) { 172 | /* Logic connective: AND, OR, etc */ 173 | var connective = block.getFieldValue('CONNECTIVE'); 174 | var operator = Blockly.Latex.symbolToLatex[connective][0]; 175 | var order = Blockly.Latex.symbolToLatex[connective][1]; 176 | var argument0 = Blockly.Latex.valueToCode(block, 'LEFTINPUT', order) || Blockly.Latex.blank; 177 | var argument1 = Blockly.Latex.valueToCode(block, 'RIGHTINPUT', order) || Blockly.Latex.blank; 178 | var code = argument0 + ' ' + operator + ' ' + argument1; 179 | return [code, order]; 180 | }; 181 | 182 | Blockly.Latex['logic_negation'] = function(block) { 183 | var str = "\\sim " + 184 | (Blockly.Latex.valueToCode(block, "INPUT", Blockly.Latex.ORDER_NOT) || Blockly.Latex.blank); 185 | return [str, Blockly.Latex.ORDER_NOT]; 186 | }; 187 | 188 | Blockly.Latex['logic_prop_variable'] = function(block) { 189 | return [block.getFieldValue( "VARNAME" ) || Blockly.Latex.blank, Blockly.Latex.ORDER_ATOMIC]; 190 | }; 191 | 192 | Blockly.Latex['set_number'] = function(block) { 193 | return( [block.getFieldValue("SET") || Blockly.Latex.blank, Blockly.Latex.ORDER_ATOMIC] ); 194 | }; 195 | 196 | Blockly.Latex['set_comprehension'] = function(block) { 197 | var string = "\\left\\{ "; 198 | string += block.getFieldValue( "VARNAME" ); 199 | string += " ∈ "; 200 | string += (Blockly.Latex.valueToCode(block, "DOMAIN", Blockly.Latex.ORDER_NONE) || Blockly.Latex.blank); 201 | string += " : "; 202 | string += (Blockly.Latex.valueToCode(block, "CONDITION", Blockly.Latex.ORDER_NONE) || Blockly.Latex.blank); 203 | string += "\\right\\}"; 204 | return( [string, Blockly.Latex.ORDER_ATOMIC] ); 205 | }; 206 | 207 | Blockly.Latex['set_r'] = function(block) { 208 | return ["\\mathbb{R}", Blockly.Latex.ORDER_ATOMIC]; /* Or use Unicode? */ 209 | }; 210 | 211 | Blockly.Latex['set_c'] = function(block) { 212 | return ["\\mathbb{C}", Blockly.Latex.ORDER_ATOMIC]; 213 | }; 214 | 215 | Blockly.Latex['set_q'] = function(block) { 216 | return ["\\mathbb{Q}", Blockly.Latex.ORDER_ATOMIC]; 217 | }; 218 | 219 | Blockly.Latex['set_z'] = function(block) { 220 | return ["\\mathbb{Z}", Blockly.Latex.ORDER_ATOMIC]; 221 | }; 222 | 223 | Blockly.Latex['set_n'] = function(block) { 224 | return ["\\mathbb{N}", Blockly.Latex.ORDER_ATOMIC]; 225 | }; 226 | 227 | Blockly.Latex['set_nullset'] = function(block) { 228 | return ["\\emptyset", Blockly.Latex.ORDER_ATOMIC]; 229 | }; 230 | 231 | Blockly.Latex['set_variable'] = function(block) { 232 | return [block.getFieldValue( "VARNAME" ) || Blockly.Latex.blank, Blockly.Latex.ORDER_ATOMIC]; 233 | }; 234 | 235 | Blockly.Latex['set_membership'] = function(block) { 236 | var str = (Blockly.Latex.valueToCode(block, "ELEMENT", Blockly.Latex.ORDER_SET_ELEMENT) || Blockly.Latex.blank) 237 | + " \\in " + 238 | (Blockly.Latex.valueToCode(block, "SET", Blockly.Latex.ORDER_SET_SET) || Blockly.Latex.blank); 239 | return [str, Blockly.Latex.ORDER_SET_MEMBERSHIP]; 240 | }; 241 | 242 | Blockly.Latex['set_operations'] = function(block) { 243 | /* Set operations: union, intersection, exclusion */ 244 | var connective = block.getFieldValue('OPERATION'); 245 | var operator = Blockly.Latex.symbolToLatex[connective][0]; 246 | var order = Blockly.Latex.symbolToLatex[connective][1]; 247 | var argument0 = Blockly.Latex.valueToCode(block, 'LEFTINPUT', order) || Blockly.Latex.blank; 248 | var argument1 = Blockly.Latex.valueToCode(block, 'RIGHTINPUT', order) || Blockly.Latex.blank; 249 | var code = argument0 + ' ' + operator + ' ' + argument1; 250 | return [code, order]; 251 | 252 | }; 253 | 254 | Blockly.Latex['set_complement'] = function(block) { 255 | var str = (Blockly.Latex.valueToCode(block, "SET", Blockly.Latex.ORDER_SET_COMPLEMENT) || Blockly.Latex.blank) + "^c"; 256 | return [str, Blockly.Latex.ORDER_SET_COMPLEMENT]; 257 | }; 258 | 259 | Blockly.Latex['set_comparison'] = function(block) { 260 | var connective = block.getFieldValue('OPERATOR'); 261 | var operator = Blockly.Latex.symbolToLatex[connective][0]; 262 | var order = Blockly.Latex.symbolToLatex[connective][1]; 263 | var argument0 = Blockly.Latex.valueToCode(block, 'LEFTINPUT', order) || Blockly.Latex.blank; 264 | var argument1 = Blockly.Latex.valueToCode(block, 'RIGHTINPUT', order) || Blockly.Latex.blank; 265 | var code = argument0 + ' ' + operator + ' ' + argument1; 266 | return [code, order]; 267 | }; 268 | 269 | Blockly.Latex['set_bounds'] = function(block) { 270 | /* Set bounds: sup, inf, min, max */ 271 | var connective = block.getFieldValue('OPERATOR'); 272 | var operator = Blockly.Latex.symbolToLatex[connective][0]; 273 | var order = Blockly.Latex.symbolToLatex[connective][1]; 274 | var code = operator + " " + (Blockly.Latex.valueToCode(block, 'SET', order) || Blockly.Latex.blank); 275 | return [code, order]; 276 | }; 277 | 278 | /* Could re-use function for set, propositional variable? */ 279 | Blockly.Latex['number_variable'] = function(block) { 280 | return [block.getFieldValue( "VARNAME" ) || Blockly.Latex.blank, Blockly.Latex.ORDER_ATOMIC]; 281 | }; 282 | 283 | Blockly.Latex['number_0'] = function(block) { 284 | return ["0", Blockly.Latex.ORDER_ATOMIC]; 285 | }; 286 | 287 | Blockly.Latex['number_1'] = function(block) { 288 | return ["1", Blockly.Latex.ORDER_ATOMIC]; 289 | }; 290 | 291 | Blockly.Latex['number_pi'] = function(block) { 292 | return ["\\pi", Blockly.Latex.ORDER_ATOMIC]; 293 | }; 294 | 295 | Blockly.Latex['number_e'] = function(block) { 296 | return ["e", Blockly.Latex.ORDER_ATOMIC]; 297 | }; 298 | 299 | Blockly.Latex['number_add_inv'] = function(block) { 300 | var str = "-" + (Blockly.Latex.valueToCode(block, "INPUT", Blockly.Latex.ORDER_SET_COMPLEMENT) || Blockly.Latex.blank); 301 | return [str, Blockly.Latex.ORDER_ADD_INVERSE]; 302 | }; 303 | 304 | Blockly.Latex['number_mult_inv'] = function(block) { 305 | var str = (Blockly.Latex.valueToCode(block, "INPUT", Blockly.Latex.ORDER_SET_COMPLEMENT) || Blockly.Latex.blank) + "^{-1}"; 306 | return [str, Blockly.Latex.ORDER_MULT_INVERSE]; 307 | }; 308 | 309 | Blockly.Latex['number_squared'] = function(block) { 310 | var str = (Blockly.Latex.valueToCode(block, "INPUT", Blockly.Latex.ORDER_SET_COMPLEMENT) || Blockly.Latex.blank) + "^{2}"; 311 | return [str, Blockly.Latex.ORDER_POWER]; 312 | }; 313 | 314 | Blockly.Latex['number_abs'] = function(block) { 315 | var str = "\\left|" + (Blockly.Latex.valueToCode(block, "INPUT", Blockly.Latex.ORDER_ATOMIC) || Blockly.Latex.blank) + "\\right|"; 316 | return [str, Blockly.Latex.ORDER_ATOMIC]; /* Using ORDER_ATOMIC as |...| act as brackets themselves. */ /* TODO: is this correct? Should be ORDER_NONE? */ 317 | }; 318 | 319 | Blockly.Latex['number_log_function'] = function(block) { 320 | var str = "\\log\\left(" + (Blockly.Latex.valueToCode(block, "INPUT", Blockly.Latex.ORDER_ATOMIC) || Blockly.Latex.blank) + "\\right)"; 321 | return [str, Blockly.Latex.ORDER_ATOMIC]; 322 | }; 323 | 324 | Blockly.Latex['number_trig_functions'] = function(block) { 325 | var str = "\\" + block.getFieldValue( "FUNCTION" ) 326 | + "\\left(" + (Blockly.Latex.valueToCode(block, "INPUT", Blockly.Latex.ORDER_ATOMIC) || Blockly.Latex.blank) + "\\right)"; 327 | return [str, Blockly.Latex.ORDER_ATOMIC]; 328 | }; 329 | 330 | Blockly.Latex['function_variable'] = function(block) { 331 | var str = block.getFieldValue( "FUNCNAME" ) + "\\left( " + 332 | (Blockly.Latex.valueToCode(block, "INPUT", Blockly.Latex.ORDER_ATOMIC) || Blockly.Latex.blank) + " \\right)"; 333 | return [str, Blockly.Latex.ORDER_ATOMIC]; 334 | } 335 | 336 | Blockly.Latex['function_fn'] = function(block) { 337 | return ["f(n)", Blockly.Latex.ORDER_ATOMIC]; 338 | } 339 | 340 | Blockly.Latex['number_comparison'] = function(block) { 341 | /* Number comparisons: equal, not equal, less than, etc */ 342 | var connective = block.getFieldValue('COMPARISON_OPERATOR'); 343 | var operator = Blockly.Latex.symbolToLatex[connective][0]; 344 | var order = Blockly.Latex.symbolToLatex[connective][1]; 345 | var argument0 = Blockly.Latex.valueToCode(block, 'LEFTINPUT', order) || Blockly.Latex.blank; 346 | var argument1 = Blockly.Latex.valueToCode(block, 'RIGHTINPUT', order) || Blockly.Latex.blank; 347 | var code = argument0 + ' ' + operator + ' ' + argument1; 348 | return [code, order]; 349 | }; 350 | 351 | Blockly.Latex['number_comparison_3'] = function(block) { 352 | /* Number comparisons: equal, not equal, less than, etc */ 353 | var comparison0 = block.getFieldValue('COMPARISON_OPERATOR0'); 354 | var comparison1 = block.getFieldValue('COMPARISON_OPERATOR1'); 355 | var compsymbol0 = Blockly.Latex.symbolToLatex[comparison0][0]; 356 | var compsymbol1 = Blockly.Latex.symbolToLatex[comparison1][0]; 357 | var order = Blockly.Latex.symbolToLatex[comparison0][1]; 358 | var argument0 = Blockly.Latex.valueToCode(block, 'LEFTINPUT', order) || Blockly.Latex.blank; 359 | var argument1 = Blockly.Latex.valueToCode(block, 'MIDDLEINPUT', order) || Blockly.Latex.blank; 360 | var argument2 = Blockly.Latex.valueToCode(block, 'RIGHTINPUT', order) || Blockly.Latex.blank; 361 | var code = argument0 + ' ' + compsymbol0 + ' ' + argument1 + ' ' + compsymbol1 + ' ' + argument2; 362 | return [code, order]; 363 | }; 364 | 365 | 366 | /* Generator for Blockly standard number block. Replace with our own block? */ 367 | Blockly.Latex['math_number'] = function(block) { 368 | return [block.getFieldValue( "NUM" ), Blockly.Latex.ORDER_ATOMIC]; 369 | }; 370 | 371 | /* Generator for Blockly standard arithmetic block. Replace with our own block? */ 372 | Blockly.Latex['math_arithmetic'] = function(block) { 373 | /* Number comparisons: equal, not equal, less than, etc */ 374 | var op = block.getFieldValue('OP'); 375 | var operator = Blockly.Latex.symbolToLatex[op][0]; 376 | var order = Blockly.Latex.symbolToLatex[op][1]; 377 | var argument0 = Blockly.Latex.valueToCode(block, 'A', order) || Blockly.Latex.blank; 378 | var argument1 = Blockly.Latex.valueToCode(block, 'B', order) || Blockly.Latex.blank; /* TODO: need to use different order for exponent */ 379 | var code; 380 | if( op == "POWER" ) { 381 | code = argument0 + "^{" + argument1 + "}"; /* Need braces around argument */ 382 | } else { 383 | code = argument0 + ' ' + operator + ' ' + argument1; 384 | } 385 | return [code, order]; 386 | }; 387 | 388 | 389 | 390 | /* Blocks for translation exercise */ 391 | Blockly.Latex['logic_quantifier_set_restricted_1'] = Blockly.Latex['logic_quantifier_set_restricted_2'] = function(block) { 392 | // console.log( block.getFieldValue("QUANTIFIER") ); 393 | var order = Blockly.Latex.symbolToLatex[block.getFieldValue( "QUANTIFIER" )][1] 394 | var varname = block.getFieldValue( "VAR" ); 395 | if( !varname || varname == "BLANK" ) varname = Blockly.Latex.blank; 396 | var str = Blockly.Latex.symbolToLatex[block.getFieldValue( "QUANTIFIER" )][0] + " " + varname + 397 | " ∈ ℤ " + 398 | // (block.getFieldValue( "QUANTIFIER" ) == "∃" ? "\\text{ s.t. }" : "\\; " ) + 399 | (Blockly.Latex.valueToCode( block, "PREDICATE", Blockly.Latex.ORDER_EXISTS ) || Blockly.Latex.blank ); 400 | return [str, order]; 401 | }; 402 | 403 | Blockly.Latex['predicate_multiple_of_3'] = Blockly.Latex['predicate_multiple_of_6'] = function(block) { 404 | var str = (Blockly.Latex.valueToCode( block, "NUM", Blockly.Latex.ORDER_ATOMIC ) || Blockly.Latex.blank ) 405 | + " \\text{ is a multiple of " + (block.type == "predicate_multiple_of_3" ? "3" : "6") + "}"; 406 | return [str, Blockly.Latex.ORDER_ATOMIC]; 407 | }; 408 | 409 | Blockly.Latex['number_multiplication'] = function(block) { 410 | var argument0 = Blockly.Latex.valueToCode(block, 'A', Blockly.Latex.ORDER_MULTIPLICATION) || Blockly.Latex.blank; 411 | var argument1 = Blockly.Latex.valueToCode(block, 'B', Blockly.Latex.ORDER_MULTIPLICATION) || Blockly.Latex.blank; 412 | var code = argument0 + " \\times " + argument1; 413 | return [code, Blockly.Latex.ORDER_MULTIPLICATION]; 414 | }; 415 | 416 | Blockly.Latex['number_variable_restricted'] = function(block) { 417 | var varname = block.getFieldValue("VAR"); 418 | if( varname == "BLANK" ) varname = Blockly.Latex.blank; 419 | return [varname, Blockly.Latex.ORDER_ATOMIC]; 420 | }; 421 | 422 | Blockly.Latex['set_dropdown'] = function(block) { 423 | var setname = block.getFieldValue("SET"); 424 | if( setname == " " ) varname = Blockly.Latex.blank; 425 | return [setname, Blockly.Latex.ORDER_ATOMIC]; 426 | }; 427 | -------------------------------------------------------------------------------- /js/math-blocks-limit-exercise.js: -------------------------------------------------------------------------------- 1 | /*** Blockly block definitions for mathematical expression construction ***/ 2 | /* By Anthony Morphett, awmorp@gmail.com */ 3 | 4 | /* 5 | Blocks for online activity 1 6 | 7 | Assumes that math-blocks.js is loaded. 8 | */ 9 | 10 | 11 | var varlist = [[" ", "BLANK"],["ϵ", "EPSILON"], ["δ", "DELTA"], ["L","L"], ["M", "M"], ["n", "n"], ["x", "x"]]; 12 | var valuelist = [[" ", "BLANK"],["0","0"],["ϵ", "EPSILON"], ["δ", "DELTA"], ["L","L"], ["M", "M"], ["n", "n"], ["x", "x"]]; 13 | var setlist = [[" ", "BLANK"], ["ℝ", "REAL"], ["ℚ", "RATIONAL"], ["ℤ", "INTEGERS"], ["ℕ", "NATURALS"]]; 14 | var quantifierlist = [[" ", "BLANK"],["∀", "FORALL"],["∃","EXISTS"]]; 15 | 16 | /****** Quantifiers ******/ 17 | Blockly.Blocks['logic_quantifier_set_restricted'] = { 18 | init: function() { 19 | this.appendValueInput("PREDICATE") 20 | .appendField(new Blockly.FieldDropdown(quantifierlist), "QUANTIFIER") 21 | .appendField(new Blockly.FieldDropdown(varlist), "VAR") 22 | .appendField("∈") 23 | .appendField(new Blockly.FieldDropdown(setlist), "SET") 24 | .setCheck("Boolean"); 25 | this.setInputsInline(true); 26 | this.setOutput(true, "Boolean"); 27 | this.setColour(booleanQuantifierHue); 28 | this.setTooltip('Quantifier'); 29 | } 30 | }; 31 | 32 | Blockly.Blocks['logic_quantifier_condition_restricted'] = { 33 | init: function() { 34 | this.appendDummyInput() 35 | .appendField(new Blockly.FieldDropdown(quantifierlist), "QUANTIFIER") 36 | .appendField(new Blockly.FieldDropdown(varlist), "VAR") 37 | .appendField(new Blockly.FieldDropdown([[">", ">"], ["≥", "≥"], ["<", "<"], ["≤", "v"], ["≠", "≠"]]), "COMPARISON_OPERATOR") 38 | .appendField(new Blockly.FieldDropdown(valuelist), "BOUND"); 39 | this.appendValueInput("PREDICATE") 40 | .setCheck("Boolean"); 41 | this.setInputsInline(true); 42 | this.setOutput(true, "Boolean"); 43 | this.setColour(booleanQuantifierHue); 44 | this.setTooltip('Quantifier with condition'); 45 | } 46 | }; 47 | 48 | Blockly.Blocks['function_fn'] = { 49 | init: function() { 50 | this.appendDummyInput() 51 | .appendField("f(n)"); 52 | this.setInputsInline(true); 53 | this.setOutput(true, "Number"); 54 | this.setColour(numberHue); 55 | this.setTooltip(''); 56 | } 57 | }; 58 | 59 | Blockly.Blocks['number_variable_restricted'] = { 60 | init: function() { 61 | this.appendDummyInput() 62 | .appendField(new Blockly.FieldDropdown(valuelist), "VAR"); 63 | this.setInputsInline(true); 64 | this.setOutput(true, "Number"); 65 | this.setColour(numberHue); 66 | this.setTooltip(''); 67 | } 68 | }; 69 | 70 | Blockly.Blocks['set_dropdown'] = { 71 | init: function() { 72 | this.appendDummyInput() 73 | .appendField(new Blockly.FieldDropdown(setlist), "SET"); 74 | this.setInputsInline(true); 75 | this.setOutput(true, "Set"); 76 | this.setColour(setHue); 77 | this.setTooltip(''); 78 | } 79 | }; 80 | 81 | -------------------------------------------------------------------------------- /js/math-blocks-logic-exercise.js: -------------------------------------------------------------------------------- 1 | /* Propositional variables with negation */ 2 | Blockly.Blocks['logic_p_notp'] = { 3 | init: function() { 4 | this.appendDummyInput() 5 | .appendField( new Blockly.FieldDropdown([["P", "P"], ["~P", "NOTP"]]), "VAR"); 6 | this.setInputsInline(true); 7 | this.setOutput(true, "Boolean"); 8 | this.setColour(booleanHue); 9 | this.setTooltip('Propositional variable P'); 10 | this.setHelpUrl(); 11 | } 12 | }; 13 | 14 | Blockly.Blocks['logic_q_notq'] = { 15 | init: function() { 16 | this.appendDummyInput() 17 | .appendField( new Blockly.FieldDropdown([["Q", "Q"], ["~Q", "NOTQ"]]), "VAR"); 18 | this.setInputsInline(true); 19 | this.setOutput(true, "Boolean"); 20 | this.setColour(booleanHue); 21 | this.setTooltip('Propositional variable Q'); 22 | this.setHelpUrl(); 23 | } 24 | }; 25 | 26 | Blockly.Blocks['logic_r_notr'] = { 27 | init: function() { 28 | this.appendDummyInput() 29 | .appendField( new Blockly.FieldDropdown([["R", "R"], ["~R", "NOTR"]]), "VAR"); 30 | this.setInputsInline(true); 31 | this.setOutput(true, "Boolean"); 32 | this.setColour(booleanHue); 33 | this.setTooltip('Propositional variable R'); 34 | this.setHelpUrl(); 35 | } 36 | }; 37 | 38 | /* Implication */ 39 | Blockly.Blocks['logic_implies'] = { 40 | init: function() { 41 | this.appendValueInput("LEFTINPUT") 42 | .setCheck("Boolean"); 43 | this.appendValueInput("RIGHTINPUT") 44 | .setCheck("Boolean") 45 | .appendField( "⇒" ); 46 | this.setInputsInline(true); 47 | this.setOutput(true, "Boolean"); 48 | this.setColour(booleanHue); 49 | this.setTooltip('Logical implication'); 50 | this.setHelpUrl(); 51 | } 52 | }; 53 | 54 | /* Square of number */ 55 | Blockly.Blocks['number_square'] = { 56 | init: function() { 57 | this.appendValueInput("NUM") 58 | .setCheck("Number"); 59 | this.appendDummyInput() 60 | .appendField( "²" ); 61 | this.setInputsInline(true); 62 | this.setOutput(true, "Number"); 63 | this.setColour(numberHue); 64 | this.setTooltip('Square'); 65 | this.setHelpUrl(); 66 | } 67 | }; 68 | 69 | Blockly.Blocks['number_cube'] = { 70 | init: function() { 71 | this.appendValueInput("NUM") 72 | .setCheck("Number"); 73 | this.appendDummyInput() 74 | .appendField( "³" ); 75 | this.setInputsInline(true); 76 | this.setOutput(true, "Number"); 77 | this.setColour(numberHue); 78 | this.setTooltip('Cube'); 79 | this.setHelpUrl(); 80 | } 81 | }; 82 | 83 | Blockly.Blocks['function_fn'] = { 84 | init: function() { 85 | this.appendDummyInput() 86 | .appendField( "f(" ) 87 | .appendField(new Blockly.FieldMathVariable("n", "Number"), "VARNAME") 88 | .appendField(")"); 89 | this.setInputsInline(true); 90 | this.setOutput(true, "Number"); 91 | this.setColour(numberHue); 92 | this.setTooltip('f(n)'); 93 | this.setHelpUrl(); 94 | } 95 | }; 96 | 97 | Blockly.Blocks['function_fn+1'] = { 98 | init: function() { 99 | this.appendDummyInput() 100 | .appendField( "f(" ) 101 | .appendField(new Blockly.FieldMathVariable("n", "Number"), "VARNAME") 102 | .appendField("+1)"); 103 | this.setInputsInline(true); 104 | this.setOutput(true, "Number"); 105 | this.setColour(numberHue); 106 | this.setTooltip('f(n+1)'); 107 | this.setHelpUrl(); 108 | } 109 | }; 110 | 111 | Blockly.Blocks['quantifier_abstract'] = { 112 | init: function() { 113 | var varField = new Blockly.FieldMathVariable("x", "Abstract", null, true); 114 | /* Override CSS so that this field is displayed in number colour rather than boolean colour */ 115 | varField.addCSSClass( "blocklyQuantifierVarField" ); 116 | this.appendDummyInput("VARINPUT") 117 | .appendField(new Blockly.FieldDropdown([["∀", "∀"], ["∃", "∃"]]), "QUANTIFIER") 118 | .appendField(varField, "VAR") 119 | .appendField(" "); 120 | // this.appendDummyInput("STLABEL") 121 | // .appendField("s.t.", "ST"); 122 | this.appendValueInput("PREDICATE") 123 | .setCheck("Boolean"); 124 | this.setInputsInline(true); 125 | this.setOutput(true, "Boolean"); 126 | this.setColourByType(); 127 | this.setTooltip('Quantifier'); 128 | this.setHelpUrl(); 129 | this.isQuantifier = true; 130 | }, 131 | getVars: function() { 132 | var varlist = [[this.getFieldValue( 'VAR' ), "Abstract"]]; 133 | return varlist; 134 | } 135 | }; 136 | 137 | Blockly.Blocks['abstract_variable'] = { 138 | init: function() { 139 | this.appendDummyInput() 140 | .appendField(new Blockly.FieldMathVariable("x", "Abstract"), "VARNAME"); 141 | this.setInputsInline(true); 142 | this.setOutput(true, "Abstract"); 143 | this.setColourByType(); 144 | this.setTooltip('A variable representing an unspecified object'); 145 | this.setHelpUrl(); 146 | }, 147 | getVars: function() { 148 | return [[this.getFieldValue('VARNAME'), "Abstract"]]; 149 | } 150 | }; 151 | 152 | 153 | Blockly.Blocks['predicate_glitters'] = { 154 | init: function() { 155 | this.appendValueInput( "VALUE" ) 156 | .setCheck( "Abstract" ); 157 | this.appendDummyInput() 158 | .appendField( "glitters" ); 159 | this.setInputsInline(true); 160 | this.setOutput(true,"Boolean"); 161 | this.setColourByType(); 162 | this.setTooltip( "'glitters' predicate" ); 163 | } 164 | }; 165 | 166 | Blockly.Blocks['predicate_gold'] = { 167 | init: function() { 168 | this.appendValueInput( "VALUE" ) 169 | .setCheck( "Abstract" ); 170 | this.appendDummyInput() 171 | .appendField( "is gold" ); 172 | this.setInputsInline(true); 173 | this.setOutput(true,"Boolean"); 174 | this.setColourByType(); 175 | this.setTooltip( "'gold' predicate" ); 176 | } 177 | }; -------------------------------------------------------------------------------- /js/math-blocks-old.js: -------------------------------------------------------------------------------- 1 | /* Variant math blocks not currently used. Removed from main math-blocks.js to reduce code size. */ 2 | 3 | Blockly.Blocks['logic_forall_condition'] = { 4 | init: function() { 5 | this.appendValueInput("SCOPE") 6 | .setCheck("Number") 7 | .appendField("?") 8 | .appendField(new Blockly.FieldMathVariable("x", "Number"), "VAR") 9 | .appendField(new Blockly.FieldDropdown([[">", ">"], ["=", "="], ["<", "<"], ["=", "="], ["?", "?"]]), "COMPARISON_OPERATOR") 10 | .parentVarsInScope_ = false; 11 | /* Note: using the mathematical symbol as the blockly 'language neutral' identifier (as maths is a universal language :) */ 12 | this.appendValueInput("PREDICATE") 13 | .setCheck("Boolean"); 14 | this.setInputsInline(true); 15 | this.setOutput(true, "Boolean"); 16 | this.setColour(booleanQuantifierHue); 17 | this.setTooltip('Universal (\'for all\') quantifier with condition'); 18 | this.setHelpUrl(); 19 | this.isQuantifier = true; 20 | }, 21 | getVars: function() { 22 | return [[this.getFieldValue('VAR'),"Number"]]; 23 | } 24 | }; 25 | 26 | 27 | /* https://blockly-demo.appspot.com/static/demos/blockfactory/index.html#hkxkys */ 28 | Blockly.Blocks['logic_exists'] = { 29 | init: function() { 30 | this.appendValueInput("SCOPE") 31 | .setCheck("Set") 32 | .appendField("?") 33 | .appendField(new Blockly.FieldMathVariable("x", "Number"), "VAR") 34 | .appendField("?") 35 | .parentVarsInScope_ = false; 36 | this.appendValueInput("PREDICATE") 37 | // .appendField(" s.t.") 38 | .setCheck("Boolean"); 39 | this.setInputsInline(true); 40 | this.setOutput(true, "Boolean"); 41 | this.setColour(booleanQuantifierHue); 42 | this.setTooltip('Existential (\'exists\') quantifier'); 43 | this.setHelpUrl(); 44 | this.isQuantifier = true; 45 | }, 46 | getVars: function() { 47 | return [[this.getFieldValue('VAR'),"Number"]]; 48 | } 49 | }; 50 | 51 | /* https://blockly-demo.appspot.com/static/demos/blockfactory/index.html#yt9arv */ 52 | Blockly.Blocks['logic_exists_condition'] = { 53 | init: function() { 54 | this.appendValueInput("SCOPE") 55 | .setCheck("Number") 56 | .appendField("?") 57 | .appendField(new Blockly.FieldMathVariable("x", "Number"), "VAR") 58 | .appendField(new Blockly.FieldDropdown([[">", ">"], ["=", "="], ["<", "<"], ["=", "v"], ["?", "?"]]), "COMPARISON_OPERATOR") 59 | .parentVarsInScope_ = false; 60 | this.appendValueInput("PREDICATE") 61 | // .appendField(" s.t.") 62 | .setCheck("Boolean"); 63 | this.setInputsInline(true); 64 | this.setOutput(true, "Boolean"); 65 | this.setColour(booleanQuantifierHue); 66 | this.setTooltip(''); 67 | this.setHelpUrl(); 68 | this.isQuantifier = true; 69 | }, 70 | getVars: function() { 71 | return [[this.getFieldValue('VAR'),"Number"]]; 72 | }, 73 | }; 74 | -------------------------------------------------------------------------------- /js/math-blocks-translation-exercise.js: -------------------------------------------------------------------------------- 1 | /*** Blockly block definitions for mathematical expression construction ***/ 2 | /* By Anthony Morphett, awmorp@gmail.com */ 3 | 4 | /* 5 | Blocks for English -> Mathematics translation activity 6 | 7 | Assumes that math-blocks.js is loaded. 8 | */ 9 | 10 | 11 | var varlist1 = [[" ", "BLANK"],["a", "a"]]; 12 | var varlist2 = [[" ", "BLANK"],["b", "b"],["c","c"]]; 13 | var valuelist = [[" ", "BLANK"],["a", "a"], ["b", "b"],["c","c"]];; 14 | var setlist = [[" ", "BLANK"], ["ℝ", "REAL"], ["ℚ", "RATIONAL"], ["ℤ", "INTEGERS"], ["ℕ", "NATURALS"]]; 15 | var quantifierlist = [[" ", "BLANK"],["∀", "FORALL"],["∃","EXISTS"]]; 16 | 17 | /****** Quantifiers ******/ 18 | Blockly.Blocks['logic_quantifier_set_restricted_1'] = { 19 | init: function() { 20 | this.appendValueInput("PREDICATE") 21 | .appendField(new Blockly.FieldDropdown(quantifierlist), "QUANTIFIER") 22 | .appendField(new Blockly.FieldDropdown(varlist1), "VAR") 23 | .appendField("∈ ℤ") 24 | .setCheck("Boolean"); 25 | this.setInputsInline(true); 26 | this.setOutput(true, "Boolean"); 27 | this.setColour(booleanQuantifierHue); 28 | this.setTooltip('Quantifier'); 29 | } 30 | }; 31 | 32 | Blockly.Blocks['logic_quantifier_set_restricted_2'] = { 33 | init: function() { 34 | this.appendValueInput("PREDICATE") 35 | .appendField(new Blockly.FieldDropdown(quantifierlist), "QUANTIFIER") 36 | .appendField(new Blockly.FieldDropdown(varlist2), "VAR") 37 | .appendField("∈ ℤ") 38 | .setCheck("Boolean"); 39 | this.setInputsInline(true); 40 | this.setOutput(true, "Boolean"); 41 | this.setColour(booleanQuantifierHue); 42 | this.setTooltip('Quantifier'); 43 | } 44 | }; 45 | 46 | Blockly.Blocks['predicate_multiple_of_3'] = { 47 | init: function() { 48 | this.appendValueInput( "NUM" ) 49 | .setCheck("Number"); 50 | this.appendDummyInput() 51 | .appendField("is a multiple of 3"); 52 | this.setInputsInline(true); 53 | this.setOutput(true, "Boolean"); 54 | this.setColour(booleanHue); 55 | this.setTooltip(''); 56 | } 57 | }; 58 | 59 | Blockly.Blocks['predicate_multiple_of_6'] = { 60 | init: function() { 61 | this.appendValueInput( "NUM" ) 62 | .setCheck("Number"); 63 | this.appendDummyInput() 64 | .appendField("is a multiple of 6"); 65 | this.setInputsInline(true); 66 | this.setOutput(true, "Boolean"); 67 | this.setColour(booleanHue); 68 | this.setTooltip(''); 69 | } 70 | }; 71 | 72 | Blockly.Blocks['number_multiplication'] = { 73 | init: function() { 74 | this.appendValueInput( "A" ) 75 | .setCheck("Number"); 76 | this.appendValueInput( "B" ) 77 | .setCheck("Number") 78 | .appendField("×"); 79 | this.setInputsInline(true); 80 | this.setOutput(true, "Number"); 81 | this.setColourByType(); 82 | this.setTooltip(''); 83 | } 84 | }; 85 | 86 | Blockly.Blocks['number_variable_restricted'] = { 87 | init: function() { 88 | this.appendDummyInput() 89 | .appendField(new Blockly.FieldDropdown(valuelist), "VAR"); 90 | this.setInputsInline(true); 91 | this.setOutput(true, "Number"); 92 | this.setColour(numberHue); 93 | this.setTooltip(''); 94 | } 95 | }; 96 | 97 | Blockly.Blocks['set_dropdown'] = { 98 | init: function() { 99 | this.appendDummyInput() 100 | .appendField(new Blockly.FieldDropdown(setlist), "SET"); 101 | this.setInputsInline(true); 102 | this.setOutput(true, "Set"); 103 | this.setColour(setHue); 104 | this.setTooltip(''); 105 | } 106 | }; 107 | 108 | -------------------------------------------------------------------------------- /js/math-blocks-trig.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/js/math-blocks-trig.js -------------------------------------------------------------------------------- /js/math-blocks-vectors.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awmorp/math-blockly/72556fa5d0edb525edf5454f41fb1ef52e9fbe01/js/math-blocks-vectors.js -------------------------------------------------------------------------------- /js/shaped-connectors.js: -------------------------------------------------------------------------------- 1 | /** Adds angled, rounded and brace-shaped connectors, and chooses connector shape based on type **/ 2 | /* Author: Anthony Morphett, awmorp@gmail.com */ 3 | 4 | alert( "Warning: shaped-connectors.js was loaded. This file is obsolete and shouldn't be used." ); 5 | 6 | /** 7 | * SVG path for drawing an angled tab from top to bottom. 8 | * @const 9 | */ 10 | Blockly.BlockSvg.TAB_PATH_DOWN_ANGLE = 'v 5 l -' + Blockly.BlockSvg.TAB_WIDTH + ',' + 7.5 + 11 | ' l ' + Blockly.BlockSvg.TAB_WIDTH + ',' + 7.5; 12 | 13 | /** 14 | * SVG path for drawing a round-shaped tab from top to bottom. 15 | * @const 16 | */ 17 | Blockly.BlockSvg.TAB_PATH_DOWN_ROUND = 'v 5 a ' + Blockly.BlockSvg.TAB_WIDTH + ' ' + 7.5 + 18 | ' 0 0 0 0 ' + 1.75*Blockly.BlockSvg.TAB_WIDTH; 19 | 20 | /** 21 | * SVG path for drawing a brace-shaped tab from top to bottom. 22 | * @const 23 | */ 24 | /*Blockly.BlockSvg.TAB_PATH_DOWN_BRACE = 'v 2.5 c -5,0 -2,9 -10,10 c 8,1 5,10 10,10'; */ 25 | Blockly.BlockSvg.TAB_PATH_DOWN_BRACE = 'v 5 c -' + (0.5*Blockly.BlockSvg.TAB_WIDTH) + ',0' + 26 | ' -' + (0.2*Blockly.BlockSvg.TAB_WIDTH) + ',' + (0.9*7.5) + 27 | ' -' + (0.75*Blockly.BlockSvg.TAB_WIDTH) + ',' + 7.5 + 28 | ' c ' + (0.55*Blockly.BlockSvg.TAB_WIDTH) + ',' + (0.1*7.5) + 29 | ' ' + (0.5*0.75*Blockly.BlockSvg.TAB_WIDTH) + ',' + (7.5) + /* Not sure this is quite symmetric? */ 30 | ' ' + (0.75*Blockly.BlockSvg.TAB_WIDTH) + ',' + (7.5); 31 | 32 | /** 33 | * SVG path for drawing an arrow-shaped tab from top to bottom. 34 | * @const 35 | */ 36 | Blockly.BlockSvg.TAB_PATH_DOWN_ARROW = 'v 5 h -3 v -3 l -3 7 l 3 7 v -3 h 3 v 5'; 37 | 38 | /* Define new getConnector function. 39 | Determine shape based on connector type. 40 | */ 41 | Blockly.BlockSvg.getConnectorPath = function(connector) { 42 | alert( "Warning: obsolete shaped-connector.js getConnectorPath called!" ); 43 | var typeArray = connector.check_; 44 | if( typeArray && typeArray.length == 1 ) { 45 | switch( typeArray[0] ) { 46 | case "Number": return( Blockly.BlockSvg.TAB_PATH_DOWN_ROUND ); 47 | case "Boolean": return( Blockly.BlockSvg.TAB_PATH_DOWN_ANGLE ); 48 | case "Set": return( Blockly.BlockSvg.TAB_PATH_DOWN_BRACE ); 49 | case "Vector": return( Blockly.BlockSvg.TAB_PATH_DOWN_ARROW ); 50 | default: return( Blockly.BlockSvg.TAB_PATH_DOWN_PUZZLE ); 51 | } 52 | } else { 53 | return( Blockly.BlockSvg.TAB_PATH_DOWN_PUZZLE ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /js/utils.js: -------------------------------------------------------------------------------- 1 | /* Set content of element 'id' to 'text' */ 2 | function setContentById( id, text ) 3 | { 4 | var e = document.getElementById( id ); 5 | if( e ) { 6 | e.innerHTML = ""; 7 | e.appendChild( document.createTextNode( text ) ); 8 | } 9 | } 10 | 11 | function dumpXML() 12 | { 13 | var xml = Blockly.Xml.workspaceToDom(workspace); 14 | var xml_text = Blockly.Xml.domToPrettyText(xml); 15 | console.log( xml ); 16 | console.log( xml_text ); 17 | } 18 | 19 | function debug() { 20 | var nodes = goog.dom.getElementsByClass( "debug" ); 21 | Array.prototype.forEach.call(nodes, function(x) {x.style.display = "block";} ); // nodes is a NodeList rather than Array 22 | } 23 | 24 | 25 | /* Load local MathJax if viewing page locally, otherwise use remote MathJax */ 26 | function loadMathjax() { 27 | var scriptNode = document.createElement( "script" ); 28 | scriptNode.type = "text/javascript"; 29 | if( window.location.protocol == "file:" ) { 30 | scriptNode.src = "../MathJax/unpacked/MathJax.js"; 31 | } else { 32 | scriptNode.src = "https://cdn.mathjax.org/mathjax/latest/MathJax.js"; 33 | } 34 | document.getElementsByTagName('head')[0].appendChild( scriptNode ); 35 | } 36 | 37 | /* Render block expression on workspace change */ 38 | var gPreviousLatex; 39 | function displayLatex(workspace) { 40 | var code = Blockly.Latex.workspaceToCode( workspace ); 41 | if( code != gPreviousLatex ) { 42 | gPreviousLatex = code; 43 | renderLatex( "\\( " + code + " \\)" ); 44 | } 45 | } 46 | 47 | function renderLatex( code ) { 48 | /* Display latex source, if desired */ 49 | var latexNode = document.getElementById( "latex-output" ); 50 | if( latexNode ) setContentById( 'latex-output', code ); 51 | /* Render source into new div asynchronously */ 52 | var newNode = document.createElement( "div" ); 53 | newNode.innerHTML = code; 54 | var callback = function() { 55 | var container = document.getElementById( "mathjax-output" ); 56 | /* Clear old Mathjax */ 57 | while( container.firstChild ) container.removeChild(container.firstChild); 58 | /* Add newly rendered node */ 59 | container.appendChild( newNode ); 60 | }; 61 | MathJax.Hub.Queue(["Typeset", MathJax.Hub, newNode, callback]); 62 | }; 63 | 64 | function setupAutoLatex( workspace ) 65 | { 66 | workspace.addChangeListener( function() { displayLatex( workspace ); } ); 67 | if( window.MathJax ) { 68 | /* Do initial render, if MathJax loaded */ 69 | displayLatex( workspace ); 70 | } 71 | } 72 | 73 | 74 | /* We must wait until MathJax has loaded itself (at least to the point that MathJax.Hub.queue exists) before loading Blockly. 75 | * This function will be called by a MathJax signal handler once MathJax is loaded. 76 | */ 77 | var workspace; 78 | function loadBlockly() { 79 | if( window.MathJax ) { 80 | console.warn( "Warning: loadBlockly() must be called before MathJax is loaded!" ); 81 | return; 82 | } 83 | 84 | var injectBlockly = function() { 85 | workspace = Blockly.inject('blocklyDiv', 86 | { 87 | media: '../blockly/media/', 88 | disable: false, 89 | toggleInline: false, 90 | toolbox: document.getElementById('toolbox'), 91 | trashcan: true, 92 | zoom: {controls: true, wheel: true, startScale: 1} 93 | }); 94 | // Blockly.Xml.domToWorkspace( workspace, document.getElementById("workspace-initial") ); 95 | } 96 | 97 | /* Set up a hook to load Blockly */ 98 | window.MathJax = { AuthorInit: function() { 99 | MathJax.Hub.Register.StartupHook( "End", injectBlockly ); 100 | }}; 101 | } 102 | 103 | 104 | if( window.location.search.search("debug") >= 0 ) { 105 | if( window.addEventListener ){ 106 | window.addEventListener( 'load', debug ) 107 | } else { 108 | window.attachEvent( 'onload', debug ) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /js/video-utils.js: -------------------------------------------------------------------------------- 1 | var videoState = 0; 2 | var handle; 3 | 4 | function playPause() { 5 | if( videoState == 2 ) { // Fading out 6 | if( handle ) { 7 | window.clearTimeout( handle ); 8 | handle = null; 9 | } else { 10 | fadeoutStep(); 11 | } 12 | } else if( videoState == 3 ) { 13 | if( handle ) { 14 | window.clearTimeout( handle ); 15 | handle = null; 16 | } else { 17 | fadeinStep(); 18 | } 19 | } else { 20 | if( videoElement.paused ) { 21 | videoElement.play(); 22 | } else { 23 | videoElement.pause(); 24 | } 25 | } 26 | } 27 | 28 | function nextVideo() { 29 | // console.log( "nextVideo" ); 30 | videoIndex++; 31 | if( videoIndex >= videoSources.length ) videoIndex = 0; 32 | var pauseState = videoElement.paused; 33 | videoElement.src = videoSources[videoIndex]; 34 | videoState = 3; // Fading in 35 | handle = window.setTimeout( fadeinStep, 30 ); 36 | // if( pauseState ) { 37 | // videoElement.pause(); 38 | // } else { 39 | // videoElement.play(); 40 | // } 41 | } 42 | 43 | function videoEnded() { 44 | // console.log( "videoEnded" ); 45 | videoState = 2; // Fading out 46 | handle = window.setTimeout( fadeoutStep, 500 ); 47 | } 48 | 49 | function fadeinStep() { 50 | // console.log( "fadein" ); 51 | if( Number( videoElement.style.opacity ) < 0.95 ) { 52 | videoElement.style.opacity = Number( videoElement.style.opacity ) + 0.05; 53 | handle = window.setTimeout( fadeinStep, 30 ); 54 | } else { 55 | videoElement.style.opacity = 1; 56 | videoState = 1; // Playing 57 | videoElement.play(); 58 | } 59 | } 60 | 61 | function fadeoutStep() { 62 | // console.log( "fadeout" ); 63 | if( Number( videoElement.style.opacity ) > 0.05 ) { 64 | videoElement.style.opacity = Number( videoElement.style.opacity ) - 0.05; 65 | handle = window.setTimeout( fadeoutStep, 30 ); 66 | } else { 67 | videoElement.style.opacity = 0; 68 | nextVideo(); 69 | } 70 | } 71 | --------------------------------------------------------------------------------