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