├── 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 = '
';
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 = $('