1 #include "rogueviz.h"
2
3 namespace rogueviz {
4
5 namespace magic { void magic(int i); }
6
7 namespace colorpicker {
8
9 int current_step;
10 color_t current_color;
11 cell *current_center;
12
get_color_at(cell * c)13 color_t get_color_at(cell *c) {
14 crystal::coord oldc = crystal::get_coord(current_center->master);
15 crystal::coord newc = crystal::get_coord(c->master);
16 color_t res;
17 for(int i=0; i<3; i++) {
18 int val = part(current_color, 2-i) + current_step * ((newc[i] - oldc[i]) / 2);
19 if(val < 0) val = 0;
20 if(val > 255) val = 255;
21 part(res, 2-i) = val;
22 }
23 return res;
24 }
25
color_markers(cell * c,const shiftmatrix & V)26 bool color_markers(cell *c, const shiftmatrix& V) {
27 if(!centerover) return false;
28 if(centerover != current_center) {
29 current_color = get_color_at(centerover);
30 current_center = centerover;
31 }
32 c->landparam = get_color_at(c);
33 return false;
34 }
35
color_key(int sym,int uni)36 bool color_key(int sym, int uni) {
37 if((cmode & sm::NORMAL) && (uni >= '0' && uni <= '6')) {
38 current_step = (1 << (uni - '0'));
39 return true;
40 }
41 if((cmode & sm::NORMAL) && uni == '[') {
42 current_step = (1 + current_step) / 2;
43 return true;
44 }
45 if((cmode & sm::NORMAL) && uni == ']') {
46 current_step = 2 * current_step;
47 return true;
48 }
49 if((cmode & sm::NORMAL) && uni >= 1000 && uni < 1010) {
50 current_step = 1 << (uni - 1000);
51 return true;
52 }
53 return false;
54 }
55
color_prestats()56 bool color_prestats() {
57 nohelp = true;
58 for(int k= 0; k <= 6; k++) {
59 int v = 1<< k;
60 if(displayButtonS(10 + k * vid.fsize * 2, 10 + vid.fsize, its(k), v == current_step ? 0xFF2020 : 0xC0C0C0, 0, vid.fsize))
61 getcstat = 1000 + k;
62 }
63 if(mouseover) {
64 displayButtonS(10 + 7 * vid.fsize * 2, 10 + vid.fsize, itsh(mouseover->landparam & 0xFFFFFF), mouseover->landparam, 0, vid.fsize);
65 }
66 return true;
67 }
68
run_cpick()69 void run_cpick() {
70 crystal::compass_probability = 0;
71 stop_game();
72 crystal::set_crystal(6);
73 set_variation(eVariation::pure);
74 firstland = specialland = laCanvas;
75 patterns::whichCanvas = 'g';
76 patterns::canvasback = 0;
77 check_cgi();
78 start_game();
79 current_center = currentmap->gamestart();
80 current_color = 0x808080;
81 current_step = 32;
82 mapeditor::drawplayer = false;
83 vid.smart_range_detail = 1;
84 vid.use_smart_range = 2;
85 rv_hook(hooks_drawcell, 100, color_markers);
86 rv_hook(hooks_handleKey, 50, color_key);
87 rv_hook(hooks_prestats, 150, color_prestats);
88 }
89
__anonf14c1a130102null90 auto cphook = addHook(hooks_args, 100, [] {
91 using namespace arg;
92
93 if(0) ;
94 else if(argis("-cpick")) {
95 PHASEFROM(2);
96 run_cpick();
97 }
98 else return 1;
99 return 0;
100 });
101 }
102
103 namespace sokoban {
104
105 bool on;
106
107 bool created;
108
109 hpcshape sokowall[10][7];
110
111 hyperpoint tpoint[6];
112
push3(hyperpoint h)113 void push3(hyperpoint h) {
114 cgi.first = false;
115 cgi.hpc.push_back(h);
116 }
117
create_sokowalls(cell * c)118 void create_sokowalls(cell *c) {
119 for(int a=0; a<6; a++) tpoint[a] = currentmap->adj(c, a) * C0;
120 const int qfr = 8;
121 auto v = [&] (int a, int b, int fr) {
122 hyperpoint h0 = get_corner_position(c, b);
123 hyperpoint h1 = get_corner_position(c, b+1);
124 hyperpoint h2 = normalize(h0 * (qfr-fr) + h1 * fr);
125 return mscale(h2, 1 / (1 - a / 6.1));
126 };
127
128 for(int a=0; a<9; a++)
129 for(int b=0; b<6; b++) {
130 cgi.bshape(sokowall[a][b], PPR::FLOOR + 2 * a + 1);
131 for(int f=0; f<=qfr; f++)
132 push3(v(a, b, f));
133 for(int f=0; f<=qfr; f++)
134 push3(v(a+1, b, qfr-f));
135 push3(v(a, b, 0));
136 }
137
138 for(int a=0; a<9; a++) {
139 cgi.bshape(sokowall[a][6], PPR::FLOOR + 2 * a);
140 for(int b=0; b<6; b++)
141 for(int f=0; f<qfr; f++)
142 push3(v(a, b, f));
143 push3(v(a, 0, 0));
144 }
145
146 cgi.finishshape();
147 cgi.extra_vertices();
148 }
149
sokomap(cell * c,const shiftmatrix & V)150 bool sokomap(cell *c, const shiftmatrix& V) {
151 if(!created) {
152 created = true;
153 create_sokowalls(c);
154 }
155
156 crystal::coord v = crystal::get_coord(c->master);
157 bool high = (v[0] ^ v[1] ^ v[2]) & 2;
158
159
160 color_t col = 0x00C000FF;
161
162 poly_outline = 0xFF;
163
164 int lev = 0;
165
166 int phase = ((v[0] ^ v[2] ^ v[4]) & 2) >> 1;
167
168 // high = v[0] >= 0 && v[0] <= 8 && v[1] >= 0 && v[1] <= 8 && v[2] == 0 && !(v[0] > 0 && v[0] < 8 && v[1] > 0 && v[1] < 8);
169 // if(high) col = 0xFFFF00FF, lev = 3;
170
171 high = true;
172 lev = ((v[0] + v[1] + v[2])/2+1) & 3;
173 if(lev == 0) col = 0x008000FF;
174 if(lev == 1) col = 0x00C000FF;
175 if(lev == 2) col = 0x40C000FF;
176 if(lev == 3) col = 0x80FF00FF;
177
178 // if(v[0] == 4 && v[1] == 4 && v[2] == 0) col = 0xFFFFFFFF;
179
180 c->landparam = lev;
181
182 if(high) {
183 ld d = hdist0(tC0(V));
184 color_t dark1 = col - ((col & 0xFCFCFC00) >> 1);
185 color_t dark2 = col - ((col & 0xFCFCFC00) >> 2);
186 for(int b=0; b<6; b++)
187 for(int a=c->move(b)->landparam; a<lev; a++) if(hdist0(V * tpoint[b]) < d) {
188 /* auto& p = */
189 queuepoly(V, sokowall[a][b], (((b+phase)&1) ? dark1 : dark2));
190 // p.flags |= POLY_PRECISE_WIDE;
191 // p.linewidth = 2;
192 }
193 queuepoly(V, sokowall[lev][6], col);
194 }
195 else {
196 queuepoly(V, sokowall[0][6], col);
197 }
198
199 c->wall = waInvisibleFloor;
200 return false;
201 }
202
run_sb()203 void run_sb() {
204 crystal::compass_probability = 0;
205 stop_game();
206 crystal::set_crystal(6);
207 set_variation(eVariation::pure);
208 firstland = specialland = laCanvas;
209 patterns::whichCanvas = 'g';
210 patterns::canvasback = 0;
211 check_cgi();
212 rv_hook(hooks_drawcell, 100, sokomap);
213 start_game();
214 mapeditor::drawplayer = false;
215 vid.smart_range_detail = 1;
216 vid.use_smart_range = 2;
217 }
218
__anonf14c1a130302null219 auto sbhook = addHook(hooks_args, 100, [] {
220 using namespace arg;
221
222 if(0) ;
223 else if(argis("-sb")) {
224 PHASEFROM(2);
225 sokoban::run_sb();
226 }
227 else return 1;
228 return 0;
229 });
230
231
232 }
233
234 const flagtype VC = 1;
235 const flagtype NO_VC = 2;
236 const flagtype PLAYER = 4;
237
sync(int mode,flagtype flags)238 void sync(int mode, flagtype flags) {
239 using namespace tour;
240 if(mode == pmStart) {
241 crystal::compass_probability = 0;
242 crystal::crystal_period = 0;
243 firstland = specialland = laCanvas;
244 mapeditor::drawplayer = (flags & PLAYER);
245 vid.smart_range_detail = 1;
246 vid.use_smart_range = 2;
247 crystal::view_coordinates = (flags & VC);
248 smooth_scrolling = true;
249 }
250 if(mode == pmKey && !(flags & NO_VC))
251 crystal::view_coordinates = !crystal::view_coordinates;
252 if(cwt.at != centerover && !playermoved && !(flags & PLAYER)) {
253 cwt.at = centerover;
254 current_display->which_copy = gmatrix[cwt.at].T;
255 }
256 }
257
258 vector<tour::slide> high_slides;
259
260 int shapeid;
261
mycanvas(cell * c)262 int mycanvas(cell *c) {
263
264 int dim = crystal::get_dim();
265
266 auto d = crystal::get_coord(c->master);
267 for(int i=0; i<dim; i++) d[i] >>= 1;
268
269 color_t col = 0;
270
271 d[0] ++;
272
273 int ones = 0;
274 for(int i=0; i<dim; i++) if((d[i] & 1) == 1) ones++;
275
276 switch(shapeid) {
277
278 case 0: {
279 auto dx = d; dx[0] -= 2;
280 bool grid = false;
281 for(int i=1; i<dim; i++) if((dx[i] & 3) == 2) grid = true;
282
283 for(int i=0; i<3; i++) part(col, i) = 0xFF + 0x30 * (d[i]-2);
284 if(grid) col |= 0x1000000;
285 if(dx == crystal::c0) col = 0x1FFD500;
286 if(dx[0] == 2 && dx[1] == 0 && dx[2] == 0 && dx[3] == 0 && dx[4] == 0 && dx[5] == 0) col = 0;
287 if(dx[0] == 6 && dx[1] == 0 && dx[2] == 0 && dx[3] == 0 && dx[4] == 0 && dx[5] == 0) col = 0;
288 return col;
289 }
290
291 case 1: {
292 if(d[1] == 0 && d[2] == 0 && d[3] == 0 && d[4] == 0 && d[5] == 0) ;
293 else col = gradient(0x1FF0000, 0x1FFFF00, 9, d[0], 16);
294 part(col, 0) = 0x80 + d[1] * 0x70;
295 return col;
296 }
297
298 case 2: {
299 col = gradient(0xFFFF00, 0x00FF00, 17, d[0], 24);
300 for(int i=0; i<1; i++) part(col, i) = 0xFF + 0x30 * (d[i+1]-2);
301 c->landparam = col;
302 if(ones == dim-1) col |= 0x1000000;
303 return col;
304 }
305
306 case 3: {
307 if(d[3] == 1) col = (ones & 1) ? 0x1C0FFC0 : 0x180FF80;
308 if(d[3] == -1) col = (ones & 1) ? 0x18080FF : 0x14040FF;
309 if(d[2] == 1) col = (ones & 1) ? 0x1FFCC00 : 0x1FF8080;
310 if(d[2] == -1) col = (ones & 1) ? 0x180FFFF : 0x1408080;
311
312 if(d[4] == 1) col = 0x1FFFFFF;
313 if(d[4] == -1) col = 0x1FFFFFF;
314 if(d[5] == 1) col = 0x1FFFFFF;
315 if(d[5] == -1) col = 0x1FFFFFF;
316 return col;
317 }
318
319 case 4: {
320 if(d[3] == 1) col = (ones & 1) ? 0x1C0FFC0 : 0x180FF80;
321 if(d[3] == -1) col = (ones & 1) ? 0x18080FF : 0x14040FF;
322
323 if(d[4] == 1) col = 0x1FFFFFF;
324 if(d[4] == -1) col = 0x1FFFFFF;
325 return col;
326 }
327
328 case 5: {
329 if(d[3] == 1) col = (ones & 1) ? 0x1C0FFC0 : 0x180FF80;
330 if(d[3] == -2) col = (ones & 1) ? 0x180FFFF : 0x140FFFF;
331 return col;
332 }
333
334 case 6: {
335 if(d[3] == 1) col = (ones & 1) ? 0x1FF8080 : 0x1FF6060;
336 if(d[2] == -1) col = (ones & 1) ? 0x1FFFF70 : 0x1E0E060;
337 return col;
338 }
339
340 case 7: {
341 if(d[1] == 0 || d[2] == 0) ;
342 else if(d[1] > 0 && d[2] > 0) col = 0x1FF0000;
343 else if(d[1] > 0 && d[2] < 0) col = 0x1FFFF00;
344 else if(d[1] < 0 && d[2] < 0) col = 0x100FFFF;
345 else if(d[1] < 0 && d[2] > 0) col = 0x10000FF;
346 return col;
347 }
348
349 case 8: {
350 int s = d[1] + d[2] + d[3] + d[4] + d[5];
351 if(s > 0) col = 0x1FFD500;
352 else if (s < -1) col = 0x17851a9;
353 return col;
354 }
355
356 case 9: {
357 int s = d[1] + d[2] + d[3] + d[4] + d[5] + d[0];
358 if(s > 0) col = 0x1FF20FF;
359 else if (s < -1) col = 0x1C0C0C0;
360 return col;
361 }
362
363 case 10: /* house */ {
364 d[0]--;
365 int is0 = 0, is1 = 0, is2 = 0, ismore = 0;
366 for(int a=0; a<dim; a++) {
367 int v = abs(d[a]);
368 if(v == 0) is0++;
369 else if(v == 1) is1++;
370 else if(v == 2) is2++;
371 else if(v > 2) ismore++;
372 }
373 if(d[dim-1]) return 0x101010;
374 else if(ismore) return 0x101010;
375 else if(is2) return 0x1800000;
376 else if(is1) return 0xFFFF00;
377 else return 0x1FFFFFF;
378 }
379
380 case 11: /* orthoplex */ {
381 int s = abs(d[0] - 4) + abs(d[1] - 3) + abs(d[2] - 2) + abs(d[3] - 3);
382 if(s == 0) return 0x1FFFFFF;
383 else if(s == 12) return 0x1FF0000;
384 else if(s < 12) return 0x800000;
385 // else if(s == 13) return 0x1FF8000;
386 else return 0x202020;
387 }
388
389 default:
390 return -1;
391 }
392 }
393
enable()394 void enable() {
395 rv_hook(patterns::hooks_generate_canvas, 100, mycanvas);
396 }
397
explore_structure(int _shapeid)398 auto explore_structure(int _shapeid) {
399 using namespace tour;
400 return [=] (presmode mode) {
401 sync(mode, NO_VC);
402 if(mode == pmStart) {
403 tour::slide_backup(mapeditor::drawplayer, false);
404 tour::slide_backup(smooth_scrolling, true);
405 stop_game();
406 set_geometry(geometry == gCrystal534 ? gCrystal534 : gCrystal344);
407 firstland = specialland = laCanvas;
408 patterns::whichCanvas = ' ';
409 shapeid = _shapeid;
410 enable();
411 crystal::crystal_period = 4;
412 start_game();
413 #if CAP_RAY
414 ray::max_cells = 4096;
415 #endif
416 }
417 if(mode == pmKey || mode == pmGeometrySpecial) {
418 stop_game();
419 set_geometry(geometry == gCrystal534 ? gCrystal344 : gCrystal534);
420 enable();
421 start_game();
422 }
423 };
424 }
425
add_explore_structure(vector<tour::slide> & v,int id,string nshort,string nlong)426 void add_explore_structure(vector<tour::slide>& v, int id, string nshort, string nlong) {
427 string hds = "high-dimensional shapes/";
428 v.emplace_back(
429 tour::slide{hds+nshort, 999, tour::LEGAL::SPECIAL,
430 nlong + "\n\n"
431 "In these slides, press 5 to switch between 4-dimensional and 6-dimensional space."
432 "Press End/Home to move forward/backward.",
433 explore_structure(id)
434 });
435 }
436
house(int sides,int shape=10)437 void house(int sides, int shape = 10) {
438 stop_game();
439 if(sides < 0)
440 set_geometry(gCrystal344);
441 else
442 crystal::set_crystal(sides);
443 set_variation(eVariation::pure);
444 firstland = specialland = laCanvas;
445 patterns::whichCanvas = ' ';
446 shapeid = shape;
447 check_cgi();
448 enable();
449 start_game();
450 }
451
452 #if CAP_RVSLIDES
gen_high_demo()453 tour::slide *gen_high_demo() {
454 high_slides.clear();
455 using namespace tour;
456 auto& v = high_slides;
457 v.emplace_back(
458 slide{"Introduction/Three-dimensional space", 999, LEGAL::NONE,
459 "This is our 2D visualization of 3-dimensional space.\n\n"
460 "In most slides you can press '5' to enable or disable the coordinate display. "
461 "You can move the focus with numpad, arrowkeys, or clicking cells with mouse."
462 ,
463 [] (presmode mode) {
464 sync(mode, VC);
465 if(mode == pmStart) {
466 crystal::set_crystal(6);
467 patterns::whichCanvas = 'K';
468 start_game();
469 }
470 }
471 });
472
473 v.emplace_back(
474 slide{"Introduction/Four-dimensional space", 999, LEGAL::NONE,
475 "This is our 2D visualization of 4-dimensional space.",
476 [] (presmode mode) {
477 sync(mode, VC);
478 if(mode == pmStart) {
479 crystal::set_crystal(8);
480 patterns::whichCanvas = 'K';
481 start_game();
482 }
483 }
484 });
485
486 v.emplace_back(
487 slide{"Introduction/Color picker", 999, LEGAL::NONE,
488 "Color picker. You can press '0' to '6' to adjust how fast the colors change.",
489 [] (presmode mode) {
490 sync(mode, 0);
491 if(mode == pmStart)
492 colorpicker::run_cpick();
493 }
494 });
495
496 v.emplace_back(
497 slide{"games and puzzles/house 3D", 999, LEGAL::NONE,
498 "A house in three dimensions. This is a 5x5 square, its center is white, rest of its interior is yellow, and its perimeter is red. "
499 "By using the third dimension, you can leave the square, or enter it back.",
500 [] (presmode mode) {
501 sync(mode, 0);
502 if(mode == pmStart) {
503 house(6);
504 }
505 }
506 });
507
508 v.emplace_back(
509 slide{"games and puzzles/house 4D", 999, LEGAL::NONE,
510 "A house in four dimensions. This is a 5x5x5 cube."
511 ,
512 [] (presmode mode) {
513 sync(mode, 0);
514 if(mode == pmStart) {
515 house(8);
516 }
517 }
518 });
519
520 v.emplace_back(
521 slide{"games and puzzles/house, 3D visualization", 999, LEGAL::NONE,
522 "A house in four dimensions, using the 3D version of our visualization. This visualization is harder to understand. Press End/Home to move forward/backward.",
523 [] (presmode mode) {
524 sync(mode, 0);
525 if(mode == pmStart) {
526 house(-1);
527 }
528 }
529 });
530
531 v.emplace_back(
532 slide{"games and puzzles/4D orthoplex using 2D", 999, LEGAL::NONE,
533 "Try to find the center of the orthoplex in four dimensions. This is a 4D analog of the octahedron.\n\n"
534 "The faces of the orthoplex are bright red, the outside is dark gray, and the center is white."
535 ,
536 [] (presmode mode) {
537 sync(mode, 0);
538 if(mode == pmStart) {
539 house(8, 11);
540 }
541 }
542 });
543
544 v.emplace_back(
545 slide{"games and puzzles/4D orthoplex using 3D", 999, LEGAL::NONE,
546 "The same visualization in 3D.",
547 [] (presmode mode) {
548 sync(mode, 0);
549 if(mode == pmStart) {
550 house(8, 11);
551 }
552 }
553 });
554
555 v.emplace_back(
556 slide{"games and puzzles/basic roguelike", 999, LEGAL::NONE,
557 "This is a basic roguelike in three dimensions. Even though it appears that it should be possible to move in such a way that "
558 "one of the enemies is no longer adjacent to us, it turns out that one of its other 'copies' will always manage "
559 "to chase us. This is clear when we imagine the actual higher-dimensional space.",
560 [] (presmode mode) {
561 sync(mode, NO_VC | PLAYER);
562 if(mode == pmStart) {
563 crystal::set_crystal(6);
564 patterns::whichCanvas = 'c';
565 colortables['c'][0] = 0x208020;
566 colortables['c'][1] = 0x105010;
567 patterns::canvasback = 0x101010;
568 start_game();
569 auto & us = vid.cs;
570 us.charid = 4;
571 /* us.skincolor = 0xB55239FF;
572 us.haircolor = 0xB55239FF;
573 us.dresscolor = 0xB55239FF; */
574 us.skincolor = 0x202020FF;
575 us.haircolor = 0x202020FF;
576 us.dresscolor = 0x202020FF;
577 us.eyecolor = 0xC000FF;
578 cwt.at->move(0)->monst = moRedFox;
579 cwt.at->move(1)->monst = moLavaWolf;
580 minf[moLavaWolf].color = 0x909090;
581 minf[moLavaWolf].name = "Wolf";
582 minf[moRedFox].name = "Fox";
583 }
584 }
585 });
586
587 v.emplace_back(
588 slide{"games and puzzles/gravity mockup", 999, LEGAL::NONE,
589 "A mockup of a 4D game with gravity. We use the 3D version of our visualization, while the gravity dimension is shown using perspective.",
590 [] (presmode mode) {
591 sync(mode, 0);
592 if(mode == pmStart) {
593 sokoban::run_sb();
594 }
595 }
596 });
597
598 add_explore_structure(v, 0, "Cage", "In this series of slides we show various structures in four-dimensional space, using the three-dimensional variant of our visualization.\n\nWe start with a 4x4x4x4 cage with a golden point in the center.");
599 add_explore_structure(v, 1, "Tunnel", "One-dimensional tunnel");
600 add_explore_structure(v, 2, "Skeleton", "The 1-skeleton of the tessellation of Z^4 with cubes of edge 2");
601 add_explore_structure(v, 3, "Tunnel2", "Two-dimensional tunnel");
602 add_explore_structure(v, 4, "Hyperplanes", "Two hyperplanes in distance 2, i.e., three-dimensional tunnel.");
603 add_explore_structure(v, 5, "Far hyperplanes", "Two hyperplanes in distance 3.");
604 add_explore_structure(v, 6, "Orthogonal", "Two orthogonal hyperplanes.");
605 add_explore_structure(v, 7, "Quarterspaces", "Four quarterspaces.");
606 add_explore_structure(v, 8, "Diagonal", "Diagonal tunnel in all coordinates except one.");
607 add_explore_structure(v, 9, "Diagonal", "Diagonal tunnel in all coordinates.");
608
609 v.emplace_back(
610 slide{"Magic cube/Standard magic cube", 999, LEGAL::NONE,
611 "Magic Cube (aka Rubik's Cube) using two dimensions. This is an example of a visualization which is difficult for our method, because we are moving complex objects in the 3D space."
612 "Press 'r' to rotate the face (while the mouse pointer is on its center -- two coodinates must be 0 and one must be non-zero), Shift+R to reset.",
613 [] (presmode mode) {
614 sync(mode, 0);
615 if(mode == pmStart) {
616 magic::magic(6);
617 }
618 }
619 });
620
621 v.emplace_back(
622 slide{"Magic cube/Four-dimensional magic cube", 999, LEGAL::NONE,
623 "Magic Cube (4D) using two dimensions. Keys are the same as in the previous slide.\n\n"
624 "Use 'r' to rotate the 2D face under the mouse pointer (two coodinates must be 0 and two must be non-zero)."
625 ,
626 [] (presmode mode) {
627 sync(mode, 0);
628 if(mode == pmStart) {
629 magic::magic(8);
630 }
631 }
632 });
633
634 v.emplace_back(
635 slide{"Magic cube/Four-dimensional magic cube, 3D visualization", 999, LEGAL::NONE,
636 "Magic Cube (4D) using three dimensions.",
637 [] (presmode mode) {
638 sync(mode, 0);
639 if(mode == pmStart) {
640 magic::magic(-1);
641 }
642 }
643 });
644
645 callhooks(pres::hooks_build_rvtour, "highdim", high_slides);
646 pres::add_end(v);
647 return &high_slides[0];
648 }
649 #endif
650
651 auto highdim_hooks =
__anonf14c1a131202(tour::ss::slideshow_callback cb) 652 addHook_slideshows(120, [] (tour::ss::slideshow_callback cb) {
653
654 if(high_slides.empty()) gen_high_demo();
655
656 cb(XLAT("visualizing higher-dimensional spaces"), &high_slides[0], 'h');
657 });
658
659 }