1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.vectomatic.svg.edu.client.push;
19
20 import java.util.ArrayList;
21 import java.util.List;
22
23 import org.vectomatic.dom.svg.OMNode;
24 import org.vectomatic.dom.svg.OMSVGClipPathElement;
25 import org.vectomatic.dom.svg.OMSVGDefsElement;
26 import org.vectomatic.dom.svg.OMSVGGElement;
27 import org.vectomatic.dom.svg.OMSVGMatrix;
28 import org.vectomatic.dom.svg.OMSVGPoint;
29 import org.vectomatic.dom.svg.OMSVGRect;
30 import org.vectomatic.dom.svg.OMSVGRectElement;
31 import org.vectomatic.dom.svg.OMSVGSVGElement;
32 import org.vectomatic.dom.svg.OMSVGTransform;
33 import org.vectomatic.dom.svg.OMSVGTransformList;
34 import org.vectomatic.dom.svg.OMSVGUseElement;
35 import org.vectomatic.dom.svg.ui.SVGPushButton;
36 import org.vectomatic.dom.svg.utils.AsyncXmlLoader;
37 import org.vectomatic.dom.svg.utils.AsyncXmlLoaderCallback;
38 import org.vectomatic.dom.svg.utils.SVGConstants;
39 import org.vectomatic.svg.edu.client.commons.CommonBundle;
40 import org.vectomatic.svg.edu.client.commons.CommonConstants;
41 import org.vectomatic.svg.edu.client.commons.DifficultyPicker;
42 import org.vectomatic.svg.edu.client.commons.LicenseBox;
43 import org.vectomatic.svg.edu.client.commons.Utils;
44
45 import com.google.gwt.animation.client.Animation;
46 import com.google.gwt.core.client.Duration;
47 import com.google.gwt.core.client.EntryPoint;
48 import com.google.gwt.core.client.GWT;
49 import com.google.gwt.dom.client.Style.Unit;
50 import com.google.gwt.dom.client.StyleInjector;
51 import com.google.gwt.event.dom.client.ClickEvent;
52 import com.google.gwt.event.dom.client.MouseDownEvent;
53 import com.google.gwt.event.dom.client.MouseDownHandler;
54 import com.google.gwt.event.dom.client.MouseEvent;
55 import com.google.gwt.event.logical.shared.ValueChangeEvent;
56 import com.google.gwt.event.shared.EventHandler;
57 import com.google.gwt.uibinder.client.UiBinder;
58 import com.google.gwt.uibinder.client.UiField;
59 import com.google.gwt.uibinder.client.UiHandler;
60 import com.google.gwt.user.client.Command;
61 import com.google.gwt.user.client.DeferredCommand;
62 import com.google.gwt.user.client.Element;
63 import com.google.gwt.user.client.Random;
64 import com.google.gwt.user.client.Timer;
65 import com.google.gwt.user.client.Window;
66 import com.google.gwt.user.client.ui.FlowPanel;
67 import com.google.gwt.user.client.ui.HTML;
68 import com.google.gwt.user.client.ui.RootPanel;
69 import com.google.gwt.user.client.ui.Widget;
70
71 public class PushMain implements MouseDownHandler, EntryPoint {
72 private static final String DIR = "push";
73 private static final String ID_CLIP_PATH = "cp";
74 private static final String ID_CLIP_RECT = "cpr";
75 private static final String ID_IMAGE = "puzzle";
76 private static final String ID_TILE = "t";
77 private static final int MARGIN = 3;
78
79 PushBundle resources = PushBundle.INSTANCE;
80 @UiField(provided=true)
81 CommonBundle common = CommonBundle.INSTANCE;
82 PushCss style = resources.getCss();
83 @UiField
84 SVGPushButton prevButton;
85 @UiField
86 SVGPushButton nextButton;
87 @UiField
88 HTML svgContainer;
89 @UiField
90 DifficultyPicker difficultyPicker;
91 @UiField
92 FlowPanel navigationPanel;
93 Widget menuWidget;
94
95
96
97
98 AsyncXmlLoader loader;
99 private String[] levels;
100 private int currentLevel;
101
102
103
104 private OMSVGSVGElement srcSvg;
105
106
107
108 private OMSVGSVGElement pushSvg;
109
110
111
112 private OMSVGRect bbox;
113
114
115
116 private int[][] game;
117
118
119
120 private int hole;
121
122
123
124 int xcount;
125
126
127
128 int ycount;
129
130
131
132
133
134
135 private OMSVGUseElement[] tiles;
136
137
138
139 private boolean playing;
140
141
142
143
144 boolean frozen;
145
146
147
148 private Timer waitTimer;
149
150
151
152 private Timer scrambleTimer;
153
154
155
156 private int animCount;
157 private Animation animation = new Animation() {
158 @Override
159 protected void onUpdate(double progress) {
160 for (int i = 0; i < xcount; i++) {
161 for (int j = 0; j < ycount; j++) {
162 int index = i * ycount + j;
163 if (index != hole) {
164 OMSVGTransformList xformList = tiles[index].getTransform().getBaseVal();
165 xformList.clear();
166 OMSVGTransform r = pushSvg.createSVGTransform();
167 r.setRotate((float)(360 * progress * ((animCount % 2 == 0) ? 1f : -1f)), (i + 0.5f) * bbox.getWidth() / xcount, (j + 0.5f) * bbox.getHeight() / ycount);
168 xformList.appendItem(r);
169 }
170 }
171 }
172 }
173 @Override
174 protected void onComplete() {
175 super.onComplete();
176 animCount++;
177 if (animCount < 5) {
178 DeferredCommand.addCommand(new Command() {
179 @Override
180 public void execute() {
181 animation.run(1000, Duration.currentTimeMillis() + 500);
182 }
183 });
184 } else {
185 playing = true;
186 GWT.log(getDescription());
187 }
188 }
189 };
190
191 interface PushMainBinder extends UiBinder<FlowPanel, PushMain> {
192 }
193 private static PushMainBinder mainBinder = GWT.create(PushMainBinder.class);
194
195
196
197
198 public PushMain() {
199 }
200
201
202
203 public PushMain(Widget menuWidget) {
204 this.menuWidget = menuWidget;
205 }
206
207
208
209
210 @Override
211 public void onModuleLoad() {
212
213 common.css().ensureInjected();
214 common.mediaQueries().ensureInjected();
215 Utils.injectMediaQuery("(orientation:landscape)", common.mediaQueriesLandscape());
216 Utils.injectMediaQuery("(orientation:portrait)", common.mediaQueriesPortrait());
217 StyleInjector.inject(style.getText(), true);
218
219
220 levels = resources.levels().getText().split("\\s");
221 loader = GWT.create(AsyncXmlLoader.class);
222
223
224 FlowPanel panel = mainBinder.createAndBindUi(this);
225 if (menuWidget == null) {
226 menuWidget = LicenseBox.createAboutButton();
227 }
228 navigationPanel.insert(menuWidget, 0);
229 RootPanel.get(CommonConstants.ID_UIROOT).add(panel);
230 readPushDef();
231 }
232
233 @UiHandler("prevButton")
234 public void prevButton(ClickEvent event) {
235 currentLevel--;
236 if (currentLevel < 0) {
237 currentLevel = levels.length - 1;
238 }
239 readPushDef();
240 }
241 @UiHandler("nextButton")
242 public void nextButton(ClickEvent event) {
243 currentLevel++;
244 if (currentLevel >= levels.length) {
245 currentLevel = 0;
246 }
247 readPushDef();
248 }
249
250 @UiHandler("difficultyPicker")
251 public void levelChange(ValueChangeEvent<Integer> event) {
252 generate();
253 }
254
255 private void generate() {
256 OMSVGSVGElement rootSvg = new OMSVGSVGElement();
257 rootSvg.addClassNameBaseVal(style.rootSvg());
258 rootSvg.addMouseDownHandler(this);
259 OMSVGDefsElement defs = new OMSVGDefsElement();
260 rootSvg.appendChild(defs);
261
262
263
264 OMSVGGElement imgGroup = new OMSVGGElement();
265 imgGroup.setId(ID_IMAGE);
266 for (OMNode node : srcSvg.getChildNodes()) {
267 imgGroup.appendChild(node.cloneNode(true));
268 }
269 defs.appendChild(imgGroup);
270
271 OMSVGRect viewBox = srcSvg.getViewBox().getBaseVal();
272 float width = viewBox.getWidth();
273 float height = viewBox.getHeight();
274 bbox = rootSvg.createSVGRect();
275 viewBox.assignTo(bbox);
276
277
278 if (width < height) {
279 xcount = difficultyPicker.getDifficulty() + 3;
280 ycount = (int)(xcount * height / width);
281 } else {
282 ycount = difficultyPicker.getDifficulty() + 3;
283 xcount = (int)(ycount * width / height);
284 }
285 hole = xcount * ycount - 1;
286
287
288
289
290
291
292 float borderWidth = (int)(0.075f * width);
293 float borderHeight = (int)(0.075f * height);
294 float borderRx = (int)(0.025f * width);
295 float borderRy = (int)(0.025f * height);
296 OMSVGRectElement borderOut = new OMSVGRectElement(
297 viewBox.getX() - borderWidth - MARGIN,
298 viewBox.getY() - borderHeight - MARGIN,
299 viewBox.getWidth() + 2 * (borderWidth + MARGIN),
300 viewBox.getHeight() + 2 * (borderHeight + MARGIN),
301 borderRx,
302 borderRy);
303 borderOut.setClassNameBaseVal(style.borderOut());
304 OMSVGRectElement borderIn = new OMSVGRectElement(
305 viewBox.getX() - MARGIN,
306 viewBox.getY() - MARGIN,
307 viewBox.getWidth() + 2 * MARGIN,
308 viewBox.getHeight() + 2 * MARGIN,
309 borderRx,
310 borderRy);
311 borderIn.setClassNameBaseVal(style.borderIn());
312 rootSvg.appendChild(borderOut);
313 rootSvg.appendChild(borderIn);
314
315
316 rootSvg.setViewBox(
317 viewBox.getX() - borderWidth - MARGIN,
318 viewBox.getY() - borderHeight - MARGIN,
319 viewBox.getWidth() + 2 * (borderWidth + MARGIN),
320 viewBox.getHeight() + 2 * (borderHeight + MARGIN));
321 rootSvg.getWidth().getBaseVal().newValueSpecifiedUnits(Unit.PCT, 100);
322 rootSvg.getHeight().getBaseVal().newValueSpecifiedUnits(Unit.PCT, 100);
323
324
325
326
327
328 OMSVGClipPathElement clipPath = new OMSVGClipPathElement();
329 clipPath.setId(ID_CLIP_PATH);
330 OMSVGRectElement clipRect = new OMSVGRectElement(
331 viewBox.getX(),
332 viewBox.getY(),
333 width / xcount,
334 height / ycount,
335 borderRx,
336 borderRy);
337 clipRect.setId(ID_CLIP_RECT);
338 clipPath.appendChild(clipRect);
339 defs.appendChild(clipPath);
340
341
342 tiles = new OMSVGUseElement[xcount * ycount];
343 game = new int[xcount][];
344 for (int i = 0; i < xcount; i++) {
345 game[i] = new int[ycount];
346 for (int j = 0; j < ycount; j++) {
347 int index = i * ycount + j;
348 if (index != hole) {
349
350
351
352
353
354
355
356
357
358
359 OMSVGGElement tileDef = new OMSVGGElement();
360 tileDef.setId(ID_TILE + index);
361 OMSVGGElement tileClipPath = new OMSVGGElement();
362 tileClipPath.getStyle().setSVGProperty(SVGConstants.CSS_CLIP_PATH_PROPERTY, "url(#" + ID_CLIP_PATH + ")");
363 OMSVGGElement tileTransform = new OMSVGGElement();
364 OMSVGTransform xform = rootSvg.createSVGTransform();
365 xform.setTranslate(
366 viewBox.getX() - i * width / xcount,
367 viewBox.getY() - j * height / ycount);
368 tileTransform.getTransform().getBaseVal().appendItem(xform);
369 OMSVGUseElement imgUse = new OMSVGUseElement();
370 imgUse.getX().getBaseVal().setValue(viewBox.getX());
371 imgUse.getY().getBaseVal().setValue(viewBox.getY());
372 imgUse.getHref().setBaseVal("#" + ID_IMAGE);
373 OMSVGUseElement tileBorder = new OMSVGUseElement();
374 tileBorder.getX().getBaseVal().setValue(viewBox.getX());
375 tileBorder.getY().getBaseVal().setValue(viewBox.getY());
376 tileBorder.getHref().setBaseVal("#" + ID_CLIP_RECT);
377 tileBorder.setClassNameBaseVal(style.tileBorder());
378 tileDef.appendChild(tileClipPath);
379 tileClipPath.appendChild(tileTransform);
380 tileTransform.appendChild(imgUse);
381 tileDef.appendChild(tileBorder);
382 defs.appendChild(tileDef);
383
384
385
386 tiles[index] = new OMSVGUseElement();
387 tiles[index].getHref().setBaseVal("#" + ID_TILE + index);
388 rootSvg.appendChild(tiles[index]);
389 }
390 setTile(i, j, index);
391 }
392 }
393
394
395 Element div = svgContainer.getElement();
396 if (pushSvg != null) {
397 div.replaceChild(rootSvg.getElement(), pushSvg.getElement());
398 } else {
399 div.appendChild(rootSvg.getElement());
400 }
401 pushSvg = rootSvg;
402 if (!GWT.isScript()) {
403 GWT.log(pushSvg.getMarkup());
404 }
405
406
407 waitTimer = new Timer() {
408 public void run() {
409 scrambleTimer = new Timer() {
410 int repeatCount;
411 @Override
412 public void run() {
413 int tileCount = xcount * ycount;
414 List<Integer> array = new ArrayList<Integer>();
415 for (int i = 0; i < tileCount; i++) {
416 array.add(i);
417 }
418
419 for (int i = 0; i < xcount; i++) {
420 for (int j = 0; j < ycount; j++) {
421 setTile(i, j, array.remove(Random.nextInt(tileCount--)));
422 }
423 }
424 repeatCount++;
425 if (repeatCount >= 5) {
426 playing = true;
427 cancel();
428 }
429 }
430 };
431 if ("true".equals(Window.Location.getParameter("win"))) {
432 winAnimation();
433 } else {
434 scrambleTimer.scheduleRepeating(200);
435 }
436 }
437 };
438 waitTimer.schedule(1000);
439 }
440
441 public boolean gameOver() {
442 for (int i = 0; i < xcount; i++) {
443 for (int j = 0; j < ycount; j++) {
444 if (!(getTile(i, j) == i * ycount + j)) {
445 return false;
446 }
447 }
448 }
449 return true;
450 }
451
452 public void setTile(int x, int y, int value) {
453 game[x][y] = value;
454 if (value != hole) {
455 tiles[value].getX().getBaseVal().setValue(x * bbox.getWidth() / xcount);
456 tiles[value].getY().getBaseVal().setValue(y * bbox.getHeight() / ycount);
457 }
458 }
459 public int getTile(int x, int y) {
460 return game[x][y];
461 }
462
463 private String getDescription() {
464 StringBuilder builder = new StringBuilder();
465 for (int i = 0; i < xcount; i++) {
466 builder.append("| ");
467 for (int j = 0; j < ycount; j++) {
468 builder.append(" ");
469 builder.append(getTile(i, j));
470 }
471 builder.append(" |\n");
472 }
473 return builder.toString();
474 }
475
476 @Override
477 public void onMouseDown(MouseDownEvent event) {
478 if (playing) {
479 OMSVGPoint coords = getTileCoordinates(event);
480 if (coords != null) {
481 GWT.log("mouseDown: " + coords.getDescription());
482 int x = (int) coords.getX();
483 int y = (int) coords.getY();
484 boolean shifted = false;
485 for (int i = 0; i < xcount; i++) {
486 if (game[i][y] == hole) {
487 if (x < i) {
488 for (int j = i; j > x; j--) {
489 setTile(j, y, getTile(j - 1, y));
490 }
491 } else {
492 for (int j = i; j < x; j++) {
493 setTile(j, y, getTile(j + 1, y));
494 }
495 }
496 setTile(x, y, hole);
497 GWT.log(getDescription());
498 shifted = true;
499 }
500 }
501 if (!shifted) {
502 for (int i = 0; i < ycount; i++) {
503 if (game[x][i] == hole) {
504 if (y < i) {
505 for (int j = i; j > y; j--) {
506 setTile(x, j, getTile(x, j - 1));
507 }
508 } else {
509 for (int j = i; j < y; j++) {
510 setTile(x, j, getTile(x, j + 1));
511 }
512 }
513 setTile(x, y, hole);
514 GWT.log(getDescription());
515 }
516 }
517 }
518 if (gameOver()) {
519 winAnimation();
520 }
521 event.stopPropagation();
522 }
523 }
524 }
525
526 public OMSVGPoint getTileCoordinates(MouseEvent<? extends EventHandler> e) {
527 OMSVGPoint p = pushSvg.createSVGPoint(e.getClientX(), e.getClientY());
528 OMSVGMatrix m = pushSvg.getScreenCTM().inverse();
529 p = p.matrixTransform(m).substract(pushSvg.createSVGPoint(bbox.getX(), bbox.getY())).product(pushSvg.createSVGPoint(xcount / bbox.getWidth(), ycount / bbox.getHeight())).floor();
530 return pushSvg.createSVGRect(0, 0, xcount - 1, ycount - 1).contains(p) ? p : null;
531 }
532
533 public void winAnimation() {
534 playing = false;
535 animCount = 0;
536 animation.run(1000, Duration.currentTimeMillis() + 500);
537 }
538
539
540 private String getLevelUrl() {
541 return GWT.getModuleBaseURL() + DIR + "/" + levels[currentLevel];
542 }
543
544 public void readPushDef() {
545 playing = false;
546 if (waitTimer != null) {
547 waitTimer.cancel();
548 }
549 if (scrambleTimer != null) {
550 scrambleTimer.cancel();
551 }
552 if (animation != null) {
553 animation.cancel();
554 }
555 String url = GWT.getModuleBaseURL() + DIR + "/" + levels[currentLevel];
556 loader.loadResource(url, new AsyncXmlLoaderCallback() {
557 @Override
558 public void onError(String resourceName, Throwable error) {
559 svgContainer.setHTML("Cannot find resource");
560 }
561
562 @Override
563 public void onSuccess(String resourceName, com.google.gwt.dom.client.Element root) {
564 srcSvg = OMNode.convert(root);
565 generate();
566 }
567 });
568 }
569 }
570