1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.vectomatic.svg.edu.client.maze;
19
20 import org.vectomatic.dom.svg.OMNode;
21 import org.vectomatic.dom.svg.OMSVGDocument;
22 import org.vectomatic.dom.svg.OMSVGGElement;
23 import org.vectomatic.dom.svg.OMSVGLength;
24 import org.vectomatic.dom.svg.OMSVGPathElement;
25 import org.vectomatic.dom.svg.OMSVGRect;
26 import org.vectomatic.dom.svg.OMSVGSVGElement;
27 import org.vectomatic.dom.svg.OMSVGStyleElement;
28 import org.vectomatic.dom.svg.OMText;
29 import org.vectomatic.dom.svg.ui.SVGPushButton;
30 import org.vectomatic.dom.svg.utils.AsyncXmlLoader;
31 import org.vectomatic.dom.svg.utils.AsyncXmlLoaderCallback;
32 import org.vectomatic.dom.svg.utils.SVGConstants;
33 import org.vectomatic.svg.edu.client.commons.CommonBundle;
34 import org.vectomatic.svg.edu.client.commons.CommonConstants;
35 import org.vectomatic.svg.edu.client.commons.DifficultyPicker;
36 import org.vectomatic.svg.edu.client.commons.LicenseBox;
37 import org.vectomatic.svg.edu.client.commons.Utils;
38
39 import com.google.gwt.core.client.EntryPoint;
40 import com.google.gwt.core.client.GWT;
41 import com.google.gwt.core.client.JavaScriptObject;
42 import com.google.gwt.core.client.Scheduler;
43 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
44 import com.google.gwt.dom.client.Document;
45 import com.google.gwt.dom.client.NativeEvent;
46 import com.google.gwt.event.dom.client.ClickEvent;
47 import com.google.gwt.event.dom.client.KeyCodes;
48 import com.google.gwt.event.dom.client.KeyDownEvent;
49 import com.google.gwt.event.dom.client.MouseDownEvent;
50 import com.google.gwt.event.logical.shared.ValueChangeEvent;
51 import com.google.gwt.uibinder.client.UiBinder;
52 import com.google.gwt.uibinder.client.UiField;
53 import com.google.gwt.uibinder.client.UiHandler;
54 import com.google.gwt.user.client.Element;
55 import com.google.gwt.user.client.Timer;
56 import com.google.gwt.user.client.Window;
57 import com.google.gwt.user.client.ui.FlowPanel;
58 import com.google.gwt.user.client.ui.FocusPanel;
59 import com.google.gwt.user.client.ui.RootPanel;
60 import com.google.gwt.user.client.ui.Widget;
61
62
63
64
65 public class MazeMain implements EntryPoint {
66 private static final String DIR = "maze";
67 private static final String ID_MAZE = "maze";
68
69 interface MazeMainBinder extends UiBinder<FlowPanel, MazeMain> {
70 }
71 private static MazeMainBinder mainBinder = GWT.create(MazeMainBinder.class);
72
73 @UiField(provided=true)
74 static MazeBundle resources = MazeBundle.INSTANCE;
75 @UiField(provided=true)
76 static MazeLayoutCss mazeLayoutCss = MazeBundle.INSTANCE.mazeLayout();
77 @UiField(provided=true)
78 static CommonBundle common = CommonBundle.INSTANCE;
79 static MazeCss style = resources.getCss();
80
81 OMSVGDocument document;
82
83
84
85 OMSVGSVGElement svgRoot;
86
87
88
89
90 OMSVGPathElement mazeDef;
91
92
93
94 OMSVGGElement cellGroup;
95
96
97
98 OMSVGPathElement borderPath;
99
100
101
102 OMSVGPathElement wallPath;
103
104 @UiField
105 FocusPanel focusPanel;
106 @UiField
107 SVGPushButton generateButton;
108 @UiField
109 SVGPushButton leftButton;
110 @UiField
111 SVGPushButton rightButton;
112 @UiField
113 SVGPushButton upButton;
114 @UiField
115 SVGPushButton downButton;
116 @UiField
117 SVGPushButton backButton;
118 @UiField
119 SVGPushButton helpButton;
120 @UiField
121 SVGPushButton prevButton;
122 @UiField
123 SVGPushButton nextButton;
124 @UiField
125 DifficultyPicker difficultyPicker;
126 @UiField
127 FlowPanel navigationPanel;
128 Widget menuWidget;
129
130
131
132 String[] levels;
133
134
135
136 int level;
137
138
139
140 static Timer positionTimer;
141
142
143
144 static Timer solutionTimer;
145
146
147
148
149 static JavaScriptObject pathRule;
150
151
152
153
154 boolean frozen;
155
156
157
158 RectangularMaze maze;
159
160
161
162
163 AsyncXmlLoader loader;
164
165
166
167
168 public MazeMain() {
169 }
170
171
172
173 public MazeMain(Widget menuWidget) {
174 this.menuWidget = menuWidget;
175 }
176
177
178
179
180 @Override
181 public void onModuleLoad() {
182
183 common.css().ensureInjected();
184 common.mediaQueries().ensureInjected();
185 Utils.injectMediaQuery("(orientation:landscape)", common.mediaQueriesLandscape());
186 Utils.injectMediaQuery("(orientation:portrait)", common.mediaQueriesPortrait());
187 Utils.injectMediaQuery("(orientation:landscape)", resources.mazeLayoutLandscape());
188 Utils.injectMediaQuery("(orientation:portrait)", resources.mazeLayoutPortrait());
189 style.ensureInjected();
190 mazeLayoutCss.ensureInjected();
191
192 if (solutionTimer != null) {
193 solutionTimer.cancel();
194 }
195
196
197 levels = resources.levels().getText().split("\\s");
198 loader = GWT.create(AsyncXmlLoader.class);
199
200
201 FlowPanel panel = mainBinder.createAndBindUi(this);
202 if (menuWidget == null) {
203 menuWidget = LicenseBox.createAboutButton();
204 }
205 navigationPanel.insert(menuWidget, 0);
206
207 int difficulty = 0;
208 String difficultyParam = Window.Location.getParameter("difficulty");
209 if (difficultyParam != null) {
210 try {
211 difficulty = Integer.parseInt(difficultyParam);
212 } catch(NumberFormatException e) {
213 }
214 }
215 difficultyPicker.setDifficulty(difficulty);
216 RootPanel.get(CommonConstants.ID_UIROOT).add(panel);
217
218 String levelParam = Window.Location.getParameter("level");
219 if (levelParam != null) {
220 try {
221 int value = Integer.parseInt(levelParam);
222 if (value >= 0 && value < levels.length) {
223 level = value;
224 }
225 } catch(NumberFormatException e) {
226 GWT.log("Cannot parse level=" + levelParam, e);
227 }
228 }
229 readMazeDef();
230 }
231
232 @UiHandler("generateButton")
233 public void generate(ClickEvent event) {
234 maze.perfectRandomize();
235 if (solutionTimer != null) {
236 solutionTimer.cancel();
237 }
238 String dumpParam = Window.Location.getParameter("dump");
239 if ((!GWT.isScript()) && (dumpParam != null)) {
240 OMSVGSVGElement root2 = (OMSVGSVGElement)svgRoot.cloneNode(true);
241 OMSVGRect viewBox = root2.getViewBox().getBaseVal();
242 if (viewBox.getWidth() <= viewBox.getHeight()) {
243 root2.setWidth(OMSVGLength.SVG_LENGTHTYPE_CM, 21f);
244 root2.setHeight(OMSVGLength.SVG_LENGTHTYPE_CM, 29.7f);
245 } else {
246 root2.setWidth(OMSVGLength.SVG_LENGTHTYPE_CM, 29.7f);
247 root2.setHeight(OMSVGLength.SVG_LENGTHTYPE_CM, 21f);
248 }
249 OMSVGStyleElement styleElement = new OMSVGStyleElement();
250 styleElement.setType(SVGConstants.CSS_TYPE);
251 styleElement.appendChild(new OMText(style.getText()));
252 root2.insertBefore(styleElement, root2.getFirstChild());
253
254 GWT.log(root2.getMarkup());
255 }
256 setFillProperty(pathRule, SVGConstants.CSS_LIGHTGREEN_VALUE);
257 update();
258 }
259
260 void clear() {
261 if (positionTimer != null) {
262 positionTimer.cancel();
263 }
264 OMSVGGElement g = new OMSVGGElement();
265 if (cellGroup != null) {
266 svgRoot.replaceChild(g, cellGroup);
267 } else {
268 svgRoot.appendChild(g);
269 }
270 cellGroup = g;
271
272 String wallStroke = mazeDef.getAttributeNS(RectangularMaze.VECTOMATIC_NS, RectangularMaze.WALL_TAG);
273 OMSVGPathElement p = new OMSVGPathElement();
274 p.setClassNameBaseVal(style.wall());
275 p.getStyle().setSVGProperty(SVGConstants.CSS_STROKE_PROPERTY, wallStroke);
276 if (wallPath != null) {
277 svgRoot.replaceChild(p, wallPath);
278 } else {
279 svgRoot.appendChild(p);
280 }
281 wallPath = p;
282
283 String borderStroke = mazeDef.getAttributeNS(RectangularMaze.VECTOMATIC_NS, RectangularMaze.BORDER_TAG);
284 p = new OMSVGPathElement();
285 p.setClassNameBaseVal(style.border());
286 p.getStyle().setSVGProperty(SVGConstants.CSS_STROKE_PROPERTY, borderStroke);
287 if (borderPath != null) {
288 svgRoot.replaceChild(p, borderPath);
289 } else {
290 svgRoot.appendChild(p);
291 }
292 borderPath = p;
293
294 String[] res = mazeDef.getAttributeNS(RectangularMaze.VECTOMATIC_NS, RectangularMaze.RES_TAG + (difficultyPicker.getDifficulty() + 1)).split("x");
295 int colCount = Integer.parseInt(res[0]);
296 int rowCount = Integer.parseInt(res[1]);
297 maze = RectangularMaze.createMaze(colCount, rowCount, document, mazeDef, cellGroup, borderPath, wallPath);
298
299 positionTimer = new Timer() {
300 @Override
301 public void run() {
302 if (maze != null) {
303 maze.updateCurrent();
304 }
305 }
306 };
307 positionTimer.scheduleRepeating(250);
308 }
309
310 @UiHandler("leftButton")
311 public void left(MouseDownEvent event) {
312 maze.left();
313 update();
314 }
315 @UiHandler("rightButton")
316 public void right(MouseDownEvent event) {
317 maze.right();
318 update();
319 }
320 @UiHandler("upButton")
321 public void up(MouseDownEvent event) {
322 maze.up();
323 update();
324 }
325 @UiHandler("downButton")
326 public void down(MouseDownEvent event) {
327 maze.down();
328 update();
329 }
330 @UiHandler("backButton")
331 public void backButton(MouseDownEvent event) {
332 maze.back();
333 update();
334 }
335 private void update() {
336 frozen = false;
337 difficultyPicker.setEnabled(true);
338 leftButton.setEnabled(maze.canGoLeft());
339 rightButton.setEnabled(maze.canGoRight());
340 upButton.setEnabled(maze.canGoUp());
341 downButton.setEnabled(maze.canGoDown());
342 backButton.setEnabled(maze.canGoBack());
343 Scheduler.get().scheduleDeferred(new ScheduledCommand() {
344 @Override
345 public void execute() {
346
347
348 focusPanel.setFocus(true);
349 }
350 });
351 if (maze.gameWon()) {
352 freeze();
353
354
355 if (pathRule == null) {
356 pathRule = getRule("." + style.path());
357 GWT.log(pathRule.toString());
358 }
359 solutionTimer = new Timer() {
360 private int H = 120, S = 40, V = 93;
361 @Override
362 public void run() {
363 H += 7;
364 H = H % 360;
365 int R = 0, G = 0, B = 0;
366 int h = (H / 60);
367 int p = (255 * V * (100 - S)) / 10000;
368 int q = (255 * V * (6000 - S * (H - 60 * h))) / 600000;
369 int t = (255 * V * (6000 - S * (60 - (H - 60 * h)))) / 600000;
370 switch(h) {
371 case 0:
372 R = V * 255 / 100;
373 G = t;
374 B = p;
375 break;
376 case 1:
377 R = q;
378 G = V * 255 / 100;
379 B = p;
380 break;
381 case 2:
382 R = p;
383 G = V * 255 / 100;
384 B = t;
385 break;
386 case 3:
387 R = p;
388 G = q;
389 B = V * 255 / 100;
390 break;
391 case 4:
392 R = t;
393 G = p;
394 B = V * 255 / 100;
395 break;
396 case 5:
397 R = V * 255 / 100;
398 G = p;
399 B = q;
400 break;
401 }
402 setFillProperty(pathRule, "rgb(" + R + "," + G + "," + B +")");
403 }
404
405 };
406 solutionTimer.scheduleRepeating(50);
407 }
408 }
409 private static final native JavaScriptObject getRule(String selector)
410
411
412
413
414
415
416
417
418
419
420 ;
421
422 private static final native void setFillProperty(JavaScriptObject rule, String color)
423
424
425
426 ;
427
428 private void freeze() {
429 helpButton.setEnabled(false);
430 difficultyPicker.setEnabled(false);
431 leftButton.setEnabled(false);
432 rightButton.setEnabled(false);
433 upButton.setEnabled(false);
434 downButton.setEnabled(false);
435 backButton.setEnabled(false);
436 frozen = true;
437 }
438
439 @UiHandler("helpButton")
440 public void help(ClickEvent event) {
441 freeze();
442 generateButton.setEnabled(false);
443 maze.displaySolution(true);
444 Timer solutionTimer = new Timer() {
445 @Override
446 public void run() {
447 maze.displaySolution(false);
448 helpButton.setEnabled(true);
449 generateButton.setEnabled(true);
450 difficultyPicker.setEnabled(true);
451 update();
452 }
453 };
454 solutionTimer.schedule(3000);
455 }
456 @UiHandler("prevButton")
457 public void prevButton(ClickEvent event) {
458 level--;
459 if (level < 0) {
460 level = levels.length - 1;
461 }
462 readMazeDef();
463 }
464 @UiHandler("nextButton")
465 public void nextButton(ClickEvent event) {
466 level++;
467 if (level >= levels.length) {
468 level = 0;
469 }
470 readMazeDef();
471 }
472
473 private static final native int eventGetKeyCode(NativeEvent evt)
474
475
476
477
478 ;
479
480 @UiHandler("focusPanel")
481 public void onKeyDown(KeyDownEvent event) {
482 if (!frozen) {
483 int code = eventGetKeyCode(event.getNativeEvent());
484 switch (code) {
485 case KeyCodes.KEY_DOWN:
486 maze.down();
487 break;
488 case KeyCodes.KEY_RIGHT:
489 maze.right();
490 break;
491 case KeyCodes.KEY_UP:
492 maze.up();
493 break;
494 case KeyCodes.KEY_LEFT:
495 maze.left();
496 break;
497 case ' ':
498 maze.back();
499 break;
500 default:
501
502 GWT.log("key code:" + (int)event.getNativeKeyCode());
503 }
504 update();
505 }
506 }
507
508 @UiHandler("difficultyPicker")
509 public void levelChange(ValueChangeEvent<Integer> event) {
510 clear();
511 generate(null);
512 }
513
514 private void displayMazeDef(OMSVGSVGElement svg) {
515
516 Element div = focusPanel.getElement();
517 if (svgRoot != null) {
518 div.replaceChild(svg.getElement(), svgRoot.getElement());
519 } else {
520 div.appendChild(svg.getElement());
521 }
522 svgRoot = svg;
523 document = (OMSVGDocument) svgRoot.getOwnerDocument();
524 mazeDef = (OMSVGPathElement) document.getElementById(ID_MAZE);
525 cellGroup = null;
526 wallPath = null;
527 borderPath = null;
528 clear();
529 generate(null);
530 }
531
532 public void readMazeDef() {
533 String url = GWT.getModuleBaseURL() + DIR + "/" + levels[level];
534 loader.loadResource(url, new AsyncXmlLoaderCallback() {
535 @Override
536 public void onError(String resourceName, Throwable error) {
537 focusPanel.getElement().appendChild(Document.get().createTextNode("Cannot find resource"));
538 }
539
540 @Override
541 public void onSuccess(String resourceName, com.google.gwt.dom.client.Element root) {
542 OMSVGSVGElement svg = OMNode.convert(root);
543 displayMazeDef(svg);
544 }
545 });
546 }
547 }