Facade the SDK's GameManager out
[troll.git] / src / main / java / com / codingame / game / View.java
1 package com.codingame.game;
2
3 import java.util.Random;
4 import java.util.ArrayList;
5 import java.util.Comparator;
6
7 import com.codingame.gameengine.core.GameManager;
8 import com.codingame.gameengine.core.MultiplayerGameManager;
9 import com.codingame.gameengine.module.entities.GraphicEntityModule;
10 import com.codingame.gameengine.module.entities.Rectangle;
11 import com.codingame.gameengine.module.entities.Sprite;
12 import com.codingame.gameengine.module.entities.SpriteAnimation;
13 import com.codingame.gameengine.module.entities.Text;
14 import com.codingame.gameengine.module.entities.TextBasedEntity;
15 import com.codingame.gameengine.module.entities.Group;
16 import com.codingame.gameengine.module.entities.Curve;
17 import com.google.inject.Inject;
18
19 class View {
20     @Inject private MultiplayerGameManager<com.codingame.game.Player> gameManager;
21     @Inject private GraphicEntityModule graphicEntityModule;
22     @Inject PantsModule pantsModule;
23
24     /*
25      * Frame timings, for a base frame length of 2s:
26      *   - first half: stone throw
27      *   - second half: troll move
28      * The troll message is anchored around the troll move.
29      */
30     private final double STONE_THROW_START = 0.0;
31     private final double STONE_THROW_PEAK = 0.25;
32     private final double STONE_THROW_END = 0.5;
33     private final double TROLL_MOVE_START = 0.5;
34     private final double TROLL_MOVE_END = 1.0;
35     private final double TROLL_MESSAGE_START = 0.5;
36     private final double TROLL_MESSAGE_END = 1.0;
37
38     /*
39      * Castle destruction and endgame message pertain to an endgame
40      * frame only.
41      */
42     private final double AVATAR_ANIMATION_START = 0.5;
43     private final double CASTLE_DESTRUCTION_START = 0.0;
44     private final double CASTLE_DESTRUCTION_END = 0.5;
45
46     class Player {
47         Model.Player model;
48
49         int colorToken;
50         String nicknameToken;
51         String avatarToken;
52
53         double frameRot;
54
55         Group avatar;
56         Text stoneCounter;
57         Text message;
58         Sprite castle;
59         Text stone;
60         Text stoneReminder;
61
62         void init(com.codingame.game.Player p) {
63             model = p.model;
64             colorToken = p.getColorToken();
65             nicknameToken = p.getNicknameToken();
66             avatarToken = p.getAvatarToken();
67
68             boolean p0 = model.index == 0;
69             int x = p0 ? 280 : 1920 - 280;
70             int y = 220;
71
72             Sprite frame = graphicEntityModule.createSprite()
73                 .setImage("frame.png")
74                 .setAnchor(0.5)
75                 .setRotation(frameRot)
76                 .setZIndex(22)
77                 .setTint(colorToken);
78
79             Sprite frameBg = graphicEntityModule.createSprite()
80                 .setImage("frame_bg.png")
81                 .setAnchor(0.5)
82                 .setRotation(frameRot)
83                 .setZIndex(20);
84
85             Sprite avatarSprite = graphicEntityModule.createSprite()
86                 .setZIndex(21)
87                 .setImage(avatarToken)
88                 .setAnchor(0.5)
89                 .setBaseHeight(116)
90                 .setBaseWidth(116);
91
92             avatar = graphicEntityModule
93                 .createGroup(frame, frameBg, avatarSprite)
94                 .setX(x).setY(y);
95
96             Text text = graphicEntityModule.createText(nicknameToken)
97                     .setX(x)
98                     .setY(y + 120)
99                     .setZIndex(20)
100                     .setFontSize(40)
101                     .setFillColor(0x7f3f00)
102                     .setAnchor(0.5);
103
104             stoneCounter = graphicEntityModule.createText()
105                 .setX(x)
106                 .setY(y+200)
107                 .setZIndex(20)
108                 .setFontSize(40)
109                 .setFillColor(0x7f3f00)
110                 .setAnchor(0.5);
111             updateStoneCounter();
112
113             message = graphicEntityModule.createText()
114                 .setX(p0 ? 15 : 1920-15)
115                 .setY(680)
116                 .setZIndex(1)
117                 .setFontSize(40)
118                 .setFillColor(0xffbf7f)
119                 .setAnchorX(p0 ? 0 : 1)
120                 .setAnchorY(1);
121
122             castle = graphicEntityModule.createSprite()
123                 .setImage("castle.png")
124                 .setTint(colorToken)
125                 .setX(p0 ? 160 : 1920-160)
126                 .setY(p0 ? 890 : 880)
127                 .setZIndex(1)
128                 .setAnchorX(0.5)
129                 .setAnchorY(1)
130                 .setScaleX(p0 ? 1 : -1);
131
132             stone = graphicEntityModule.createText()
133                 .setZIndex(3)
134                 .setFontSize(150)
135                 .setFillColor(0x12322a)
136                 .setAnchor(0.5)
137                 .setAlpha(0);
138
139             stoneReminder = graphicEntityModule.createText()
140                 .setX(p0 ? x + 100 : x - 100)
141                 .setY(y)
142                 .setZIndex(20)
143                 .setFontSize(80)
144                 .setFontFamily("monospace")
145                 .setStrokeColor(0xff0080)
146                 .setFillColor(0xff0080)
147                 .setAnchorX(p0 ? 0 : 1)
148                 .setAnchorY(0.5);
149             pantsModule.displayOnToggleState(stoneReminder, "debug", true);
150         }
151
152         void startTurn() {
153             graphicEntityModule.commitEntityState(0.0, stoneReminder);
154         }
155
156         void disqualify(String message) {
157             gameManager.addToGameSummary(GameManager.formatErrorMessage(nicknameToken + " " + message));
158         }
159
160         void victory() {
161             gameManager.addToGameSummary(GameManager.formatSuccessMessage(nicknameToken + " wins."));
162             View.this.endgameFrame();
163             markWinner();
164         }
165
166         void defeat() {
167             gameManager.addToGameSummary(GameManager.formatErrorMessage(trollRace.starter + " destroys " + nicknameToken + "."));
168             destroyCastle();
169         }
170
171         // ========== Player/avatar markings
172
173         void markTimeout() {
174             animateLoss(avatar.getX(), avatar.getY(), 100, "SLOW\nPOKE");
175         }
176
177         void markIllegal() {
178             animateLoss(avatar.getX(), avatar.getY(), 100, "STUPID");
179         }
180
181         void markCheat() {
182             animateLoss(avatar.getX(), avatar.getY(), 100, "CHEATER");
183         }
184
185         void markWinner() {
186             graphicEntityModule.commitEntityState(AVATAR_ANIMATION_START, avatar);
187             avatar.setScaleX(1.5, Curve.EASE_OUT);
188             avatar.setScaleY(1.5, Curve.EASE_OUT);
189             avatar.setRotation((random.nextDouble() - 0.5) * Math.PI / 18,
190                                Curve.ELASTIC);
191         }
192
193         void markLoser() {
194             graphicEntityModule.commitEntityState(AVATAR_ANIMATION_START, avatar);
195             int dir = random.nextInt(2) == 1 ? 1 : -1;
196             avatar.setRotation(dir * 170 * Math.PI / 180, Curve.ELASTIC);
197         }
198
199         // ==========Player/stones
200
201         void throwStones(int stones) {
202             gameManager.addToGameSummary(String.format("%s throws %d stone%s at " + trollRace.nonStarter(), nicknameToken, stones, stones == 1 ? "" : "s"));
203         }
204
205         void threwMoreStonesThanHad() {
206             gameManager.addToGameSummary(GameManager.formatErrorMessage(nicknameToken + " tried to throw more stones than they had.  I'll let it slide for this time.  (But not let them throw that much!)"));
207         }
208
209         void failedToThrowStonesAndShouldHave() {
210             gameManager.addToGameSummary(GameManager.formatErrorMessage(nicknameToken + " tried not throwing any stones.  Fixing that for them because I'm in a good mood today."));
211         }
212
213         void updateStoneCounter() {
214             int stones = model.getStones();
215             if (stones <= 0) {
216                 stoneCounter.setText("Out of stones!");
217                 stoneCounter.setFillColor(0xff7777);
218             }
219             else if (stones == 1) {
220                 stoneCounter.setText("1 stone");
221                 stoneCounter.setFillColor(0xffbb77);
222             }
223             else {
224                 stoneCounter.setText(stones + " stones");
225             }
226             graphicEntityModule.commitEntityState(STONE_THROW_PEAK, stoneCounter);
227         }
228
229         void animateStones(int stones) {
230             String stonesString = Integer.valueOf(stones).toString();
231             stone.setX(castle.getX());
232             stone.setY(castle.getY() - 100);
233             stone.setText(stonesString);
234             stone.setAlpha(1);
235             graphicEntityModule.commitEntityState(STONE_THROW_START, stone);
236     
237             int peakX = (castle.getX() + troll.getX()) / 2;
238             int peakY = 540;
239             stone.setX(peakX);
240             stone.setY(peakY, Curve.EASE_OUT);
241             graphicEntityModule.commitEntityState(STONE_THROW_PEAK,
242                                                   stone,
243                                                   stoneCounter);
244     
245             stone.setX(troll.getX());
246             stone.setY(troll.getY() - 50, Curve.EASE_IN);
247             stone.setAlpha(0, Curve.EASE_IN);
248             graphicEntityModule.commitEntityState(STONE_THROW_END, stone);
249
250             stoneReminder.setText(stonesString);
251             graphicEntityModule.commitEntityState(0.0, stoneReminder);
252         }
253
254         // ========== Player/castle
255
256         void displayMessage(String msg) {
257             message.setText(msg);
258             graphicEntityModule.commitEntityState(0.0, message);
259         }
260
261         void destroyCastle() {
262             graphicEntityModule.commitEntityState(CASTLE_DESTRUCTION_START, castle);
263             castle.setX(castle.getX(), Curve.ELASTIC);
264             castle.setScaleY(-0.2, Curve.EASE_IN);
265             graphicEntityModule.commitEntityState(CASTLE_DESTRUCTION_END, castle);
266         }
267     } // class Player
268
269     Model model;
270     Random random = new Random();
271     Text trollMessage;
272     Group troll;
273     Text trollPositionGauge;
274     Player p0 = new Player(), p1 = new Player();
275     Text turnCounter; int _turns = 0;
276
277     // ==================== Referee interface
278
279     void init(Model m) {
280         model = m;
281
282         gameManager.setFrameDuration(2000);
283         drawBackground();
284
285         /*
286          * Random π/2-grained rotation of the avatar frames.  Avoid
287          * having them π/2 apart, though, as one of them is likely
288          * going to end upside-down and the trick would be revealed.
289          * And I'd have to "draw" a new frame. Ewww.
290          */
291         p0.frameRot = random.nextInt(4) * Math.PI / 2;
292         p0.init(gameManager.getPlayer(0));
293         p1.frameRot = p1.frameRot +
294             (random.nextInt(2) == 1 ? 1 : -1) * Math.PI / 2;
295         p1.init(gameManager.getPlayer(1));
296
297         drawTroll();
298
299         drawDebug();
300     }
301
302     void startTurn() {
303         p0.startTurn();
304         p1.startTurn();
305
306         trollMessage.setX(troll.getX());
307
308         animateTurnCounter();
309     }
310
311     void endgameFrame() {
312         gameManager.setFrameDuration(2000);
313     }
314
315     void doubleDefeat() {
316         gameManager.addToGameSummary(GameManager.formatErrorMessage("Everybody loses!"));
317         endgameFrame();
318         animateLoss(1920/2, 680, 150, "L0SERZ!");
319     }
320
321     void draw() {
322         gameManager.addToGameSummary("Draw.");
323         endgameFrame();
324         animateLoss(1920/2, 680, 200, "DRAW");
325     }
326
327     // drawBackground() helper class
328     // @Java nerds: is this avoidable?
329     private class Pos {
330         int x, y;
331         Pos(int _x, int _y) { x = _x; y = _y; }
332     }
333
334     private void drawBackground() {
335         graphicEntityModule.createSprite()
336                 .setImage("background.png")
337                 .setAnchor(0);
338
339         int numMountains = random.nextInt(5);
340         while (numMountains --> 0) {
341             final int pngWidth = 366;
342             double scale = 0.5 * (1 + random.nextDouble());
343             int x = random.nextInt(1920 + (int) (scale*pngWidth))
344                 - (int) (scale*pngWidth/2);
345             int baseTint = 64 + random.nextInt(128);
346             Sprite mountain = graphicEntityModule.createSprite()
347                 .setImage("mountain.png")
348                 .setX(x)
349                 .setY(680)
350                 .setAnchorX(0.5)
351                 .setAnchorY(283.0 / 321.0)
352                 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
353                 .setScaleX(random.nextInt(2) == 0 ? scale : -scale)
354                 .setScaleY(scale * (1 + (random.nextDouble() - 0.5) / 2))
355                 .setSkewX((random.nextDouble() - 0.5) / 4)
356                 .setSkewY((random.nextDouble() - 0.5) / 8)
357                 .setTint((baseTint + random.nextInt(16) - 8) * 0x010000
358                          + (baseTint + random.nextInt(16) - 8) * 0x0100
359                          + (baseTint + random.nextInt(16) - 8) * 0x01);
360             graphicEntityModule.createSprite().setImage("mountaintop.png")
361                 .setX(mountain.getX())
362                 .setY(mountain.getY())
363                 .setAnchorX(mountain.getAnchorX())
364                 .setAnchorY(mountain.getAnchorY())
365                 .setRotation(mountain.getRotation())
366                 .setScaleX(mountain.getScaleX())
367                 .setScaleY(mountain.getScaleY())
368                 .setSkewX(mountain.getSkewX())
369                 .setSkewY(mountain.getSkewY());
370         }
371
372         int numTrees = random.nextInt(21);
373         ArrayList<Pos> poss = new ArrayList<Pos>(numTrees);
374         while (numTrees --> 0) {
375             int x, y;
376             do {
377                 x = random.nextInt(1920);
378                 // yes, this biases randomness wrt perspective! :-(
379                 y = 700 + random.nextInt(175);
380             } while (y > 880 && (x < 200 || x > 1720));
381             poss.add(new Pos(x, y));
382         }
383         poss.sort(new Comparator<Pos>() {
384                 public int compare(Pos a, Pos b) { return a.y < b.y ? -1 : 1; }
385             });
386
387         for (Pos p : poss) {
388             double scale = ( 90.0 / 433.0           // base height from PNG
389                              * (p.y - 680) / (875 - 680) ); // perspective
390             graphicEntityModule.createSprite()
391                 .setImage(random.nextInt(2) == 0 ? "Alshockv1.png"
392                                                  : "Alshockv2.png")
393                 .setAnchorX(0.5)
394                 .setAnchorY(1)
395                 .setX(p.x)
396                 .setY(p.y)
397                 .setScaleX(scale * (random.nextInt(2) == 0 ? -1 : 1)
398                            * (1 + (random.nextDouble() - 0.5) / 6))
399                 .setScaleY(scale * (1 + (random.nextDouble() -0.5) / 6))
400                 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
401                 .setSkewX((random.nextDouble() - 0.5) /4)
402                 .setSkewY((random.nextDouble() - 0.5) /8);
403         }
404
405         // base png: 514×387
406         Sprite f7u12 = graphicEntityModule.createSprite()
407             .setImage("f7u12.png")
408             .setX(1920 / 2)
409             .setY(1080 / 2)
410             .setAnchorX(0.5)
411             .setAnchorY(0.5)
412             .setBaseWidth(514*1080/387)
413             .setBaseHeight(1080)
414             .setZIndex(200);
415         pantsModule.displayOnToggleState(f7u12, "troll", true);
416     }
417
418     // ==================== Troll
419
420     enum TrollRace {
421         Troll("The troll", 0xfac200, "bland"),
422         IceTroll("The ice troll", 0x59a2a2, "ice"),
423         RockTroll("The rock troll", 0x78877f, "rock"),
424         WaterTroll("The water troll", 0x2b2fc6, "water"),
425         OlogHai("The Olog-Hai", 0x5b2e7d, "ologhai");
426         String starter, parser; int tint;
427         TrollRace(String s, int t, String p) {
428             starter = s;
429             tint = t;
430             parser = p;
431         }
432         String nonStarter() {
433             return Character.toLowerCase(starter.charAt(0))
434                 + starter.substring(1);
435         }
436     }
437     TrollRace trollRace;
438
439     private void drawTroll() {
440         int r, league = gameManager.getLeagueLevel();
441         if (league <= 1) r = 4;
442         else if (league <= 2) r = 8;
443         else r = 10;
444
445         r = random.nextInt(r);
446         if (r < 4) trollRace = TrollRace.Troll;
447         else if (r < 6) trollRace = TrollRace.IceTroll;
448         else if (r < 8) trollRace = TrollRace.RockTroll;
449         else if (r < 9) trollRace = TrollRace.WaterTroll;
450         else if (r < 10) trollRace = TrollRace.OlogHai;
451         else throw new RuntimeException("Internal error: unknown troll race " + r);
452
453         // We read it for debugging purposes, but don't echo it back
454         // to the IDE.  It is, after all, *not* a map parameter!
455         String buf = gameManager.getGameParameters().getProperty("ehtnicity");
456         if (buf != null) {
457             String key = "";
458             for (char c : buf.toCharArray())
459                 if (Character.isLetter(c))
460                     key += Character.toLowerCase(c);
461             iHateJava: do {
462                 for (TrollRace race : TrollRace.values()) {
463                     if (key.equals(race.parser)) {
464                         trollRace = race;
465                         break/*ing news: */ iHateJava;
466                     }
467                 }
468                 gameManager.addToGameSummary("Ignoring unknown troll race: " + buf);
469             } while (false);
470         }
471         photoFinish: ; // The race is through, but Java has no goto :-(
472
473         Sprite trollBody = graphicEntityModule.createSprite()
474             .setImage("troll_body.png")
475             .setAnchorX(0.5)
476             .setAnchorY(1)
477             .setTint(trollRace.tint);
478         Sprite trollPantsRed = graphicEntityModule.createSprite()
479             .setImage("pants_red.png")
480             .setAnchorX(0.5)
481             .setAnchorY(1);
482         pantsModule.displayOnPantsState(trollPantsRed, 1);
483         Sprite trollPantsGreen = graphicEntityModule.createSprite()
484             .setImage("pants_green.png")
485             .setAnchorX(0.5)
486             .setAnchorY(1);
487         pantsModule.displayOnPantsState(trollPantsGreen, 2);
488         Sprite trollPantsBlue = graphicEntityModule.createSprite()
489             .setImage("pants_blue.png")
490             .setAnchorX(0.5)
491             .setAnchorY(1);
492         pantsModule.displayOnPantsState(trollPantsBlue, 3);
493         Sprite trollPantsPerv = graphicEntityModule.createSprite()
494             .setImage("pants_perv.png")
495             .setAnchorX(0.5)
496             .setAnchorY(1);
497         pantsModule.displayOnPantsState(trollPantsPerv, 4);
498         troll = graphicEntityModule
499             .createGroup(trollBody, trollPantsRed,
500                          trollPantsGreen, trollPantsBlue, trollPantsPerv)
501             .setX(1920/2)
502             .setY(880)
503             .setScaleX(random.nextInt(2) == 0 ? 1 : -1)
504             .setZIndex(2);
505         trollPositionGauge = graphicEntityModule.createText()
506             .setZIndex(2)
507             .setAnchor(0.5)
508             .setFontSize(40)
509             .setX(1980/2)
510             .setY(980)
511             .setFillColor(0xffffff);
512         moveTroll();
513
514         trollMessage = graphicEntityModule.createText()
515             .setZIndex(1)
516             .setX(1902/2)
517             .setY(680)
518             .setAnchorX(0.5)
519             .setAnchorY(0)
520             .setTextAlign(TextBasedEntity.TextAlign.CENTER)
521             .setStrokeColor(0xFFFF00)
522             .setFillColor(0xFFFF00)
523             .setFontSize(40);
524         pantsModule.displayOnToggleState(trollMessage, "verboseTrolling", true);
525     }
526
527     void moveTroll() {
528         graphicEntityModule.commitEntityState(TROLL_MOVE_START, troll, trollPositionGauge);
529         int x0 = p0.castle.getX(), x1 = p1.castle.getX();
530         int y0 = p0.castle.getY(), y1 = p1.castle.getY();
531         troll.setX(x0 + model.trollPosition * (x1-x0) / model.roadLength,
532                    Curve.ELASTIC);
533         troll.setY(y0 + model.trollPosition * (y1-y0) / model.roadLength,
534                    Curve.ELASTIC);
535
536         trollPositionGauge.setX((trollPositionGauge.getX() + troll.getX()) / 2);
537         int distLeft = model.trollPosition;
538         int distRight = model.roadLength - model.trollPosition;
539         if (distLeft <= 0) {
540             trollPositionGauge.setText("← " + distRight);
541         }
542         else if (distRight <= 0) {
543             trollPositionGauge.setText(distLeft + " →");
544         }
545         else {
546             trollPositionGauge.setText(distLeft + " ↔ " + distRight);
547         }
548         final double moveMid = (TROLL_MOVE_START + TROLL_MOVE_END) / 2;
549         graphicEntityModule.commitEntityState(moveMid, trollPositionGauge);
550         trollPositionGauge.setX(troll.getX());
551     }
552
553     enum Dir {
554         LEFT("walks left.", 0),
555         STILL("stands still.", 1),
556         RIGHT("walks right.", 2);
557
558         String movement; int index;
559         Dir(String mvt, int i) { movement = mvt; index = i; }
560     }
561
562     void moveTroll(Dir d) {
563         moveTroll();
564         gameManager.addToGameSummary(trollRace.starter + " " + d.movement);
565
566         trollMessage.setText(selectTrollMessage(d)).setAlpha(1, Curve.NONE);
567         graphicEntityModule.commitEntityState(TROLL_MESSAGE_START, trollMessage);
568         trollMessage.setAlpha(0, Curve.EASE_IN);
569         graphicEntityModule.commitEntityState(TROLL_MESSAGE_END, trollMessage);
570     }
571
572     String selectTrollMessage(Dir d) {
573         if (random.nextInt(10000) == 0) {
574             return TrollText.specials[random.nextInt(TrollText.specials.length)];
575         }
576
577         // yup, still biased
578         int i = random.nextInt(TrollText.directed.length + TrollText.isotropic.length / 3);
579         if (i < TrollText.directed.length) {
580             return TrollText.directed[i][d.index];
581         }
582         else {
583             return TrollText.isotropic[random.nextInt(TrollText.isotropic.length)];
584         }
585     }
586
587     // ==================== Debug information
588
589     void animateTurnCounter() {
590         for (int i = 0; i < 10; i++) {
591             turnCounter.setText("T" + _turns + "." + i);
592             // The following line is likely not a bug.
593             graphicEntityModule.commitEntityState((double) i/9, turnCounter);
594         }
595         _turns++;
596     }
597
598     void drawDebug() {
599         String[] debugModePngs = graphicEntityModule.createSpriteSheetSplitter()
600             .setSourceImage("debug.png")
601             .setImageCount(2)
602             .setWidth(900)
603             .setHeight(150)
604             .setOrigRow(0)
605             .setOrigCol(0)
606             .setImagesPerRow(1)
607             .setName("debug")
608             .split();
609         SpriteAnimation debugMode = graphicEntityModule.createSpriteAnimation()
610             .setImages(debugModePngs)
611             .setX(1920 / 2)
612             .setY(60)
613             .setAnchorX(0.5)
614             .setLoop(true);
615         pantsModule.displayOnToggleState(debugMode, "debug", true);
616
617         turnCounter = graphicEntityModule.createText()
618             .setAnchorX(0.5)
619             .setAnchorY(0)
620             .setX(1920 / 2)
621             .setY(260)
622             .setStrokeColor(0xff0080)
623             .setFillColor(0xff0080)
624             .setFontFamily("monospace")
625             .setFontWeight(Text.FontWeight.BOLD)
626             .setFontSize(100);
627         pantsModule.displayOnToggleState(turnCounter, "debug", true);
628         animateTurnCounter();
629     }
630
631     // ==================== Endgame status
632
633     void animateLoss(int x, int y, int size, String message) {
634         int startX;
635         if (x < 1920/2) { startX = 1920; }
636         else if (x > 1920/2) { startX = 0; }
637         else { startX = 1920 * random.nextInt(2); }
638
639         Text msg = graphicEntityModule.createText(message)
640             .setX(startX)
641             .setY(1080)
642             .setAnchorX(0.5)
643             .setAnchorY(0.5)
644             .setScaleX(3*random.nextDouble() - 1)
645             .setScaleY(3*random.nextDouble() - 1)
646             .setSkewX(2*random.nextDouble() - 1)
647             .setSkewY(2*random.nextDouble() - 1)
648             .setRotation(4*Math.PI * (1 + random.nextDouble())
649                          * (random.nextInt(2) == 0 ? 1 : -1))
650             .setFontSize(0)
651             .setStrokeColor(0xff7f7f)
652             .setFillColor(0xff7f7f)
653             .setFontWeight(Text.FontWeight.BOLD)
654             .setTextAlign(TextBasedEntity.TextAlign.CENTER);
655         graphicEntityModule.commitEntityState(0.0, msg);
656         Curve curve = Curve.ELASTIC;
657         msg.setX(x, Curve.EASE_OUT)
658             .setY(y, Curve.ELASTIC)
659             .setScaleX(1, curve)
660             .setScaleY(1, curve)
661             .setSkewX(0, curve)
662             .setSkewY(0, curve)
663             .setRotation(2*Math.PI * (random.nextDouble() - 0.5), Curve.LINEAR)
664             .setFontSize(size, curve);
665     }
666 }