├── README.md ├── boards.js ├── crossword.css ├── crossword.js ├── hex.png ├── index.html └── jquery.1.6.1.min.js /README.md: -------------------------------------------------------------------------------- 1 | regex-crossword 2 | =============== 3 | 4 | Implementation of a RegExp crossword. 5 | 6 | View the current version at http://jimbly.github.io/regex-crossword/ 7 | 8 | Original puzzle written by Dan Gulotta for the 2013 MIT Mystery Hunt: https://web.mit.edu/puzzle/www/2013/coinheist.com/rubik/a_regular_crossword/ popularized through http://www.i-programmer.info/news/144-graphics-and-games/5450-can-you-do-the-regular-expression-crossword.html 9 | 10 | TODO List 11 | ========= 12 | * Auto-check against known solution 13 | * Allow choosing between multiple crosswords puzzles 14 | * Allow users to save their own puzzles and load other people's 15 | * Track time spent solving puzzle 16 | * Rotate buttons 17 | * Undo/redo 18 | 19 | -------------------------------------------------------------------------------- /boards.js: -------------------------------------------------------------------------------- 1 | all_boards = { 2 | 'original': { 3 | size: 13, 4 | author: 'Dan Gulotta', 5 | x: [ 6 | '[^X]*(DN|TE|NI)' 7 | ,'[RONMHC]*I[RONMHC]*' 8 | ,'.*(..)\\1P+' 9 | ,'(E|RC|NM)*' 10 | ,'([^MC]|MM|CC)*' 11 | ,'R?(CR)*MC[MA]*' 12 | ,'.*' 13 | ,'.*CDD.*RRP.*' 14 | ,'(XHH|[^XH])*' 15 | ,'([^CME]|ME)*' 16 | ,'.*RXO.*' 17 | ,'.*LR.*RL.*' 18 | ,'.*EU.*ES.*' 19 | ], 20 | y: [ 21 | '.*H.*H.*' 22 | ,'(DI|NS|TH|OM)*' 23 | ,'F.*[AO].*[AO].*' 24 | ,'(O|RHH|MM)*' 25 | ,'.*' 26 | ,'C*MC(CCC|MM)*' 27 | ,'[^C]*[^R]*III.*' 28 | ,'(...?)\\1*' 29 | ,'([^X]|XCC)*' 30 | ,'(RR|HHH)*.?' 31 | ,'N.*X.X.X.*E' 32 | ,'R*D*M*' 33 | ,'.(C|HH)*' 34 | ], 35 | z: [ 36 | '.*H.*V.*G.*' 37 | ,'[RC]*' 38 | ,'M*XEX.*' 39 | ,'.*MCC.*DD.*' 40 | ,'.*X.*RCHX.*' 41 | ,'.*(.)(.)(.)(.)\\4\\3\\2\\1.*' 42 | ,'(NI|ES|IH).*' 43 | ,'[^C]*MMM[^C]*' 44 | ,'.*(.)X\\1C\\1.*' 45 | ,'[ROMEA]*HO[UMIEC]*' 46 | ,'(XR|[^R])*' 47 | ,'[^M]*M[^M]*' 48 | ,'(S|MM|HHH)*' 49 | ], 50 | }, 51 | 'tumbler': { 52 | size: 3, 53 | author: 'Kelly Boothby', 54 | x: ['(AC|KR|SH)', '.?[JKR][ACH].?', '(CS|KH|RE)'], 55 | y: ['(CK|HJ|RA)', '.?[AEH][AKS].?', '(AK|HR|JC)'], 56 | z: ['(AH|KJ|SA)', '.?[KRS][CHK].?', '(AS|JH|KE)'] 57 | }, 58 | 'colors': { 59 | size: 13, 60 | author: 'PiggyPig', 61 | x: [ 62 | '.*RED.*' 63 | ,'.*BLUE.*' 64 | ,'.*BLACK.*' 65 | ,'.*VIOLET.*' 66 | ,'([^K]|K[^K])*K?' 67 | ,'..[RED].*[RED]{3}' 68 | ,'.[ORANGE]{4}.{4}[BLUE]{4}' 69 | ,'(.)(.)(.)(W|\\1|\\2|\\3)*' 70 | ,'..R.{5}[GREEN]*.' 71 | ,'[^RGB]*' 72 | ,'(...)\\1*.?' 73 | ,'[FUCHSIA].W[FUCHSIA].*W' 74 | ,'(BLACK|LACK|ACK|K)*' 75 | ], 76 | y: [ 77 | '([^T]|TK)*(.)\\2' 78 | ,'(.).(.)(\\1|\\2)*.' 79 | ,'[COLOR]*' 80 | ,'[RAINBOW]*' 81 | ,'(WLL[^RED])*[^L]*' 82 | ,'T.?[PURPLE]...[DRY].*' 83 | ,'T*PINK.*BLACK.*' 84 | ,'(.*)\\1+.?' 85 | ,'.?.K[IR]*...' 86 | ,'...[^GREY][GREY]*...' 87 | ,'[RGB]+[^RGB]R.*' 88 | ,'(.)(\\1|RED|BLUE|BLACK)*' 89 | ,'[DARK_RED]*' 90 | ], 91 | z: [ 92 | '.(..)\\1*(WHITE)*' 93 | ,'.*(RED)?R.*RED' 94 | ,'[WHITE]*' 95 | ,'[BLUE]+([RGB]*[PINK]*){2}' 96 | ,'...*[^E]...' 97 | ,'.*W.*[IR]{2}..' 98 | ,'.[^RED]*' 99 | ,'(RED)?.*(BLUE)+.*(GREEN)?' 100 | ,'([RT].[RT])+...[PINK]+' 101 | ,'.*(WHITE|PINK|BLACK|LIGHT|DARK)' 102 | ,'...[OR][AN][GE]...' 103 | ,'...[RGBLD]*...' 104 | ,'.[LIGHT_BLUE]*...' 105 | ], 106 | }, 107 | 'digital': { 108 | 'size': '13', 109 | 'author': 'Emi', 110 | 'x': [ 111 | '(IF|AA|LS)*.', 112 | '.*AYA.*', 113 | '.*([16])[FMA]*\\1', 114 | '.*OFF.*ON.*', 115 | '(.C|.CPP.)+..B.?', 116 | '[^LED]*[BIN].', 117 | '(.+)(.)\\1\\2[BE]+(.+\\2)\\3.', 118 | '[JMP]{5}.*[IO]{5}', 119 | '.[MAP]*BFS.*[TEST]', 120 | '.([^BP]|BP)*B?', 121 | '[STACK]*', 122 | '([CAST]).\\1.*X86.*', 123 | '.?(YYYY|MM|DD)*.?' 124 | ], 125 | 'y': [ 126 | '#[0-9A-F]{6}', 127 | '[BINARY]*', 128 | '[AND]*[OR]*[NOT]*', 129 | 'F*.B.B*', 130 | '[FUNCTION]*.[SH]*', 131 | '[FORK_BOMB]*.', 132 | '.*IF.*[THEN].*ELSE.*', 133 | '([^FP]|FP)*', 134 | '.[BMP]*[SYM]{3}', 135 | '[CODE]*[MORE]{3}[TIS100]*.', 136 | '.*JAVA.*', 137 | '.[PRINT]*[DIRS]*', 138 | '(..).*\\1.*' 139 | ], 140 | 'z': [ 141 | '.*MOUSE.*', 142 | '.(C|CPP)*', 143 | '([^IO]|IO)*', 144 | 'F*.[JPEG]*', 145 | '..(..)\\1+[PROGRAM]*', 146 | 'A*F*H+[^PARTITION]*', 147 | '.*KEY.*BOARD.*', 148 | '[MAP]*OF[N][^MAPS]*', 149 | '(.)(.).*\\2\\1.*\\1.', 150 | '[BIT]*[BYTE]*', 151 | '(.)(.)(.)\\3\\1\\2\\1.*', 152 | '.*([^ABI]).*\\1.*', 153 | '(..).*\\1.?' 154 | ] 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /crossword.css: -------------------------------------------------------------------------------- 1 | body 2 | { 3 | font-family: Helvetica, Arial, sans-serif; 4 | font-size: 18px; 5 | } 6 | 7 | #board { 8 | padding-left: 200px; 9 | padding-top: 150px; 10 | } 11 | .cell_input, 12 | .rule { 13 | font-family: monospace; 14 | } 15 | .cell_input { 16 | width: 24px; 17 | font-size: 24px; 18 | text-align: center; 19 | padding: 0 2px; 20 | margin: 1px; 21 | border: 1px none #AAA; 22 | background: rgba(0, 0, 0, 0); 23 | outline: none; 24 | -opacity: 0.9; 25 | } 26 | .highlighted-cell { 27 | border: 1px dashed; 28 | border-color: rgba(0,0,0, 0.5); 29 | border-radius: 100%; 30 | } 31 | .cell { 32 | overflow: hidden; 33 | display: inline-block; 34 | width: 40px; 35 | height: 50px; 36 | background: url('hex.png'); 37 | margin-right: -1px; 38 | margin-left: -1px; 39 | margin-bottom: -18px; 40 | margin-top: -10px; 41 | padding-top: 8px; 42 | background-repeat: no-repeat; 43 | } 44 | .rule_y { 45 | } 46 | .rule_x { 47 | display: inline-block; 48 | transform:rotate(-60deg); 49 | -ms-transform:rotate(-60deg); /* IE 9 */ 50 | -moz-transform:rotate(-60deg); /* Firefox */ 51 | -webkit-transform:rotate(-60deg); /* Safari and Chrome */ 52 | -o-transform:rotate(-60deg); /* Opera */ 53 | } 54 | 55 | .rule_z { 56 | transform:rotate(60deg); 57 | -ms-transform:rotate(60deg); /* IE 9 */ 58 | -moz-transform:rotate(60deg); /* Firefox */ 59 | -webkit-transform:rotate(60deg); /* Safari and Chrome */ 60 | -o-transform:rotate(60deg); /* Opera */ 61 | } 62 | 63 | .top, .bottom { 64 | width: 38px; 65 | } 66 | .leftside { 67 | right: 5px; 68 | top: 3px; 69 | } 70 | .side { 71 | left: 2px; 72 | top: 0px; 73 | } 74 | .rule.side { 75 | width: 37px; 76 | } 77 | .top { 78 | bottom: 6px; 79 | } 80 | .bottom { 81 | top: 6px; 82 | } 83 | 84 | .rule_parent { 85 | position: relative; 86 | display: inline-block; 87 | height: 24px; 88 | border-color: #E66100; 89 | } 90 | .rule { 91 | position: absolute; 92 | font-size: 16px; 93 | display: inline-block; 94 | white-space: nowrap; 95 | } 96 | 97 | .colorblind .match { 98 | color: #5D3A9B; 99 | font-weight: bold; 100 | } 101 | .colorblind .nomatch { 102 | color: #E66100; 103 | } 104 | .match { 105 | color: #484; 106 | font-weight: bold; 107 | } 108 | .nomatch { 109 | color: #F44; 110 | } 111 | .highlighted { 112 | text-decoration: underline; 113 | } 114 | 115 | span { 116 | -border: 1px dotted black; 117 | } 118 | 119 | #debug { 120 | display: none; 121 | } 122 | -------------------------------------------------------------------------------- /crossword.js: -------------------------------------------------------------------------------- 1 | var board_data; 2 | var mid; 3 | var size; 4 | var user_data; 5 | var use_editor; 6 | 7 | function loadPuzzle(data) { 8 | return JSON.parse(atob(data)); 9 | } 10 | 11 | function savePuzzle(data) { 12 | // return a url to this puzzle 13 | var base_url = window.location.href.split('?')[0]; 14 | var puzzle_data = btoa(JSON.stringify(data)); 15 | return base_url + "?puzzle=" + puzzle_data; 16 | } 17 | 18 | function localLoad(key, deflt) { 19 | var ret = deflt; 20 | try { 21 | ret = JSON.parse(localStorage[key]); 22 | } catch (e) { 23 | // ignored, use default 24 | } 25 | return ret; 26 | } 27 | function localSave(key, value) { 28 | try { 29 | localStorage[key] = JSON.stringify(value); 30 | } catch (e) { 31 | // No localstorage 32 | } 33 | } 34 | 35 | function loadData() { 36 | user_data = localLoad('xword_data_' + board_data.name); 37 | if (!user_data || !user_data.rows) { 38 | user_data = { rows: [] }; 39 | } 40 | } 41 | 42 | function saveData() { 43 | localSave('xword_data_' + board_data.name, user_data); 44 | } 45 | 46 | function rowSize(ii) { 47 | var extra = ii; 48 | if (extra > mid) { 49 | extra = size - 1 - ii; 50 | } 51 | return mid + 1 + extra; 52 | } 53 | 54 | function getData() { 55 | var ii, jj; 56 | var rows = []; 57 | for (ii = 0; ii < size; ++ii) { 58 | var row = []; 59 | for (jj = 0; jj < rowSize(ii); ++jj) { 60 | var id = 'cell_' + ii + '_' + jj; 61 | var v = $('#' + id).val(); 62 | if (v.toUpperCase() !== v) { 63 | v = v.toUpperCase(); 64 | $('#' + id).val(v.toUpperCase()); 65 | } 66 | row.push(v || '?'); 67 | } 68 | rows.push(row); 69 | } 70 | user_data.rows = rows; 71 | saveData(); 72 | } 73 | 74 | function strReverse(str) { 75 | var ret = ''; 76 | for (ii = 0; ii < str.length; ++ii) { 77 | ret += str[str.length - ii - 1]; 78 | } 79 | return ret; 80 | } 81 | 82 | function checkRules() { 83 | var ii; 84 | var debug = []; 85 | 86 | function check(str, axis, idx) { 87 | var rule = board_data[axis][idx]; 88 | var regex = new RegExp('^' + rule + '$'); 89 | var match = str.match(regex); 90 | if (match) { 91 | $('#rule_' + axis + '_' + idx).removeClass('nomatch'); 92 | $('#rule_' + axis + '_' + idx).addClass('match'); 93 | } else { 94 | $('#rule_' + axis + '_' + idx).removeClass('match'); 95 | $('#rule_' + axis + '_' + idx).addClass('nomatch'); 96 | } 97 | debug.push(axis + idx + ': ' + str + (match?' (match)':'') ); 98 | } 99 | 100 | 101 | for (ii = 0; ii < size; ++ii) { 102 | var str = ''; 103 | for (jj = 0; jj < rowSize(ii); ++jj) { 104 | str += user_data.rows[ii][jj]; 105 | } 106 | check(str, 'y', ii); 107 | 108 | str = ''; 109 | for (jj = 0; jj < size; ++jj) { 110 | var i = jj; 111 | var j = ii; 112 | if (jj > mid) { 113 | j -= (jj - mid); 114 | } 115 | if (user_data.rows[i][j] !== undefined) { 116 | str += user_data.rows[i][j]; 117 | } 118 | } 119 | str = strReverse(str); 120 | check(str, 'x', ii); 121 | 122 | str = ''; 123 | for (jj = 0; jj < size; ++jj) { 124 | var i = jj; 125 | var j = ii; 126 | if (jj < mid) { 127 | j -= (mid - jj); 128 | } 129 | if (user_data.rows[i][j] !== undefined) { 130 | str += user_data.rows[i][j]; 131 | } 132 | } 133 | check(str, 'z', ii); 134 | 135 | } 136 | $('#debug').html(debug.join('
')); 137 | } 138 | 139 | function checkArrowKeys(elem, event) { 140 | if (event) { 141 | var di = 0, dj = 0; 142 | if (event.which == 37) { // left arrow 143 | dj = -1; 144 | } else if (event.which == 38) { // up arrow 145 | di = -1; 146 | } else if (event.which == 39) { // right arrow 147 | dj = 1; 148 | } else if (event.which == 40) { // down arrow 149 | di = 1; 150 | } 151 | if (di != 0 || dj != 0) { 152 | var matches = $(elem).attr('id').match(/\d+/g); 153 | var i = parseInt(matches[0]) + di; 154 | var j = parseInt(matches[1]) + dj; 155 | if (i < size) { 156 | // move along same diagonal in top and bottom half 157 | if (di == 1 && i > size/2) { 158 | j--; 159 | } else if (di == -1 && i > size/2 - 1) { 160 | j++; 161 | } 162 | } 163 | $('#cell_' + i + '_' + j).focus().select(); 164 | return true; 165 | } 166 | } 167 | return false; 168 | } 169 | 170 | function onInputChange(event) { 171 | if (!checkArrowKeys(this, event)) { 172 | getData(); 173 | checkRules(); 174 | } 175 | } 176 | 177 | function onFocus() { 178 | $(this).select(); 179 | } 180 | 181 | function onClickCell() { 182 | var elem = $('#' + this.id.slice('wrap_'.length)); 183 | elem.focus(); 184 | elem.select(); 185 | 186 | } 187 | 188 | function onFocusCell() { 189 | // Deselect all previous highlighted rules 190 | $('.highlighted').removeClass('highlighted'); 191 | 192 | // Get position of current cell 193 | var pos_match = this.id.match(/cell_(\d+)_(\d+)/); 194 | var a = parseInt(pos_match[1], 10); 195 | var b = parseInt(pos_match[2], 10); 196 | var y = a; 197 | var x = y > mid ? b + a - mid : b; 198 | var z = y < mid ? b - a + mid : b; 199 | 200 | // Set the relevant rules as highlighted 201 | $('#rule_x_' + x).addClass('highlighted'); 202 | $('#rule_y_' + y).addClass('highlighted'); 203 | $('#rule_z_' + z).addClass('highlighted'); 204 | 205 | $('.highlighted-cell').removeClass('highlighted-cell'); 206 | 207 | function highlightCell(a, b) { 208 | $('#cell_' + a + '_' + b).addClass('highlighted-cell'); 209 | } 210 | 211 | for (var bb = 0; bb < rowSize(a); ++bb) { 212 | highlightCell(a, bb); 213 | } 214 | 215 | var bb = x; 216 | for (var aa = 0; aa < size; ++aa) { 217 | if (aa > mid) { 218 | --bb; 219 | } 220 | highlightCell(aa, bb); 221 | } 222 | 223 | var bb = z; 224 | for (var aa = size - 1; aa >= 0; --aa) { 225 | if (aa < mid) { 226 | --bb; 227 | } 228 | highlightCell(aa, bb); 229 | } 230 | } 231 | 232 | function reset() { 233 | user_data.rows = []; 234 | saveData(); 235 | $('.cell_input').val(''); 236 | getData(); 237 | checkRules(); 238 | } 239 | 240 | function editRule(axis, idx) { 241 | var rule_span = document.getElementById('rule_' + axis + '_' + idx); 242 | rule_span.innerHTML = ruleInput(axis,idx); 243 | } 244 | 245 | function updateRule(axis, idx, value) { 246 | if (value == '') { 247 | value = '.*'; 248 | } 249 | board_data[axis][idx] = value; 250 | var rule_span = document.getElementById('rule_' + axis + '_' + idx); 251 | rule_span.innerHTML = ruleDisplay(axis, idx); 252 | checkRules(); 253 | } 254 | 255 | function ruleInput(axis, idx) { 256 | var form = '
' 257 | return form + '
'; 258 | } 259 | 260 | function ruleDisplay(axis, idx) { 261 | var open = ''; 262 | var close = ''; 263 | if (use_editor) { 264 | open = ''; 265 | close = ''; 266 | } 267 | return open + board_data[axis][idx] + close; 268 | } 269 | 270 | function getSearchParams(){ 271 | var p={}; 272 | location.search.replace( 273 | /[?&]+([^=&]+)=([^&]*)/gi, 274 | function(s,k,v) { p[k]=v; } 275 | ) 276 | return p; 277 | } 278 | 279 | function blankRules(n) { 280 | blanks = [] 281 | for(var i = 0; i < n; i++) { 282 | blanks.push('.*'); 283 | } 284 | return blanks; 285 | } 286 | 287 | var is_colorblind = false; 288 | function toggleColorBlind() { 289 | is_colorblind = !is_colorblind; 290 | localSave('colorblind', is_colorblind); 291 | if (is_colorblind) { 292 | $('body').addClass('colorblind'); 293 | $('.mode_normal').hide(); 294 | $('.mode_colorblind').show(); 295 | } else { 296 | $('body').removeClass('colorblind'); 297 | $('.mode_normal').show(); 298 | $('.mode_colorblind').hide(); 299 | } 300 | } 301 | 302 | function init() { 303 | for (var name in all_boards) { 304 | var option = $('